Browse Source

Merge commit 'cda6ed22642b18dcf02f87949506ce899b10a832' into NCDBOSS-39

pull/5419/head
gitstart 2 years ago
parent
commit
7634a05db0
  1. 1
      README.md
  2. 52
      packages/nc-cli/package-lock.json
  3. 7
      packages/nc-gui/components.d.ts
  4. 16
      packages/nc-gui/components/cell/MultiSelect.vue
  5. 17
      packages/nc-gui/components/dashboard/settings/AuditTab.vue
  6. 2
      packages/nc-gui/components/smartsheet/Pagination.vue
  7. 2
      packages/nc-gui/components/tabs/auth/UserManagement.vue
  8. 2
      packages/nc-gui/components/webhook/CallLog.vue
  9. 21
      packages/nc-gui/composables/useExpandedFormStore.ts
  10. 9
      packages/nc-gui/composables/useKanbanViewStore.ts
  11. 9
      packages/nc-gui/composables/useViewData.ts
  12. 6
      packages/nc-gui/lang/pt_BR.json
  13. 8
      packages/nc-gui/lang/zh-Hans.json
  14. 2
      packages/nc-gui/package-lock.json
  15. 2
      packages/nc-lib-gui/package.json
  16. 5
      packages/noco-docs/content/en/developer-resources/webhooks.md
  17. 4
      packages/nocodb-sdk/package-lock.json
  18. 2
      packages/nocodb-sdk/package.json
  19. 6
      packages/nocodb-sdk/src/lib/Api.ts
  20. 6
      packages/nocodb-sdk/src/lib/globals.ts
  21. 32
      packages/nocodb/package-lock.json
  22. 4
      packages/nocodb/package.json
  23. 9
      packages/nocodb/src/lib/controllers/user/user.ctl.ts
  24. 2
      packages/nocodb/src/lib/db/sql-client/lib/pg/PgClient.ts
  25. 46
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts
  26. 2
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2.ts
  27. 6
      packages/nocodb/src/lib/models/Audit.ts
  28. 18
      packages/nocodb/src/lib/models/Model.ts
  29. 2
      packages/nocodb/src/lib/services/audit.svc.ts
  30. 12
      packages/nocodb/src/lib/services/column.svc.ts
  31. 4
      packages/nocodb/src/lib/services/orgUser.svc.ts
  32. 30
      packages/nocodb/src/lib/services/projectUser.svc.ts
  33. 6
      packages/nocodb/src/lib/services/table.svc.ts
  34. 38
      packages/nocodb/src/lib/services/user/index.ts
  35. 6
      packages/nocodb/src/schema/swagger.json
  36. 22
      packages/nocodb/tests/unit/model/tests/baseModelSql.test.ts
  37. 4
      tests/playwright/tests/tableOperations.spec.ts

1
README.md

@ -15,7 +15,6 @@ Turns any MySQL, PostgreSQL, SQL Server, SQLite & MariaDB into a smart spreadshe
<div align="center"> <div align="center">
[![Build Status](https://travis-ci.org/dwyl/esta.svg?branch=master)](https://travis-ci.com/github/NocoDB/NocoDB)
[![Node version](https://img.shields.io/badge/node-%3E%3D%2016.14.0-brightgreen)](http://nodejs.org/download/) [![Node version](https://img.shields.io/badge/node-%3E%3D%2016.14.0-brightgreen)](http://nodejs.org/download/)
[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-green.svg)](https://conventionalcommits.org) [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-green.svg)](https://conventionalcommits.org)

52
packages/nc-cli/package-lock.json generated

@ -135,19 +135,19 @@
"integrity": "sha512-dG76W7ElfLi+fbTjnZVGj+M9e0BIEJmRxU6fHaUQ12bZBe8EJKYb2GV50YWNaP2uJiVQ5+7nXEVj1VN1UQtaEw==" "integrity": "sha512-dG76W7ElfLi+fbTjnZVGj+M9e0BIEJmRxU6fHaUQ12bZBe8EJKYb2GV50YWNaP2uJiVQ5+7nXEVj1VN1UQtaEw=="
}, },
"node_modules/@azure/ms-rest-js": { "node_modules/@azure/ms-rest-js": {
"version": "2.6.0", "version": "2.6.6",
"resolved": "https://registry.npmjs.org/@azure/ms-rest-js/-/ms-rest-js-2.6.0.tgz", "resolved": "https://registry.npmjs.org/@azure/ms-rest-js/-/ms-rest-js-2.6.6.tgz",
"integrity": "sha512-4C5FCtvEzWudblB+h92/TYYPiq7tuElX8icVYToxOdggnYqeec4Se14mjse5miInKtZahiFHdl8lZA/jziEc5g==", "integrity": "sha512-WYIda8VvrkZE68xHgOxUXvjThxNf1nnGPPe0rAljqK5HJHIZ12Pi3YhEDOn3Ge7UnwaaM3eFO0VtAy4nGVI27Q==",
"dependencies": { "dependencies": {
"@azure/core-auth": "^1.1.4", "@azure/core-auth": "^1.1.4",
"abort-controller": "^3.0.0", "abort-controller": "^3.0.0",
"form-data": "^2.5.0", "form-data": "^2.5.0",
"node-fetch": "^2.6.0", "node-fetch": "^2.6.7",
"tough-cookie": "^3.0.1", "tough-cookie": "^3.0.1",
"tslib": "^1.10.0", "tslib": "^1.10.0",
"tunnel": "0.0.6", "tunnel": "0.0.6",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"xml2js": "^0.4.19" "xml2js": "^0.5.0"
} }
}, },
"node_modules/@azure/ms-rest-js/node_modules/tslib": { "node_modules/@azure/ms-rest-js/node_modules/tslib": {
@ -9692,14 +9692,22 @@
"dev": true "dev": true
}, },
"node_modules/node-fetch": { "node_modules/node-fetch": {
"version": "2.6.6", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
"integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==",
"dependencies": { "dependencies": {
"whatwg-url": "^5.0.0" "whatwg-url": "^5.0.0"
}, },
"engines": { "engines": {
"node": "4.x || >=6.0.0" "node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
} }
}, },
"node_modules/node-releases": { "node_modules/node-releases": {
@ -15416,9 +15424,9 @@
} }
}, },
"node_modules/xml2js": { "node_modules/xml2js": {
"version": "0.4.23", "version": "0.5.0",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
"integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
"dependencies": { "dependencies": {
"sax": ">=0.6.0", "sax": ">=0.6.0",
"xmlbuilder": "~11.0.0" "xmlbuilder": "~11.0.0"
@ -15576,19 +15584,19 @@
"integrity": "sha512-dG76W7ElfLi+fbTjnZVGj+M9e0BIEJmRxU6fHaUQ12bZBe8EJKYb2GV50YWNaP2uJiVQ5+7nXEVj1VN1UQtaEw==" "integrity": "sha512-dG76W7ElfLi+fbTjnZVGj+M9e0BIEJmRxU6fHaUQ12bZBe8EJKYb2GV50YWNaP2uJiVQ5+7nXEVj1VN1UQtaEw=="
}, },
"@azure/ms-rest-js": { "@azure/ms-rest-js": {
"version": "2.6.0", "version": "2.6.6",
"resolved": "https://registry.npmjs.org/@azure/ms-rest-js/-/ms-rest-js-2.6.0.tgz", "resolved": "https://registry.npmjs.org/@azure/ms-rest-js/-/ms-rest-js-2.6.6.tgz",
"integrity": "sha512-4C5FCtvEzWudblB+h92/TYYPiq7tuElX8icVYToxOdggnYqeec4Se14mjse5miInKtZahiFHdl8lZA/jziEc5g==", "integrity": "sha512-WYIda8VvrkZE68xHgOxUXvjThxNf1nnGPPe0rAljqK5HJHIZ12Pi3YhEDOn3Ge7UnwaaM3eFO0VtAy4nGVI27Q==",
"requires": { "requires": {
"@azure/core-auth": "^1.1.4", "@azure/core-auth": "^1.1.4",
"abort-controller": "^3.0.0", "abort-controller": "^3.0.0",
"form-data": "^2.5.0", "form-data": "^2.5.0",
"node-fetch": "^2.6.0", "node-fetch": "^2.6.7",
"tough-cookie": "^3.0.1", "tough-cookie": "^3.0.1",
"tslib": "^1.10.0", "tslib": "^1.10.0",
"tunnel": "0.0.6", "tunnel": "0.0.6",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"xml2js": "^0.4.19" "xml2js": "^0.5.0"
}, },
"dependencies": { "dependencies": {
"tslib": { "tslib": {
@ -22944,9 +22952,9 @@
"dev": true "dev": true
}, },
"node-fetch": { "node-fetch": {
"version": "2.6.6", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
"integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==",
"requires": { "requires": {
"whatwg-url": "^5.0.0" "whatwg-url": "^5.0.0"
} }
@ -27284,9 +27292,9 @@
} }
}, },
"xml2js": { "xml2js": {
"version": "0.4.23", "version": "0.5.0",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
"integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
"requires": { "requires": {
"sax": ">=0.6.0", "sax": ">=0.6.0",
"xmlbuilder": "~11.0.0" "xmlbuilder": "~11.0.0"

7
packages/nc-gui/components.d.ts vendored

@ -126,13 +126,6 @@ declare module '@vue/runtime-core' {
MdiGestureDoubleTap: typeof import('~icons/mdi/gesture-double-tap')['default'] MdiGestureDoubleTap: typeof import('~icons/mdi/gesture-double-tap')['default']
MdiHeart: typeof import('~icons/mdi/heart')['default'] MdiHeart: typeof import('~icons/mdi/heart')['default']
MdiHistory: typeof import('~icons/mdi/history')['default'] MdiHistory: typeof import('~icons/mdi/history')['default']
MdiHook: typeof import('~icons/mdi/hook')['default']
MdiInformation: typeof import('~icons/mdi/information')['default']
MdiJson: typeof import('~icons/mdi/json')['default']
MdiKey: typeof import('~icons/mdi/key')['default']
MdiKeyboard: typeof import('~icons/mdi/keyboard')['default']
MdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default']
MdiKeyChange: typeof import('~icons/mdi/key-change')['default']
MdiKeyStar: typeof import('~icons/mdi/key-star')['default'] MdiKeyStar: typeof import('~icons/mdi/key-star')['default']
MdiMenuDown: typeof import('~icons/mdi/menu-down')['default'] MdiMenuDown: typeof import('~icons/mdi/menu-down')['default']
MdiMicrosoftTeams: typeof import('~icons/mdi/microsoft-teams')['default'] MdiMicrosoftTeams: typeof import('~icons/mdi/microsoft-teams')['default']

16
packages/nc-gui/components/cell/MultiSelect.vue

@ -122,13 +122,13 @@ const selectedTitles = computed(() =>
? typeof modelValue === 'string' ? typeof modelValue === 'string'
? isMysql(column.value.base_id) ? isMysql(column.value.base_id)
? modelValue.split(',').sort((a, b) => { ? modelValue.split(',').sort((a, b) => {
const opa = options.value.find((el) => el.title === a) const opa = options.value.find((el) => el.title === a)
const opb = options.value.find((el) => el.title === b) const opb = options.value.find((el) => el.title === b)
if (opa && opb) { if (opa && opb) {
return opa.order! - opb.order! return opa.order! - opb.order!
} }
return 0 return 0
}) })
: modelValue.split(',') : modelValue.split(',')
: modelValue : modelValue
: [], : [],
@ -243,7 +243,7 @@ async function addIfMissingAndSave() {
// Mysql escapes single quotes with backslash so we keep quotes but others have to unescaped // Mysql escapes single quotes with backslash so we keep quotes but others have to unescaped
if (!isMysql(column.value.base_id)) { if (!isMysql(column.value.base_id)) {
updatedColMeta.cdf = updatedColMeta.cdf.replace(/''/g, '\'') updatedColMeta.cdf = updatedColMeta.cdf.replace(/''/g, "'")
} }
} }

17
packages/nc-gui/components/dashboard/settings/AuditTab.vue

@ -67,6 +67,7 @@ const columns = [
title: tableHeaderRenderer(t('labels.description')), title: tableHeaderRenderer(t('labels.description')),
dataIndex: 'description', dataIndex: 'description',
key: 'description', key: 'description',
customRender: (value: { text: string }) => h('pre', {}, value.text),
}, },
{ {
// User // User
@ -83,6 +84,7 @@ const columns = [
sort: 'desc', sort: 'desc',
customRender: (value: { text: string }) => customRender: (value: { text: string }) =>
h(ATooltip, { placement: 'bottom', title: h('span', {}, value.text) }, () => timeAgo(value.text)), h(ATooltip, { placement: 'bottom', title: h('span', {}, value.text) }, () => timeAgo(value.text)),
width: '10%',
}, },
] ]
</script> </script>
@ -102,7 +104,7 @@ const columns = [
<a-pagination <a-pagination
v-model:current="currentPage" v-model:current="currentPage"
:page-size="currentLimit" v-model:page-size="currentLimit"
:total="totalRows" :total="totalRows"
show-less-items show-less-items
@change="loadAudits" @change="loadAudits"
@ -110,7 +112,7 @@ const columns = [
</div> </div>
<a-table <a-table
class="w-full" class="nc-audit-table w-full"
size="small" size="small"
:data-source="audits ?? []" :data-source="audits ?? []"
:columns="columns" :columns="columns"
@ -124,3 +126,14 @@ const columns = [
</a-table> </a-table>
</div> </div>
</template> </template>
<style lang="scss">
.nc-audit-table pre {
display: table;
table-layout: fixed;
width: 100%;
white-space: break-spaces;
font-size: unset;
font-family: unset;
}
</style>

2
packages/nc-gui/components/smartsheet/Pagination.vue

@ -28,10 +28,10 @@ const page = computed({
<a-pagination <a-pagination
v-if="count !== Infinity" v-if="count !== Infinity"
v-model:current="page" v-model:current="page"
v-model:page-size="size"
size="small" size="small"
class="!text-xs !m-1 nc-pagination" class="!text-xs !m-1 nc-pagination"
:total="count" :total="count"
:page-size="size"
show-less-items show-less-items
:show-size-changer="false" :show-size-changer="false"
/> />

2
packages/nc-gui/components/tabs/auth/UserManagement.vue

@ -369,9 +369,9 @@ const isSuperAdmin = (user: { main_roles?: string }) => {
<a-pagination <a-pagination
v-model:current="currentPage" v-model:current="currentPage"
v-model:page-size="currentLimit"
hide-on-single-page hide-on-single-page
class="mt-4" class="mt-4"
:page-size="currentLimit"
:total="totalRows" :total="totalRows"
show-less-items show-less-items
@change="loadUsers" @change="loadUsers"

2
packages/nc-gui/components/webhook/CallLog.vue

@ -159,7 +159,7 @@ onBeforeMount(async () => {
<a-layout-footer class="!bg-white text-center"> <a-layout-footer class="!bg-white text-center">
<a-pagination <a-pagination
v-model:current="currentPage" v-model:current="currentPage"
:page-size="currentLimit" v-model:page-size="currentLimit"
:total="totalRows" :total="totalRows"
show-less-items show-less-items
@change="loadHookLogs" @change="loadHookLogs"

21
packages/nc-gui/composables/useExpandedFormStore.ts

@ -7,7 +7,6 @@ import {
computed, computed,
extractPkFromRow, extractPkFromRow,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
getHTMLEncodedText,
message, message,
populateInsertObject, populateInsertObject,
ref, ref,
@ -127,7 +126,7 @@ const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((m
await api.utils.commentRow({ await api.utils.commentRow({
fk_model_id: meta.value?.id as string, fk_model_id: meta.value?.id as string,
row_id: rowId, row_id: rowId,
description: comment.value, description: `The following comment has been created: ${comment.value}`,
}) })
comment.value = '' comment.value = ''
@ -253,22 +252,8 @@ const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((m
}) })
} }
for (const key of Object.keys(updateOrInsertObj)) { if (commentsDrawer.value) {
// audit await loadCommentsAndLogs()
$api.utils
.auditRowUpdate(id, {
fk_model_id: meta.value.id,
column_name: key,
row_id: id,
value: getHTMLEncodedText(updateOrInsertObj[key]),
prev_value: getHTMLEncodedText(row.value.oldRow[key]),
})
.then(async () => {
/** load latest comments/audit if right drawer is open */
if (commentsDrawer.value) {
await loadCommentsAndLogs()
}
})
} }
} else { } else {
// No columns to update // No columns to update

9
packages/nc-gui/composables/useKanbanViewStore.ts

@ -8,7 +8,6 @@ import {
enumColor, enumColor,
extractPkFromRow, extractPkFromRow,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
getHTMLEncodedText,
inject, inject,
message, message,
parseProp, parseProp,
@ -401,14 +400,6 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
// query: { ignoreWebhook: !saved } // query: { ignoreWebhook: !saved }
// } // }
) )
// audit
$api.utils.auditRowUpdate(id, {
fk_model_id: meta.value?.id as string,
column_name: property,
row_id: id,
value: getHTMLEncodedText(toUpdate.row[property]),
prev_value: getHTMLEncodedText(toUpdate.oldRow[property]),
})
if (!undo) { if (!undo) {
const oldRowIndex = moveHistory.value.find((ele) => ele.op === 'removed' && ele.pk === id) const oldRowIndex = moveHistory.value.find((ele) => ele.op === 'removed' && ele.pk === id)

9
packages/nc-gui/composables/useViewData.ts

@ -8,7 +8,6 @@ import {
computed, computed,
extractPkFromRow, extractPkFromRow,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
getHTMLEncodedText,
message, message,
populateInsertObject, populateInsertObject,
ref, ref,
@ -350,14 +349,6 @@ export function useViewData(
// query: { ignoreWebhook: !saved } // query: { ignoreWebhook: !saved }
// } // }
) )
// audit
$api.utils.auditRowUpdate(encodeURIComponent(id), {
fk_model_id: metaValue?.id as string,
column_name: property,
row_id: id,
value: getHTMLEncodedText(toUpdate.row[property]),
prev_value: getHTMLEncodedText(toUpdate.oldRow[property]),
})
if (!undo) { if (!undo) {
addUndo({ addUndo({

6
packages/nc-gui/lang/pt_BR.json

@ -101,7 +101,7 @@
"form": "Formulário", "form": "Formulário",
"kanban": "Kanban", "kanban": "Kanban",
"calendar": "Calendário", "calendar": "Calendário",
"map": "Map" "map": "Mapa"
}, },
"user": "Do utilizador", "user": "Do utilizador",
"users": "Comercial", "users": "Comercial",
@ -210,7 +210,7 @@
"advancedSettings": "Configurações avançadas", "advancedSettings": "Configurações avançadas",
"codeSnippet": "Código Snippet", "codeSnippet": "Código Snippet",
"keyboardShortcut": "Atalhos de teclado", "keyboardShortcut": "Atalhos de teclado",
"generateRandomName": "Generate Random Name", "generateRandomName": "Gerar um código aleatório",
"findRowByScanningCode": "Find row by scanning a QR or Barcode" "findRowByScanningCode": "Find row by scanning a QR or Barcode"
}, },
"labels": { "labels": {
@ -472,7 +472,7 @@
"map": { "map": {
"mappedBy": "Mapped By", "mappedBy": "Mapped By",
"chooseMappingField": "Choose a Mapping Field", "chooseMappingField": "Choose a Mapping Field",
"openInGoogleMaps": "Google Maps", "openInGoogleMaps": "Google Mapas",
"openInOpenStreetMap": "OSM" "openInOpenStreetMap": "OSM"
}, },
"toggleMobileMode": "Toggle Mobile Mode" "toggleMobileMode": "Toggle Mobile Mode"

8
packages/nc-gui/lang/zh-Hans.json

@ -238,7 +238,7 @@
"action": "操作", "action": "操作",
"actions": "操作", "actions": "操作",
"operation": "操作", "operation": "操作",
"operationSub": "Sub Operation", "operationSub": "子操作",
"operationType": "操作类型", "operationType": "操作类型",
"operationSubType": "子操作类型", "operationSubType": "子操作类型",
"description": "描述", "description": "描述",
@ -471,11 +471,11 @@
}, },
"map": { "map": {
"mappedBy": "Mapped By", "mappedBy": "Mapped By",
"chooseMappingField": "Choose a Mapping Field", "chooseMappingField": "选择映射字段",
"openInGoogleMaps": "谷歌地图", "openInGoogleMaps": "谷歌地图",
"openInOpenStreetMap": "OSM" "openInOpenStreetMap": "OSM"
}, },
"toggleMobileMode": "Toggle Mobile Mode" "toggleMobileMode": "切换移动模式"
}, },
"tooltip": { "tooltip": {
"saveChanges": "保存更改", "saveChanges": "保存更改",
@ -779,7 +779,7 @@
"userDeletedFromProject": "踢出用户成功", "userDeletedFromProject": "踢出用户成功",
"inviteEmailSent": "邀请邮件发送成功", "inviteEmailSent": "邀请邮件发送成功",
"inviteURLCopied": "邀请URL已复制到剪贴板", "inviteURLCopied": "邀请URL已复制到剪贴板",
"commentCopied": "Comment copied to clipboard", "commentCopied": "已复制评论到剪贴板",
"passwordResetURLCopied": "密码重置网址已复制到剪贴板", "passwordResetURLCopied": "密码重置网址已复制到剪贴板",
"shareableURLCopied": "已将可共享的基础URL复制到剪贴板!", "shareableURLCopied": "已将可共享的基础URL复制到剪贴板!",
"embeddableHTMLCodeCopied": "已复制可嵌入的 HTML 代码!", "embeddableHTMLCodeCopied": "已复制可嵌入的 HTML 代码!",

2
packages/nc-gui/package-lock.json generated

@ -110,7 +110,7 @@
} }
}, },
"../nocodb-sdk": { "../nocodb-sdk": {
"version": "0.106.0-beta.0", "version": "0.106.0",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"axios": "^0.21.1", "axios": "^0.21.1",

2
packages/nc-lib-gui/package.json

@ -1,6 +1,6 @@
{ {
"name": "nc-lib-gui", "name": "nc-lib-gui",
"version": "0.106.0-beta.0", "version": "0.106.0",
"description": "NocoDB GUI", "description": "NocoDB GUI",
"author": { "author": {
"name": "NocoDB", "name": "NocoDB",

5
packages/noco-docs/content/en/developer-resources/webhooks.md

@ -28,7 +28,8 @@ Some types of notifications can be triggered by a webhook after a particular eve
- Webhook Conditional Trigger - Webhook Conditional Trigger
- Only records meeting the criteria will trigger webhook - Only records meeting the criteria will trigger webhook
![Screenshot 2022-09-14 at 10 35 39 AM](https://user-images.githubusercontent.com/86527202/190064668-37245025-81f6-491c-b639-83c8fd131bc3.png) <!-- ![Screenshot 2022-09-14 at 10 35 39 AM](https://user-images.githubusercontent.com/86527202/190064668-37245025-81f6-491c-b639-83c8fd131bc3.png) -->
![Screenshot 2023-04-06 at 11 39 49 AM](https://user-images.githubusercontent.com/86527202/230288581-c613e591-1c32-4151-a2d1-df2bbf1367fd.png)
<!-- ![image](https://user-images.githubusercontent.com/35857179/166660248-a3c81a34-4334-48c2-846a-65759d761559.png) --> <!-- ![image](https://user-images.githubusercontent.com/35857179/166660248-a3c81a34-4334-48c2-846a-65759d761559.png) -->
@ -222,4 +223,4 @@ Webhook v2 is available after v0.106.0. Here's the differences.
- Support the following bulk operations: - Support the following bulk operations:
- AFTER BULK INSERT - AFTER BULK INSERT
- AFTER BULK UPDATE - AFTER BULK UPDATE
- AFTER BULK DELETE - AFTER BULK DELETE

4
packages/nocodb-sdk/package-lock.json generated

@ -1,12 +1,12 @@
{ {
"name": "nocodb-sdk", "name": "nocodb-sdk",
"version": "0.106.0-beta.0", "version": "0.106.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "nocodb-sdk", "name": "nocodb-sdk",
"version": "0.106.0-beta.0", "version": "0.106.0",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"axios": "^0.21.1", "axios": "^0.21.1",

2
packages/nocodb-sdk/package.json

@ -1,6 +1,6 @@
{ {
"name": "nocodb-sdk", "name": "nocodb-sdk",
"version": "0.106.0-beta.0", "version": "0.106.0",
"description": "NocoDB SDK", "description": "NocoDB SDK",
"main": "build/main/index.js", "main": "build/main/index.js",
"typings": "build/main/index.d.ts", "typings": "build/main/index.d.ts",

6
packages/nocodb-sdk/src/lib/Api.ts

@ -154,13 +154,11 @@ export interface AuditType {
| 'LINK_RECORD' | 'LINK_RECORD'
| 'UNLINK_RECORD' | 'UNLINK_RECORD'
| 'DELETE' | 'DELETE'
| 'CREATED' | 'CREATE'
| 'DELETED' | 'RENAME'
| 'RENAMED'
| 'IMPORT_FROM_ZIP' | 'IMPORT_FROM_ZIP'
| 'EXPORT_TO_FS' | 'EXPORT_TO_FS'
| 'EXPORT_TO_ZIP' | 'EXPORT_TO_ZIP'
| 'UPDATED'
| 'SIGNIN' | 'SIGNIN'
| 'SIGNUP' | 'SIGNUP'
| 'PASSWORD_RESET' | 'PASSWORD_RESET'

6
packages/nocodb-sdk/src/lib/globals.ts

@ -47,13 +47,11 @@ export enum AuditOperationSubTypes {
LINK_RECORD = 'LINK_RECORD', LINK_RECORD = 'LINK_RECORD',
UNLINK_RECORD = 'UNLINK_RECORD', UNLINK_RECORD = 'UNLINK_RECORD',
DELETE = 'DELETE', DELETE = 'DELETE',
CREATED = 'CREATED', CREATE = 'CREATE',
DELETED = 'DELETED', RENAME = 'RENAME',
RENAMED = 'RENAMED',
IMPORT_FROM_ZIP = 'IMPORT_FROM_ZIP', IMPORT_FROM_ZIP = 'IMPORT_FROM_ZIP',
EXPORT_TO_FS = 'EXPORT_TO_FS', EXPORT_TO_FS = 'EXPORT_TO_FS',
EXPORT_TO_ZIP = 'EXPORT_TO_ZIP', EXPORT_TO_ZIP = 'EXPORT_TO_ZIP',
UPDATED = 'UPDATED',
SIGNIN = 'SIGNIN', SIGNIN = 'SIGNIN',
SIGNUP = 'SIGNUP', SIGNUP = 'SIGNUP',
PASSWORD_RESET = 'PASSWORD_RESET', PASSWORD_RESET = 'PASSWORD_RESET',

32
packages/nocodb/package-lock.json generated

@ -1,12 +1,12 @@
{ {
"name": "nocodb", "name": "nocodb",
"version": "0.106.0-beta.0", "version": "0.106.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "nocodb", "name": "nocodb",
"version": "0.106.0-beta.0", "version": "0.106.0",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@google-cloud/storage": "^5.7.2", "@google-cloud/storage": "^5.7.2",
@ -67,7 +67,7 @@
"mysql2": "^2.2.5", "mysql2": "^2.2.5",
"nanoid": "^3.1.20", "nanoid": "^3.1.20",
"nc-help": "0.2.87", "nc-help": "0.2.87",
"nc-lib-gui": "0.106.0-beta.0", "nc-lib-gui": "0.106.0",
"nc-plugin": "0.1.2", "nc-plugin": "0.1.2",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"nocodb-sdk": "file:../nocodb-sdk", "nocodb-sdk": "file:../nocodb-sdk",
@ -156,7 +156,7 @@
} }
}, },
"../nocodb-sdk": { "../nocodb-sdk": {
"version": "0.106.0-beta.0", "version": "0.106.0",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"axios": "^0.21.1", "axios": "^0.21.1",
@ -11330,9 +11330,9 @@
} }
}, },
"node_modules/nc-lib-gui": { "node_modules/nc-lib-gui": {
"version": "0.106.0-beta.0", "version": "0.106.0",
"resolved": "https://registry.npmjs.org/nc-lib-gui/-/nc-lib-gui-0.106.0-beta.0.tgz", "resolved": "https://registry.npmjs.org/nc-lib-gui/-/nc-lib-gui-0.106.0.tgz",
"integrity": "sha512-DN6H5lvGHhOF6/X3yCd/IMm5DIiFwllcEfOncCer5/46jXNO1e84VpCnxFkD4lfZBi6Di05ioa5lsV/ydFn5Xw==", "integrity": "sha512-BZDheNWKi+iKo5MfOKaxmYT6piNQ3K6SgrH3lH5PhTGzjN9dM+xs0Y+Wgo8r8rjNQj6pNEB17IuMZK3amCntxQ==",
"dependencies": { "dependencies": {
"express": "^4.17.1" "express": "^4.17.1"
} }
@ -17364,9 +17364,9 @@
"dev": true "dev": true
}, },
"node_modules/vm2": { "node_modules/vm2": {
"version": "3.9.13", "version": "3.9.15",
"resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.13.tgz", "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.15.tgz",
"integrity": "sha512-0rvxpB8P8Shm4wX2EKOiMp7H2zq+HUE/UwodY0pCZXs9IffIKZq6vUti5OgkVCTakKo9e/fgO4X1fkwfjWxE3Q==", "integrity": "sha512-XqNqknHGw2avJo13gbIwLNZUumvrSHc9mLqoadFZTpo3KaNEJoe1I0lqTFhRXmXD7WkLyG01aaraXdXT0pa4ag==",
"dependencies": { "dependencies": {
"acorn": "^8.7.0", "acorn": "^8.7.0",
"acorn-walk": "^8.2.0" "acorn-walk": "^8.2.0"
@ -28042,9 +28042,9 @@
} }
}, },
"nc-lib-gui": { "nc-lib-gui": {
"version": "0.106.0-beta.0", "version": "0.106.0",
"resolved": "https://registry.npmjs.org/nc-lib-gui/-/nc-lib-gui-0.106.0-beta.0.tgz", "resolved": "https://registry.npmjs.org/nc-lib-gui/-/nc-lib-gui-0.106.0.tgz",
"integrity": "sha512-DN6H5lvGHhOF6/X3yCd/IMm5DIiFwllcEfOncCer5/46jXNO1e84VpCnxFkD4lfZBi6Di05ioa5lsV/ydFn5Xw==", "integrity": "sha512-BZDheNWKi+iKo5MfOKaxmYT6piNQ3K6SgrH3lH5PhTGzjN9dM+xs0Y+Wgo8r8rjNQj6pNEB17IuMZK3amCntxQ==",
"requires": { "requires": {
"express": "^4.17.1" "express": "^4.17.1"
} }
@ -32786,9 +32786,9 @@
"dev": true "dev": true
}, },
"vm2": { "vm2": {
"version": "3.9.13", "version": "3.9.15",
"resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.13.tgz", "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.15.tgz",
"integrity": "sha512-0rvxpB8P8Shm4wX2EKOiMp7H2zq+HUE/UwodY0pCZXs9IffIKZq6vUti5OgkVCTakKo9e/fgO4X1fkwfjWxE3Q==", "integrity": "sha512-XqNqknHGw2avJo13gbIwLNZUumvrSHc9mLqoadFZTpo3KaNEJoe1I0lqTFhRXmXD7WkLyG01aaraXdXT0pa4ag==",
"requires": { "requires": {
"acorn": "^8.7.0", "acorn": "^8.7.0",
"acorn-walk": "^8.2.0" "acorn-walk": "^8.2.0"

4
packages/nocodb/package.json

@ -1,6 +1,6 @@
{ {
"name": "nocodb", "name": "nocodb",
"version": "0.106.0-beta.0", "version": "0.106.0",
"description": "NocoDB Backend", "description": "NocoDB Backend",
"main": "dist/bundle.js", "main": "dist/bundle.js",
"author": { "author": {
@ -109,7 +109,7 @@
"mysql2": "^2.2.5", "mysql2": "^2.2.5",
"nanoid": "^3.1.20", "nanoid": "^3.1.20",
"nc-help": "0.2.87", "nc-help": "0.2.87",
"nc-lib-gui": "0.106.0-beta.0", "nc-lib-gui": "0.106.0",
"nc-plugin": "0.1.2", "nc-plugin": "0.1.2",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"nocodb-sdk": "file:../nocodb-sdk", "nocodb-sdk": "file:../nocodb-sdk",

9
packages/nocodb/src/lib/controllers/user/user.ctl.ts

@ -1,4 +1,5 @@
import { promisify } from 'util'; import { promisify } from 'util';
import { AuditOperationSubTypes, AuditOperationTypes } from 'nocodb-sdk';
import * as ejs from 'ejs'; import * as ejs from 'ejs';
import passport from 'passport'; import passport from 'passport';
import catchError, { NcError } from '../../meta/helpers/catchError'; import catchError, { NcError } from '../../meta/helpers/catchError';
@ -65,8 +66,8 @@ async function successfulSignIn({
setTokenCookie(res, refreshToken); setTokenCookie(res, refreshToken);
await Audit.insert({ await Audit.insert({
op_type: 'AUTHENTICATION', op_type: AuditOperationTypes.AUTHENTICATION,
op_sub_type: 'SIGNIN', op_sub_type: AuditOperationSubTypes.SIGNIN,
user: user.email, user: user.email,
ip: req.clientIp, ip: req.clientIp,
description: auditDescription, description: auditDescription,
@ -92,7 +93,7 @@ async function signin(req, res, next) {
info, info,
req, req,
res, res,
auditDescription: 'signed in', auditDescription: 'User has signed in successfully',
}) })
)(req, res, next); )(req, res, next);
} }
@ -111,7 +112,7 @@ async function googleSignin(req, res, next) {
info, info,
req, req,
res, res,
auditDescription: 'signed in using Google Auth', auditDescription: 'User has signed in successfully using Google Auth ',
}) })
)(req, res, next); )(req, res, next);
} }

2
packages/nocodb/src/lib/db/sql-client/lib/pg/PgClient.ts

@ -484,7 +484,7 @@ class PGClient extends KnexClient {
) )
).rows?.[0]; ).rows?.[0];
if(!schemaExists) { if (!schemaExists) {
await this.sqlClient.raw( await this.sqlClient.raw(
`CREATE SCHEMA IF NOT EXISTS ?? AUTHORIZATION ?? `, `CREATE SCHEMA IF NOT EXISTS ?? AUTHORIZATION ?? `,
[schemaName, this.connectionConfig.connection.user] [schemaName, this.connectionConfig.connection.user]

46
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts

@ -1864,7 +1864,7 @@ class BaseModelSqlv2 {
await this.execAndParse(query); await this.execAndParse(query);
const newData = await this.readByPk(id); const newData = await this.readByPk(id);
await this.afterUpdate(prevData, newData, trx, cookie); await this.afterUpdate(prevData, newData, trx, cookie, updateObj);
return newData; return newData;
} catch (e) { } catch (e) {
console.log(e); console.log(e);
@ -2296,7 +2296,7 @@ class BaseModelSqlv2 {
op_type: AuditOperationTypes.DATA, op_type: AuditOperationTypes.DATA,
op_sub_type: AuditOperationSubTypes.INSERT, op_sub_type: AuditOperationSubTypes.INSERT,
description: DOMPurify.sanitize( description: DOMPurify.sanitize(
`${id} inserted into ${this.model.title}` `Record with ID ${id} has been inserted into Table ${this.model.title}`
), ),
// details: JSON.stringify(data), // details: JSON.stringify(data),
ip: req?.clientIp, ip: req?.clientIp,
@ -2322,7 +2322,9 @@ class BaseModelSqlv2 {
op_type: AuditOperationTypes.DATA, op_type: AuditOperationTypes.DATA,
op_sub_type: AuditOperationSubTypes.BULK_UPDATE, op_sub_type: AuditOperationSubTypes.BULK_UPDATE,
description: DOMPurify.sanitize( description: DOMPurify.sanitize(
`${noOfUpdatedRecords} records bulk updated in ${this.model.title}` `${noOfUpdatedRecords} ${
noOfUpdatedRecords > 1 ? 'records have' : 'record has'
} been bulk updated in ${this.model.title}`
), ),
// details: JSON.stringify(data), // details: JSON.stringify(data),
ip: req?.clientIp, ip: req?.clientIp,
@ -2347,7 +2349,9 @@ class BaseModelSqlv2 {
op_type: AuditOperationTypes.DATA, op_type: AuditOperationTypes.DATA,
op_sub_type: AuditOperationSubTypes.BULK_DELETE, op_sub_type: AuditOperationSubTypes.BULK_DELETE,
description: DOMPurify.sanitize( description: DOMPurify.sanitize(
`${noOfDeletedRecords} records bulk deleted in ${this.model.title}` `${noOfDeletedRecords} ${
noOfDeletedRecords > 1 ? 'records have' : 'record has'
} been bulk deleted in ${this.model.title}`
), ),
// details: JSON.stringify(data), // details: JSON.stringify(data),
ip: req?.clientIp, ip: req?.clientIp,
@ -2363,7 +2367,9 @@ class BaseModelSqlv2 {
op_type: AuditOperationTypes.DATA, op_type: AuditOperationTypes.DATA,
op_sub_type: AuditOperationSubTypes.BULK_INSERT, op_sub_type: AuditOperationSubTypes.BULK_INSERT,
description: DOMPurify.sanitize( description: DOMPurify.sanitize(
`${data.length} records bulk inserted into ${this.model.title}` `${data.length} ${
data.length > 1 ? 'records have' : 'record has'
} been bulk inserted in ${this.model.title}`
), ),
// details: JSON.stringify(data), // details: JSON.stringify(data),
ip: req?.clientIp, ip: req?.clientIp,
@ -2387,16 +2393,32 @@ class BaseModelSqlv2 {
prevData: any, prevData: any,
newData: any, newData: any,
_trx: any, _trx: any,
req req,
updateObj?: Record<string, any>
): Promise<void> { ): Promise<void> {
const id = this._extractPksValues(newData); const id = this._extractPksValues(newData);
let desc = `Record with ID ${id} has been updated in Table ${this.model.title}.`;
if (updateObj) {
updateObj = await this.model.mapColumnToAlias(updateObj);
for (const k of Object.keys(updateObj)) {
const prevValue =
typeof prevData[k] === 'object'
? JSON.stringify(prevData[k])
: prevData[k];
const newValue =
typeof newData[k] === 'object'
? JSON.stringify(newData[k])
: newData[k];
desc += `\n`;
desc += `Column "${k}" got changed from "${prevValue}" to "${newValue}"`;
}
}
await Audit.insert({ await Audit.insert({
fk_model_id: this.model.id, fk_model_id: this.model.id,
row_id: id, row_id: id,
op_type: AuditOperationTypes.DATA, op_type: AuditOperationTypes.DATA,
op_sub_type: AuditOperationSubTypes.UPDATE, op_sub_type: AuditOperationSubTypes.UPDATE,
description: DOMPurify.sanitize(`${id} updated in ${this.model.title}`), description: DOMPurify.sanitize(desc),
// details: JSON.stringify(data), // details: JSON.stringify(data),
ip: req?.clientIp, ip: req?.clientIp,
user: req?.user?.email, user: req?.user?.email,
@ -2424,7 +2446,9 @@ class BaseModelSqlv2 {
row_id: id, row_id: id,
op_type: AuditOperationTypes.DATA, op_type: AuditOperationTypes.DATA,
op_sub_type: AuditOperationSubTypes.DELETE, op_sub_type: AuditOperationSubTypes.DELETE,
description: DOMPurify.sanitize(`${id} deleted from ${this.model.title}`), description: DOMPurify.sanitize(
`Record with ID ${id} has been deleted in Table ${this.model.title}`
),
// details: JSON.stringify(data), // details: JSON.stringify(data),
ip: req?.clientIp, ip: req?.clientIp,
user: req?.user?.email, user: req?.user?.email,
@ -2695,7 +2719,7 @@ class BaseModelSqlv2 {
op_sub_type: AuditOperationSubTypes.LINK_RECORD, op_sub_type: AuditOperationSubTypes.LINK_RECORD,
row_id: rowId, row_id: rowId,
description: DOMPurify.sanitize( description: DOMPurify.sanitize(
`Record [id:${childId}] record linked with record [id:${rowId}] record in ${this.model.title}` `Record [id:${childId}] has been linked with record [id:${rowId}] in ${this.model.title}`
), ),
// details: JSON.stringify(data), // details: JSON.stringify(data),
ip: req?.clientIp, ip: req?.clientIp,
@ -2797,7 +2821,7 @@ class BaseModelSqlv2 {
op_sub_type: AuditOperationSubTypes.UNLINK_RECORD, op_sub_type: AuditOperationSubTypes.UNLINK_RECORD,
row_id: rowId, row_id: rowId,
description: DOMPurify.sanitize( description: DOMPurify.sanitize(
`Record [id:${childId}] record unlinked with record [id:${rowId}] record in ${this.model.title}` `Record [id:${childId}] has been unlinked with record [id:${rowId}] in ${this.model.title}`
), ),
// details: JSON.stringify(data), // details: JSON.stringify(data),
ip: req?.clientIp, ip: req?.clientIp,

2
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2.ts

@ -656,7 +656,7 @@ async function _formulaQueryBuilder(
} else if (pt.type === 'Identifier') { } else if (pt.type === 'Identifier') {
const { builder } = await aliasToColumn?.[pt.name]?.(); const { builder } = await aliasToColumn?.[pt.name]?.();
if (typeof builder === 'function') { if (typeof builder === 'function') {
return { builder: knex.raw(`??${colAlias}`, await builder(pt.fnName)) }; return { builder: knex.raw(`??${colAlias}`, builder(pt.fnName)) };
} }
return { builder: knex.raw(`??${colAlias}`, [builder || pt.name]) }; return { builder: knex.raw(`??${colAlias}`, [builder || pt.name]) };
} else if (pt.type === 'BinaryExpression') { } else if (pt.type === 'BinaryExpression') {

6
packages/nocodb/src/lib/models/Audit.ts

@ -30,13 +30,11 @@ const opSubTypes = <const>[
'LINK_RECORD', 'LINK_RECORD',
'UNLINK_RECORD', 'UNLINK_RECORD',
'DELETE', 'DELETE',
'CREATED', 'CREATE',
'DELETED', 'RENAME',
'RENAMED',
'IMPORT_FROM_ZIP', 'IMPORT_FROM_ZIP',
'EXPORT_TO_FS', 'EXPORT_TO_FS',
'EXPORT_TO_ZIP', 'EXPORT_TO_ZIP',
'UPDATED',
'SIGNIN', 'SIGNIN',
'SIGNUP', 'SIGNUP',
'PASSWORD_RESET', 'PASSWORD_RESET',

18
packages/nocodb/src/lib/models/Model.ts

@ -456,6 +456,24 @@ export default class Model implements TableType {
return insertObj; return insertObj;
} }
async mapColumnToAlias(data) {
const res = {};
for (const col of await this.getColumns()) {
if (isVirtualCol(col)) continue;
let val =
data?.[col.title] !== undefined
? data?.[col.title]
: data?.[col.column_name];
if (val !== undefined) {
if (col.uidt === UITypes.Attachment && typeof val !== 'string') {
val = JSON.stringify(val);
}
res[sanitize(col.title)] = val;
}
}
return res;
}
static async updateAliasAndTableName( static async updateAliasAndTableName(
tableId, tableId,
title: string, title: string,

2
packages/nocodb/src/lib/services/audit.svc.ts

@ -36,7 +36,7 @@ export async function auditRowUpdate(param: {
op_type: AuditOperationTypes.DATA, op_type: AuditOperationTypes.DATA,
op_sub_type: AuditOperationSubTypes.UPDATE, op_sub_type: AuditOperationSubTypes.UPDATE,
description: DOMPurify.sanitize( description: DOMPurify.sanitize(
`Table ${model.table_name} : field ${param.body.column_name} got changed from ${param.body.prev_value} to ${param.body.value}` `The column ${param.body.column_name} in Table ${model.table_name} has been changed from ${param.body.prev_value} to ${param.body.value}`
), ),
details: DOMPurify.sanitize(`<span class="">${param.body.column_name}</span> details: DOMPurify.sanitize(`<span class="">${param.body.column_name}</span>
: <span class="text-decoration-line-through red px-2 lighten-4 black--text">${param.body.prev_value}</span> : <span class="text-decoration-line-through red px-2 lighten-4 black--text">${param.body.prev_value}</span>

12
packages/nocodb/src/lib/services/column.svc.ts

@ -822,9 +822,9 @@ export async function columnUpdate(param: {
await Audit.insert({ await Audit.insert({
project_id: base.project_id, project_id: base.project_id,
op_type: AuditOperationTypes.TABLE_COLUMN, op_type: AuditOperationTypes.TABLE_COLUMN,
op_sub_type: AuditOperationSubTypes.UPDATED, op_sub_type: AuditOperationSubTypes.UPDATE,
user: param.req?.user?.email, user: param.req?.user?.email,
description: `updated column ${column.column_name} with alias ${column.title} from table ${table.table_name}`, description: `The column ${column.column_name} with alias ${column.title} from table ${table.table_name} has been updated`,
ip: param.req?.clientIp, ip: param.req?.clientIp,
}).then(() => {}); }).then(() => {});
@ -1127,9 +1127,9 @@ export async function columnAdd(param: {
await Audit.insert({ await Audit.insert({
project_id: base.project_id, project_id: base.project_id,
op_type: AuditOperationTypes.TABLE_COLUMN, op_type: AuditOperationTypes.TABLE_COLUMN,
op_sub_type: AuditOperationSubTypes.CREATED, op_sub_type: AuditOperationSubTypes.CREATE,
user: param?.req.user?.email, user: param?.req.user?.email,
description: `created column ${colBody.column_name} with alias ${colBody.title} from table ${table.table_name}`, description: `The column ${colBody.column_name} with alias ${colBody.title} from table ${table.table_name} has been created`,
ip: param?.req.clientIp, ip: param?.req.clientIp,
}).then(() => {}); }).then(() => {});
@ -1339,9 +1339,9 @@ export async function columnDelete(param: { req?: any; columnId: string }) {
await Audit.insert({ await Audit.insert({
project_id: base.project_id, project_id: base.project_id,
op_type: AuditOperationTypes.TABLE_COLUMN, op_type: AuditOperationTypes.TABLE_COLUMN,
op_sub_type: AuditOperationSubTypes.DELETED, op_sub_type: AuditOperationSubTypes.DELETE,
user: param?.req?.user?.email, user: param?.req?.user?.email,
description: `deleted column ${column.column_name} with alias ${column.title} from table ${table.table_name}`, description: `The column ${column.column_name} with alias ${column.title} from table ${table.table_name} has been deleted`,
ip: param?.req.clientIp, ip: param?.req.clientIp,
}).then(() => {}); }).then(() => {});

4
packages/nocodb/src/lib/services/orgUser.svc.ts

@ -147,7 +147,7 @@ export async function userAdd(param: {
op_type: AuditOperationTypes.ORG_USER, op_type: AuditOperationTypes.ORG_USER,
op_sub_type: AuditOperationSubTypes.INVITE, op_sub_type: AuditOperationSubTypes.INVITE,
user: param.req.user.email, user: param.req.user.email,
description: `invited ${email} to ${param.projectId} project `, description: `${email} has been invited to ${param.projectId} project`,
ip: param.req.clientIp, ip: param.req.clientIp,
}); });
// in case of single user check for smtp failure // in case of single user check for smtp failure
@ -218,7 +218,7 @@ export async function userInviteResend(param: {
op_type: AuditOperationTypes.ORG_USER, op_type: AuditOperationTypes.ORG_USER,
op_sub_type: AuditOperationSubTypes.RESEND_INVITE, op_sub_type: AuditOperationSubTypes.RESEND_INVITE,
user: user.email, user: user.email,
description: `resent a invite to ${user.email} `, description: `${user.email} has been re-invited`,
ip: param.req.clientIp, ip: param.req.clientIp,
}); });

30
packages/nocodb/src/lib/services/projectUser.svc.ts

@ -1,9 +1,13 @@
import { OrgUserRoles } from 'nocodb-sdk'; import {
AuditOperationSubTypes,
AuditOperationTypes,
OrgUserRoles,
PluginCategory,
} from 'nocodb-sdk';
import { T } from 'nc-help'; import { T } from 'nc-help';
import validator from 'validator'; import validator from 'validator';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import * as ejs from 'ejs'; import * as ejs from 'ejs';
import { PluginCategory } from 'nocodb-sdk';
import { validatePayload } from '../meta/api/helpers'; import { validatePayload } from '../meta/api/helpers';
import { PagedResponseImpl } from '../meta/helpers/PagedResponse'; import { PagedResponseImpl } from '../meta/helpers/PagedResponse';
import ProjectUser from '../models/ProjectUser'; import ProjectUser from '../models/ProjectUser';
@ -91,10 +95,10 @@ export async function userInvite(param: {
await Audit.insert({ await Audit.insert({
project_id: param.projectId, project_id: param.projectId,
op_type: 'AUTHENTICATION', op_type: AuditOperationTypes.AUTHENTICATION,
op_sub_type: 'INVITE', op_sub_type: AuditOperationSubTypes.INVITE,
user: param.req.user.email, user: param.req.user.email,
description: `invited ${email} to ${param.projectId} project `, description: `${email} has been invited to ${param.projectId} project`,
ip: param.req.clientIp, ip: param.req.clientIp,
}); });
} else { } else {
@ -120,8 +124,8 @@ export async function userInvite(param: {
await Audit.insert({ await Audit.insert({
project_id: param.projectId, project_id: param.projectId,
op_type: 'AUTHENTICATION', op_type: AuditOperationTypes.AUTHENTICATION,
op_sub_type: 'INVITE', op_sub_type: AuditOperationSubTypes.INVITE,
user: param.req.user.email, user: param.req.user.email,
description: `invited ${email} to ${param.projectId} project `, description: `invited ${email} to ${param.projectId} project `,
ip: param.req.clientIp, ip: param.req.clientIp,
@ -202,10 +206,10 @@ export async function projectUserUpdate(param: {
); );
await Audit.insert({ await Audit.insert({
op_type: 'AUTHENTICATION', op_type: AuditOperationTypes.AUTHENTICATION,
op_sub_type: 'ROLES_MANAGEMENT', op_sub_type: AuditOperationSubTypes.ROLES_MANAGEMENT,
user: param.req.user.email, user: param.req.user.email,
description: `updated roles for ${user.email} with ${param.projectUser.roles} `, description: `Roles for ${user.email} with has been updated to ${param.projectUser.roles}`,
ip: param.req.clientIp, ip: param.req.clientIp,
}); });
@ -274,10 +278,10 @@ export async function projectUserInviteResend(param: {
await sendInviteEmail(user.email, invite_token, param.req); await sendInviteEmail(user.email, invite_token, param.req);
await Audit.insert({ await Audit.insert({
op_type: 'AUTHENTICATION', op_type: AuditOperationTypes.AUTHENTICATION,
op_sub_type: 'RESEND_INVITE', op_sub_type: AuditOperationSubTypes.RESEND_INVITE,
user: user.email, user: user.email,
description: `resent a invite to ${user.email} `, description: `${user.email} has been re-invited`,
ip: param.req.clientIp, ip: param.req.clientIp,
project_id: param.projectId, project_id: param.projectId,
}); });

6
packages/nocodb/src/lib/services/table.svc.ts

@ -185,7 +185,7 @@ export async function tableDelete(param: {
project_id: project.id, project_id: project.id,
base_id: base.id, base_id: base.id,
op_type: AuditOperationTypes.TABLE, op_type: AuditOperationTypes.TABLE,
op_sub_type: AuditOperationSubTypes.DELETED, op_sub_type: AuditOperationSubTypes.DELETE,
user: param.user?.email, user: param.user?.email,
description: `Deleted ${table.type} ${table.table_name} with alias ${table.title} `, description: `Deleted ${table.type} ${table.table_name} with alias ${table.title} `,
ip: param.req?.clientIp, ip: param.req?.clientIp,
@ -433,9 +433,9 @@ export async function tableCreate(param: {
project_id: project.id, project_id: project.id,
base_id: base.id, base_id: base.id,
op_type: AuditOperationTypes.TABLE, op_type: AuditOperationTypes.TABLE,
op_sub_type: AuditOperationSubTypes.CREATED, op_sub_type: AuditOperationSubTypes.CREATE,
user: param.user?.email, user: param.user?.email,
description: `created table ${tableCreatePayLoad.table_name} with alias ${tableCreatePayLoad.title} `, description: `Table ${tableCreatePayLoad.table_name} with alias ${tableCreatePayLoad.title} has been created`,
ip: param.req?.clientIp, ip: param.req?.clientIp,
}).then(() => {}); }).then(() => {});

38
packages/nocodb/src/lib/services/user/index.ts

@ -1,6 +1,10 @@
import { promisify } from 'util'; import { promisify } from 'util';
import { validatePassword } from 'nocodb-sdk'; import {
import { OrgUserRoles } from 'nocodb-sdk'; AuditOperationSubTypes,
AuditOperationTypes,
OrgUserRoles,
validatePassword,
} from 'nocodb-sdk';
import { T } from 'nc-help'; import { T } from 'nc-help';
import * as ejs from 'ejs'; import * as ejs from 'ejs';
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
@ -120,10 +124,10 @@ export async function passwordChange(param: {
}); });
await Audit.insert({ await Audit.insert({
op_type: 'AUTHENTICATION', op_type: AuditOperationTypes.AUTHENTICATION,
op_sub_type: 'PASSWORD_CHANGE', op_sub_type: AuditOperationSubTypes.PASSWORD_CHANGE,
user: user.email, user: user.email,
description: `changed password `, description: `Password has been changed`,
ip: param.req?.clientIp, ip: param.req?.clientIp,
}); });
@ -178,10 +182,10 @@ export async function passwordForgot(param: {
} }
await Audit.insert({ await Audit.insert({
op_type: 'AUTHENTICATION', op_type: AuditOperationTypes.AUTHENTICATION,
op_sub_type: 'PASSWORD_FORGOT', op_sub_type: AuditOperationSubTypes.PASSWORD_FORGOT,
user: user.email, user: user.email,
description: `requested for password reset `, description: `Password Reset has been requested`,
ip: param.req?.clientIp, ip: param.req?.clientIp,
}); });
} else { } else {
@ -254,10 +258,10 @@ export async function passwordReset(param: {
}); });
await Audit.insert({ await Audit.insert({
op_type: 'AUTHENTICATION', op_type: AuditOperationTypes.AUTHENTICATION,
op_sub_type: 'PASSWORD_RESET', op_sub_type: AuditOperationSubTypes.PASSWORD_RESET,
user: user.email, user: user.email,
description: `did reset password `, description: `Password has been reset`,
ip: req.clientIp, ip: req.clientIp,
}); });
@ -286,10 +290,10 @@ export async function emailVerification(param: {
}); });
await Audit.insert({ await Audit.insert({
op_type: 'AUTHENTICATION', op_type: AuditOperationTypes.AUTHENTICATION,
op_sub_type: 'EMAIL_VERIFICATION', op_sub_type: AuditOperationSubTypes.EMAIL_VERIFICATION,
user: user.email, user: user.email,
description: `verified email `, description: `Email has been verified`,
ip: req.clientIp, ip: req.clientIp,
}); });
@ -442,10 +446,10 @@ export async function signup(param: {
user = (param.req as any).user; user = (param.req as any).user;
await Audit.insert({ await Audit.insert({
op_type: 'AUTHENTICATION', op_type: AuditOperationTypes.AUTHENTICATION,
op_sub_type: 'SIGNUP', op_sub_type: AuditOperationSubTypes.SIGNUP,
user: user.email, user: user.email,
description: `signed up `, description: `User has signed up`,
ip: (param.req as any).clientIp, ip: (param.req as any).clientIp,
}); });

6
packages/nocodb/src/schema/swagger.json

@ -14020,13 +14020,11 @@
"LINK_RECORD", "LINK_RECORD",
"UNLINK_RECORD", "UNLINK_RECORD",
"DELETE", "DELETE",
"CREATED", "CREATE",
"DELETED", "RENAME",
"RENAMED",
"IMPORT_FROM_ZIP", "IMPORT_FROM_ZIP",
"EXPORT_TO_FS", "EXPORT_TO_FS",
"EXPORT_TO_ZIP", "EXPORT_TO_ZIP",
"UPDATED",
"SIGNIN", "SIGNIN",
"SIGNUP", "SIGNUP",
"PASSWORD_RESET", "PASSWORD_RESET",

22
packages/nocodb/tests/unit/model/tests/baseModelSql.test.ts

@ -78,7 +78,7 @@ function baseModelSqlTests() {
row_id: '1', row_id: '1',
op_type: 'DATA', op_type: 'DATA',
op_sub_type: 'INSERT', op_sub_type: 'INSERT',
description: '1 inserted into Table1_Title', description: 'Record with ID 1 has been inserted into Table Table1_Title',
}); });
}); });
@ -123,7 +123,7 @@ function baseModelSqlTests() {
op_type: 'DATA', op_type: 'DATA',
op_sub_type: 'BULK_INSERT', op_sub_type: 'BULK_INSERT',
status: null, status: null,
description: '10 records bulk inserted into Table1_Title', description: '10 records have been bulk inserted in Table1_Title',
details: null, details: null,
}); });
}); });
@ -156,7 +156,7 @@ function baseModelSqlTests() {
row_id: '1', row_id: '1',
op_type: 'DATA', op_type: 'DATA',
op_sub_type: 'UPDATE', op_sub_type: 'UPDATE',
description: '1 updated in Table1_Title', description: 'Record with ID 1 has been updated in Table Table1_Title.\nColumn "Title" got changed from "test-0" to "test"',
}); });
}); });
@ -199,7 +199,7 @@ function baseModelSqlTests() {
op_type: 'DATA', op_type: 'DATA',
op_sub_type: 'BULK_UPDATE', op_sub_type: 'BULK_UPDATE',
status: null, status: null,
description: '10 records bulk updated in Table1_Title', description: '10 records have been bulk updated in Table1_Title',
details: null, details: null,
}); });
}); });
@ -250,7 +250,7 @@ function baseModelSqlTests() {
op_type: 'DATA', op_type: 'DATA',
op_sub_type: 'BULK_UPDATE', op_sub_type: 'BULK_UPDATE',
status: null, status: null,
description: '4 records bulk updated in Table1_Title', description: '4 records have been bulk updated in Table1_Title',
details: null, details: null,
}); });
}); });
@ -288,7 +288,7 @@ function baseModelSqlTests() {
row_id: '1', row_id: '1',
op_type: 'DATA', op_type: 'DATA',
op_sub_type: 'DELETE', op_sub_type: 'DELETE',
description: '1 deleted from Table1_Title', description: 'Record with ID 1 has been deleted in Table Table1_Title',
}); });
}); });
@ -330,7 +330,7 @@ function baseModelSqlTests() {
op_type: 'DATA', op_type: 'DATA',
op_sub_type: 'BULK_DELETE', op_sub_type: 'BULK_DELETE',
status: null, status: null,
description: '4 records bulk deleted in Table1_Title', description: '4 records have been bulk deleted in Table1_Title',
details: null, details: null,
}); });
}); });
@ -378,7 +378,7 @@ function baseModelSqlTests() {
op_type: 'DATA', op_type: 'DATA',
op_sub_type: 'BULK_DELETE', op_sub_type: 'BULK_DELETE',
status: null, status: null,
description: '4 records bulk deleted in Table1_Title', description: '4 records have been bulk deleted in Table1_Title',
details: null, details: null,
}); });
}); });
@ -438,7 +438,7 @@ function baseModelSqlTests() {
row_id: '1', row_id: '1',
op_type: 'DATA', op_type: 'DATA',
op_sub_type: 'INSERT', op_sub_type: 'INSERT',
description: '1 inserted into Table1_Title', description: 'Record with ID 1 has been inserted into Table Table1_Title',
}); });
}); });
@ -506,7 +506,7 @@ function baseModelSqlTests() {
op_type: 'DATA', op_type: 'DATA',
op_sub_type: 'LINK_RECORD', op_sub_type: 'LINK_RECORD',
description: description:
'Record [id:1] record linked with record [id:1] record in Table1_Title', 'Record [id:1] has been linked with record [id:1] in Table1_Title',
}); });
}); });
@ -581,7 +581,7 @@ function baseModelSqlTests() {
op_type: 'DATA', op_type: 'DATA',
op_sub_type: 'UNLINK_RECORD', op_sub_type: 'UNLINK_RECORD',
description: description:
'Record [id:1] record unlinked with record [id:1] record in Table1_Title', 'Record [id:1] has been unlinked with record [id:1] in Table1_Title',
}); });
}); });
} }

4
tests/playwright/tests/tableOperations.spec.ts

@ -25,13 +25,13 @@ test.describe('Table Operations', () => {
await settings.audit.verifyRow({ await settings.audit.verifyRow({
index: 0, index: 0,
opType: 'TABLE', opType: 'TABLE',
opSubtype: 'DELETED', opSubtype: 'DELETE',
user: 'user@nocodb.com', user: 'user@nocodb.com',
}); });
await settings.audit.verifyRow({ await settings.audit.verifyRow({
index: 1, index: 1,
opType: 'TABLE', opType: 'TABLE',
opSubtype: 'CREATED', opSubtype: 'CREATE',
user: 'user@nocodb.com', user: 'user@nocodb.com',
}); });
await settings.close(); await settings.close();

Loading…
Cancel
Save