mirror of
https://github.com/nocodb/nocodb.git
synced 2026-04-30 20:46:44 +00:00
Feat - Next release improvements and bug fixes (#2120)
* refactor: include log lev3l in progress Signed-off-by: Pranav C <pranavxc@gmail.com> * feat: migration logs classification Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * fix: escape `?` in query Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: handle leading/trailing whitespace in table name re #2073 Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: replace knex.raw replace `knex.raw` with `knex.from` since response is different for each client Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: created time & modified time handling as dateTime datatype Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * fix: at import issue and data list api corrections Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: richtext migration support Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * fix: filter to ignore dateTime datatype along with date datatype Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * fix: replace all occurance of . from column name Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * refactoring Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: correction in read api Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: keep correct dtxp value Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: replace ? with _ during column name processing Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * refactoring Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: allow singleLineText to text type instead of tinytext Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * chore: start scripts for pg Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * refactor: thumbnail size increased by 3x Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * fix: exclude whitespace from table name and single select rendering correction Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: execute without extracting raw query in pg Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: upgrade nc-help Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: replace special characters in column name with an _ Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * fix: handle duplicate table name Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: replace , in select options with a . Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * fix: for title, trim only spaces Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * fix: multiselect and single select import and rendering Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: unique column name generator Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: mmlist query correction Signed-off-by: Pranav C <pranavxc@gmail.com> * refactor: use common function for column/table name generation Signed-off-by: Pranav C <pranavxc@gmail.com> * refactor: type correction Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: upgrade nc-help Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: form view field alias & help text migration Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * fix: handle column name referenced by $ * refactor: rename system field, change its position during creation Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * fix: replace . in column name with _ Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * fix: skip rollup for checkbox Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * fix: replace title with id's in viewRowData APIs Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * chore: code cleanup and we are hiring button Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: ignore escaping . in alias Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * fix: headercell overflow Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: support presence of existing tables during migration Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * enhancement: add reach out here link Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: skip default value configuration during import Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * enhancement: webhook prefill default values Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: add missing component properties Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: missing gallery view cover image re # 2099 Signed-off-by: Pranav C <pranavxc@gmail.com> * cache: fix view:[object Object] * fix: hide websocket button and other buttons from shared form view re # 2107 Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: hide virtual columns which are not relevant in expanded form(add) Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: handle view cache based on returned value Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: add galleryViewGet permission for roles Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: upgrade nc-help Signed-off-by: Pranav C <pranavxc@gmail.com> * refactor: add beta label Signed-off-by: Pranav C <pranavxc@gmail.com> * refactor: add beta label Signed-off-by: Pranav C <pranavxc@gmail.com> * test/cypress: fix- corrections for baseShare UI change Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test/cypress: fix view menu count Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * fix: disable default autocomplete Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: update docs link Signed-off-by: Pranav C <pranavxc@gmail.com> Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com> Co-authored-by: Wing-Kam Wong <wingkwong.code@gmail.com>
This commit is contained in:
15
.run/Run NocoDB Sqlite.run.xml
Normal file
15
.run/Run NocoDB Sqlite.run.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Run NocoDB Sqlite" type="js.build_tools.npm" activateToolWindowBeforeRun="false">
|
||||
<package-json value="$PROJECT_DIR$/packages/nocodb/package.json" />
|
||||
<command value="run" />
|
||||
<scripts>
|
||||
<script value="watch:run" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<envs>
|
||||
<env name="NC_DISABLE_CACHE1" value="true" />
|
||||
<env name="NC_DISABLE_TELE" value="true" />
|
||||
</envs>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -54,6 +54,7 @@
|
||||
:key="`${pid}||${(tab._nodes && tab._nodes).type || ''}||${
|
||||
(tab._nodes && tab._nodes.dbAlias) || ''
|
||||
}||${tab.name}`"
|
||||
class="nc-main-tab-item"
|
||||
:value="`${(tab._nodes && tab._nodes.type) || ''}||${
|
||||
(tab._nodes && tab._nodes.dbAlias) || ''
|
||||
}||${tab.name}`"
|
||||
@@ -670,7 +671,7 @@ export default {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
/deep/ .v-window-item:not(.v-window-item--active){
|
||||
/deep/ .nc-main-tab-item:not(.v-window-item--active){
|
||||
display:none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
type="list-item,list-item-three-line@3,list-item@2,list-item-three-line@3"
|
||||
/>
|
||||
|
||||
<v-treeview
|
||||
<!-- <v-treeview
|
||||
v-else-if="isTreeView"
|
||||
v-model="tree"
|
||||
class="mt-5 project-tree nc-project-tree"
|
||||
@@ -96,9 +96,9 @@
|
||||
<v-icon size="16">
|
||||
mdi-database
|
||||
</v-icon>
|
||||
<!-- <img-->
|
||||
<!-- class="grey lighten-3"-->
|
||||
<!-- :width="16" :src="`/db-icons/${dbIcons[item._nodes.dbConnection.client]}`"/>-->
|
||||
<!– <img–>
|
||||
<!– class="grey lighten-3"–>
|
||||
<!– :width="16" :src="`/db-icons/${dbIcons[item._nodes.dbConnection.client]}`"/>–>
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-icon
|
||||
@@ -130,7 +130,7 @@
|
||||
<span>{{ item.tooltip || item.name }}</span>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
</v-treeview>
|
||||
</v-treeview>-->
|
||||
<v-container v-else fluid class="px-1 pt-0">
|
||||
<v-list
|
||||
height="30"
|
||||
@@ -159,7 +159,7 @@
|
||||
<template #activator>
|
||||
<v-list-item-icon>
|
||||
<v-icon
|
||||
v-if="open && icons[item._nodes.type].openIcon"
|
||||
v-if="icons[item._nodes.type].openIcon"
|
||||
small
|
||||
style="cursor: auto"
|
||||
:color="icons[item._nodes.type].openColor"
|
||||
@@ -436,7 +436,7 @@
|
||||
>
|
||||
<v-list-item-icon>
|
||||
<v-icon
|
||||
v-if="open && icons[item._nodes.type].openIcon"
|
||||
v-if="icons[item._nodes.type].openIcon"
|
||||
small
|
||||
style="cursor: auto"
|
||||
:color="icons[item._nodes.type].openColor"
|
||||
@@ -613,7 +613,9 @@
|
||||
<span class="font-weight-regular caption">{{
|
||||
$t("title.audit")
|
||||
}}</span>
|
||||
</v-list-item-title>
|
||||
</v-list-item-title
|
||||
</v-list-item
|
||||
>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<!-- Meta Management -->
|
||||
@@ -857,7 +859,6 @@ export default {
|
||||
},
|
||||
loadingProjects: true,
|
||||
caseInsensitive: true,
|
||||
open: [],
|
||||
search: null,
|
||||
menuVisible: false,
|
||||
quickImportDialog: false,
|
||||
@@ -1197,31 +1198,31 @@ export default {
|
||||
const currentlyOpened = JSON.parse(JSON.stringify(this.open));
|
||||
currentlyOpened.push(item._nodes.key);
|
||||
this.activeListItem = item._nodes.key;
|
||||
this.open = currentlyOpened;
|
||||
// this.open = currentlyOpened;
|
||||
} else if (item._nodes.type === "viewDir" && !open) {
|
||||
await this.loadViews(item);
|
||||
const currentlyOpened = JSON.parse(JSON.stringify(this.open));
|
||||
currentlyOpened.push(item._nodes.key);
|
||||
this.activeListItem = item._nodes.key;
|
||||
this.open = currentlyOpened;
|
||||
// this.open = currentlyOpened;
|
||||
} else if (item._nodes.type === "functionDir" && !open) {
|
||||
await this.loadFunctions(item);
|
||||
const currentlyOpened = JSON.parse(JSON.stringify(this.open));
|
||||
currentlyOpened.push(item._nodes.key);
|
||||
this.activeListItem = item._nodes.key;
|
||||
this.open = currentlyOpened;
|
||||
// this.open = currentlyOpened;
|
||||
} else if (item._nodes.type === "procedureDir" && !open) {
|
||||
await this.loadProcedures(item);
|
||||
const currentlyOpened = JSON.parse(JSON.stringify(this.open));
|
||||
currentlyOpened.push(item._nodes.key);
|
||||
this.activeListItem = item._nodes.key;
|
||||
this.open = currentlyOpened;
|
||||
// this.open = currentlyOpened;
|
||||
} else if (item._nodes.type === "sequenceDir" && !open) {
|
||||
await this.loadSequences(item);
|
||||
const currentlyOpened = JSON.parse(JSON.stringify(this.open));
|
||||
currentlyOpened.push(item._nodes.key);
|
||||
this.activeListItem = item._nodes.key;
|
||||
this.open = currentlyOpened;
|
||||
// this.open = currentlyOpened;
|
||||
} else if (item._nodes.type === "env") {
|
||||
return;
|
||||
} else {
|
||||
|
||||
@@ -14,99 +14,109 @@
|
||||
<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/setup-and-usages/import-airtable-to-sql-database-within-a-minute-for-free" 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 nc-input-api-key"
|
||||
: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 nc-input-shared-base"
|
||||
: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']"
|
||||
class="nc-btn-airtable-import"
|
||||
: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"
|
||||
:class="{'pb-4 mt-4' : step === 2, 'py-6': step === 1}"
|
||||
class=" elevation-0"
|
||||
min-height="500"
|
||||
>
|
||||
<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
|
||||
<template v-if="step === 1">
|
||||
<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>
|
||||
<!-- <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"
|
||||
<a href="https://docs.nocodb.com/setup-and-usages/import-airtable-to-sql-database-within-a-minute-for-free/#get-airtable-credentials-for-importing-to-nocodb" 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 nc-input-api-key"
|
||||
:type="isPasswordVisible ? 'text':'password'"
|
||||
autocomplete="off"
|
||||
: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 nc-input-shared-base"
|
||||
: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']"
|
||||
class="nc-btn-airtable-import"
|
||||
:disabled="!valid"
|
||||
large
|
||||
color="primary"
|
||||
@click="saveAndSync"
|
||||
>
|
||||
Import
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</template>
|
||||
<template
|
||||
v-else-if="step === 2"
|
||||
>
|
||||
<v-btn large color="primary" class="nc-btn-go-dashboard" @click="airtableModal=false">
|
||||
Go to dashboard
|
||||
</v-btn>
|
||||
<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" class="nc-btn-go-dashboard" @click="airtableModal=false">
|
||||
Go to dashboard
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
<div class="text-center pa-4 pb-0">
|
||||
<a class="caption grey--text" href="https://github.com/nocodb/nocodb/issues/2052" target="_blank">Questions / Help - reach out here</a>
|
||||
<br>
|
||||
<span class="caption grey--text"> This feature is currently in beta and more information can be found <a href="https://github.com/nocodb/nocodb/discussions/2122" class="caption grey--text" target="_blank">here</a>.</span>
|
||||
</div>
|
||||
</v-card>
|
||||
</div>
|
||||
|
||||
@@ -1102,8 +1102,8 @@ export default {
|
||||
const insertedData = await this.$api.dbViewRow.create(
|
||||
'noco',
|
||||
this.projectName,
|
||||
this.meta.title,
|
||||
this.selectedView.title,
|
||||
this.meta.id,
|
||||
this.selectedView.id,
|
||||
insertObj
|
||||
)
|
||||
|
||||
@@ -1192,8 +1192,8 @@ export default {
|
||||
const newData = await this.$api.dbViewRow.update(
|
||||
'noco',
|
||||
this.projectName,
|
||||
this.meta.title,
|
||||
this.selectedView.title,
|
||||
this.meta.id,
|
||||
this.selectedView.id,
|
||||
id,
|
||||
{
|
||||
[column.title]: rowObj[column.title]
|
||||
@@ -1255,8 +1255,8 @@ export default {
|
||||
await this.$api.dbViewRow.delete(
|
||||
'noco',
|
||||
this.projectName,
|
||||
this.meta.title,
|
||||
this.selectedView.title,
|
||||
this.meta.id,
|
||||
this.selectedView.id,
|
||||
id
|
||||
)
|
||||
}
|
||||
@@ -1292,8 +1292,8 @@ export default {
|
||||
await this.$api.dbViewRow.delete(
|
||||
'noco',
|
||||
this.projectName,
|
||||
this.meta.title,
|
||||
this.selectedView.title,
|
||||
this.meta.id,
|
||||
this.selectedView.id,
|
||||
id
|
||||
)
|
||||
}
|
||||
@@ -1426,8 +1426,8 @@ export default {
|
||||
const { list, pageInfo } = await this.$api.dbViewRow.list(
|
||||
'noco',
|
||||
this.projectName,
|
||||
this.meta.title,
|
||||
this.selectedView.title,
|
||||
this.meta.id,
|
||||
this.selectedView.id,
|
||||
this.listQueryParams
|
||||
)
|
||||
|
||||
@@ -1685,8 +1685,8 @@ export default {
|
||||
const { count } = await this.$api.dbViewRow.count(
|
||||
'noco',
|
||||
this.$store.getters['project/GtrProjectName'],
|
||||
this.meta.title,
|
||||
this.selectedView.title
|
||||
this.meta.id,
|
||||
this.selectedView.id
|
||||
)
|
||||
this.count = count
|
||||
}
|
||||
|
||||
@@ -1,75 +1,88 @@
|
||||
<template>
|
||||
<div class="nc-container" :class="{active:modal}" @click="modal=false">
|
||||
<div class="nc-snippet elevation-3 pa-4" @click.stop>
|
||||
<h3 class="font-weight-medium mb-4">
|
||||
Code Snippet
|
||||
</h3>
|
||||
<div class="nc-snippet elevation-3 pa-4 d-flex flex-column" @click.stop>
|
||||
<div>
|
||||
<h3 class="font-weight-medium mb-4">
|
||||
Code Snippet
|
||||
</h3>
|
||||
|
||||
<v-icon class="nc-snippet-close" @click="modal=false">
|
||||
mdi-close
|
||||
</v-icon>
|
||||
<v-icon class="nc-snippet-close" @click="modal=false">
|
||||
mdi-close
|
||||
</v-icon>
|
||||
|
||||
<div v-if="modal">
|
||||
<v-tabs v-model="tab" height="30" show-arrows @change="client=null">
|
||||
<v-tab
|
||||
v-for="{lang} in langs"
|
||||
:key="lang"
|
||||
v-t="['c:snippet:tab', {lang}]"
|
||||
class="caption"
|
||||
>
|
||||
{{ lang }}
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
<div class="nc-snippet-wrapper mt-4">
|
||||
<div class="nc-snippet-actions d-flex">
|
||||
<v-btn
|
||||
v-t="['c:snippet:copy', {client: langs[tab].clients && (client || langs[tab].clients[0]), lang: langs[tab ||0].lang}]"
|
||||
color="primary"
|
||||
class="rounded caption "
|
||||
@click="copyToClipboard"
|
||||
<div v-if="modal">
|
||||
<v-tabs v-model="tab" height="30" show-arrows @change="client=null">
|
||||
<v-tab
|
||||
v-for="{lang} in langs"
|
||||
:key="lang"
|
||||
v-t="['c:snippet:tab', {lang}]"
|
||||
class="caption"
|
||||
>
|
||||
<v-icon small>
|
||||
mdi-clipboard-outline
|
||||
</v-icon>
|
||||
Copy To Clipboard
|
||||
</v-btn>
|
||||
<div
|
||||
v-if="langs[tab].clients"
|
||||
class=" ml-2 d-flex align-center"
|
||||
>
|
||||
<v-menu bottom offset-y>
|
||||
<template #activator="{on}">
|
||||
<v-btn class="caption text-uppercase" color="primary" v-on="on">
|
||||
{{ client || langs[tab].clients[0] }}
|
||||
<v-icon small>
|
||||
mdi-chevron-down
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list dense>
|
||||
<v-list-item
|
||||
v-for="c in langs[tab].clients"
|
||||
:key="c"
|
||||
dense
|
||||
@click="client = c"
|
||||
>
|
||||
<v-list-item-title class="text-uppercase">
|
||||
{{ c }}
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
{{ lang }}
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
<div class="nc-snippet-wrapper mt-4">
|
||||
<div class="nc-snippet-actions d-flex">
|
||||
<v-btn
|
||||
v-t="['c:snippet:copy', {client: langs[tab].clients && (client || langs[tab].clients[0]), lang: langs[tab ||0].lang}]"
|
||||
color="primary"
|
||||
class="rounded caption "
|
||||
@click="copyToClipboard"
|
||||
>
|
||||
<v-icon small>
|
||||
mdi-clipboard-outline
|
||||
</v-icon>
|
||||
Copy To Clipboard
|
||||
</v-btn>
|
||||
<div
|
||||
v-if="langs[tab].clients"
|
||||
class=" ml-2 d-flex align-center"
|
||||
>
|
||||
<v-menu bottom offset-y>
|
||||
<template #activator="{on}">
|
||||
<v-btn class="caption text-uppercase" color="primary" v-on="on">
|
||||
{{ client || langs[tab].clients[0] }}
|
||||
<v-icon small>
|
||||
mdi-chevron-down
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list dense>
|
||||
<v-list-item
|
||||
v-for="c in langs[tab].clients"
|
||||
:key="c"
|
||||
dense
|
||||
@click="client = c"
|
||||
>
|
||||
<v-list-item-title class="text-uppercase">
|
||||
{{ c }}
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</div>
|
||||
</div>
|
||||
<custom-monaco-editor
|
||||
hide-line-num
|
||||
:theme="$store.state.settings.darkTheme ? 'vs-dark' : 'vs-light'"
|
||||
style="min-height:500px;max-width: 100%"
|
||||
:value="code"
|
||||
read-only
|
||||
/>
|
||||
</div>
|
||||
<custom-monaco-editor
|
||||
hide-line-num
|
||||
:theme="$store.state.settings.darkTheme ? 'vs-dark' : 'vs-light'"
|
||||
style="min-height:500px;max-width: 100%"
|
||||
:value="code"
|
||||
read-only
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
v-t="['e:hiring']"
|
||||
color="primary"
|
||||
outlined
|
||||
class="caption my-2 mx-auto"
|
||||
href="https://angel.co/company/nocodb"
|
||||
target="_blank"
|
||||
>
|
||||
🚀 We are Hiring! 🚀
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -400,20 +400,19 @@ export default {
|
||||
return !!Object.keys(this.changedColumns).length
|
||||
},
|
||||
fields() {
|
||||
let fields
|
||||
if (this.availableColumns) {
|
||||
return this.availableColumns
|
||||
}
|
||||
//
|
||||
// const hideCols = ['created_at', 'updated_at']
|
||||
if (this.showSystemFields) {
|
||||
return this.meta.columns || []
|
||||
fields = this.availableColumns
|
||||
} else if (this.showSystemFields) {
|
||||
fields = this.meta.columns || []
|
||||
} else {
|
||||
return (
|
||||
fields = (
|
||||
this.meta.columns.filter(
|
||||
c => !isSystemColumn(c)
|
||||
) || []
|
||||
)
|
||||
}
|
||||
return this.isNew ? fields.filter(f => ![UITypes.Formula, UITypes.Rollup, UITypes.Lookup].includes(f.uidt)) : fields
|
||||
},
|
||||
isChanged() {
|
||||
return Object.values(this.changedColumns).some(Boolean)
|
||||
@@ -559,7 +558,8 @@ export default {
|
||||
value: updatedObj[key],
|
||||
prev_value: this.oldRow[key]
|
||||
})
|
||||
.then(() => {})
|
||||
.then(() => {
|
||||
})
|
||||
}
|
||||
} else {
|
||||
return this.$toast.info('No columns to update').goAway(3000)
|
||||
|
||||
@@ -14,16 +14,20 @@
|
||||
}"
|
||||
v-on="on"
|
||||
>
|
||||
<v-icon small class="mr-1" color="#777"> mdi-eye-off-outline </v-icon>
|
||||
<v-icon small class="mr-1" color="#777">
|
||||
mdi-eye-off-outline
|
||||
</v-icon>
|
||||
<!-- Fields -->
|
||||
{{ $t("objects.fields") }}
|
||||
<v-icon small color="#777"> mdi-menu-down </v-icon>
|
||||
<v-icon small color="#777">
|
||||
mdi-menu-down
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</v-badge>
|
||||
</template>
|
||||
|
||||
<v-list dense class="pt-0" min-width="280" @click.stop>
|
||||
<template v-if="isGallery">
|
||||
<template v-if="isGallery && _isUIAllowed('updateCoverImage')">
|
||||
<div class="pa-2">
|
||||
<v-select
|
||||
v-model="coverImageFieldLoc"
|
||||
@@ -38,7 +42,9 @@
|
||||
@click.stop
|
||||
>
|
||||
<template #prepend-inner>
|
||||
<v-icon small class="field-icon"> mdi-image </v-icon>
|
||||
<v-icon small class="field-icon">
|
||||
mdi-image
|
||||
</v-icon>
|
||||
</template>
|
||||
</v-select>
|
||||
</div>
|
||||
@@ -60,7 +66,9 @@
|
||||
@click.stop
|
||||
>
|
||||
<template #prepend-inner>
|
||||
<v-icon small class="field-icon"> mdi-select-group </v-icon>
|
||||
<v-icon small class="field-icon">
|
||||
mdi-select-group
|
||||
</v-icon>
|
||||
</template>
|
||||
</v-select>
|
||||
</div>
|
||||
@@ -98,10 +106,10 @@
|
||||
(field.title || '')
|
||||
.toLowerCase()
|
||||
.includes(fieldFilter.toLowerCase())) &&
|
||||
!(
|
||||
!showSystemFieldsLoc &&
|
||||
systemColumnsIds.includes(field.fk_column_id)
|
||||
)
|
||||
!(
|
||||
!showSystemFieldsLoc &&
|
||||
systemColumnsIds.includes(field.fk_column_id)
|
||||
)
|
||||
"
|
||||
:key="field.id"
|
||||
dense
|
||||
@@ -162,13 +170,13 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import draggable from "vuedraggable";
|
||||
import { getSystemColumnsIds } from "nocodb-sdk";
|
||||
import draggable from 'vuedraggable'
|
||||
import { getSystemColumnsIds } from 'nocodb-sdk'
|
||||
|
||||
export default {
|
||||
name: "FieldsMenu",
|
||||
name: 'FieldsMenu',
|
||||
components: {
|
||||
draggable,
|
||||
draggable
|
||||
},
|
||||
props: {
|
||||
coverImageField: String,
|
||||
@@ -182,278 +190,278 @@ export default {
|
||||
fieldList: [Array, Object],
|
||||
showSystemFields: {
|
||||
type: [Boolean, Number],
|
||||
default: false,
|
||||
default: false
|
||||
},
|
||||
isLocked: Boolean,
|
||||
isPublic: Boolean,
|
||||
viewId: String,
|
||||
viewId: String
|
||||
},
|
||||
data: () => ({
|
||||
fields: [],
|
||||
fieldFilter: "",
|
||||
fieldFilter: '',
|
||||
showFields: {},
|
||||
fieldsOrderLoc: [],
|
||||
fieldsOrderLoc: []
|
||||
}),
|
||||
computed: {
|
||||
systemColumnsIds() {
|
||||
return getSystemColumnsIds(this.meta && this.meta.columns);
|
||||
return getSystemColumnsIds(this.meta && this.meta.columns)
|
||||
},
|
||||
attachmentFields() {
|
||||
return [
|
||||
...(this.meta && this.meta.columns
|
||||
? this.meta.columns.filter((f) => f.uidt === "Attachment")
|
||||
? this.meta.columns.filter(f => f.uidt === 'Attachment')
|
||||
: []),
|
||||
{
|
||||
alias: "None",
|
||||
id: null,
|
||||
},
|
||||
];
|
||||
alias: 'None',
|
||||
id: null
|
||||
}
|
||||
]
|
||||
},
|
||||
singleSelectFields() {
|
||||
return [
|
||||
...(this.meta && this.meta.columns
|
||||
? this.meta.columns.filter((f) => f.uidt === "SingleSelect")
|
||||
? this.meta.columns.filter(f => f.uidt === 'SingleSelect')
|
||||
: []),
|
||||
{
|
||||
alias: "None",
|
||||
id: null,
|
||||
},
|
||||
];
|
||||
alias: 'None',
|
||||
id: null
|
||||
}
|
||||
]
|
||||
},
|
||||
coverImageFieldLoc: {
|
||||
get() {
|
||||
return this.coverImageField;
|
||||
return this.coverImageField
|
||||
},
|
||||
set(val) {
|
||||
this.$emit("update:coverImageField", val);
|
||||
},
|
||||
this.$emit('update:coverImageField', val)
|
||||
}
|
||||
},
|
||||
groupingFieldLoc: {
|
||||
get() {
|
||||
return this.groupingField;
|
||||
return this.groupingField
|
||||
},
|
||||
set(val) {
|
||||
this.$emit("update:groupingField", val);
|
||||
},
|
||||
this.$emit('update:groupingField', val)
|
||||
}
|
||||
},
|
||||
columnMeta() {
|
||||
return this.meta && this.meta.columns
|
||||
? this.meta.columns.reduce(
|
||||
(o, c) => ({
|
||||
...o,
|
||||
[c.title]: c,
|
||||
}),
|
||||
{}
|
||||
)
|
||||
: {};
|
||||
(o, c) => ({
|
||||
...o,
|
||||
[c.title]: c
|
||||
}),
|
||||
{}
|
||||
)
|
||||
: {}
|
||||
},
|
||||
|
||||
isAnyFieldHidden() {
|
||||
return this.fields.some(
|
||||
(f) =>
|
||||
f =>
|
||||
!(
|
||||
!this.showSystemFieldsLoc &&
|
||||
this.systemColumnsIds.includes(f.fk_column_id)
|
||||
) && !f.show
|
||||
); // Object.values(this.showFields).some(v => !v)
|
||||
) // Object.values(this.showFields).some(v => !v)
|
||||
},
|
||||
showSystemFieldsLoc: {
|
||||
get() {
|
||||
return this.showSystemFields;
|
||||
return this.showSystemFields
|
||||
},
|
||||
set(v) {
|
||||
this.$emit("update:showSystemFields", v);
|
||||
this.$emit('update:showSystemFields', v)
|
||||
this.showFields = this.fields.reduce(
|
||||
(o, c) => ({ [c.title]: c.show, ...o }),
|
||||
{}
|
||||
);
|
||||
)
|
||||
this.$emit(
|
||||
"update:fieldsOrder",
|
||||
this.fields.map((c) => c.title)
|
||||
);
|
||||
'update:fieldsOrder',
|
||||
this.fields.map(c => c.title)
|
||||
)
|
||||
|
||||
this.$e("a:fields:system-fields");
|
||||
},
|
||||
},
|
||||
this.$e('a:fields:system-fields')
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
async viewId(v) {
|
||||
if (v) {
|
||||
await this.loadFields();
|
||||
await this.loadFields()
|
||||
}
|
||||
},
|
||||
fieldList(f) {
|
||||
this.fieldsOrderLoc = [...f];
|
||||
this.fieldsOrderLoc = [...f]
|
||||
},
|
||||
showFields: {
|
||||
handler(v) {
|
||||
this.$nextTick(() => {
|
||||
this.$emit("input", v);
|
||||
});
|
||||
this.$emit('input', v)
|
||||
})
|
||||
},
|
||||
deep: true,
|
||||
deep: true
|
||||
},
|
||||
value(v) {
|
||||
this.showFields = v || [];
|
||||
this.showFields = v || []
|
||||
},
|
||||
fieldsOrder(n, o) {
|
||||
if ((n && n.join()) !== (o && o.join())) {
|
||||
this.fieldsOrderLoc = n;
|
||||
this.fieldsOrderLoc = n
|
||||
}
|
||||
|
||||
this.fieldsOrderLoc = n && n.length ? n : [...this.fieldList];
|
||||
this.fieldsOrderLoc = n && n.length ? n : [...this.fieldList]
|
||||
},
|
||||
fieldsOrderLoc: {
|
||||
handler(n, o) {
|
||||
if ((n && n.join()) !== (o && o.join())) {
|
||||
this.$emit("update:fieldsOrder", n);
|
||||
this.$emit('update:fieldsOrder', n)
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadFields();
|
||||
this.showFields = this.value;
|
||||
this.loadFields()
|
||||
this.showFields = this.value
|
||||
this.fieldsOrderLoc =
|
||||
this.fieldsOrder && this.fieldsOrder.length
|
||||
? this.fieldsOrder
|
||||
: [...this.fieldList];
|
||||
: [...this.fieldList]
|
||||
},
|
||||
methods: {
|
||||
async loadFields() {
|
||||
let fields = [];
|
||||
let order = 1;
|
||||
let fields = []
|
||||
let order = 1
|
||||
if (this.viewId) {
|
||||
const data = await this.$api.dbViewColumn.list(this.viewId);
|
||||
const data = await this.$api.dbViewColumn.list(this.viewId)
|
||||
const fieldById = data.reduce(
|
||||
(o, f) => ({
|
||||
...o,
|
||||
[f.fk_column_id]: f,
|
||||
}),
|
||||
{}
|
||||
);
|
||||
fields = this.meta.columns
|
||||
.map((c) => ({
|
||||
title: c.title,
|
||||
fk_column_id: c.id,
|
||||
...(fieldById[c.id] ? fieldById[c.id] : {}),
|
||||
order: (fieldById[c.id] && fieldById[c.id].order) || order++,
|
||||
}))
|
||||
.sort((a, b) => a.order - b.order);
|
||||
} else if (this.isPublic) {
|
||||
fields = this.meta.columns;
|
||||
}
|
||||
|
||||
this.fields = fields;
|
||||
|
||||
this.$emit(
|
||||
"input",
|
||||
this.fields.reduce(
|
||||
(o, c) => ({
|
||||
...o,
|
||||
[c.title]: c.show,
|
||||
[f.fk_column_id]: f
|
||||
}),
|
||||
{}
|
||||
)
|
||||
);
|
||||
fields = this.meta.columns
|
||||
.map(c => ({
|
||||
title: c.title,
|
||||
fk_column_id: c.id,
|
||||
...(fieldById[c.id] ? fieldById[c.id] : {}),
|
||||
order: (fieldById[c.id] && fieldById[c.id].order) || order++
|
||||
}))
|
||||
.sort((a, b) => a.order - b.order)
|
||||
} else if (this.isPublic) {
|
||||
fields = this.meta.columns
|
||||
}
|
||||
|
||||
this.fields = fields
|
||||
|
||||
this.$emit(
|
||||
"update:fieldsOrder",
|
||||
this.fields.map((c) => c.title)
|
||||
);
|
||||
'input',
|
||||
this.fields.reduce(
|
||||
(o, c) => ({
|
||||
...o,
|
||||
[c.title]: c.show
|
||||
}),
|
||||
{}
|
||||
)
|
||||
)
|
||||
this.$emit(
|
||||
'update:fieldsOrder',
|
||||
this.fields.map(c => c.title)
|
||||
)
|
||||
},
|
||||
async saveOrUpdate(field, i) {
|
||||
if (!this.isPublic && this._isUIAllowed("fieldsSync")) {
|
||||
if (!this.isPublic && this._isUIAllowed('fieldsSync')) {
|
||||
if (field.id) {
|
||||
await this.$api.dbViewColumn.update(this.viewId, field.id, field);
|
||||
await this.$api.dbViewColumn.update(this.viewId, field.id, field)
|
||||
} else {
|
||||
this.fields[i] = await this.$api.dbViewColumn.create(
|
||||
this.viewId,
|
||||
field
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
this.$emit("updated");
|
||||
this.$emit('updated')
|
||||
this.$emit(
|
||||
"input",
|
||||
'input',
|
||||
this.fields.reduce(
|
||||
(o, c) => ({
|
||||
...o,
|
||||
[c.title]: c.show,
|
||||
[c.title]: c.show
|
||||
}),
|
||||
{}
|
||||
)
|
||||
);
|
||||
)
|
||||
this.$emit(
|
||||
"update:fieldsOrder",
|
||||
this.fields.map((c) => c.title)
|
||||
);
|
||||
'update:fieldsOrder',
|
||||
this.fields.map(c => c.title)
|
||||
)
|
||||
|
||||
this.$e("a:fields:show-hide");
|
||||
this.$e('a:fields:show-hide')
|
||||
},
|
||||
async showAll() {
|
||||
if (!this.isPublic) {
|
||||
await this.$api.dbView.showAllColumn(this.viewId);
|
||||
await this.$api.dbView.showAllColumn(this.viewId)
|
||||
}
|
||||
for (const f of this.fields) {
|
||||
f.show = true;
|
||||
f.show = true
|
||||
}
|
||||
this.$emit("updated");
|
||||
this.$emit('updated')
|
||||
|
||||
// eslint-disable-next-line no-return-assign,no-sequences
|
||||
this.showFields = (
|
||||
this.fieldsOrderLoc || Object.keys(this.showFields)
|
||||
).reduce((o, k) => ((o[k] = true), o), {});
|
||||
).reduce((o, k) => ((o[k] = true), o), {})
|
||||
|
||||
this.$e("a:fields:show-all");
|
||||
this.$e('a:fields:show-all')
|
||||
},
|
||||
async hideAll() {
|
||||
if (!this.isPublic) {
|
||||
await this.$api.dbView.hideAllColumn(this.viewId);
|
||||
await this.$api.dbView.hideAllColumn(this.viewId)
|
||||
}
|
||||
for (const f of this.fields) {
|
||||
f.show = false;
|
||||
f.show = false
|
||||
}
|
||||
this.$emit("updated");
|
||||
this.$emit('updated')
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.showFields = (
|
||||
this.fieldsOrderLoc || Object.keys(this.showFields)
|
||||
).reduce((o, k) => ((o[k] = false), o), {});
|
||||
});
|
||||
).reduce((o, k) => ((o[k] = false), o), {})
|
||||
})
|
||||
|
||||
this.$e("a:fields:hide-all");
|
||||
this.$e('a:fields:hide-all')
|
||||
},
|
||||
onMove(event) {
|
||||
if (this.fields.length - 1 === event.moved.newIndex) {
|
||||
this.$set(
|
||||
this.fields[event.moved.newIndex],
|
||||
"order",
|
||||
'order',
|
||||
this.fields[event.moved.newIndex - 1].order + 1
|
||||
);
|
||||
)
|
||||
} else if (event.moved.newIndex === 0) {
|
||||
this.$set(
|
||||
this.fields[event.moved.newIndex],
|
||||
"order",
|
||||
'order',
|
||||
this.fields[1].order / 2
|
||||
);
|
||||
)
|
||||
} else {
|
||||
this.$set(
|
||||
this.fields[event.moved.newIndex],
|
||||
"order",
|
||||
'order',
|
||||
(this.fields[event.moved.newIndex - 1].order +
|
||||
this.fields[event.moved.newIndex + 1].order) /
|
||||
2
|
||||
);
|
||||
)
|
||||
}
|
||||
this.saveOrUpdate(
|
||||
this.fields[event.moved.newIndex],
|
||||
event.moved.newIndex
|
||||
);
|
||||
this.$e("a:fields:reorder");
|
||||
},
|
||||
},
|
||||
};
|
||||
)
|
||||
this.$e('a:fields:reorder')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -273,7 +273,7 @@
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div v-if="!isSharedBase">
|
||||
<v-btn
|
||||
v-t="['c:snippet:open']"
|
||||
color="primary"
|
||||
@@ -286,7 +286,7 @@
|
||||
</v-btn>
|
||||
<code-snippet v-model="codeSnippetModal" :query-params="queryParams" :meta="meta" :view="selectedView" />
|
||||
</div>
|
||||
<div>
|
||||
<div v-if="_isUIAllowed('webhook')" class="mb-2">
|
||||
<v-btn
|
||||
v-t="['c:actions:webhook']"
|
||||
color="primary"
|
||||
@@ -302,7 +302,7 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="time - $store.state.settings.miniSponsorCard > 15 * 60 * 1000"
|
||||
v-if="!isSharedBase && time - $store.state.settings.miniSponsorCard > 15 * 60 * 1000"
|
||||
class="py-2 sponsor-wrapper"
|
||||
>
|
||||
<v-icon small class="close-icon" @click="hideMiniSponsorCard">
|
||||
@@ -310,7 +310,7 @@
|
||||
</v-icon>
|
||||
|
||||
<!-- <extras />-->
|
||||
<v-divider class="my-2" />
|
||||
<v-divider class="mb-2" />
|
||||
|
||||
<extras class="pl-1" />
|
||||
<v-btn
|
||||
@@ -326,113 +326,6 @@
|
||||
|
||||
<!-- <sponsor-mini nav />-->
|
||||
</div>
|
||||
<!--<div class="text-center">
|
||||
<v-hover >
|
||||
<template v-slot:default="{hover}">
|
||||
<v-btn
|
||||
:color="hover ?'primary' : 'grey'" class="mb-2" small outlined href="https://github.com/sponsors/nocodb"
|
||||
target="_blank">
|
||||
<v-icon small color="red" class="mr-2">mdi-heart-outline</v-icon>
|
||||
Sponsor Us
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-hover>
|
||||
</div>
|
||||
-->
|
||||
<!-- <div v-if="_isUIAllowed('table-advanced')">
|
||||
<v-divider />
|
||||
<v-list
|
||||
dense
|
||||
:class="{
|
||||
'advanced-border': overAdvShieldIcon,
|
||||
}"
|
||||
>
|
||||
<v-list-item dense>
|
||||
<span
|
||||
class="body-2 font-weight-medium"
|
||||
@dblclick="$emit('update:showAdvanceOptions', !showAdvanceOptions)"
|
||||
>Advanced</span>
|
||||
<v-tooltip top>
|
||||
<template #activator="{ on }">
|
||||
<x-icon
|
||||
color="pink textColor"
|
||||
icon-class="ml-2"
|
||||
small
|
||||
v-on="on"
|
||||
@mouseenter="overAdvShieldIcon = true"
|
||||
@mouseleave="overAdvShieldIcon = false"
|
||||
>
|
||||
mdi-shield-lock-outline
|
||||
</x-icon>
|
||||
</template>
|
||||
<span class="caption">
|
||||
<!– Only visible to Creator –>
|
||||
{{ $t('msg.info.onlyCreator') }}
|
||||
</span>
|
||||
</v-tooltip>
|
||||
</v-list-item>
|
||||
<!– <v-tooltip bottom>–>
|
||||
<!– <template v-slot:activator="{on}">–>
|
||||
<!– <v-menu offset-x left>–>
|
||||
<!– <template v-slot:activator="{on}">–>
|
||||
|
||||
<!–
|
||||
TODO:
|
||||
- Add selectedView.show_as === 'kanban' when it is ready
|
||||
–>
|
||||
<!– <v-list-item
|
||||
v-show="
|
||||
selectedView && (selectedView.type === 'view' || selectedView.type === 'table' || selectedView.show_as === 'form' || selectedView.show_as === 'grid' )
|
||||
"
|
||||
v-if="_isUIAllowed('shareview')"
|
||||
@click="genShareLink"
|
||||
>
|
||||
<v-icon x-small class="mr-2 nc-share-view">
|
||||
mdi-open-in-new
|
||||
</v-icon>
|
||||
<span class="caption">
|
||||
<!– Share View –>
|
||||
{{ $t('activity.shareView') }}
|
||||
</span>
|
||||
<v-spacer />
|
||||
<v-menu offset-y>
|
||||
<template #activator="{ on }">
|
||||
<v-icon small @click.stop v-on="on">
|
||||
mdi-dots-vertical
|
||||
</v-icon>
|
||||
</template>
|
||||
<v-list dense>
|
||||
<v-list-item dense @click="$emit('showAdditionalFeatOverlay', 'shared-views')">
|
||||
<v-list-item-title>
|
||||
<span class="font-weight-regular">
|
||||
<!– Views List –>
|
||||
{{ $t('activity.ListView') }}
|
||||
</span>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-list-item>–>
|
||||
|
||||
<!– <v-tooltip bottom>–>
|
||||
<!– <template #activator="{ on }">–>
|
||||
<!– <v-list-item v-on="on" @click="copyapiUrlToClipboard">–>
|
||||
<!– <v-icon x-small class="mr-2">–>
|
||||
<!– mdi-content-copy–>
|
||||
<!– </v-icon>–>
|
||||
<!– <!– Copy API URL –>–>
|
||||
<!– <span class="caption">{{ $t('activity.ListView') }}</span>–>
|
||||
<!– </v-list-item>–>
|
||||
<!– </template>–>
|
||||
<!– <!– Copy API URL –>–>
|
||||
<!– {{ $t('activity.ListView') }}–>
|
||||
<!– </v-tooltip>–>
|
||||
<template v-if="_isUIAllowed('model')">
|
||||
<!– <v-divider class="advance-menu-divider" />–>
|
||||
<slot />
|
||||
</template>
|
||||
</v-list>
|
||||
</div>-->
|
||||
</div>
|
||||
</v-container>
|
||||
|
||||
@@ -620,6 +513,9 @@ export default {
|
||||
}
|
||||
}),
|
||||
computed: {
|
||||
isSharedBase() {
|
||||
return this.$route.params && this.$route.params.shared_base_id
|
||||
},
|
||||
viewsList: {
|
||||
set(v) {
|
||||
this.$emit('update:views', v)
|
||||
|
||||
@@ -36,10 +36,9 @@
|
||||
{{ rollupIcon }}
|
||||
</v-icon>
|
||||
</template>
|
||||
<span v-on="on">
|
||||
<span class="name flex-grow-1" style="white-space: nowrap" :title="column.title" v-html="alias" />
|
||||
<span v-if="column.rqd || required" class="error--text text--lighten-1"> *</span>
|
||||
</span>
|
||||
|
||||
<span class="name" style="white-space: nowrap" :title="column.title" v-on="on" v-html="alias" />
|
||||
<span v-if="column.rqd || required" class="error--text text--lighten-1" v-on="on"> *</span>
|
||||
</template>
|
||||
<span class="caption" v-html="tooltipMsg" />
|
||||
</v-tooltip>
|
||||
@@ -258,9 +257,9 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
.name {
|
||||
max-width: calc(100% - 40px);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
<!--
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<span
|
||||
v-for="v in (value||'').split(',')"
|
||||
v-for="v in [(value||'').replace(/\\'/g, '\'').replace(/^'|'$/g, '')]"
|
||||
:key="v"
|
||||
:style="{
|
||||
background:colors[v]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-chip
|
||||
v-for="v in (value || '').split(',')"
|
||||
v-for="v in selectedValues"
|
||||
v-show="v || setValues.includes(v)"
|
||||
:key="v"
|
||||
small
|
||||
@@ -26,6 +26,9 @@ export default {
|
||||
return this.column.dtxp.split(',').map(v => v.replace(/\\'/g, '\'').replace(/^'|'$/g, ''))
|
||||
}
|
||||
return []
|
||||
},
|
||||
selectedValues() {
|
||||
return this.value ? this.value.split(',').map(v => v.replace(/\\'/g, '\'').replace(/^'|'$/g, '')) : []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
v-if="isImage(item.title)"
|
||||
lazy-src="https://via.placeholder.com/60.png?text=Loading..."
|
||||
alt="#"
|
||||
max-height="33px"
|
||||
max-height="99px"
|
||||
contain
|
||||
:src="item.url || item.data"
|
||||
v-on="on"
|
||||
@@ -418,15 +418,15 @@ export default {
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
height: 33px;
|
||||
width: 33px;
|
||||
height: 99px;
|
||||
width: 99px;
|
||||
margin: 2px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.thumbnail img {
|
||||
/*max-height: 33px;*/
|
||||
max-width: 33px;
|
||||
max-width: 99px;
|
||||
}
|
||||
|
||||
.main {
|
||||
|
||||
@@ -50,7 +50,7 @@ export default {
|
||||
computed: {
|
||||
localState: {
|
||||
get() {
|
||||
return this.value
|
||||
return this.value && this.value.replace(/\\'/g, '\'').replace(/^'|'$/g, '')
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('input', val)
|
||||
@@ -59,7 +59,9 @@ export default {
|
||||
},
|
||||
enumValues() {
|
||||
if (this.column && this.column.dtxp) {
|
||||
return this.column.dtxp.split(',').map(v => v.replace(/\\'/g, '\'').replace(/^'|'$/g, ''))
|
||||
return this.column.dtxp
|
||||
.split(',')
|
||||
.map(v => v.replace(/\\'/g, '\'').replace(/^'|'$/g, ''))
|
||||
}
|
||||
return []
|
||||
},
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
<v-combobox
|
||||
v-model="localState"
|
||||
:items="setValues"
|
||||
@@ -52,7 +51,9 @@ export default {
|
||||
computed: {
|
||||
localState: {
|
||||
get() {
|
||||
return this.value && this.value.split(',')
|
||||
return this.value && this.value
|
||||
.match(/(?:[^',]|\\')+(?='?(?:,|$))/g)
|
||||
.map(v => v.replace(/\\'/g, '\''))
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('input', val.filter(v => this.setValues.includes(v)).join(','))
|
||||
@@ -61,7 +62,9 @@ export default {
|
||||
},
|
||||
setValues() {
|
||||
if (this.column && this.column.dtxp) {
|
||||
return this.column.dtxp.split(',').map(v => v.replace(/\\'/g, '\'').replace(/^'|'$/g, ''))
|
||||
return this.column.dtxp
|
||||
.match(/(?:[^']|\\')+(?='?(?:,|$))/g)
|
||||
.map(v => v.replace(/\\'/g, '\'').replace(/^'|'$/g, ''))
|
||||
}
|
||||
return []
|
||||
},
|
||||
|
||||
@@ -139,8 +139,8 @@ export default {
|
||||
if (this.showSystemFields) {
|
||||
return this.meta.columns || []
|
||||
} else {
|
||||
return this.meta.columns.filter(c => !(c.pk && c.ai) && !hideCols.includes(c.column_name) &&
|
||||
!((this.meta.v || []).some(v => v.bt && v.bt.column_name === c.column_name))
|
||||
return this.meta.columns.filter(c => !(c.pk && c.ai) && !hideCols.includes(c.title) &&
|
||||
!((this.meta.v || []).some(v => v.bt && v.bt.title === c.title))
|
||||
) || []
|
||||
}
|
||||
}
|
||||
@@ -166,10 +166,10 @@ export default {
|
||||
},
|
||||
getCovers(row) {
|
||||
if (this.attachmentColumn &&
|
||||
row[this.attachmentColumn.column_name] && row[this.attachmentColumn.column_name][0] &&
|
||||
row[this.attachmentColumn.column_name]) {
|
||||
row[this.attachmentColumn.title] && row[this.attachmentColumn.title][0] &&
|
||||
row[this.attachmentColumn.title]) {
|
||||
try {
|
||||
return JSON.parse(row[this.attachmentColumn.column_name])
|
||||
return JSON.parse(row[this.attachmentColumn.title])
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
|
||||
@@ -131,7 +131,8 @@ export default {
|
||||
response: {},
|
||||
perf: {},
|
||||
meta: {}
|
||||
}
|
||||
},
|
||||
tab: 0
|
||||
}),
|
||||
computed: {
|
||||
|
||||
|
||||
@@ -255,8 +255,13 @@ export default {
|
||||
meta: Object
|
||||
},
|
||||
data: () => ({
|
||||
notification: {},
|
||||
loading: false,
|
||||
notification: {
|
||||
method: 'POST',
|
||||
body: '{{ json data }}'
|
||||
},
|
||||
hook: {
|
||||
title: 'Webhook',
|
||||
notification: {
|
||||
type: 'URL'
|
||||
}
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
<template>
|
||||
<nc-slider v-model="webhookSlider">
|
||||
<v-card
|
||||
v-if="webhookSlider"
|
||||
width="100%"
|
||||
min-height="350px"
|
||||
class="pa-4 elevation-0"
|
||||
>
|
||||
<webhook-editor v-if="editOrAdd" ref="editor" :meta="meta" @backToList="editOrAdd = false" />
|
||||
<webhook-list v-else :meta="meta" @edit="editHook" @add="editOrAdd = true" />
|
||||
</v-card>
|
||||
<div style="min-height:calc(100vh - 32px)" class="d-flex flex-column">
|
||||
<v-card
|
||||
v-if="webhookSlider"
|
||||
width="100%"
|
||||
min-height="350px"
|
||||
class="pa-4 elevation-0"
|
||||
>
|
||||
<webhook-editor v-if="editOrAdd" ref="editor" :meta="meta" @backToList="editOrAdd = false" />
|
||||
<webhook-list v-else :meta="meta" @edit="editHook" @add="editOrAdd = true" />
|
||||
</v-card>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
v-t="['e:hiring']"
|
||||
color="primary"
|
||||
outlined
|
||||
class="caption my-2 mx-auto"
|
||||
href="https://angel.co/company/nocodb"
|
||||
target="_blank"
|
||||
>
|
||||
🚀 We are Hiring! 🚀
|
||||
</v-btn>
|
||||
</div>
|
||||
</nc-slider>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -197,7 +197,7 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
'table.alias'(v) {
|
||||
this.$set(this.table, 'name', `${this.projectPrefix || ''}${inflection.underscore(v)}`)
|
||||
this.$set(this.table, 'name', `${this.projectPrefix || ''}${inflection.underscore(v.replace(/^\s+|\s+$/g, m => new Array(m.length).fill('_').join('')))}`)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -214,8 +214,8 @@ export default {
|
||||
methods: {
|
||||
populateDefaultTitle() {
|
||||
let c = 1
|
||||
while (this.tables.some(t => t.title === `sheet${c}`)) { c++ }
|
||||
this.$set(this.table, 'alias', `sheet${c}`)
|
||||
while (this.tables.some(t => t.title === `Sheet${c}`)) { c++ }
|
||||
this.$set(this.table, 'alias', `Sheet${c}`)
|
||||
},
|
||||
validateTableName(v) {
|
||||
return validateTableName(v, this.$store.getters['project/GtrProjectIsGraphql'])
|
||||
@@ -223,6 +223,9 @@ export default {
|
||||
validateDuplicateAlias(v) {
|
||||
return (this.tables || []).every(t => t.title !== (v || '')) || 'Duplicate table alias'
|
||||
},
|
||||
validateLedingOrTrailingWhiteSpace(v) {
|
||||
return !/^\s+|\s+$/.test(v) || 'Leading or trailing whitespace not allowed in table name'
|
||||
},
|
||||
validateDuplicate(v) {
|
||||
return (this.tables || []).every(t => t.table_name.toLowerCase() !== (v || '').toLowerCase()) || 'Duplicate table name'
|
||||
},
|
||||
|
||||
7
packages/nc-gui/helpers/weAreHiring.js
Normal file
7
packages/nc-gui/helpers/weAreHiring.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export default function weAreHiring() {
|
||||
const fn = () => {
|
||||
console.log('%c🚀 We are Hiring!!! 🚀%c\n%cJoin the forces http://careers.nocodb.com', 'color:#1348ba;font-size:3rem;padding:20px;', 'display:none', 'font-size:1.5rem;padding:20px')
|
||||
}
|
||||
fn()
|
||||
setInterval(fn, 300000)
|
||||
}
|
||||
@@ -342,6 +342,7 @@ import Loader from '~/components/Loader'
|
||||
import PreviewAs from '~/components/PreviewAs'
|
||||
import ShareOrInviteModal from '~/components/auth/ShareOrInviteModal'
|
||||
import ImportantAnnouncement from '../components/ImportantAnnouncement.vue'
|
||||
import weAreHiring from '~/helpers/weAreHiring'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -448,6 +449,9 @@ export default {
|
||||
this.selectedEnv = this.$store.getters['project/GtrActiveEnv']
|
||||
this.loadProjectInfo()
|
||||
},
|
||||
created() {
|
||||
weAreHiring()
|
||||
},
|
||||
methods: {
|
||||
...mapActions({ changeActiveTab: 'tabs/changeActiveTab' }),
|
||||
...mapMutations({
|
||||
|
||||
@@ -538,6 +538,21 @@
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<v-divider />
|
||||
<v-list-item
|
||||
v-t="['e:hiring']"
|
||||
dense
|
||||
target="_blank"
|
||||
href="http://careers.nocodb.com"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
<span class="ml-2" style="font-size:20px">🚀</span>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-title>
|
||||
We are Hiring!!!
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import theme from '@nuxt/content-theme-docs'
|
||||
import path from 'path'
|
||||
|
||||
export default theme({
|
||||
docs: {
|
||||
@@ -6,6 +7,9 @@ export default theme({
|
||||
},
|
||||
css: [
|
||||
"./assets/main.css"
|
||||
],
|
||||
plugins: [
|
||||
{src: path.join(__dirname, 'plugins','nc.js'), ssr:false}
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
37
packages/noco-docs/plugins/nc.js
Normal file
37
packages/noco-docs/plugins/nc.js
Normal file
@@ -0,0 +1,37 @@
|
||||
console.log('%c🚀 We are Hiring!!! 🚀%c\n%cJoin the forces http://careers.nocodb.com', 'color:#1348ba;font-size:3rem;padding:20px;', 'display:none', 'font-size:1.5rem;padding:20px')
|
||||
|
||||
export default () => {
|
||||
const linkEl = document.createElement('a')
|
||||
linkEl.setAttribute('href', "http://careers.nocodb.com")
|
||||
linkEl.setAttribute('target', '_blank')
|
||||
linkEl.setAttribute('class', 'we-are-hiring')
|
||||
linkEl.innerHTML = '🚀 We are Hiring!!! 🚀'
|
||||
const styleEl = document.createElement('style');
|
||||
styleEl.innerHTML = `
|
||||
.we-are-hiring {
|
||||
position: fixed;
|
||||
bottom: 50px;
|
||||
right: -250px;
|
||||
opacity: 0;
|
||||
background: orange;
|
||||
border-radius: 4px;
|
||||
padding: 19px;
|
||||
z-index: 200;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
color: black;
|
||||
transition: 1s opacity, 1s right;
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.we-are-hiring.active {
|
||||
opacity: 1;
|
||||
right:25px;
|
||||
}
|
||||
`
|
||||
document.body.appendChild(linkEl, document.body.firstChild)
|
||||
document.body.appendChild(styleEl, document.body.firstChild)
|
||||
setTimeout(() => linkEl.classList.add('active'), 2000)
|
||||
|
||||
}
|
||||
@@ -84,6 +84,9 @@ export function substituteColumnIdWithAliasInFormula(
|
||||
c.title === colNameOrId
|
||||
);
|
||||
pt.name = column?.title || ptRaw?.name || pt?.name;
|
||||
if (pt.name[0] != '$' && pt.name[pt.name.length - 1] != '$') {
|
||||
pt.name = '$' + pt.name + '$';
|
||||
}
|
||||
} else if (pt.type === 'BinaryExpression') {
|
||||
substituteId(pt.left, ptRaw?.left);
|
||||
substituteId(pt.right, ptRaw?.right);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import UITypes from "../UITypes";
|
||||
import UITypes from '../UITypes';
|
||||
|
||||
import { MssqlUi } from './MssqlUi';
|
||||
import { MysqlUi } from './MysqlUi';
|
||||
@@ -23,8 +23,6 @@ export class SqlUiFactory {
|
||||
// if (connectionConfig.meta.dbtype === "vitess")
|
||||
// return Vitess;
|
||||
|
||||
console.log('- - - -In Mysql UI');
|
||||
|
||||
return MysqlUi;
|
||||
}
|
||||
|
||||
@@ -68,7 +66,7 @@ export type SqlUIColumn = {
|
||||
uidt?: UITypes;
|
||||
uip?: string;
|
||||
uicn?: string;
|
||||
altered?:number;
|
||||
altered?: number;
|
||||
};
|
||||
/**
|
||||
* @copyright Copyright (c) 2021, Xgene Cloud Ltd
|
||||
|
||||
1612
packages/nocodb/package-lock.json
generated
1612
packages/nocodb/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -70,6 +70,7 @@
|
||||
"watch:run": "cross-env NC_DISABLE_TELE1=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/example/docker --log-error --project tsconfig.json\"",
|
||||
"watch:run:cypress": "cross-env EE=true nodemon -e ts,js -w ./src -x \"ts-node src/example/docker --log-error --project tsconfig.json\"",
|
||||
"watch:run:mysql": "cross-env NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/example/dockerRunMysql --log-error --project tsconfig.json\"",
|
||||
"watch:run:pg": "cross-env NC_DISABLE_TELE=true EE=true nodemon -e ts,js -w ./src -x \"ts-node src/example/dockerRunPG --log-error --project tsconfig.json\"",
|
||||
"run": "ts-node src/example/docker",
|
||||
"watch:try": "nodemon -e ts,js -w ./src -x \"ts-node src/example/try --log-error --project tsconfig.json\"",
|
||||
"example:docker": "ts-node ./src/example/docker.ts"
|
||||
@@ -119,7 +120,6 @@
|
||||
"emittery": "^0.7.1",
|
||||
"express": "^4.17.1",
|
||||
"express-graphql": "^0.11.0",
|
||||
"express-status-monitor": "^1.3.3",
|
||||
"extract-zip": "^2.0.1",
|
||||
"fast-levenshtein": "^2.0.6",
|
||||
"fs-extra": "^9.0.1",
|
||||
@@ -152,7 +152,7 @@
|
||||
"mysql2": "^2.2.5",
|
||||
"nanoid": "^3.1.20",
|
||||
"nc-common": "0.0.6",
|
||||
"nc-help": "0.2.49",
|
||||
"nc-help": "0.2.56",
|
||||
"nc-lib-gui": "0.90.11",
|
||||
"nc-plugin": "^0.1.1",
|
||||
"ncp": "^2.0.0",
|
||||
|
||||
59
packages/nocodb/src/example/dockerRunPG.ts
Normal file
59
packages/nocodb/src/example/dockerRunPG.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import cors from 'cors';
|
||||
import express from 'express';
|
||||
|
||||
import Noco from '../lib/noco/Noco';
|
||||
process.env.NC_VERSION = '0009044';
|
||||
|
||||
const server = express();
|
||||
server.use(
|
||||
cors({
|
||||
exposedHeaders: 'xc-db-response'
|
||||
})
|
||||
);
|
||||
|
||||
server.set('view engine', 'ejs');
|
||||
|
||||
const date = new Date();
|
||||
const metaDb = `meta_v2_${date.getFullYear()}_${(date.getMonth() + 1)
|
||||
.toString()
|
||||
.padStart(2, '0')}_${date
|
||||
.getDate()
|
||||
.toString()
|
||||
.padStart(2, '0')}`;
|
||||
// process.env[`NC_DB`] = `mysql2://localhost:3306?u=root&p=password&d=${metaDb}`;
|
||||
// process.env[`NC_DB`] = `pg:/2/localhost:3306?u=root&p=password&d=mar_24`;
|
||||
process.env[`NC_DB`] = `pg://localhost:5432?u=postgres&p=password&d=${metaDb}`;
|
||||
// process.env[`NC_TRY`] = 'true';
|
||||
// process.env[`NC_DASHBOARD_URL`] = '/test';
|
||||
|
||||
process.env[`DEBUG`] = 'xc*';
|
||||
|
||||
(async () => {
|
||||
const httpServer = server.listen(process.env.PORT || 8080, () => {
|
||||
console.log(`App started successfully.\nVisit -> ${Noco.dashboardUrl}`);
|
||||
});
|
||||
server.use(await Noco.init({}, httpServer, server));
|
||||
})().catch(e => console.log(e));
|
||||
|
||||
/**
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
@@ -93,7 +93,9 @@ class BaseModelSqlv2 {
|
||||
|
||||
await this.selectObject({ qb });
|
||||
|
||||
const data = await qb.where(this.model.primaryKey.column_name, id).first();
|
||||
qb.where(this.model.primaryKey.column_name, id);
|
||||
|
||||
const data = (await this.extractRawQueryAndExec(qb))?.[0];
|
||||
|
||||
if (data) {
|
||||
const proto = await this.getProto();
|
||||
@@ -239,7 +241,9 @@ class BaseModelSqlv2 {
|
||||
if (!ignoreFilterSort) applyPaginate(qb, rest);
|
||||
const proto = await this.getProto();
|
||||
|
||||
return (await qb).map(d => {
|
||||
const data = await this.extractRawQueryAndExec(qb);
|
||||
|
||||
return data?.map(d => {
|
||||
d.__proto__ = proto;
|
||||
return d;
|
||||
});
|
||||
@@ -351,7 +355,6 @@ class BaseModelSqlv2 {
|
||||
async multipleHmList({ colId, ids }, args?: { limit?; offset? }) {
|
||||
try {
|
||||
// todo: get only required fields
|
||||
let fields = '*';
|
||||
|
||||
// const { cn } = this.hasManyRelations.find(({ tn }) => tn === child) || {};
|
||||
const relColumn = (await this.model.getColumns()).find(
|
||||
@@ -367,14 +370,6 @@ class BaseModelSqlv2 {
|
||||
dbDriver: this.dbDriver
|
||||
});
|
||||
await parentTable.getColumns();
|
||||
// if (fields !== '*' && fields.split(',').indexOf(cn) === -1) {
|
||||
// fields += ',' + cn;
|
||||
// }
|
||||
|
||||
fields = fields
|
||||
.split(',')
|
||||
.map(c => `${chilCol.column_name}.${c}`)
|
||||
.join(',');
|
||||
|
||||
const qb = this.dbDriver(childTable.table_name);
|
||||
await childModel.selectObject({ qb });
|
||||
@@ -404,8 +399,7 @@ class BaseModelSqlv2 {
|
||||
.as('list')
|
||||
);
|
||||
|
||||
const children = await childQb;
|
||||
|
||||
const children = await this.extractRawQueryAndExec(childQb);
|
||||
const proto = await (
|
||||
await Model.getBaseModelSQL({
|
||||
id: childTable.id,
|
||||
@@ -465,19 +459,8 @@ class BaseModelSqlv2 {
|
||||
|
||||
async hmList({ colId, id }, args?: { limit?; offset? }) {
|
||||
try {
|
||||
// const {
|
||||
// where,
|
||||
// limit,
|
||||
// offset,
|
||||
// conditionGraph,
|
||||
// sort
|
||||
// // ...restArgs
|
||||
// } = this.dbModels[child]._getChildListArgs(args);
|
||||
// let { fields } = restArgs;
|
||||
// todo: get only required fields
|
||||
let fields = '*';
|
||||
|
||||
// const { cn } = this.hasManyRelations.find(({ tn }) => tn === child) || {};
|
||||
const relColumn = (await this.model.getColumns()).find(
|
||||
c => c.id === colId
|
||||
);
|
||||
@@ -491,14 +474,6 @@ class BaseModelSqlv2 {
|
||||
dbDriver: this.dbDriver
|
||||
});
|
||||
await parentTable.getColumns();
|
||||
// if (fields !== '*' && fields.split(',').indexOf(cn) === -1) {
|
||||
// fields += ',' + cn;
|
||||
// }
|
||||
|
||||
fields = fields
|
||||
.split(',')
|
||||
.map(c => `${chilCol.column_name}.${c}`)
|
||||
.join(',');
|
||||
|
||||
const qb = this.dbDriver(childTable.table_name);
|
||||
|
||||
@@ -515,7 +490,7 @@ class BaseModelSqlv2 {
|
||||
|
||||
await childModel.selectObject({ qb });
|
||||
|
||||
const children = await qb;
|
||||
const children = await this.extractRawQueryAndExec(qb);
|
||||
|
||||
const proto = await (
|
||||
await Model.getBaseModelSQL({
|
||||
@@ -610,7 +585,7 @@ class BaseModelSqlv2 {
|
||||
!this.isSqlite
|
||||
);
|
||||
|
||||
const children = await finalQb;
|
||||
const children = await this.extractRawQueryAndExec(finalQb);
|
||||
const proto = await (
|
||||
await Model.getBaseModelSQL({
|
||||
id: rtnId,
|
||||
@@ -663,7 +638,7 @@ class BaseModelSqlv2 {
|
||||
qb.limit(args?.limit || 20);
|
||||
qb.offset(args?.offset || 0);
|
||||
|
||||
const children = await qb;
|
||||
const children = await this.extractRawQueryAndExec(qb);
|
||||
const proto = await (
|
||||
await Model.getBaseModelSQL({ id: rtnId, dbDriver: this.dbDriver })
|
||||
).getProto();
|
||||
@@ -1266,7 +1241,7 @@ class BaseModelSqlv2 {
|
||||
await populatePk(this.model, data);
|
||||
|
||||
// todo: filter based on view
|
||||
const insertObj = await this.model.mapAliasToColumn(data);
|
||||
const insertObj = await this.model.mapAliasToColumn(data, sanitize);
|
||||
|
||||
await this.validate(insertObj);
|
||||
|
||||
@@ -1394,6 +1369,9 @@ class BaseModelSqlv2 {
|
||||
get isPg() {
|
||||
return this.clientType === 'pg';
|
||||
}
|
||||
get isMySQL() {
|
||||
return this.clientType === 'mysql2' || this.clientType === 'mysql';
|
||||
}
|
||||
|
||||
get clientType() {
|
||||
return this.dbDriver.clientType();
|
||||
@@ -2023,6 +2001,14 @@ class BaseModelSqlv2 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async extractRawQueryAndExec(qb: QueryBuilder) {
|
||||
return this.isPg
|
||||
? qb
|
||||
: await this.dbDriver.from(
|
||||
this.dbDriver.raw(qb.toString()).wrap('(', ') __nc_alias')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function extractSortsObject(
|
||||
@@ -2156,8 +2142,8 @@ function getCompositePk(primaryKeys: Column[], row) {
|
||||
return primaryKeys.map(c => row[c.title]).join('___');
|
||||
}
|
||||
|
||||
function sanitize(v) {
|
||||
return v?.replace(/\?/g, '\\?');
|
||||
export function sanitize(v) {
|
||||
return v?.replace(/([^\\]|^)([?])/g, '$1\\$2');
|
||||
}
|
||||
|
||||
export { BaseModelSqlv2 };
|
||||
|
||||
@@ -19,8 +19,8 @@ export default abstract class JobsMgr {
|
||||
) => void,
|
||||
options?: {
|
||||
onSuccess?: (payload: any) => void;
|
||||
onFailure?: (payload: any, msg: string) => void;
|
||||
onProgress?: (payload: any, msgOrData: any) => void;
|
||||
onFailure?: (payload: any, errorData: any) => void;
|
||||
onProgress?: (payload: any, progressData: any) => void;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -29,13 +29,13 @@ export default abstract class JobsMgr {
|
||||
this.successCbks[jobName].push(cbk);
|
||||
}
|
||||
|
||||
addFailureCbk(jobName: string, cbk: (payload: any, msg: string) => void) {
|
||||
addFailureCbk(jobName: string, cbk: (payload: any, errorData: any) => void) {
|
||||
this.failureCbks[jobName] = this.failureCbks[jobName] || [];
|
||||
this.failureCbks[jobName].push(cbk);
|
||||
}
|
||||
addProgressCbk(
|
||||
jobName: string,
|
||||
cbk: (payload: any, progress: string) => void
|
||||
cbk: (payload: any, progressData: any) => void
|
||||
) {
|
||||
this.progressCbks[jobName] = this.progressCbks[jobName] || [];
|
||||
this.progressCbks[jobName].push(cbk);
|
||||
@@ -56,10 +56,10 @@ export default abstract class JobsMgr {
|
||||
protected async invokeProgressCbks(
|
||||
jobName: string,
|
||||
payload: any,
|
||||
msg?: string
|
||||
data?: any
|
||||
) {
|
||||
await Promise.all(
|
||||
this.progressCbks?.[jobName]?.map(cb => cb(payload, msg))
|
||||
this.progressCbks?.[jobName]?.map(cb => cb(payload, data))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,12 +399,13 @@ export default class Model implements TableType {
|
||||
return true;
|
||||
}
|
||||
|
||||
async mapAliasToColumn(data) {
|
||||
async mapAliasToColumn(data, sanitize = v => v) {
|
||||
const insertObj = {};
|
||||
for (const col of await this.getColumns()) {
|
||||
if (isVirtualCol(col)) continue;
|
||||
const val = data?.[col.column_name] ?? data?.[col.title];
|
||||
if (val !== undefined) insertObj[col.column_name] = val;
|
||||
const val =
|
||||
data?.[sanitize(col.column_name)] ?? data?.[sanitize(col.title)];
|
||||
if (val !== undefined) insertObj[sanitize(col.column_name)] = val;
|
||||
}
|
||||
return insertObj;
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ export default class View implements ViewType {
|
||||
));
|
||||
if (!view) {
|
||||
view = await ncMeta.metaGet2(null, null, MetaTable.VIEWS, viewId);
|
||||
await NocoCache.set(`${CacheScope.VIEW}:${viewId}`, view);
|
||||
await NocoCache.set(`${CacheScope.VIEW}:${view.id}`, view);
|
||||
}
|
||||
|
||||
return view && new View(view);
|
||||
@@ -146,6 +146,7 @@ export default class View implements ViewType {
|
||||
]
|
||||
}
|
||||
);
|
||||
// todo: cache - titleOrId can be viewId so we need a different scope here
|
||||
await NocoCache.set(
|
||||
`${CacheScope.VIEW}:${fk_model_id}:${titleOrId}`,
|
||||
view.id
|
||||
@@ -153,7 +154,7 @@ export default class View implements ViewType {
|
||||
await NocoCache.set(`${CacheScope.VIEW}:${fk_model_id}:${view.id}`, view);
|
||||
return view && new View(view);
|
||||
}
|
||||
return viewId && this.get(viewId);
|
||||
return viewId && this.get(viewId?.id || viewId);
|
||||
}
|
||||
|
||||
public static async list(modelId: string, ncMeta = Noco.ncMeta) {
|
||||
|
||||
@@ -40,6 +40,7 @@ import NcPluginMgrv2 from './meta/helpers/NcPluginMgrv2';
|
||||
import User from '../noco-models/User';
|
||||
import { Tele } from 'nc-help';
|
||||
import * as http from 'http';
|
||||
import weAreHiring from '../utils/weAreHiring';
|
||||
|
||||
const log = debug('nc:app');
|
||||
require('dotenv').config();
|
||||
@@ -95,10 +96,6 @@ export default class Noco {
|
||||
private config: NcConfig;
|
||||
private requestContext: any;
|
||||
|
||||
private io: any;
|
||||
// @ts-ignore
|
||||
private socketClient: any;
|
||||
|
||||
constructor() {
|
||||
process.env.PORT = process.env.PORT || '8080';
|
||||
// todo: move
|
||||
@@ -178,8 +175,6 @@ export default class Noco {
|
||||
this.initSentry();
|
||||
NocoCache.init();
|
||||
|
||||
this.initWebSocket();
|
||||
|
||||
// this.apiInfInfoList = [];
|
||||
//
|
||||
// this.startTime = Date.now();
|
||||
@@ -269,6 +264,7 @@ export default class Noco {
|
||||
next();
|
||||
});
|
||||
Tele.emit('evt_app_started', await User.count());
|
||||
weAreHiring();
|
||||
return this.router;
|
||||
}
|
||||
|
||||
@@ -488,71 +484,6 @@ export default class Noco {
|
||||
}
|
||||
}
|
||||
|
||||
private initWebSocket(): void {
|
||||
// todo: Auth
|
||||
|
||||
this.router.get(`${this.config.dashboardPath}/demo`, (_req, res) => {
|
||||
(Noco._ncMeta as any).updateKnex({
|
||||
client: 'sqlite3',
|
||||
connection: {
|
||||
filename: 'xcDemo.db'
|
||||
}
|
||||
});
|
||||
|
||||
res.json({ msg: 'done' });
|
||||
});
|
||||
|
||||
this.io = require('socket.io')();
|
||||
this.io.listen(8083);
|
||||
this.io.on('connection', client => {
|
||||
this.socketClient = client;
|
||||
|
||||
client.on('disconnect', () => {
|
||||
this.socketClient = null;
|
||||
});
|
||||
});
|
||||
|
||||
const statusMonitor = require('express-status-monitor')({
|
||||
websocket: this.io,
|
||||
port: 8083
|
||||
});
|
||||
|
||||
this.router.use(statusMonitor);
|
||||
this.router.get(
|
||||
`${this.config.dashboardPath}/status`,
|
||||
statusMonitor.pageRoute
|
||||
);
|
||||
|
||||
/*
|
||||
title: 'Express Status', // Default title
|
||||
theme: 'default.css', // Default styles
|
||||
path: '/status',
|
||||
socketPath: '/socket.io', // In case you use a custom path
|
||||
websocket: existingSocketIoInstance,
|
||||
spans: [{
|
||||
interval: 1, // Every second
|
||||
retention: 60 // Keep 60 datapoints in memory
|
||||
}, {
|
||||
interval: 5, // Every 5 seconds
|
||||
retention: 60
|
||||
}, {
|
||||
interval: 15, // Every 15 seconds
|
||||
retention: 60
|
||||
}],
|
||||
chartVisibility: {
|
||||
cpu: true,
|
||||
mem: true,
|
||||
load: true,
|
||||
eventLoop: true,
|
||||
heap: true,
|
||||
responseTime: true,
|
||||
rps: true,
|
||||
statusCodes: true
|
||||
},
|
||||
healthChecks: [],
|
||||
ignoreStartsWith: '/admin'*/
|
||||
}
|
||||
|
||||
private async readOrGenJwtSecret(): Promise<any> {
|
||||
if (this.config?.auth?.jwt && !this.config.auth.jwt.secret) {
|
||||
let secret = (
|
||||
|
||||
@@ -508,6 +508,11 @@ export async function columnAdd(req: Request, res: Response<TableType>) {
|
||||
await Column.insert({
|
||||
...colBody,
|
||||
...insertedColumnMeta,
|
||||
dtxp: [UITypes.MultiSelect, UITypes.SingleSelect].includes(
|
||||
colBody.uidt as any
|
||||
)
|
||||
? colBody.dtxp
|
||||
: insertedColumnMeta.dtxp,
|
||||
fk_model_id: table.id
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ export default `<!DOCTYPE html>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
||||
|
||||
<!--
|
||||
Redoc doesn't change outer page styles
|
||||
-->
|
||||
@@ -20,5 +19,40 @@ export default `<!DOCTYPE html>
|
||||
<body>
|
||||
<redoc spec-url='./swagger.json'></redoc>
|
||||
<script src="https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js"> </script>
|
||||
<script>
|
||||
console.log('%c🚀 We are Hiring!!! 🚀%c\\n%cJoin the forces http://careers.nocodb.com', 'color:#1348ba;font-size:3rem;padding:20px;', 'display:none', 'font-size:1.5rem;padding:20px')
|
||||
const linkEl = document.createElement('a')
|
||||
linkEl.setAttribute('href', "http://careers.nocodb.com")
|
||||
linkEl.setAttribute('target', '_blank')
|
||||
linkEl.setAttribute('class', 'we-are-hiring')
|
||||
linkEl.innerHTML = '🚀 We are Hiring!!! 🚀'
|
||||
const styleEl = document.createElement('style');
|
||||
styleEl.innerHTML = \`
|
||||
.we-are-hiring {
|
||||
position: fixed;
|
||||
bottom: 50px;
|
||||
right: -250px;
|
||||
opacity: 0;
|
||||
background: orange;
|
||||
border-radius: 4px;
|
||||
padding: 19px;
|
||||
z-index: 200;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
color: black;
|
||||
transition: 1s opacity, 1s right;
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.we-are-hiring.active {
|
||||
opacity: 1;
|
||||
right:25px;
|
||||
}
|
||||
\`
|
||||
document.body.appendChild(linkEl, document.body.firstChild)
|
||||
document.body.appendChild(styleEl, document.body.firstChild)
|
||||
setTimeout(() => linkEl.classList.add('active'), 2000)
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
@@ -20,6 +20,39 @@ export default `<!DOCTYPE html>
|
||||
SwaggerUIBundle.SwaggerUIStandalonePreset
|
||||
],
|
||||
})
|
||||
console.log('%c🚀 We are Hiring!!! 🚀%c\\n%cJoin the forces http://careers.nocodb.com', 'color:#1348ba;font-size:3rem;padding:20px;', 'display:none', 'font-size:1.5rem;padding:20px');
|
||||
const linkEl = document.createElement('a')
|
||||
linkEl.setAttribute('href', "http://careers.nocodb.com")
|
||||
linkEl.setAttribute('target', '_blank')
|
||||
linkEl.setAttribute('class', 'we-are-hiring')
|
||||
linkEl.innerHTML = '🚀 We are Hiring!!! 🚀'
|
||||
const styleEl = document.createElement('style');
|
||||
styleEl.innerHTML = \`
|
||||
.we-are-hiring {
|
||||
position: fixed;
|
||||
bottom: 50px;
|
||||
right: -250px;
|
||||
opacity: 0;
|
||||
background: orange;
|
||||
border-radius: 4px;
|
||||
padding: 19px;
|
||||
z-index: 200;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
color: black;
|
||||
transition: 1s opacity, 1s right;
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.we-are-hiring.active {
|
||||
opacity: 1;
|
||||
right:25px;
|
||||
}
|
||||
\`
|
||||
document.body.appendChild(linkEl, document.body.firstChild)
|
||||
document.body.appendChild(styleEl, document.body.firstChild)
|
||||
setTimeout(() => linkEl.classList.add('active'), 2000)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,13 +20,14 @@ export default (router: Router, clients: { [id: string]: Socket }) => {
|
||||
NocoJobs.jobsMgr.addJobWorker(AIRTABLE_IMPORT_JOB, job);
|
||||
NocoJobs.jobsMgr.addProgressCbk(AIRTABLE_IMPORT_JOB, (payload, progress) => {
|
||||
clients?.[payload?.id]?.emit('progress', {
|
||||
msg: progress,
|
||||
msg: progress?.msg,
|
||||
level: progress?.level,
|
||||
status: SyncStatus.PROGRESS
|
||||
});
|
||||
});
|
||||
NocoJobs.jobsMgr.addSuccessCbk(AIRTABLE_IMPORT_JOB, payload => {
|
||||
clients?.[payload?.id]?.emit('progress', {
|
||||
msg: 'completed',
|
||||
msg: 'Complete!',
|
||||
status: SyncStatus.COMPLETED
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,6 +27,7 @@ import NcConnectionMgrv2 from '../../common/NcConnectionMgrv2';
|
||||
import getColumnUiType from '../helpers/getColumnUiType';
|
||||
import LinkToAnotherRecordColumn from '../../../noco-models/LinkToAnotherRecordColumn';
|
||||
import { metaApiMetrics } from '../helpers/apiMetrics';
|
||||
|
||||
export async function tableGet(req: Request, res: Response<TableType>) {
|
||||
const table = await Model.getWithInfo({
|
||||
id: req.params.tableId
|
||||
@@ -102,6 +103,13 @@ export async function tableCreate(req: Request<any, any, TableReqType>, res) {
|
||||
}
|
||||
}
|
||||
|
||||
// validate table name
|
||||
if (/^\s+|\s+$/.test(req.body.table_name)) {
|
||||
NcError.badRequest(
|
||||
'Leading or trailing whitespace not allowed in table names'
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!(await Model.checkTitleAvailable({
|
||||
table_name: req.body.table_name,
|
||||
@@ -176,6 +184,11 @@ export async function tableCreate(req: Request<any, any, TableReqType>, res) {
|
||||
...colMetaFromReq,
|
||||
uidt: colMetaFromReq?.uidt || c.uidt || getColumnUiType(base, c),
|
||||
...c,
|
||||
dtxp: [UITypes.MultiSelect, UITypes.SingleSelect].includes(
|
||||
colMetaFromReq.uidt as any
|
||||
)
|
||||
? colMetaFromReq.dtxp
|
||||
: c.dtxp,
|
||||
title: colMetaFromReq?.title || getColumnNameAlias(c.cn, base),
|
||||
column_name: c.cn,
|
||||
order: i + 1
|
||||
|
||||
@@ -132,7 +132,6 @@ export function axiosRequestMake(_apiMeta, user, data) {
|
||||
});
|
||||
} catch (e) {
|
||||
apiMeta.body = parseBody(apiMeta.body, user, data, apiMeta);
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
if (apiMeta.auth) {
|
||||
@@ -144,7 +143,6 @@ export function axiosRequestMake(_apiMeta, user, data) {
|
||||
});
|
||||
} catch (e) {
|
||||
apiMeta.auth = parseBody(apiMeta.auth, user, data, apiMeta);
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
apiMeta.response = {};
|
||||
|
||||
@@ -54,6 +54,7 @@ export default {
|
||||
formViewGet: true,
|
||||
projectInfoGet: true,
|
||||
gridColumnUpdate: true,
|
||||
galleryViewGet: true,
|
||||
|
||||
// old
|
||||
xcTableAndViewList: true,
|
||||
@@ -161,6 +162,8 @@ export default {
|
||||
dataGroupBy: true,
|
||||
commentsCount: true,
|
||||
|
||||
galleryViewGet: true,
|
||||
|
||||
xcTableAndViewList: true,
|
||||
xcVirtualTableList: true,
|
||||
projectList: true,
|
||||
@@ -204,6 +207,8 @@ export default {
|
||||
sortList: true,
|
||||
projectInfoGet: true,
|
||||
|
||||
galleryViewGet: true,
|
||||
|
||||
mmList: true,
|
||||
hmList: true,
|
||||
commentList: true,
|
||||
|
||||
13
packages/nocodb/src/lib/utils/weAreHiring.ts
Normal file
13
packages/nocodb/src/lib/utils/weAreHiring.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import boxen from 'boxen';
|
||||
|
||||
export default function() {
|
||||
console.log(`
|
||||
${boxen(`Join the forces http://careers.nocodb.com`, {
|
||||
title: '🚀 We are Hiring!!! 🚀',
|
||||
padding: 1,
|
||||
margin: 1,
|
||||
titleAlignment: 'center',
|
||||
borderColor: 'green'
|
||||
})}
|
||||
`);
|
||||
}
|
||||
@@ -189,7 +189,7 @@ export const genTest = (apiType, dbType) => {
|
||||
// right navigation menu bar
|
||||
// Editor/Viewer/Commenter : can only view 'existing' views
|
||||
// Rest: can create/edit
|
||||
_viewMenu(roleType, false);
|
||||
_viewMenu(roleType, false, 2);
|
||||
});
|
||||
|
||||
it(`[${roles[roleType].name}] Top Right Menu bar`, () => {
|
||||
|
||||
@@ -115,7 +115,7 @@ export const genTest = (apiType, dbType, roleType) => {
|
||||
// right navigation menu bar
|
||||
// Editor/Viewer/Commenter : can only view 'existing' views
|
||||
// Rest: can create/edit
|
||||
_viewMenu(roleType, true);
|
||||
_viewMenu(roleType, true, 2);
|
||||
});
|
||||
|
||||
it(`Role preview: ${roleType}: Top Right Menu bar`, () => {
|
||||
|
||||
@@ -48,7 +48,7 @@ export const genTest = (apiType, dbType) => {
|
||||
});
|
||||
|
||||
it(`${roleType}: Validate access permissions: view's menu`, () => {
|
||||
_viewMenu(roleType, false);
|
||||
_viewMenu(roleType, false, 1);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -212,9 +212,10 @@ export function _editComment(roleType, previewMode) {
|
||||
// right navigation menu bar
|
||||
// Editor/Viewer/Commenter : can only view 'existing' views
|
||||
// Rest: can create/edit
|
||||
export function _viewMenu(roleType, previewMode) {
|
||||
export function _viewMenu(roleType, previewMode, navDrawListCnt) {
|
||||
let columnName = "City";
|
||||
let navDrawListCnt = 2;
|
||||
// let navDrawListCnt = 2;
|
||||
|
||||
// Download CSV
|
||||
let actionsMenuItemsCnt = 1;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user