Browse Source

Merge branch 'develop' into chore/docker-build

pull/3273/head
Wing-Kam Wong 2 years ago
parent
commit
feef675385
  1. 4
      packages/nc-gui-v2/assets/style.scss
  2. 9
      packages/nc-gui-v2/components/dashboard/GithubStarButton.vue
  3. 19
      packages/nc-gui-v2/components/dashboard/TreeView.vue
  4. 36
      packages/nc-gui-v2/components/dashboard/settings/Modal.vue
  5. 7
      packages/nc-gui-v2/components/dashboard/settings/app-store/AppInstall.vue
  6. 26
      packages/nc-gui-v2/components/dlg/TableCreate.vue
  7. 35
      packages/nc-gui-v2/components/dlg/TableRename.vue
  8. 13
      packages/nc-gui-v2/components/general/HelpAndSupport.vue
  9. 10
      packages/nc-gui-v2/components/general/ShareBaseButton.vue
  10. 33
      packages/nc-gui-v2/components/smartsheet-toolbar/SearchData.vue
  11. 57
      packages/nc-gui-v2/components/smartsheet/sidebar/MenuBottom.vue
  12. 69
      packages/nc-gui-v2/components/smartsheet/sidebar/MenuTop.vue
  13. 4
      packages/nc-gui-v2/components/smartsheet/sidebar/RenameableMenuItem.vue
  14. 4
      packages/nc-gui-v2/components/smartsheet/sidebar/index.vue
  15. 2
      packages/nc-gui-v2/composables/useProject.ts
  16. 5
      packages/nc-gui-v2/composables/useTable.ts
  17. 4
      packages/nc-gui-v2/github-star.shims.d.ts
  18. 4
      packages/nc-gui-v2/layouts/base.vue
  19. 403
      packages/nc-gui-v2/package-lock.json
  20. 2
      packages/nc-gui-v2/package.json
  21. 9
      packages/nc-gui-v2/pages/[projectType]/[projectId]/index.vue
  22. 234
      packages/nc-gui-v2/pages/index/index.vue
  23. 140
      packages/nc-gui-v2/pages/index/index/[id].vue
  24. 134
      packages/nc-gui-v2/pages/index/index/create-external.vue
  25. 131
      packages/nc-gui-v2/pages/index/index/create.vue
  26. 211
      packages/nc-gui-v2/pages/index/index/index.vue
  27. 17
      packages/nc-gui-v2/pages/index/user/index/index.vue
  28. 5
      packages/nc-gui-v2/pages/project/index.vue
  29. 83
      packages/nc-gui-v2/pages/project/index/[id].vue
  30. 78
      packages/nc-gui-v2/pages/project/index/create.vue
  31. 2
      packages/nc-gui-v2/pages/signin.vue
  32. 12
      packages/nc-gui-v2/windi.config.ts
  33. 3
      packages/nc-gui/components/ProjectTreeView.vue
  34. 5
      packages/nocodb-sdk/src/lib/Api.ts
  35. 81
      packages/nocodb/src/lib/meta/api/tableApis.ts
  36. 16
      packages/nocodb/src/lib/models/Model.ts
  37. 12
      packages/nocodb/src/lib/plugins/smtp/SMTP.ts
  38. 13
      packages/nocodb/src/lib/plugins/smtp/index.ts
  39. 2
      packages/nocodb/tests/pg-cy-quick/01-cy-quick.sql
  40. 6
      scripts/cypress/integration/common/1a_table_operations.js
  41. 3
      scripts/cypress/support/page_objects/mainPage.js
  42. 8
      scripts/sdk/swagger.json

4
packages/nc-gui-v2/assets/style.scss

@ -210,6 +210,10 @@ a {
} }
} }
.ant-dropdown-menu {
@apply !p-0 !rounded;
}
.ant-dropdown-menu-submenu-popup { .ant-dropdown-menu-submenu-popup {
@apply scrollbar-thin-dull min-w-50 max-h-90vh overflow-auto !shadow !rounded; @apply scrollbar-thin-dull min-w-50 max-h-90vh overflow-auto !shadow !rounded;
} }

9
packages/nc-gui-v2/components/dashboard/GithubStarButton.vue

@ -1,9 +0,0 @@
<script setup lang="ts">
import GithubButton from 'vue-github-button'
</script>
<template>
<GithubButton href="https://github.com/nocodb/nocodb" data-icon="octicon-star" data-show-count="true" data-size="large"
>Star</GithubButton
>
</template>

19
packages/nc-gui-v2/components/dashboard/TreeView.vue

@ -2,6 +2,7 @@
import type { TableType } from 'nocodb-sdk' import type { TableType } from 'nocodb-sdk'
import Sortable from 'sortablejs' import Sortable from 'sortablejs'
import { Empty } from 'ant-design-vue' import { Empty } from 'ant-design-vue'
import GithubButton from 'vue-github-button'
import { import {
computed, computed,
inject, inject,
@ -401,12 +402,22 @@ function openTableCreateDialog() {
<a-divider class="!my-0" /> <a-divider class="!my-0" />
<div class="flex items-start flex-col justify-start px-4 py-3 gap-2"> <div class="flex items-start flex-col justify-start px-2 py-3 gap-2">
<GeneralShareBaseButton class="py-1 px-2 text-primary font-bold cursor-pointer select-none" /> <GeneralShareBaseButton
class="color-transition py-1.5 px-2 text-primary font-bold cursor-pointer select-none hover:text-accent"
/>
<GeneralHelpAndSupport class="py-1 px-2 text-gray-500 cursor-pointer select-none" /> <GeneralHelpAndSupport class="color-transition px-2 text-gray-500 cursor-pointer select-none hover:text-accent" />
<DashboardGithubStarButton class="ml-2 py-1" /> <GithubButton
class="ml-2 py-1"
href="https://github.com/nocodb/nocodb"
data-icon="octicon-star"
data-show-count="true"
data-size="large"
>
Star
</GithubButton>
</div> </div>
</div> </div>
</template> </template>

36
packages/nc-gui-v2/components/dashboard/settings/Modal.vue

@ -1,19 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import type { FunctionalComponent, SVGAttributes } from 'vue' import type { FunctionalComponent, SVGAttributes } from 'vue'
import { useI18n } from 'vue-i18n'
import AuditTab from './AuditTab.vue' import AuditTab from './AuditTab.vue'
import AppStore from './AppStore.vue' import AppStore from './AppStore.vue'
import Metadata from './Metadata.vue' import Metadata from './Metadata.vue'
import UIAcl from './UIAcl.vue' import UIAcl from './UIAcl.vue'
import Misc from './Misc.vue' import Misc from './Misc.vue'
import { useI18n, useUIPermission, useVModel, watch } from '#imports'
import ApiTokenManagement from '~/components/tabs/auth/ApiTokenManagement.vue' import ApiTokenManagement from '~/components/tabs/auth/ApiTokenManagement.vue'
import UserManagement from '~/components/tabs/auth/UserManagement.vue' import UserManagement from '~/components/tabs/auth/UserManagement.vue'
import StoreFrontOutline from '~icons/mdi/storefront-outline' import StoreFrontOutline from '~icons/mdi/storefront-outline'
import TeamFillIcon from '~icons/ri/team-fill' import TeamFillIcon from '~icons/ri/team-fill'
import MultipleTableIcon from '~icons/mdi/table-multiple' import MultipleTableIcon from '~icons/mdi/table-multiple'
import NootbookOutline from '~icons/mdi/notebook-outline' import NootbookOutline from '~icons/mdi/notebook-outline'
import { useUIPermission, useVModel, watch } from '#imports'
import MdiCloseIcon from '~icons/mdi/close'
interface Props { interface Props {
modelValue: boolean modelValue: boolean
@ -140,22 +138,27 @@ watch(
> >
<div class="flex flex-row justify-between w-full items-center mb-1"> <div class="flex flex-row justify-between w-full items-center mb-1">
<a-typography-title class="ml-4 select-none" type="secondary" :level="5">SETTINGS</a-typography-title> <a-typography-title class="ml-4 select-none" type="secondary" :level="5">SETTINGS</a-typography-title>
<a-button type="text" class="!rounded-md border-none -mt-1.5 -mr-1" @click="vModel = false"> <a-button type="text" class="!rounded-md border-none -mt-1.5 -mr-1" @click="vModel = false">
<template #icon> <template #icon>
<MdiCloseIcon class="cursor-pointer mt-1 nc-modal-close" /> <MdiClose class="cursor-pointer mt-1 nc-modal-close" />
</template> </template>
</a-button> </a-button>
</div> </div>
<a-layout class="mt-3 modal-body flex"> <a-layout class="mt-3 h-[75vh] overflow-y-auto flex">
<!-- Side tabs --> <!-- Side tabs -->
<a-layout-sider theme="light"> <a-layout-sider>
<a-menu v-model:selected-keys="selectedTabKeys" class="h-full" mode="inline" :open-keys="[]"> <a-menu v-model:selected-keys="selectedTabKeys" class="tabs-menu h-full" :open-keys="[]">
<a-menu-item v-for="(tab, key) of tabsInfo" :key="key"> <a-menu-item
<div class="flex flex-row items-center space-x-2"> v-for="(tab, key) of tabsInfo"
<component :is="tab.icon" class="flex" /> :key="key"
class="group active:(!ring-0) hover:(!bg-primary !bg-opacity-25)"
>
<div class="flex items-center space-x-2">
<component :is="tab.icon" class="group-hover:text-accent" />
<div class="flex select-none"> <div class="select-none">
{{ tab.title }} {{ tab.title }}
</div> </div>
</div> </div>
@ -166,7 +169,7 @@ watch(
<!-- Sub Tabs --> <!-- Sub Tabs -->
<a-layout-content class="h-auto px-4 scrollbar-thumb-gray-500"> <a-layout-content class="h-auto px-4 scrollbar-thumb-gray-500">
<a-menu v-model:selectedKeys="selectedSubTabKeys" :open-keys="[]" mode="horizontal"> <a-menu v-model:selectedKeys="selectedSubTabKeys" :open-keys="[]" mode="horizontal">
<a-menu-item v-for="(tab, key) of selectedTab.subTabs" :key="key" class="select-none"> <a-menu-item v-for="(tab, key) of selectedTab.subTabs" :key="key" class="active:(!ring-0) select-none">
{{ tab.title }} {{ tab.title }}
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
@ -177,9 +180,10 @@ watch(
</a-modal> </a-modal>
</template> </template>
<style scoped> <style lang="scss" scoped>
.modal-body { .tabs-menu {
@apply h-[75vh]; :deep(.ant-menu-item-selected) {
@apply overflow-y-auto; @apply border-r-3 border-primary bg-primary !bg-opacity-25;
}
} }
</style> </style>

7
packages/nc-gui-v2/components/dashboard/settings/app-store/AppInstall.vue

@ -103,6 +103,13 @@ const readPluginDetails = async () => {
const emptyParsedInput = formDetails.array ? [{}] : {} const emptyParsedInput = formDetails.array ? [{}] : {}
const parsedInput = res.input ? JSON.parse(res.input) : emptyParsedInput const parsedInput = res.input ? JSON.parse(res.input) : emptyParsedInput
// the type of 'secure' was XcType.SingleLineText in 0.0.1
// and it has been changed to XcType.Checkbox, since 0.0.2
// hence, change the text value to boolean here
if ('secure' in parsedInput && typeof parsedInput.secure === 'string') {
parsedInput.secure = !!parsedInput.secure
}
plugin = { ...res, formDetails, parsedInput } plugin = { ...res, formDetails, parsedInput }
pluginFormData = plugin.parsedInput pluginFormData = plugin.parsedInput
} catch (e) { } catch (e) {

26
packages/nc-gui-v2/components/dlg/TableCreate.vue

@ -19,7 +19,7 @@ const inputEl = ref<HTMLInputElement>()
const { addTab } = useTabs() const { addTab } = useTabs()
const { loadTables } = useProject() const { loadTables, isMysql, isMssql, isPg } = useProject()
const { table, createTable, generateUniqueTitle, tables, project } = useTable(async (table) => { const { table, createTable, generateUniqueTitle, tables, project } = useTable(async (table) => {
await loadTables() await loadTables()
@ -39,7 +39,29 @@ const validateDuplicateAlias = (v: string) => (tables.value || []).every((t) =>
const validators = computed(() => { const validators = computed(() => {
return { return {
title: [validateTableName, validateDuplicateAlias], title: [
validateTableName,
validateDuplicateAlias,
{
validator: (rule: any, value: any) => {
return new Promise<void>((resolve, reject) => {
let tableNameLengthLimit = 255
if (isMysql) {
tableNameLengthLimit = 64
} else if (isPg) {
tableNameLengthLimit = 63
} else if (isMssql) {
tableNameLengthLimit = 128
}
const projectPrefix = project?.value?.prefix || ''
if ((projectPrefix + value).length > tableNameLengthLimit) {
return reject(new Error(`Table name exceeds ${tableNameLengthLimit} characters`))
}
resolve()
})
},
},
],
table_name: [validateTableName], table_name: [validateTableName],
} }
}) })

35
packages/nc-gui-v2/components/dlg/TableRename.vue

@ -24,8 +24,7 @@ const dialogShow = computed({
}) })
const { updateTab } = useTabs() const { updateTab } = useTabs()
const { loadTables } = useProject() const { loadTables, tables, project, isMysql, isMssql, isPg } = useProject()
const { tables } = useProject()
const inputEl = $ref<any>() const inputEl = $ref<any>()
let loading = $ref(false) let loading = $ref(false)
@ -38,18 +37,39 @@ const validators = computed(() => {
title: [ title: [
validateTableName, validateTableName,
{ {
validator: (rule: any, value: any, callback: (errMsg?: string) => void) => { validator: (rule: any, value: any) => {
return new Promise<void>((resolve, reject) => {
let tableNameLengthLimit = 255
if (isMysql) {
tableNameLengthLimit = 64
} else if (isPg) {
tableNameLengthLimit = 63
} else if (isMssql) {
tableNameLengthLimit = 128
}
const projectPrefix = project?.value?.prefix || ''
if ((projectPrefix + value).length > tableNameLengthLimit) {
return reject(new Error(`Table name exceeds ${tableNameLengthLimit} characters`))
}
resolve()
})
},
},
{
validator: (rule: any, value: any) => {
return new Promise<void>((resolve, reject) => {
if (/^\s+|\s+$/.test(value)) { if (/^\s+|\s+$/.test(value)) {
callback('Leading or trailing whitespace not allowed in table name') return reject(new Error('Leading or trailing whitespace not allowed in table name'))
} }
if ( if (
!(tables?.value || []).every( !(tables?.value || []).every(
(t) => t.id === tableMeta.id || t.table_name.toLowerCase() !== (value || '').toLowerCase(), (t) => t.id === tableMeta.id || t.table_name.toLowerCase() !== (value || '').toLowerCase(),
) )
) { ) {
callback('Duplicate table alias') return reject(new Error('Duplicate table alias'))
} }
callback() resolve()
})
}, },
}, },
], ],
@ -71,7 +91,8 @@ const renameTable = async () => {
loading = true loading = true
try { try {
await $api.dbTable.update(tableMeta?.id as string, { await $api.dbTable.update(tableMeta?.id as string, {
title: formState.title, project_id: tableMeta?.project_id,
table_name: formState.title,
}) })
dialogShow.value = false dialogShow.value = false
loadTables() loadTables()

13
packages/nc-gui-v2/components/general/HelpAndSupport.vue

@ -15,14 +15,14 @@ const openSwaggerLink = () => {
</script> </script>
<template> <template>
<div> <div
<div @click="showDrawer = true"> class="flex items-center space-x-1 w-full cursor-pointer pl-3 py-1.5 hover:(text-primary bg-primary bg-opacity-5)"
<div class="flex items-center space-x-1"> @click="showDrawer = true"
>
<MdiCommentTextOutline class="mr-1 nc-share-base" /> <MdiCommentTextOutline class="mr-1 nc-share-base" />
<!-- todo: i18n --> <!-- todo: i18n -->
<div>APIs & Support</div> <div>APIs & Support</div>
</div> </div>
</div>
<a-drawer <a-drawer
v-model:visible="showDrawer" v-model:visible="showDrawer"
@ -36,12 +36,12 @@ const openSwaggerLink = () => {
<!-- todo: i18n --> <!-- todo: i18n -->
<a-typography-title :level="4" class="!mb-6 !text-gray-500">Help center</a-typography-title> <a-typography-title :level="4" class="!mb-6 !text-gray-500">Help center</a-typography-title>
<GeneralSocialCard show-swagger-link class="!w-full nc-social-card"> <GeneralSocialCard class="!w-full nc-social-card">
<template #before> <template #before>
<a-list-item v-if="project"> <a-list-item v-if="project">
<nuxt-link <nuxt-link
v-t="['e:docs']" v-t="['e:docs']"
class="text-primary !no-underline !text-current py-4 font-weight-medium" class="!no-underline !text-current py-4 font-semibold"
target="_blank" target="_blank"
@click="openSwaggerLink" @click="openSwaggerLink"
> >
@ -62,7 +62,6 @@ const openSwaggerLink = () => {
<div class="min-h-10 w-full" /> <div class="min-h-10 w-full" />
</div> </div>
</a-drawer> </a-drawer>
</div>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">

10
packages/nc-gui-v2/components/general/ShareBaseButton.vue

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { useRoute } from '#imports' import { useRoute, useUIPermission } from '#imports'
const route = useRoute() const route = useRoute()
@ -9,13 +9,13 @@ const { isUIAllowed } = useUIPermission()
</script> </script>
<template> <template>
<div class="flex items-center"> <div class="flex items-center w-full pl-3 hover:(text-primary bg-primary bg-opacity-5)">
<div <div
v-if=" v-if="
isUIAllowed('newUser') && isUIAllowed('newUser') &&
route.name !== 'index' && route.name !== 'index' &&
route.name !== 'project-index-create' && route.name !== 'index-index-create' &&
route.name !== 'project-index-create-external' && route.name !== 'index-index-create-external' &&
route.name !== 'index-user-index' route.name !== 'index-user-index'
" "
@click="showUserModal = true" @click="showUserModal = true"
@ -23,9 +23,11 @@ const { isUIAllowed } = useUIPermission()
<div class="flex items-center space-x-1"> <div class="flex items-center space-x-1">
<MdiAccountPlusOutline class="mr-1 nc-share-base" /> <MdiAccountPlusOutline class="mr-1 nc-share-base" />
<!-- todo: i18n <div>{{ $t('activity.share') }}</div> --> <!-- todo: i18n <div>{{ $t('activity.share') }}</div> -->
<div>{{ $t('activity.inviteTeam') }}</div> <div>{{ $t('activity.inviteTeam') }}</div>
</div> </div>
</div> </div>
<TabsAuthUserManagementUsersModal :key="showUserModal" :show="showUserModal" @closed="showUserModal = false" /> <TabsAuthUserManagementUsersModal :key="showUserModal" :show="showUserModal" @closed="showUserModal = false" />
</div> </div>
</template> </template>

33
packages/nc-gui-v2/components/smartsheet-toolbar/SearchData.vue

@ -1,6 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, ref, useSmartsheetStoreOrThrow } from '#imports' import { ReloadViewDataHookInj, computed, inject, onClickOutside, ref, useSmartsheetStoreOrThrow } from '#imports'
import { ReloadViewDataHookInj } from '~/context'
const reloadData = inject(ReloadViewDataHookInj)! const reloadData = inject(ReloadViewDataHookInj)!
@ -9,6 +8,10 @@ const { search, meta } = useSmartsheetStoreOrThrow()
// todo: where is this value supposed to come from? it's not in the store // todo: where is this value supposed to come from? it's not in the store
const isDropdownOpen = ref(false) const isDropdownOpen = ref(false)
const searchDropdown = ref(null)
onClickOutside(searchDropdown, () => (isDropdownOpen.value = false))
const columns = computed(() => const columns = computed(() =>
meta.value?.columns?.map((c) => ({ meta.value?.columns?.map((c) => ({
value: c.id, value: c.id,
@ -22,21 +25,35 @@ function onPressEnter() {
</script> </script>
<template> <template>
<a-input v-model:value="search.query" size="small" class="max-w-[200px]" placeholder="Filter query" @press-enter="onPressEnter"> <div class="flex flex-row border-1 rounded-sm">
<template #addonBefore> <div
<div class="flex items-center relative" @click="isDropdownOpen = true"> ref="searchDropdown"
class="flex items-center relative bg-gray-50 px-2 cursor-pointer border-r-1"
:class="{ '!bg-gray-100 ': isDropdownOpen }"
@click="isDropdownOpen = !isDropdownOpen"
>
<MdiMagnify class="text-grey" /> <MdiMagnify class="text-grey" />
<MdiMenuDown class="text-grey" /> <MdiMenuDown class="text-grey" />
<a-select <a-select
v-model:value="search.field" v-model:value="search.field"
:open="isDropdownOpen"
size="small" size="small"
:dropdown-match-select-width="false" :dropdown-match-select-width="false"
:options="columns" :options="columns"
dropdown-class-name="!py-0 !rounded"
class="!absolute top-0 left-0 w-full h-full z-10 !text-xs opacity-0" class="!absolute top-0 left-0 w-full h-full z-10 !text-xs opacity-0"
> />
</a-select>
</div> </div>
</template> <a-input
v-model:value="search.query"
size="small"
class="max-w-[200px]"
placeholder="Filter query"
:bordered="false"
@press-enter="onPressEnter"
>
<template #addonBefore> </template>
</a-input> </a-input>
</div>
</template> </template>

57
packages/nc-gui-v2/components/smartsheet/sidebar/MenuBottom.vue

@ -1,7 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ViewTypes } from 'nocodb-sdk' import { ViewTypes } from 'nocodb-sdk'
import { ref } from '#imports' import { ref, useUIPermission, viewIcons } from '#imports'
import { viewIcons } from '~/utils'
interface Emits { interface Emits {
(event: 'openModal', data: { type: ViewTypes; title?: string }): void (event: 'openModal', data: { type: ViewTypes; title?: string }): void
@ -13,8 +12,6 @@ const { isUIAllowed } = useUIPermission()
const isView = ref(false) const isView = ref(false)
const showWebhookDrawer = ref(false)
function onOpenModal(type: ViewTypes, title = '') { function onOpenModal(type: ViewTypes, title = '') {
emits('openModal', { type, title }) emits('openModal', { type, title })
} }
@ -22,7 +19,6 @@ function onOpenModal(type: ViewTypes, title = '') {
<template> <template>
<a-menu :selected-keys="[]" class="flex flex-col"> <a-menu :selected-keys="[]" class="flex flex-col">
<div class="flex-1"></div>
<div v-if="isUIAllowed('virtualViewsCreateOrEdit')"> <div v-if="isUIAllowed('virtualViewsCreateOrEdit')">
<h3 class="px-3 py-1 text-xs font-semibold flex items-center gap-4 text-gray-500"> <h3 class="px-3 py-1 text-xs font-semibold flex items-center gap-4 text-gray-500">
{{ $t('activity.createView') }} {{ $t('activity.createView') }}
@ -38,7 +34,7 @@ function onOpenModal(type: ViewTypes, title = '') {
{{ $t('msg.info.addView.grid') }} {{ $t('msg.info.addView.grid') }}
</template> </template>
<div class="text-xs flex items-center h-full w-full gap-2"> <div class="nc-project-menu-item text-xs flex items-center h-full w-full gap-2">
<component :is="viewIcons[ViewTypes.GRID].icon" :style="{ color: viewIcons[ViewTypes.GRID].color }" /> <component :is="viewIcons[ViewTypes.GRID].icon" :style="{ color: viewIcons[ViewTypes.GRID].color }" />
<div>{{ $t('objects.viewType.grid') }}</div> <div>{{ $t('objects.viewType.grid') }}</div>
@ -50,17 +46,13 @@ function onOpenModal(type: ViewTypes, title = '') {
</a-tooltip> </a-tooltip>
</a-menu-item> </a-menu-item>
<a-menu-item <a-menu-item key="gallery" class="group !flex !items-center !my-0 nc-create-2-view" @click="onOpenModal(ViewTypes.GALLERY)">
key="gallery"
class="group !flex !items-center !-my0 !h-[30px] nc-create-2-view"
@click="onOpenModal(ViewTypes.GALLERY)"
>
<a-tooltip :mouse-enter-delay="1" placement="left"> <a-tooltip :mouse-enter-delay="1" placement="left">
<template #title> <template #title>
{{ $t('msg.info.addView.gallery') }} {{ $t('msg.info.addView.gallery') }}
</template> </template>
<div class="text-xs flex items-center h-full w-full gap-2"> <div class="nc-project-menu-item text-xs flex items-center h-full w-full gap-2">
<component :is="viewIcons[ViewTypes.GALLERY].icon" :style="{ color: viewIcons[ViewTypes.GALLERY].color }" /> <component :is="viewIcons[ViewTypes.GALLERY].icon" :style="{ color: viewIcons[ViewTypes.GALLERY].color }" />
<div>{{ $t('objects.viewType.gallery') }}</div> <div>{{ $t('objects.viewType.gallery') }}</div>
@ -83,7 +75,7 @@ function onOpenModal(type: ViewTypes, title = '') {
{{ $t('msg.info.addView.form') }} {{ $t('msg.info.addView.form') }}
</template> </template>
<div class="text-xs flex items-center h-full w-full gap-2"> <div class="nc-project-menu-item text-xs flex items-center h-full w-full gap-2">
<component :is="viewIcons[ViewTypes.FORM].icon" :style="{ color: viewIcons[ViewTypes.FORM].color }" /> <component :is="viewIcons[ViewTypes.FORM].icon" :style="{ color: viewIcons[ViewTypes.FORM].color }" />
<div>{{ $t('objects.viewType.form') }}</div> <div>{{ $t('objects.viewType.form') }}</div>
@ -95,44 +87,7 @@ function onOpenModal(type: ViewTypes, title = '') {
</a-tooltip> </a-tooltip>
</a-menu-item> </a-menu-item>
<div class="w-full h-4"></div> <div class="w-full h-4" />
</div>
<!--
todo: bring back later
<general-flipping-card class="my-4 lg:my-6 min-h-[100px]" :triggers="['click', { duration: 15000 }]">
<template #front>
<div class="flex h-full w-full gap-6 flex-col">
<general-social />
<div>
<a
v-t="['e:hiring']"
class="px-4 py-3 rounded border text-xs text-current"
href="https://angel.co/company/nocodb"
target="_blank"
@click.stop
>
🚀 We are Hiring! 🚀
</a>
</div> </div>
</div>
</template>
<template #back>
&lt;!&ndash; todo: add project cost &ndash;&gt;
<a
href="https://github.com/sponsors/nocodb"
target="_blank"
class="group flex items-center gap-2 w-full mx-3 px-4 py-3 rounded-l border transform translate-x-4 hover:(translate-x-0 shadow-lg !opacity-100) transition duration-150 ease !text-xs text-current"
@click.stop
>
<mdi-cards-heart class="text-red-500" />
{{ $t('activity.sponsorUs') }}
</a>
</template>
</general-flipping-card> -->
<WebhookDrawer v-if="showWebhookDrawer" v-model="showWebhookDrawer" />
</a-menu> </a-menu>
</template> </template>

69
packages/nc-gui-v2/components/smartsheet/sidebar/MenuTop.vue

@ -1,14 +1,26 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { ViewType, ViewTypes } from 'nocodb-sdk' import type { ViewType } from 'nocodb-sdk'
import { ViewTypes } from 'nocodb-sdk'
import type { SortableEvent } from 'sortablejs' import type { SortableEvent } from 'sortablejs'
import type { Menu as AntMenu } from 'ant-design-vue' import type { Menu as AntMenu } from 'ant-design-vue'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import Sortable from 'sortablejs' import Sortable from 'sortablejs'
import RenameableMenuItem from './RenameableMenuItem.vue' import RenameableMenuItem from './RenameableMenuItem.vue'
import { inject, onMounted, ref, useApi, useRoute, useRouter, watch } from '#imports' import {
import { extractSdkResponseErrorMsg } from '~/utils' ActiveViewInj,
import { ActiveViewInj, ViewListInj } from '~/context' ViewListInj,
extractSdkResponseErrorMsg,
inject,
onMounted,
ref,
useApi,
useDialog,
useRoute,
useRouter,
watch,
} from '#imports'
import DlgViewDelete from '~/components/dlg/ViewDelete.vue'
interface Emits { interface Emits {
(event: 'openModal', data: { type: ViewTypes; title?: string; copyViewId?: string }): void (event: 'openModal', data: { type: ViewTypes; title?: string; copyViewId?: string }): void
@ -18,6 +30,12 @@ interface Emits {
const emits = defineEmits<Emits>() const emits = defineEmits<Emits>()
const viewTypeAlias = {
[ViewTypes.GRID as any]: 'grid',
[ViewTypes.FORM as any]: 'form',
[ViewTypes.GALLERY as any]: 'gallery',
}
const activeView = inject(ActiveViewInj, ref()) const activeView = inject(ActiveViewInj, ref())
const views = inject<Ref<any[]>>(ViewListInj, ref([])) const views = inject<Ref<any[]>>(ViewListInj, ref([]))
@ -34,11 +52,6 @@ const selected = ref<string[]>([])
/** dragging renamable view items */ /** dragging renamable view items */
let dragging = $ref(false) let dragging = $ref(false)
let deleteModalVisible = $ref(false)
/** view to delete for modal */
let toDelete = $ref<Record<string, any> | undefined>()
const menuRef = $ref<typeof AntMenu>() const menuRef = $ref<typeof AntMenu>()
let isMarked = $ref<string | false>(false) let isMarked = $ref<string | false>(false)
@ -158,25 +171,34 @@ async function onRename(view: ViewType) {
} }
/** Open delete modal */ /** Open delete modal */
async function onDelete(view: Record<string, any>) { function openDeleteDialog(view: Record<string, any>) {
toDelete = view const isOpen = ref(true)
deleteModalVisible = true
} const { close } = useDialog(DlgViewDelete, {
'modelValue': isOpen,
'view': view,
'onUpdate:modelValue': closeDialog,
'onDeleted': () => {
closeDialog()
/** View was deleted, trigger reload */
function onDeleted() {
emits('deleted') emits('deleted')
toDelete = undefined
deleteModalVisible = false
// return to the default view // return to the default view
activeView.value = views.value[0] activeView.value = views.value[0]
},
})
function closeDialog() {
isOpen.value = false
close(1000)
}
} }
</script> </script>
<template> <template>
<a-menu ref="menuRef" :class="{ dragging }" class="nc-views-menu flex-1" :selected-keys="selected"> <a-menu ref="menuRef" :class="{ dragging }" class="nc-views-menu flex-1" :selected-keys="selected">
<RenameableMenuItem <RenameableMenuItem
v-for="view of views" v-for="(view, index) of views"
:id="view.id" :id="view.id"
:key="view.id" :key="view.id"
:view="view" :view="view"
@ -184,17 +206,16 @@ function onDeleted() {
class="transition-all ease-in duration-300" class="transition-all ease-in duration-300"
:class="{ :class="{
'bg-gray-100': isMarked === view.id, 'bg-gray-100': isMarked === view.id,
'active': route.params.viewTitle && route.params.viewTitle === view.title, 'active':
[`nc-view-item nc-${view.type}-view-item`]: true, (route.params.viewTitle && route.params.viewTitle === view.title) || (route.params.viewTitle === '' && index === 0),
[`nc-view-item nc-${viewTypeAlias[view.type] || view.type}-view-item`]: true,
}" }"
@change-view="changeView" @change-view="changeView"
@open-modal="$emit('openModal', $event)" @open-modal="$emit('openModal', $event)"
@delete="onDelete" @delete="openDeleteDialog(view)"
@rename="onRename" @rename="onRename"
/> />
</a-menu> </a-menu>
<dlg-view-delete v-model="deleteModalVisible" :view="toDelete" @deleted="onDeleted" />
</template> </template>
<style lang="scss"> <style lang="scss">
@ -217,7 +238,7 @@ function onDeleted() {
} }
.ant-menu-item:not(.sortable-chosen) { .ant-menu-item:not(.sortable-chosen) {
@apply color-transition hover:!bg-transparent; @apply color-transition;
} }
.sortable-chosen { .sortable-chosen {

4
packages/nc-gui-v2/components/smartsheet/sidebar/RenameableMenuItem.vue

@ -146,14 +146,14 @@ function onStopEdit() {
<template> <template>
<a-menu-item <a-menu-item
class="select-none group !flex !items-center !my-0" class="select-none group !flex !items-center !my-0 hover:(bg-primary !bg-opacity-5)"
@dblclick.stop="isUIAllowed('virtualViewsCreateOrEdit') && onDblClick()" @dblclick.stop="isUIAllowed('virtualViewsCreateOrEdit') && onDblClick()"
@click.stop="onClick" @click.stop="onClick"
> >
<div v-t="['a:view:open', { view: vModel.type }]" class="text-xs flex items-center w-full gap-2"> <div v-t="['a:view:open', { view: vModel.type }]" class="text-xs flex items-center w-full gap-2">
<div class="flex w-auto"> <div class="flex w-auto">
<MdiDrag <MdiDrag
class="nc-drag-icon hidden group-hover:block transition-opacity opacity-0 group-hover:opacity-100 text-gray-500 cursor-move" class="nc-drag-icon hidden group-hover:block transition-opacity opacity-0 group-hover:opacity-100 text-gray-500 !cursor-move"
@click.stop.prevent @click.stop.prevent
/> />

4
packages/nc-gui-v2/components/smartsheet/sidebar/index.vue

@ -107,9 +107,7 @@ function onCreate(view: ViewType) {
<div v-if="isOpen" class="flex-1 flex flex-col min-h-0"> <div v-if="isOpen" class="flex-1 flex flex-col min-h-0">
<MenuTop @open-modal="openModal" @deleted="loadViews" @sorted="loadViews" /> <MenuTop @open-modal="openModal" @deleted="loadViews" @sorted="loadViews" />
<div v-if="isUIAllowed('virtualViewsCreateOrEdit')" class="px-3"> <div v-if="isUIAllowed('virtualViewsCreateOrEdit')" class="!my-3 w-full border-b-1" />
<div class="!my-3 w-full border-b-1 border-dashed" />
</div>
<MenuBottom @open-modal="openModal" /> <MenuBottom @open-modal="openModal" />
</div> </div>

2
packages/nc-gui-v2/composables/useProject.ts

@ -20,6 +20,7 @@ export function useProject(projectId?: MaybeRef<string>) {
const projectBaseType = $computed(() => project.value?.bases?.[0]?.type || '') const projectBaseType = $computed(() => project.value?.bases?.[0]?.type || '')
const isMysql = computed(() => ['mysql', 'mysql2'].includes(projectBaseType)) const isMysql = computed(() => ['mysql', 'mysql2'].includes(projectBaseType))
const isMssql = computed(() => projectBaseType === 'mssql')
const isPg = computed(() => projectBaseType === 'pg') const isPg = computed(() => projectBaseType === 'pg')
const sqlUi = computed( const sqlUi = computed(
() => SqlUiFactory.create({ client: projectBaseType }) as Exclude<ReturnType<typeof SqlUiFactory['create']>, typeof OracleUi>, () => SqlUiFactory.create({ client: projectBaseType }) as Exclude<ReturnType<typeof SqlUiFactory['create']>, typeof OracleUi>,
@ -82,6 +83,7 @@ export function useProject(projectId?: MaybeRef<string>) {
loadProject, loadProject,
loadTables, loadTables,
isMysql, isMysql,
isMssql,
isPg, isPg,
sqlUi, sqlUi,
isSharedBase, isSharedBase,

5
packages/nc-gui-v2/composables/useTable.ts

@ -30,12 +30,15 @@ export function useTable(onTableCreate?: (tableMeta: TableType) => void) {
return table.columns.includes(col.column_name) return table.columns.includes(col.column_name)
}) })
try {
const tableMeta = await $api.dbTable.create(project?.value?.id as string, { const tableMeta = await $api.dbTable.create(project?.value?.id as string, {
...table, ...table,
columns, columns,
}) })
onTableCreate?.(tableMeta) onTableCreate?.(tableMeta)
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
} }
watch( watch(

4
packages/nc-gui-v2/github-star.shims.d.ts vendored

@ -0,0 +1,4 @@
declare module 'vue-github-button' {
import type { Component } from '@vue/runtime-core'
export default Component
}

4
packages/nc-gui-v2/layouts/base.vue

@ -40,7 +40,7 @@ hooks.hook('page:finish', () => {
class="flex !bg-primary items-center text-white pl-4 pr-5 shadow-lg" class="flex !bg-primary items-center text-white pl-4 pr-5 shadow-lg"
> >
<div <div
v-if="route.name === 'index' || route.name === 'project-index-create' || route.name === 'project-index-create-external'" v-if="!route.params.projectType"
class="transition-all duration-200 p-2 cursor-pointer transform hover:scale-105 nc-noco-brand-icon" class="transition-all duration-200 p-2 cursor-pointer transform hover:scale-105 nc-noco-brand-icon"
@click="navigateTo('/')" @click="navigateTo('/')"
> >
@ -59,7 +59,7 @@ hooks.hook('page:finish', () => {
<GeneralReleaseInfo /> <GeneralReleaseInfo />
<GeneralShareBaseButton v-if="!isSharedBase" /> <GeneralShareBaseButton v-if="!isSharedBase" class="pr-4 font-semibold" />
<a-tooltip placement="bottom" :mouse-enter-delay="1"> <a-tooltip placement="bottom" :mouse-enter-delay="1">
<template #title> Switch language</template> <template #title> Switch language</template>

403
packages/nc-gui-v2/package-lock.json generated

@ -14,6 +14,7 @@
"dayjs": "^1.11.3", "dayjs": "^1.11.3",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"jsep": "^1.3.6", "jsep": "^1.3.6",
"json-schema-traverse": "^1.0.0",
"just-clone": "^6.1.1", "just-clone": "^6.1.1",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"locale-codes": "^1.3.1", "locale-codes": "^1.3.1",
@ -56,6 +57,7 @@
"@windicss/plugin-animations": "^1.0.9", "@windicss/plugin-animations": "^1.0.9",
"@windicss/plugin-question-mark": "^0.1.1", "@windicss/plugin-question-mark": "^0.1.1",
"@windicss/plugin-scrollbar": "^1.2.3", "@windicss/plugin-scrollbar": "^1.2.3",
"eslint": "^8.22.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"happy-dom": "^6.0.3", "happy-dom": "^6.0.3",
@ -906,7 +908,6 @@
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz",
"integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"ajv": "^6.12.4", "ajv": "^6.12.4",
"debug": "^4.3.2", "debug": "^4.3.2",
@ -927,7 +928,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0", "fast-json-stable-stringify": "^2.0.0",
@ -944,7 +944,6 @@
"resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz",
"integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"type-fest": "^0.20.2" "type-fest": "^0.20.2"
}, },
@ -959,15 +958,13 @@
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schem-traverse/-/json-schem-traverse-0.4.1.tgz", "resolved": "https://registry.npmjs.org/json-schem-traverse/-/json-schem-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true, "dev": true
"peer": true
}, },
"node_modules/@eslint/eslintrc/node_modules/type-fest": { "node_modules/@eslint/eslintrc/node_modules/type-fest": {
"version": "0.20.2", "version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true, "dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=10" "node": ">=10"
}, },
@ -976,11 +973,10 @@
} }
}, },
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.9.5", "version": "0.10.4",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz",
"integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@humanwhocodes/object-schema": "^1.2.1", "@humanwhocodes/object-schema": "^1.2.1",
"debug": "^4.1.1", "debug": "^4.1.1",
@ -990,12 +986,21 @@
"node": ">=10.10.0" "node": ">=10.10.0"
} }
}, },
"node_modules/@humanwhocodes/gitignore-to-minimatch": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz",
"integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==",
"dev": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@humanwhocodes/object-schema": { "node_modules/@humanwhocodes/object-schema": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schem-1.2.1.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
"dev": true, "dev": true
"peer": true
}, },
"node_modules/@iconify-json/bi": { "node_modules/@iconify-json/bi": {
"version": "1.1.6", "version": "1.1.6",
@ -3744,8 +3749,7 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true, "dev": true
"peer": true
}, },
"node_modules/array-includes": { "node_modules/array-includes": {
"version": "3.1.5", "version": "3.1.5",
@ -4140,7 +4144,6 @@
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true, "dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
@ -5265,8 +5268,7 @@
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true, "dev": true
"peer": true
}, },
"node_modules/deepmerge": { "node_modules/deepmerge": {
"version": "4.2.2", "version": "4.2.2",
@ -5392,7 +5394,6 @@
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"esutils": "^2.0.2" "esutils": "^2.0.2"
}, },
@ -6224,14 +6225,14 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "8.18.0", "version": "8.22.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.18.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.22.0.tgz",
"integrity": "sha512-As1EfFMVk7Xc6/CvhssHUjsAQSkpfXvUGMFC3ce8JDe6WvqCgRrLOBQbVpsBFr1X1V+RACOadnzVvcUS5ni2bA==", "integrity": "sha512-ci4t0sz6vSRKdmkOGmprBo6fmI4PrphDFMy5JEq/fNS0gQkJM3rLmrqcp8ipMcdobH3KtUP40KniAE9W19S4wA==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@eslint/eslintrc": "^1.3.0", "@eslint/eslintrc": "^1.3.0",
"@humanwhocodes/config-array": "^0.9.2", "@humanwhocodes/config-array": "^0.10.4",
"@humanwhocodes/gitignore-to-minimatch": "^1.0.2",
"ajv": "^6.10.0", "ajv": "^6.10.0",
"chalk": "^4.0.0", "chalk": "^4.0.0",
"cross-spawn": "^7.0.2", "cross-spawn": "^7.0.2",
@ -6241,14 +6242,17 @@
"eslint-scope": "^7.1.1", "eslint-scope": "^7.1.1",
"eslint-utils": "^3.0.0", "eslint-utils": "^3.0.0",
"eslint-visitor-keys": "^3.3.0", "eslint-visitor-keys": "^3.3.0",
"espree": "^9.3.2", "espree": "^9.3.3",
"esquery": "^1.4.0", "esquery": "^1.4.0",
"esutils": "^2.0.2", "esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"file-entry-cache": "^6.0.1", "file-entry-cache": "^6.0.1",
"find-up": "^5.0.0",
"functional-red-black-tree": "^1.0.1", "functional-red-black-tree": "^1.0.1",
"glob-parent": "^6.0.1", "glob-parent": "^6.0.1",
"globals": "^13.15.0", "globals": "^13.15.0",
"globby": "^11.1.0",
"grapheme-splitter": "^1.0.4",
"ignore": "^5.2.0", "ignore": "^5.2.0",
"import-fresh": "^3.0.0", "import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4", "imurmurhash": "^0.1.4",
@ -6767,7 +6771,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0", "fast-json-stable-stringify": "^2.0.0",
@ -6784,7 +6787,6 @@
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
"supports-color": "^7.1.0" "supports-color": "^7.1.0"
@ -6801,7 +6803,6 @@
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true, "dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=10" "node": ">=10"
}, },
@ -6814,7 +6815,6 @@
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
"integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"esrecurse": "^4.3.0", "esrecurse": "^4.3.0",
"estraverse": "^5.2.0" "estraverse": "^5.2.0"
@ -6828,17 +6828,31 @@
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true, "dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=4.0" "node": ">=4.0"
} }
}, },
"node_modules/eslint/node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"dev": true,
"dependencies": {
"locate-path": "^6.0.0",
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eslint/node_modules/glob-parent": { "node_modules/eslint/node_modules/glob-parent": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"is-glob": "^4.0.3" "is-glob": "^4.0.3"
}, },
@ -6851,7 +6865,6 @@
"resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz",
"integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"type-fest": "^0.20.2" "type-fest": "^0.20.2"
}, },
@ -6862,19 +6875,100 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/eslint/node_modules/globby": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
"integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
"dev": true,
"dependencies": {
"array-union": "^2.1.0",
"dir-glob": "^3.0.1",
"fast-glob": "^3.2.9",
"ignore": "^5.2.0",
"merge2": "^1.4.1",
"slash": "^3.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eslint/node_modules/json-schem-traverse": { "node_modules/eslint/node_modules/json-schem-traverse": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schem-traverse/-/json-schem-traverse-0.4.1.tgz", "resolved": "https://registry.npmjs.org/json-schem-traverse/-/json-schem-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"node_modules/eslint/node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"dev": true, "dev": true,
"peer": true "dependencies": {
"p-locate": "^5.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eslint/node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"dev": true,
"dependencies": {
"yocto-queue": "^0.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eslint/node_modules/p-locate": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
"dev": true,
"dependencies": {
"p-limit": "^3.0.2"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eslint/node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/eslint/node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true,
"engines": {
"node": ">=8"
}
}, },
"node_modules/eslint/node_modules/type-fest": { "node_modules/eslint/node_modules/type-fest": {
"version": "0.20.2", "version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true, "dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=10" "node": ">=10"
}, },
@ -6883,17 +6977,20 @@
} }
}, },
"node_modules/espree": { "node_modules/espree": {
"version": "9.3.2", "version": "9.3.3",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.3.tgz",
"integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", "integrity": "sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"acorn": "^8.7.1", "acorn": "^8.8.0",
"acorn-jsx": "^5.3.2", "acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^3.3.0" "eslint-visitor-keys": "^3.3.0"
}, },
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/esprima": { "node_modules/esprima": {
@ -7122,8 +7219,7 @@
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true, "dev": true
"peer": true
}, },
"node_modules/fastq": { "node_modules/fastq": {
"version": "1.13.0", "version": "1.13.0",
@ -7186,7 +7282,6 @@
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
"integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"flat-cache": "^3.0.4" "flat-cache": "^3.0.4"
}, },
@ -7306,7 +7401,6 @@
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
"integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"flatted": "^3.1.0", "flatted": "^3.1.0",
"rimraf": "^3.0.2" "rimraf": "^3.0.2"
@ -7319,8 +7413,7 @@
"version": "3.2.6", "version": "3.2.6",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz",
"integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==",
"dev": true, "dev": true
"peer": true
}, },
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.15.1", "version": "1.15.1",
@ -7763,6 +7856,12 @@
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
"dev": true "dev": true
}, },
"node_modules/grapheme-splitter": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
"integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
"dev": true
},
"node_modules/gzip-size": { "node_modules/gzip-size": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-7.0.0.tgz", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-7.0.0.tgz",
@ -8407,7 +8506,6 @@
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"parent-module": "^1.0.0", "parent-module": "^1.0.0",
"resolve-from": "^4.0.0" "resolve-from": "^4.0.0"
@ -8424,7 +8522,6 @@
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"callsites": "^3.0.0" "callsites": "^3.0.0"
}, },
@ -8437,7 +8534,6 @@
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true, "dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=4" "node": ">=4"
} }
@ -8447,7 +8543,6 @@
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
"dev": true, "dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=0.8.19" "node": ">=0.8.19"
} }
@ -9072,7 +9167,6 @@
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"argparse": "^2.0.1" "argparse": "^2.0.1"
}, },
@ -9200,12 +9294,16 @@
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true "dev": true
}, },
"node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
"node_modules/json-stable-stringify-without-jsonify": { "node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true, "dev": true
"peer": true
}, },
"node_modules/json5": { "node_modules/json5": {
"version": "2.2.1", "version": "2.2.1",
@ -9435,7 +9533,6 @@
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"prelude-ls": "^1.2.1", "prelude-ls": "^1.2.1",
"type-check": "~0.4.0" "type-check": "~0.4.0"
@ -9599,8 +9696,7 @@
"version": "4.6.2", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true, "dev": true
"peer": true
}, },
"node_modules/lodash.pick": { "node_modules/lodash.pick": {
"version": "4.4.0", "version": "4.4.0",
@ -10803,7 +10899,6 @@
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
"integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"deep-is": "^0.1.3", "deep-is": "^0.1.3",
"fast-levenshtein": "^2.0.6", "fast-levenshtein": "^2.0.6",
@ -11762,7 +11857,6 @@
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
"dev": true, "dev": true,
"peer": true,
"engines": { "engines": {
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
@ -13201,7 +13295,6 @@
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true, "dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
}, },
@ -13534,8 +13627,7 @@
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true, "dev": true
"peer": true
}, },
"node_modules/then-request": { "node_modules/then-request": {
"version": "6.0.2", "version": "6.0.2",
@ -13762,7 +13854,6 @@
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"prelude-ls": "^1.2.1" "prelude-ls": "^1.2.1"
}, },
@ -14166,8 +14257,7 @@
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
"integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
"dev": true, "dev": true
"peer": true
}, },
"node_modules/validate-npm-package-license": { "node_modules/validate-npm-package-license": {
"version": "3.0.4", "version": "3.0.4",
@ -14912,7 +15002,6 @@
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
"dev": true, "dev": true,
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -15747,7 +15836,6 @@
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz",
"integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==",
"dev": true, "dev": true,
"peer": true,
"requires": { "requires": {
"ajv": "^6.12.4", "ajv": "^6.12.4",
"debug": "^4.3.2", "debug": "^4.3.2",
@ -15765,7 +15853,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true, "dev": true,
"peer": true,
"requires": { "requires": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0", "fast-json-stable-stringify": "^2.0.0",
@ -15778,7 +15865,6 @@
"resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz",
"integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==",
"dev": true, "dev": true,
"peer": true,
"requires": { "requires": {
"type-fest": "^0.20.2" "type-fest": "^0.20.2"
} }
@ -15787,36 +15873,38 @@
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schem-traverse/-/json-schem-traverse-0.4.1.tgz", "resolved": "https://registry.npmjs.org/json-schem-traverse/-/json-schem-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true, "dev": true
"peer": true
}, },
"type-fest": { "type-fest": {
"version": "0.20.2", "version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true, "dev": true
"peer": true
} }
} }
}, },
"@humanwhocodes/config-array": { "@humanwhocodes/config-array": {
"version": "0.9.5", "version": "0.10.4",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz",
"integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==",
"dev": true, "dev": true,
"peer": true,
"requires": { "requires": {
"@humanwhocodes/object-schema": "^1.2.1", "@humanwhocodes/object-schema": "^1.2.1",
"debug": "^4.1.1", "debug": "^4.1.1",
"minimatch": "^3.0.4" "minimatch": "^3.0.4"
} }
}, },
"@humanwhocodes/gitignore-to-minimatch": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz",
"integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==",
"dev": true
},
"@humanwhocodes/object-schema": { "@humanwhocodes/object-schema": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schem-1.2.1.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
"dev": true, "dev": true
"peer": true
}, },
"@iconify-json/bi": { "@iconify-json/bi": {
"version": "1.1.6", "version": "1.1.6",
@ -17937,8 +18025,7 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true, "dev": true
"peer": true
}, },
"array-includes": { "array-includes": {
"version": "3.1.5", "version": "3.1.5",
@ -18218,8 +18305,7 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true, "dev": true
"peer": true
}, },
"camelcase": { "camelcase": {
"version": "6.3.0", "version": "6.3.0",
@ -19083,8 +19169,7 @@
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true, "dev": true
"peer": true
}, },
"deepmerge": { "deepmerge": {
"version": "4.2.2", "version": "4.2.2",
@ -19179,7 +19264,6 @@
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
"dev": true, "dev": true,
"peer": true,
"requires": { "requires": {
"esutils": "^2.0.2" "esutils": "^2.0.2"
} }
@ -19710,14 +19794,14 @@
} }
}, },
"eslint": { "eslint": {
"version": "8.18.0", "version": "8.22.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.18.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.22.0.tgz",
"integrity": "sha512-As1EfFMVk7Xc6/CvhssHUjsAQSkpfXvUGMFC3ce8JDe6WvqCgRrLOBQbVpsBFr1X1V+RACOadnzVvcUS5ni2bA==", "integrity": "sha512-ci4t0sz6vSRKdmkOGmprBo6fmI4PrphDFMy5JEq/fNS0gQkJM3rLmrqcp8ipMcdobH3KtUP40KniAE9W19S4wA==",
"dev": true, "dev": true,
"peer": true,
"requires": { "requires": {
"@eslint/eslintrc": "^1.3.0", "@eslint/eslintrc": "^1.3.0",
"@humanwhocodes/config-array": "^0.9.2", "@humanwhocodes/config-array": "^0.10.4",
"@humanwhocodes/gitignore-to-minimatch": "^1.0.2",
"ajv": "^6.10.0", "ajv": "^6.10.0",
"chalk": "^4.0.0", "chalk": "^4.0.0",
"cross-spawn": "^7.0.2", "cross-spawn": "^7.0.2",
@ -19727,14 +19811,17 @@
"eslint-scope": "^7.1.1", "eslint-scope": "^7.1.1",
"eslint-utils": "^3.0.0", "eslint-utils": "^3.0.0",
"eslint-visitor-keys": "^3.3.0", "eslint-visitor-keys": "^3.3.0",
"espree": "^9.3.2", "espree": "^9.3.3",
"esquery": "^1.4.0", "esquery": "^1.4.0",
"esutils": "^2.0.2", "esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"file-entry-cache": "^6.0.1", "file-entry-cache": "^6.0.1",
"find-up": "^5.0.0",
"functional-red-black-tree": "^1.0.1", "functional-red-black-tree": "^1.0.1",
"glob-parent": "^6.0.1", "glob-parent": "^6.0.1",
"globals": "^13.15.0", "globals": "^13.15.0",
"globby": "^11.1.0",
"grapheme-splitter": "^1.0.4",
"ignore": "^5.2.0", "ignore": "^5.2.0",
"import-fresh": "^3.0.0", "import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4", "imurmurhash": "^0.1.4",
@ -19758,7 +19845,6 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true, "dev": true,
"peer": true,
"requires": { "requires": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0", "fast-json-stable-stringify": "^2.0.0",
@ -19771,7 +19857,6 @@
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true, "dev": true,
"peer": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
"supports-color": "^7.1.0" "supports-color": "^7.1.0"
@ -19781,15 +19866,13 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true, "dev": true
"peer": true
}, },
"eslint-scope": { "eslint-scope": {
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
"integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
"dev": true, "dev": true,
"peer": true,
"requires": { "requires": {
"esrecurse": "^4.3.0", "esrecurse": "^4.3.0",
"estraverse": "^5.2.0" "estraverse": "^5.2.0"
@ -19799,15 +19882,23 @@
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true
},
"find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"dev": true, "dev": true,
"peer": true "requires": {
"locate-path": "^6.0.0",
"path-exists": "^4.0.0"
}
}, },
"glob-parent": { "glob-parent": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true, "dev": true,
"peer": true,
"requires": { "requires": {
"is-glob": "^4.0.3" "is-glob": "^4.0.3"
} }
@ -19817,24 +19908,74 @@
"resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz",
"integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==", "integrity": "sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==",
"dev": true, "dev": true,
"peer": true,
"requires": { "requires": {
"type-fest": "^0.20.2" "type-fest": "^0.20.2"
} }
}, },
"globby": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
"integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
"dev": true,
"requires": {
"array-union": "^2.1.0",
"dir-glob": "^3.0.1",
"fast-glob": "^3.2.9",
"ignore": "^5.2.0",
"merge2": "^1.4.1",
"slash": "^3.0.0"
}
},
"json-schem-traverse": { "json-schem-traverse": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schem-traverse/-/json-schem-traverse-0.4.1.tgz", "resolved": "https://registry.npmjs.org/json-schem-traverse/-/json-schem-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"dev": true, "dev": true,
"peer": true "requires": {
"p-locate": "^5.0.0"
}
},
"p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"dev": true,
"requires": {
"yocto-queue": "^0.1.0"
}
},
"p-locate": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
"dev": true,
"requires": {
"p-limit": "^3.0.2"
}
},
"path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true
},
"slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true
}, },
"type-fest": { "type-fest": {
"version": "0.20.2", "version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true, "dev": true
"peer": true
} }
} }
}, },
@ -20188,12 +20329,12 @@
"dev": true "dev": true
}, },
"espree": { "espree": {
"version": "9.3.2", "version": "9.3.3",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.3.tgz",
"integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", "integrity": "sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==",
"dev": true, "dev": true,
"requires": { "requires": {
"acorn": "^8.7.1", "acorn": "^8.8.0",
"acorn-jsx": "^5.3.2", "acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^3.3.0" "eslint-visitor-keys": "^3.3.0"
} }
@ -20379,8 +20520,7 @@
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true, "dev": true
"peer": true
}, },
"fastq": { "fastq": {
"version": "1.13.0", "version": "1.13.0",
@ -20423,7 +20563,6 @@
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
"integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
"dev": true, "dev": true,
"peer": true,
"requires": { "requires": {
"flat-cache": "^3.0.4" "flat-cache": "^3.0.4"
} }
@ -20521,7 +20660,6 @@
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
"integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
"dev": true, "dev": true,
"peer": true,
"requires": { "requires": {
"flatted": "^3.1.0", "flatted": "^3.1.0",
"rimraf": "^3.0.2" "rimraf": "^3.0.2"
@ -20531,8 +20669,7 @@
"version": "3.2.6", "version": "3.2.6",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz",
"integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==",
"dev": true, "dev": true
"peer": true
}, },
"follow-redirects": { "follow-redirects": {
"version": "1.15.1", "version": "1.15.1",
@ -20863,6 +21000,12 @@
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
"dev": true "dev": true
}, },
"grapheme-splitter": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
"integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
"dev": true
},
"gzip-size": { "gzip-size": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-7.0.0.tgz", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-7.0.0.tgz",
@ -21343,7 +21486,6 @@
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
"dev": true, "dev": true,
"peer": true,
"requires": { "requires": {
"parent-module": "^1.0.0", "parent-module": "^1.0.0",
"resolve-from": "^4.0.0" "resolve-from": "^4.0.0"
@ -21354,7 +21496,6 @@
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true, "dev": true,
"peer": true,
"requires": { "requires": {
"callsites": "^3.0.0" "callsites": "^3.0.0"
} }
@ -21363,8 +21504,7 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true, "dev": true
"peer": true
} }
} }
}, },
@ -21372,8 +21512,7 @@
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
"dev": true, "dev": true
"peer": true
}, },
"indent-string": { "indent-string": {
"version": "4.0.0", "version": "4.0.0",
@ -21823,7 +21962,6 @@
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true, "dev": true,
"peer": true,
"requires": { "requires": {
"argparse": "^2.0.1" "argparse": "^2.0.1"
} }
@ -21921,12 +22059,16 @@
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true "dev": true
}, },
"json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
"json-stable-stringify-without-jsonify": { "json-stable-stringify-without-jsonify": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true, "dev": true
"peer": true
}, },
"json5": { "json5": {
"version": "2.2.1", "version": "2.2.1",
@ -22111,7 +22253,6 @@
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
"dev": true, "dev": true,
"peer": true,
"requires": { "requires": {
"prelude-ls": "^1.2.1", "prelude-ls": "^1.2.1",
"type-check": "~0.4.0" "type-check": "~0.4.0"
@ -22251,8 +22392,7 @@
"version": "4.6.2", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true, "dev": true
"peer": true
}, },
"lodash.pick": { "lodash.pick": {
"version": "4.4.0", "version": "4.4.0",
@ -23210,7 +23350,6 @@
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
"integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
"dev": true, "dev": true,
"peer": true,
"requires": { "requires": {
"deep-is": "^0.1.3", "deep-is": "^0.1.3",
"fast-levenshtein": "^2.0.6", "fast-levenshtein": "^2.0.6",
@ -23878,8 +24017,7 @@
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
"dev": true, "dev": true
"peer": true
}, },
"prettier": { "prettier": {
"version": "2.7.1", "version": "2.7.1",
@ -24972,8 +25110,7 @@
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true, "dev": true
"peer": true
}, },
"strip-literal": { "strip-literal": {
"version": "0.4.0", "version": "0.4.0",
@ -25216,8 +25353,7 @@
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true, "dev": true
"peer": true
}, },
"then-request": { "then-request": {
"version": "6.0.2", "version": "6.0.2",
@ -25407,7 +25543,6 @@
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
"dev": true, "dev": true,
"peer": true,
"requires": { "requires": {
"prelude-ls": "^1.2.1" "prelude-ls": "^1.2.1"
} }
@ -25673,8 +25808,7 @@
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
"integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
"dev": true, "dev": true
"peer": true
}, },
"validate-npm-package-license": { "validate-npm-package-license": {
"version": "3.0.4", "version": "3.0.4",
@ -26223,8 +26357,7 @@
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
"dev": true, "dev": true
"peer": true
}, },
"wrap-ansi": { "wrap-ansi": {
"version": "7.0.0", "version": "7.0.0",

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

@ -22,6 +22,7 @@
"dayjs": "^1.11.3", "dayjs": "^1.11.3",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"jsep": "^1.3.6", "jsep": "^1.3.6",
"json-schema-traverse": "^1.0.0",
"just-clone": "^6.1.1", "just-clone": "^6.1.1",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"locale-codes": "^1.3.1", "locale-codes": "^1.3.1",
@ -64,6 +65,7 @@
"@windicss/plugin-animations": "^1.0.9", "@windicss/plugin-animations": "^1.0.9",
"@windicss/plugin-question-mark": "^0.1.1", "@windicss/plugin-question-mark": "^0.1.1",
"@windicss/plugin-scrollbar": "^1.2.3", "@windicss/plugin-scrollbar": "^1.2.3",
"eslint": "^8.22.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"happy-dom": "^6.0.3", "happy-dom": "^6.0.3",

9
packages/nc-gui-v2/pages/[projectType]/[projectId]/index.vue

@ -144,7 +144,7 @@ const onMenuClose = (visible: boolean) => {
</script> </script>
<template> <template>
<NuxtLayout id="content" class="flex"> <NuxtLayout id="content">
<template #sidebar> <template #sidebar>
<a-layout-sider <a-layout-sider
ref="sidebar" ref="sidebar"
@ -212,7 +212,7 @@ const onMenuClose = (visible: boolean) => {
</div> </div>
<template #overlay> <template #overlay>
<a-menu class="ml-6 !w-[300px] !text-sm !p-0 !rounded"> <a-menu class="!ml-1 !w-[300px] !text-sm">
<a-menu-item-group> <a-menu-item-group>
<template #title> <template #title>
<div class="group select-none flex items-center gap-4 py-1"> <div class="group select-none flex items-center gap-4 py-1">
@ -362,6 +362,8 @@ const onMenuClose = (visible: boolean) => {
</div> </div>
</template> </template>
<template #expandIcon></template>
<a-menu-item> <a-menu-item>
<div class="nc-project-menu-item group" @click.stop="openColorPicker('primary')"> <div class="nc-project-menu-item group" @click.stop="openColorPicker('primary')">
<ClarityColorPickerSolid class="group-hover:text-accent" /> <ClarityColorPickerSolid class="group-hover:text-accent" />
@ -403,9 +405,12 @@ const onMenuClose = (visible: boolean) => {
<DashboardTreeView v-show="isOpen" /> <DashboardTreeView v-show="isOpen" />
</a-layout-sider> </a-layout-sider>
</template> </template>
<div :key="$route.fullPath"> <div :key="$route.fullPath">
<dashboard-settings-modal v-model="dialogOpen" :open-key="openDialogKey" /> <dashboard-settings-modal v-model="dialogOpen" :open-key="openDialogKey" />
<NuxtPage /> <NuxtPage />
<GeneralPreviewAs float /> <GeneralPreviewAs float />
</div> </div>
</NuxtLayout> </NuxtLayout>

234
packages/nc-gui-v2/pages/index/index.vue

@ -1,239 +1,37 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Modal, message } from 'ant-design-vue' import { useRoute } from '#imports'
import type { ProjectType } from 'nocodb-sdk'
import {
computed,
definePageMeta,
extractSdkResponseErrorMsg,
navigateTo,
onMounted,
ref,
useApi,
useNuxtApp,
useSidebar,
useUIPermission,
} from '#imports'
definePageMeta({ const route = useRoute()
title: 'title.myProject',
})
const { $e } = useNuxtApp()
const { api, isLoading } = useApi()
const { isUIAllowed } = useUIPermission()
useSidebar({ hasSidebar: true, isOpen: true })
const filterQuery = ref('')
const projects = ref<ProjectType[]>()
const loadProjects = async () => {
const response = await api.project.list({})
projects.value = response.list
}
const filteredProjects = computed(
() =>
projects.value?.filter(
(project) => !filterQuery.value || project.title?.toLowerCase?.().includes(filterQuery.value.toLowerCase()),
) ?? [],
)
const deleteProject = (project: ProjectType) => {
$e('c:project:delete')
Modal.confirm({
title: `Do you want to delete '${project.title}' project?`,
okText: 'Yes',
okType: 'danger',
cancelText: 'No',
async onOk() {
try {
await api.project.delete(project.id as string)
$e('a:project:delete')
return projects.value?.splice(projects.value.indexOf(project), 1)
} catch (e: any) {
return message.error(await extractSdkResponseErrorMsg(e))
}
},
})
}
onMounted(() => {
loadProjects()
})
</script> </script>
<template> <template>
<NuxtLayout> <NuxtLayout>
<div class="flex flex-col md:flex-row flex-wrap gap-6 py-6 px-12">
<div class="hidden xl:(block)">
<GeneralSponsors />
</div>
<div class="min-w-2/4 flex-auto">
<a-card class="transition-all duration-300 ease-out !rounded-lg shadow">
<h1 class="flex items-center justify-center gap-2 leading-8 mb-8">
<!-- My Projects -->
<span class="text-4xl">{{ $t('title.myProject') }}</span>
<a-tooltip title="Reload projects">
<span
class="transition-all duration-200 h-full flex items-center group hover:ring active:(ring ring-accent) rounded-full mt-1"
:class="isLoading ? 'animate-spin ring ring-gray-200' : ''"
>
<MdiRefresh
v-t="['a:project:refresh']"
class="text-xl text-gray-500 group-hover:text-accent cursor-pointer"
:class="isLoading ? '!text-primary' : ''"
@click="loadProjects"
/>
</span>
</a-tooltip>
</h1>
<div class="order-1 flex mb-6">
<a-input-search
v-model:value="filterQuery"
class="max-w-[250px] nc-project-page-search rounded"
:placeholder="$t('activity.searchProject')"
/>
<div class="flex-1" />
<a-dropdown v-if="isUIAllowed('projectCreate', true)" :trigger="['click']">
<button class="nc-new-project-menu">
<div class="flex items-center w-full">
{{ $t('title.newProj') }}
<MdiMenuDown class="menu-icon" />
</div>
</button>
<template #overlay>
<a-menu class="!py-0 rounded">
<a-menu-item>
<div <div
v-t="['c:project:create:xcdb']" class="min-h-[calc(100vh_-_var(--header-height))] h-auto bg-primary bg-opacity-5 flex flex-col lg:flex-row flex-wrap gap-6 py-6 px-12 pt-65px"
class="nc-project-menu-item group"
@click="navigateTo('/project/create')"
> >
<MdiPlusOutline class="group-hover:text-accent" /> <div class="flex-1 justify-end hidden xl:(flex)">
<div>
<div>{{ $t('activity.createProject') }}</div> <GeneralSponsors />
</div>
</a-menu-item>
<a-menu-item>
<div
v-t="['c:project:create:extdb']"
class="nc-project-menu-item group"
@click="navigateTo('/project/create-external')"
>
<MdiDatabaseOutline class="group-hover:text-accent" />
<div v-html="$t('activity.createProjectExtended.extDB')" />
</div>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div> </div>
<TransitionGroup name="layout" mode="out-in">
<div v-if="isLoading">
<a-skeleton />
</div> </div>
<a-table <div class="min-w-2/4 xl:max-w-2/4 w-full mx-auto">
v-else <NuxtPage />
:custom-row="
(record) => ({
onClick: () => {
$e('a:project:open')
navigateTo(`/nc/${record.id}`)
},
class: ['group'],
})
"
:data-source="filteredProjects"
:pagination="{ position: ['bottomCenter'] }"
>
<!-- Title -->
<a-table-column key="title" :title="$t('general.title')" data-index="title">
<template #default="{ text }">
<div
class="capitalize color-transition group-hover:text-primary !w-[400px] overflow-hidden overflow-ellipsis whitespace-nowrap"
>
{{ text }}
</div>
</template>
</a-table-column>
<!-- Actions -->
<a-table-column key="id" :title="$t('labels.actions')" data-index="id">
<template #default="{ text, record }">
<div class="flex items-center gap-2">
<MdiEditOutline
v-t="['c:project:edit:rename']"
class="nc-action-btn"
@click.stop="navigateTo(`/project/${text}`)"
/>
<MdiDeleteOutline class="nc-action-btn" @click.stop="deleteProject(record)" />
</div>
</template>
</a-table-column>
</a-table>
</TransitionGroup>
</a-card>
</div> </div>
<div class="flex gap-6 md:block"> <div class="flex flex-1 justify-between gap-6 lg:block">
<template v-if="route.name === 'index-index'">
<TransitionGroup name="page" mode="out-in">
<div>
<GeneralSocialCard /> <GeneralSocialCard />
</div>
<div class="block mt-0 md:(!mt-6) xl:hidden"> <div class="block mt-0 lg:(!mt-6) xl:hidden">
<GeneralSponsors /> <GeneralSponsors />
</div> </div>
</TransitionGroup>
</template>
</div> </div>
</div> </div>
</NuxtLayout> </NuxtLayout>
</template> </template>
<style scoped>
.nc-action-btn {
@apply text-gray-500 group-hover:text-accent active:(ring ring-accent) cursor-pointer p-2 w-[30px] h-[30px] hover:bg-gray-300/50 rounded-full;
}
.nc-new-project-menu {
@apply cursor-pointer z-1 relative color-transition rounded-md px-3 py-2 text-white;
&::after {
@apply rounded-md absolute top-0 left-0 right-0 bottom-0 transition-all duration-150 ease-in-out bg-primary;
content: '';
z-index: -1;
}
&:hover::after {
@apply transform scale-110 ring ring-accent;
}
&:active::after {
@apply ring ring-accent;
}
}
:deep(.ant-table-cell) {
@apply py-1;
}
:deep(.ant-table-row) {
@apply cursor-pointer;
}
:deep(.ant-table) {
@apply min-h-[428px];
}
</style>

140
packages/nc-gui-v2/pages/index/index/[id].vue

@ -0,0 +1,140 @@
<script lang="ts" setup>
import type { Form } from 'ant-design-vue'
import type { ProjectType } from 'nocodb-sdk'
import { message } from 'ant-design-vue'
import {
extractSdkResponseErrorMsg,
navigateTo,
nextTick,
onMounted,
projectTitleValidator,
reactive,
ref,
useApi,
useRoute,
useSidebar,
} from '#imports'
const { api, isLoading } = useApi()
useSidebar({ hasSidebar: false })
const route = useRoute()
const nameValidationRules = [
{
required: true,
message: 'Project name is required',
},
projectTitleValidator,
]
const form = ref<typeof Form>()
const formState = reactive({
title: '',
})
const getProject = async () => {
try {
const result: ProjectType = await api.project.read(route.params.id as string)
formState.title = result.title as string
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
const renameProject = async () => {
try {
await api.project.update(route.params.id as string, formState)
navigateTo(`/nc/${route.params.id}`)
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
// select and focus title field on load
onMounted(async () => {
await nextTick(() => {
// todo: replace setTimeout and follow better approach
setTimeout(() => {
const input = form.value?.$el?.querySelector('input[type=text]')
input.setSelectionRange(0, formState.title.length)
input.focus()
}, 500)
})
})
await getProject()
</script>
<template>
<div
class="update-project bg-white relative flex-auto flex flex-col justify-center gap-2 p-8 md:(rounded-lg border-1 border-gray-200 shadow-xl)"
>
<general-noco-icon class="color-transition hover:(ring ring-accent)" :class="[isLoading ? 'animated-bg-gradient' : '']" />
<div
class="color-transition transform group absolute top-5 left-5 text-4xl rounded-full bg-white cursor-pointer"
@click="navigateTo('/')"
>
<MdiChevronLeft class="text-black group-hover:(text-accent scale-110)" />
</div>
<h1 class="prose-2xl font-bold self-center my-4">{{ $t('activity.editProject') }}</h1>
<a-form
ref="form"
:model="formState"
name="basic"
layout="vertical"
class="lg:max-w-3/4 w-full !mx-auto"
no-style
autocomplete="off"
@finish="renameProject"
>
<a-form-item :label="$t('labels.projName')" name="title" :rules="nameValidationRules">
<a-input v-model:value="formState.title" name="title" class="nc-metadb-project-name" />
</a-form-item>
<div class="text-center">
<button type="submit" class="submit">
<span class="flex items-center gap-2">
<MaterialSymbolsRocketLaunchOutline />
{{ $t('general.edit') }}
</span>
</button>
</div>
</a-form>
</div>
</template>
<style lang="scss">
.update-project {
.ant-input-affix-wrapper,
.ant-input {
@apply !appearance-none my-1 border-1 border-solid rounded;
}
.submit {
@apply z-1 relative color-transition rounded p-3 text-white shadow-sm;
&::after {
@apply rounded absolute top-0 left-0 right-0 bottom-0 transition-all duration-150 ease-in-out bg-primary;
content: '';
z-index: -1;
}
&:hover::after {
@apply transform scale-110 ring ring-accent;
}
&:active::after {
@apply ring ring-accent;
}
}
}
</style>

134
packages/nc-gui-v2/pages/project/index/create-external.vue → packages/nc-gui-v2/pages/index/index/create-external.vue

@ -1,28 +1,36 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted } from '@vue/runtime-core'
import { Form, Modal, message } from 'ant-design-vue' import { Form, Modal, message } from 'ant-design-vue'
import { useI18n } from 'vue-i18n'
import { computed, ref, useSidebar, watch } from '#imports'
import { navigateTo, useNuxtApp } from '#app'
import { ClientType } from '~/lib'
import type { ProjectCreateForm } from '~/utils'
import { import {
clientTypes, clientTypes,
computed,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
fieldRequiredValidator, fieldRequiredValidator,
generateUniqueName, generateUniqueName,
getDefaultConnectionConfig, getDefaultConnectionConfig,
getTestDatabaseName, getTestDatabaseName,
navigateTo,
nextTick,
onMounted,
projectTitleValidator, projectTitleValidator,
readFile, readFile,
ref,
sslUsage, sslUsage,
} from '~/utils' useApi,
useI18n,
useNuxtApp,
useSidebar,
watch,
} from '#imports'
import { ClientType } from '~/lib'
import type { ProjectCreateForm } from '~/utils'
const useForm = Form.useForm const useForm = Form.useForm
const loading = ref(false)
const testSuccess = ref(false) const testSuccess = ref(false)
const { $api, $e } = useNuxtApp() const { api, isLoading } = useApi()
const { $e } = useNuxtApp()
useSidebar({ hasSidebar: false }) useSidebar({ hasSidebar: false })
@ -123,11 +131,13 @@ const createProject = async () => {
focusInvalidInput() focusInvalidInput()
return return
} }
loading.value = true
try { try {
const connection = getConnectionConfig() const connection = getConnectionConfig()
const config = { ...formState.dataSource, connection } const config = { ...formState.dataSource, connection }
const result = await $api.project.create({
const result = await api.project.create({
title: formState.title, title: formState.title,
bases: [ bases: [
{ {
@ -139,12 +149,13 @@ const createProject = async () => {
], ],
external: true, external: true,
}) })
$e('a:project:create:extdb') $e('a:project:create:extdb')
await navigateTo(`/nc/${result.id}`) await navigateTo(`/nc/${result.id}`)
} catch (e: any) { } catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))
} }
loading.value = false
} }
const testConnection = async () => { const testConnection = async () => {
@ -154,19 +165,23 @@ const testConnection = async () => {
focusInvalidInput() focusInvalidInput()
return return
} }
$e('a:project:create:extdb:test-connection', []) $e('a:project:create:extdb:test-connection', [])
try { try {
if (formState.dataSource.client === ClientType.SQLITE) { if (formState.dataSource.client === ClientType.SQLITE) {
testSuccess.value = true testSuccess.value = true
} else { } else {
const connection: any = getConnectionConfig() const connection = getConnectionConfig()
connection.database = getTestDatabaseName(formState.dataSource)
connection.database = getTestDatabaseName(formState.dataSource)!
const testConnectionConfig = { const testConnectionConfig = {
...formState.dataSource, ...formState.dataSource,
connection, connection,
} }
const result = await $api.utils.testConnection(testConnectionConfig) const result = await api.utils.testConnection(testConnectionConfig)
if (result.code === 0) { if (result.code === 0) {
testSuccess.value = true testSuccess.value = true
@ -183,11 +198,13 @@ const testConnection = async () => {
}) })
} else { } else {
testSuccess.value = false testSuccess.value = false
message.error(`${t('msg.error.dbConnectionFailed')} ${result.message}`) message.error(`${t('msg.error.dbConnectionFailed')} ${result.message}`)
} }
} }
} catch (e: any) { } catch (e: any) {
testSuccess.value = false testSuccess.value = false
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))
} }
} }
@ -213,26 +230,34 @@ onMounted(() => {
</script> </script>
<template> <template>
<a-card class="max-w-[600px] !mx-auto !mt-100px !mb-5 !shadow-md"> <div
<GeneralNocoIcon /> class="create-external bg-white relative flex flex-col justify-center gap-2 w-full p-8 md:(rounded-lg border-1 border-gray-200 shadow-xl)"
>
<general-noco-icon class="color-transition hover:(ring ring-accent)" :class="[isLoading ? 'animated-bg-gradient' : '']" />
<h3 class="text-3xl text-center font-semibold mt-8 mb-4">{{ $t('activity.createProject') }}</h3> <div
class="color-transition transform group absolute top-5 left-5 text-4xl rounded-full bg-white cursor-pointer"
@click="navigateTo('/')"
>
<MdiChevronLeft class="text-black group-hover:(text-accent scale-110)" />
</div>
<h1 class="prose-2xl font-bold self-center my-4">{{ $t('activity.createProject') }}</h1>
<a-form <a-form
ref="form" ref="form"
:model="formState" :model="formState"
name="external-project-create-form" name="external-project-create-form"
layout="horizontal" layout="horizontal"
no-style
:label-col="{ span: 8 }" :label-col="{ span: 8 }"
:wrapper-col="{ span: 18 }"
class="!pr-5"
> >
<a-form-item :label="$t('placeholder.projName')" v-bind="validateInfos.title"> <a-form-item :label="$t('placeholder.projName')" v-bind="validateInfos.title">
<a-input v-model:value="formState.title" size="small" class="nc-extdb-proj-name" /> <a-input v-model:value="formState.title" class="nc-extdb-proj-name" />
</a-form-item> </a-form-item>
<a-form-item :label="$t('labels.dbType')" v-bind="validateInfos['dataSource.client']"> <a-form-item :label="$t('labels.dbType')" v-bind="validateInfos['dataSource.client']">
<a-select v-model:value="formState.dataSource.client" size="small" class="nc-extdb-db-type" @change="onClientChange"> <a-select v-model:value="formState.dataSource.client" class="nc-extdb-db-type" @change="onClientChange">
<a-select-option v-for="client in clientTypes" :key="client.value" :value="client.value" <a-select-option v-for="client in clientTypes" :key="client.value" :value="client.value"
>{{ client.text }} >{{ client.text }}
</a-select-option> </a-select-option>
@ -245,32 +270,28 @@ onMounted(() => {
:label="$t('labels.sqliteFile')" :label="$t('labels.sqliteFile')"
v-bind="validateInfos['dataSource.connection.connection.filename']" v-bind="validateInfos['dataSource.connection.connection.filename']"
> >
<a-input v-model:value="formState.dataSource.connection.connection.filename" size="small" /> <a-input v-model:value="formState.dataSource.connection.connection.filename" />
</a-form-item> </a-form-item>
<template v-else> <template v-else>
<!-- Host Address --> <!-- Host Address -->
<a-form-item :label="$t('labels.hostAddress')" v-bind="validateInfos['dataSource.connection.host']"> <a-form-item :label="$t('labels.hostAddress')" v-bind="validateInfos['dataSource.connection.host']">
<a-input v-model:value="formState.dataSource.connection.host" size="small" class="nc-extdb-host-address" /> <a-input v-model:value="formState.dataSource.connection.host" class="nc-extdb-host-address" />
</a-form-item> </a-form-item>
<!-- Port Number --> <!-- Port Number -->
<a-form-item :label="$t('labels.port')" v-bind="validateInfos['dataSource.connection.port']"> <a-form-item :label="$t('labels.port')" v-bind="validateInfos['dataSource.connection.port']">
<a-input-number v-model:value="formState.dataSource.connection.port" class="!w-full nc-extdb-host-port" size="small" /> <a-input-number v-model:value="formState.dataSource.connection.port" class="!w-full nc-extdb-host-port" />
</a-form-item> </a-form-item>
<!-- Username --> <!-- Username -->
<a-form-item :label="$t('labels.username')" v-bind="validateInfos['dataSource.connection.user']"> <a-form-item :label="$t('labels.username')" v-bind="validateInfos['dataSource.connection.user']">
<a-input v-model:value="formState.dataSource.connection.user" size="small" class="nc-extdb-host-user" /> <a-input v-model:value="formState.dataSource.connection.user" class="nc-extdb-host-user" />
</a-form-item> </a-form-item>
<!-- Password --> <!-- Password -->
<a-form-item :label="$t('labels.password')"> <a-form-item :label="$t('labels.password')">
<a-input-password <a-input-password v-model:value="formState.dataSource.connection.password" class="nc-extdb-host-password" />
v-model:value="formState.dataSource.connection.password"
size="small"
class="nc-extdb-host-password"
/>
</a-form-item> </a-form-item>
<!-- Database --> <!-- Database -->
@ -279,24 +300,24 @@ onMounted(() => {
<a-input <a-input
v-model:value="formState.dataSource.connection.database" v-model:value="formState.dataSource.connection.database"
:placeholder="$t('labels.dbCreateIfNotExists')" :placeholder="$t('labels.dbCreateIfNotExists')"
size="small"
class="nc-extdb-host-database" class="nc-extdb-host-database"
/> />
</a-form-item> </a-form-item>
<!-- Schema name --> <!-- Schema name -->
<a-form-item <a-form-item
v-if="[ClientType.MSSQL, ClientType.PG].includes(formState.dataSource.client) && formState.dataSource.searchPath" v-if="[ClientType.MSSQL, ClientType.PG].includes(formState.dataSource.client) && formState.dataSource.searchPath"
:label="$t('labels.schemaName')" :label="$t('labels.schemaName')"
v-bind="validateInfos['dataSource.searchPath.0']" v-bind="validateInfos['dataSource.searchPath.0']"
> >
<a-input v-model:value="formState.dataSource.searchPath[0]" size="small" /> <a-input v-model:value="formState.dataSource.searchPath[0]" />
</a-form-item> </a-form-item>
<a-collapse ghost expand-icon-position="right" class="mt-6"> <a-collapse ghost expand-icon-position="right" class="!mt-6">
<a-collapse-panel key="1" :header="$t('title.advancedParameters')"> <a-collapse-panel key="1" :header="$t('title.advancedParameters')">
<!-- todo: add in i18n --> <!-- todo: add in i18n -->
<a-form-item label="SSL mode"> <a-form-item label="SSL mode">
<a-select v-model:value="formState.sslUse" size="small" @change="onClientChange"> <a-select v-model:value="formState.sslUse" @change="onClientChange">
<a-select-option v-for="opt in sslUsage" :key="opt" :value="opt">{{ opt }}</a-select-option> <a-select-option v-for="opt in sslUsage" :key="opt" :value="opt">{{ opt }}</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
@ -308,25 +329,29 @@ onMounted(() => {
<template #title> <template #title>
<span>{{ $t('tooltip.clientCert') }}</span> <span>{{ $t('tooltip.clientCert') }}</span>
</template> </template>
<a-button :disabled="!sslFilesRequired" size="small" class="shadow" @click="certFileInput.click()">
<a-button :disabled="!sslFilesRequired" class="shadow" @click="certFileInput.click()">
{{ $t('labels.clientCert') }} {{ $t('labels.clientCert') }}
</a-button> </a-button>
</a-tooltip> </a-tooltip>
<a-tooltip placement="top"> <a-tooltip placement="top">
<!-- Select .key file --> <!-- Select .key file -->
<template #title> <template #title>
<span>{{ $t('tooltip.clientKey') }}</span> <span>{{ $t('tooltip.clientKey') }}</span>
</template> </template>
<a-button :disabled="!sslFilesRequired" size="small" class="shadow" @click="keyFileInput.click()"> <a-button :disabled="!sslFilesRequired" class="shadow" @click="keyFileInput.click()">
{{ $t('labels.clientKey') }} {{ $t('labels.clientKey') }}
</a-button> </a-button>
</a-tooltip> </a-tooltip>
<a-tooltip placement="top"> <a-tooltip placement="top">
<!-- Select CA file --> <!-- Select CA file -->
<template #title> <template #title>
<span>{{ $t('tooltip.clientCA') }}</span> <span>{{ $t('tooltip.clientCA') }}</span>
</template> </template>
<a-button :disabled="!sslFilesRequired" size="small" class="shadow" @click="caFileInput.click()">
<a-button :disabled="!sslFilesRequired" class="shadow" @click="caFileInput.click()">
{{ $t('labels.serverCA') }} {{ $t('labels.serverCA') }}
</a-button> </a-button>
</a-tooltip> </a-tooltip>
@ -334,21 +359,25 @@ onMounted(() => {
</a-form-item> </a-form-item>
<input ref="caFileInput" type="file" class="!hidden" @change="onFileSelect('ca', caFileInput)" /> <input ref="caFileInput" type="file" class="!hidden" @change="onFileSelect('ca', caFileInput)" />
<input ref="certFileInput" type="file" class="!hidden" @change="onFileSelect('cert', certFileInput)" /> <input ref="certFileInput" type="file" class="!hidden" @change="onFileSelect('cert', certFileInput)" />
<input ref="keyFileInput" type="file" class="!hidden" @change="onFileSelect('key', keyFileInput)" /> <input ref="keyFileInput" type="file" class="!hidden" @change="onFileSelect('key', keyFileInput)" />
<a-form-item :label="$t('labels.inflection.tableName')"> <a-form-item :label="$t('labels.inflection.tableName')">
<a-select v-model:value="formState.inflection.inflectionTable" size="small" @change="onClientChange"> <a-select v-model:value="formState.inflection.inflectionTable" @change="onClientChange">
<a-select-option v-for="type in inflectionTypes" :key="type" :value="type">{{ type }}</a-select-option> <a-select-option v-for="type in inflectionTypes" :key="type" :value="type">{{ type }}</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item :label="$t('labels.inflection.columnName')"> <a-form-item :label="$t('labels.inflection.columnName')">
<a-select v-model:value="formState.inflection.inflectionColumn" size="small" @change="onClientChange"> <a-select v-model:value="formState.inflection.inflectionColumn" @change="onClientChange">
<a-select-option v-for="type in inflectionTypes" :key="type" :value="type">{{ type }}</a-select-option> <a-select-option v-for="type in inflectionTypes" :key="type" :value="type">{{ type }}</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<div class="flex justify-end"> <div class="flex justify-end">
<a-button size="small" class="!shadow-md" @click="configEditDlg = true"> <a-button class="!shadow-md" @click="configEditDlg = true">
<!-- Edit connection JSON --> <!-- Edit connection JSON -->
{{ $t('activity.editConnJson') }} {{ $t('activity.editConnJson') }}
</a-button> </a-button>
@ -357,11 +386,12 @@ onMounted(() => {
</a-collapse> </a-collapse>
</template> </template>
<a-form-item class="flex justify-center mt-5"> <a-form-item class="flex justify-center !mt-5">
<div class="flex justify-center gap-2"> <div class="flex justify-center gap-2">
<a-button type="primary" ghost class="nc-extdb-btn-test-connection" @click="testConnection"> <a-button type="primary" ghost class="nc-extdb-btn-test-connection" @click="testConnection">
{{ $t('activity.testDbConn') }} {{ $t('activity.testDbConn') }}
</a-button> </a-button>
<a-button type="primary" :disabled="!testSuccess" class="nc-extdb-btn-submit !shadow" @click="createProject"> <a-button type="primary" :disabled="!testSuccess" class="nc-extdb-btn-submit !shadow" @click="createProject">
Submit Submit
</a-button> </a-button>
@ -369,15 +399,17 @@ onMounted(() => {
</a-form-item> </a-form-item>
</a-form> </a-form>
<!-- todo: needs replacement
<v-dialog v-model="configEditDlg"> <v-dialog v-model="configEditDlg">
<a-card> <a-card>
<MonacoEditor v-if="configEditDlg" v-model="formState" class="h-[400px] w-[600px]" /> <MonacoEditor v-if="configEditDlg" v-model="formState" class="h-[400px] w-[600px]" />
</a-card> </a-card>
</v-dialog> </v-dialog>
</a-card> -->
</div>
</template> </template>
<style scoped> <style lang="scss" scoped>
:deep(.ant-collapse-header) { :deep(.ant-collapse-header) {
@apply !pr-10 !-mt-4 text-right justify-end; @apply !pr-10 !-mt-4 text-right justify-end;
} }
@ -398,7 +430,17 @@ onMounted(() => {
@apply !min-h-0; @apply !min-h-0;
} }
:deep(.ant-card-head-title) { .create-external {
@apply !text-3xl; :deep(.ant-input-affix-wrapper),
:deep(.ant-input),
:deep(.ant-select) {
@apply !appearance-none border-1 border-solid rounded;
}
:deep(.ant-input-password) {
input {
@apply !border-none my-0;
}
}
} }
</style> </style>

131
packages/nc-gui-v2/pages/index/index/create.vue

@ -0,0 +1,131 @@
<script lang="ts" setup>
import type { Form } from 'ant-design-vue'
import { message } from 'ant-design-vue'
import {
extractSdkResponseErrorMsg,
navigateTo,
nextTick,
onMounted,
projectTitleValidator,
reactive,
ref,
useApi,
useNuxtApp,
useSidebar,
} from '#imports'
const { $e } = useNuxtApp()
const { api, isLoading } = useApi()
useSidebar({ hasSidebar: false })
const nameValidationRules = [
{
required: true,
message: 'Project name is required',
},
projectTitleValidator,
]
const form = ref<typeof Form>()
const formState = reactive({
title: '',
})
const createProject = async () => {
$e('a:project:create:xcdb')
try {
const result = await api.project.create({
title: formState.title,
})
await navigateTo(`/nc/${result.id}`)
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
// select and focus title field on load
onMounted(async () => {
await nextTick(() => {
// todo: replace setTimeout and follow better approach
setTimeout(() => {
const input = form.value?.$el?.querySelector('input[type=text]')
input.setSelectionRange(0, formState.title.length)
input.focus()
}, 500)
})
})
</script>
<template>
<div
class="create bg-white relative flex flex-col justify-center gap-2 w-full p-8 md:(rounded-lg border-1 border-gray-200 shadow-xl)"
>
<general-noco-icon class="color-transition hover:(ring ring-accent)" :class="[isLoading ? 'animated-bg-gradient' : '']" />
<div
class="color-transition transform group absolute top-5 left-5 text-4xl rounded-full bg-white cursor-pointer"
@click="navigateTo('/')"
>
<MdiChevronLeft class="text-black group-hover:(text-accent scale-110)" />
</div>
<h1 class="prose-2xl font-bold self-center my-4">{{ $t('activity.createProject') }}</h1>
<a-form
ref="form"
:model="formState"
name="basic"
layout="vertical"
class="lg:max-w-3/4 w-full !mx-auto"
no-style
autocomplete="off"
@finish="createProject"
>
<a-form-item :label="$t('labels.projName')" name="title" :rules="nameValidationRules" class="m-10">
<a-input v-model:value="formState.title" name="title" class="nc-metadb-project-name" />
</a-form-item>
<div class="text-center">
<button class="submit" type="submit">
<span class="flex items-center gap-2">
<MaterialSymbolsRocketLaunchOutline />
{{ $t('general.create') }}
</span>
</button>
</div>
</a-form>
</div>
</template>
<style lang="scss">
.create {
.ant-input-affix-wrapper,
.ant-input {
@apply !appearance-none my-1 border-1 border-solid rounded;
}
.submit {
@apply z-1 relative color-transition rounded p-3 text-white shadow-sm;
&::after {
@apply rounded absolute top-0 left-0 right-0 bottom-0 transition-all duration-150 ease-in-out bg-primary;
content: '';
z-index: -1;
}
&:hover::after {
@apply transform scale-110 ring ring-accent;
}
&:active::after {
@apply ring ring-accent;
}
}
}
</style>

211
packages/nc-gui-v2/pages/index/index/index.vue

@ -0,0 +1,211 @@
<script lang="ts" setup>
import { Modal, message } from 'ant-design-vue'
import type { ProjectType } from 'nocodb-sdk'
import {
computed,
definePageMeta,
extractSdkResponseErrorMsg,
navigateTo,
ref,
useApi,
useNuxtApp,
useSidebar,
useUIPermission,
} from '#imports'
definePageMeta({
title: 'title.myProject',
})
const { $e } = useNuxtApp()
const { api, isLoading } = useApi()
const { isUIAllowed } = useUIPermission()
useSidebar({ hasSidebar: true, isOpen: true })
const filterQuery = ref('')
const projects = ref<ProjectType[]>()
const loadProjects = async () => {
const response = await api.project.list({})
projects.value = response.list
}
const filteredProjects = computed(
() =>
projects.value?.filter(
(project) => !filterQuery.value || project.title?.toLowerCase?.().includes(filterQuery.value.toLowerCase()),
) ?? [],
)
const deleteProject = (project: ProjectType) => {
$e('c:project:delete')
Modal.confirm({
title: `Do you want to delete '${project.title}' project?`,
okText: 'Yes',
okType: 'danger',
cancelText: 'No',
async onOk() {
try {
await api.project.delete(project.id as string)
$e('a:project:delete')
projects.value?.splice(projects.value?.indexOf(project), 1)
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
},
})
}
await loadProjects()
</script>
<template>
<div class="bg-white relative flex flex-col justify-center gap-2 w-full p-8 md:(rounded-lg border-1 border-gray-200 shadow-xl)">
<general-noco-icon class="color-transition hover:(ring ring-accent)" :class="[isLoading ? 'animated-bg-gradient' : '']" />
<h1 class="flex items-center justify-center gap-2 leading-8 mb-8 mt-4">
<!-- My Projects -->
<span class="text-4xl">{{ $t('title.myProject') }}</span>
<a-tooltip title="Reload projects">
<span
class="transition-all duration-200 h-full flex items-center group hover:ring active:(ring ring-accent) rounded-full mt-1"
:class="isLoading ? 'animate-spin ring ring-gray-200' : ''"
>
<MdiRefresh
v-t="['a:project:refresh']"
class="text-xl text-gray-500 group-hover:text-accent cursor-pointer"
:class="isLoading ? '!text-primary' : ''"
@click="loadProjects"
/>
</span>
</a-tooltip>
</h1>
<div class="flex mb-6">
<a-input-search
v-model:value="filterQuery"
class="max-w-[250px] nc-project-page-search rounded"
:placeholder="$t('activity.searchProject')"
/>
<div class="flex-1" />
<a-dropdown v-if="isUIAllowed('projectCreate', true)" :trigger="['click']">
<button class="nc-new-project-menu">
<span class="flex items-center w-full">
{{ $t('title.newProj') }}
<MdiMenuDown class="menu-icon" />
</span>
</button>
<template #overlay>
<a-menu class="!py-0 rounded">
<a-menu-item>
<div v-t="['c:project:create:xcdb']" class="nc-project-menu-item group" @click="navigateTo('/create')">
<MdiPlusOutline class="group-hover:text-accent" />
<div>{{ $t('activity.createProject') }}</div>
</div>
</a-menu-item>
<a-menu-item>
<div v-t="['c:project:create:extdb']" class="nc-project-menu-item group" @click="navigateTo('/create-external')">
<MdiDatabaseOutline class="group-hover:text-accent" />
<div v-html="$t('activity.createProjectExtended.extDB')" />
</div>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
<TransitionGroup name="layout" mode="out-in">
<div v-if="isLoading">
<a-skeleton />
</div>
<a-table
v-else
:custom-row="
(record) => ({
onClick: () => {
$e('a:project:open')
navigateTo(`/nc/${record.id}`)
},
class: ['group'],
})
"
:data-source="filteredProjects"
:pagination="{ position: ['bottomCenter'] }"
>
<!-- Title -->
<a-table-column key="title" :title="$t('general.title')" data-index="title">
<template #default="{ text }">
<div
class="capitalize color-transition group-hover:text-primary !w-[400px] overflow-hidden overflow-ellipsis whitespace-nowrap"
>
{{ text }}
</div>
</template>
</a-table-column>
<!-- Actions -->
<a-table-column key="id" :title="$t('labels.actions')" data-index="id">
<template #default="{ text, record }">
<div class="flex items-center gap-2">
<MdiEditOutline v-t="['c:project:edit:rename']" class="nc-action-btn" @click.stop="navigateTo(`/${text}`)" />
<MdiDeleteOutline class="nc-action-btn" @click.stop="deleteProject(record)" />
</div>
</template>
</a-table-column>
</a-table>
</TransitionGroup>
</div>
</template>
<style scoped>
.nc-action-btn {
@apply text-gray-500 group-hover:text-accent active:(ring ring-accent) cursor-pointer p-2 w-[30px] h-[30px] hover:bg-gray-300/50 rounded-full;
}
.nc-new-project-menu {
@apply cursor-pointer z-1 relative color-transition rounded-md px-3 py-2 text-white;
&::after {
@apply rounded-md absolute top-0 left-0 right-0 bottom-0 transition-all duration-150 ease-in-out bg-primary bg-opacity-100;
content: '';
z-index: -1;
}
&:hover::after {
@apply transform scale-110 ring ring-accent;
}
&:active::after {
@apply ring ring-accent;
}
}
:deep(.ant-table-cell) {
@apply py-1;
}
:deep(.ant-table-row) {
@apply cursor-pointer;
}
:deep(.ant-table) {
@apply min-h-[428px];
}
</style>

17
packages/nc-gui-v2/pages/index/user/index/index.vue

@ -1,11 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useI18n } from 'vue-i18n'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { navigateTo } from '#app' import { extractSdkResponseErrorMsg, navigateTo, reactive, ref, useApi, useGlobal, useI18n } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils'
import { reactive, ref, useApi, useGlobal } from '#imports'
import MaterialSymbolsWarning from '~icons/material-symbols/warning'
import MdiKeyChange from '~icons/mdi/key-change'
const { api } = useApi() const { api } = useApi()
@ -81,7 +76,10 @@ const resetError = () => {
<Transition name="layout"> <Transition name="layout">
<div v-if="error" class="self-center mb-4 bg-red-500 text-white rounded-lg w-3/4 p-1"> <div v-if="error" class="self-center mb-4 bg-red-500 text-white rounded-lg w-3/4 p-1">
<div class="flex items-center gap-2 justify-center"><MaterialSymbolsWarning /> {{ error }}</div> <div class="flex items-center gap-2 justify-center">
<MaterialSymbolsWarning />
{{ error }}
</div>
</div> </div>
</Transition> </Transition>
@ -117,7 +115,10 @@ const resetError = () => {
<div class="flex flex-wrap gap-4 items-center mt-4 md:justify-between w-full"> <div class="flex flex-wrap gap-4 items-center mt-4 md:justify-between w-full">
<button class="submit" type="submit"> <button class="submit" type="submit">
<span class="flex items-center gap-2"><MdiKeyChange /> {{ $t('activity.changePwd') }}</span> <span class="flex items-center gap-2">
<MdiKeyChange />
{{ $t('activity.changePwd') }}
</span>
</button> </button>
</div> </div>
</div> </div>

5
packages/nc-gui-v2/pages/project/index.vue

@ -1,5 +0,0 @@
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>

83
packages/nc-gui-v2/pages/project/index/[id].vue

@ -1,83 +0,0 @@
<script lang="ts" setup>
import { onMounted } from '@vue/runtime-core'
import type { Form } from 'ant-design-vue'
import type { ProjectType } from 'nocodb-sdk'
import { ref } from 'vue'
import { message } from 'ant-design-vue'
import { navigateTo, useRoute } from '#app'
import { extractSdkResponseErrorMsg, projectTitleValidator } from '~/utils'
import MaterialSymbolsRocketLaunchOutline from '~icons/material-symbols/rocket-launch-outline'
import { nextTick, reactive, useSidebar } from '#imports'
const { api } = useApi()
useSidebar({ hasSidebar: false })
const route = useRoute()
const nameValidationRules = [
{
required: true,
message: 'Project name is required',
},
projectTitleValidator,
]
const formState = reactive({
title: '',
})
const getProject = async () => {
try {
const result: ProjectType = await api.project.read(route.params.id as string)
formState.title = result.title as string
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
const renameProject = async () => {
try {
await api.project.update(route.params.id as string, formState)
navigateTo(`/nc/${route.params.id}`)
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
const form = ref<typeof Form>()
// select and focus title field on load
onMounted(async () => {
await getProject()
await nextTick(() => {
// todo: replace setTimeout and follow better approach
setTimeout(() => {
const input = form.value?.$el?.querySelector('input[type=text]')
input.setSelectionRange(0, formState.title.length)
input.focus()
}, 500)
})
})
</script>
<template>
<a-card class="w-[500px] !mx-auto !mt-100px shadow-md">
<h3 class="text-3xl text-center font-semibold mb-2">{{ $t('activity.editProject') }}</h3>
<a-form ref="form" :model="formState" name="basic" layout="vertical" autocomplete="off" @finish="renameProject">
<a-form-item :label="$t('labels.projName')" name="title" :rules="nameValidationRules" class="my-10 mx-10">
<a-input v-model:value="formState.title" name="title" class="nc-metadb-project-name" />
</a-form-item>
<a-form-item style="text-align: center" class="mt-2">
<a-button type="primary" html-type="submit" class="mx-auto flex justify-self-center">
<MaterialSymbolsRocketLaunchOutline class="mr-1" />
<span> {{ $t('general.edit') }} </span></a-button
>
</a-form-item>
</a-form>
</a-card>
</template>

78
packages/nc-gui-v2/pages/project/index/create.vue

@ -1,78 +0,0 @@
<script lang="ts" setup>
import { onMounted } from '@vue/runtime-core'
import type { Form } from 'ant-design-vue'
import { message } from 'ant-design-vue'
import { nextTick, reactive, ref, useApi, useSidebar } from '#imports'
import { navigateTo, useNuxtApp } from '#app'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import { projectTitleValidator } from '~/utils/validation'
import MaterialSymbolsRocketLaunchOutline from '~icons/material-symbols/rocket-launch-outline'
const { $e } = useNuxtApp()
const { api, isLoading } = useApi()
useSidebar({ hasSidebar: false })
const nameValidationRules = [
{
required: true,
message: 'Project name is required',
},
projectTitleValidator,
]
const formState = reactive({
title: '',
})
const createProject = async () => {
$e('a:project:create:xcdb')
try {
const result = await api.project.create({
title: formState.title,
})
await navigateTo(`/nc/${result.id}`)
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
const form = ref<typeof Form>()
// select and focus title field on load
onMounted(async () => {
await nextTick(() => {
// todo: replace setTimeout and follow better approach
setTimeout(() => {
const input = form.value?.$el?.querySelector('input[type=text]')
input.setSelectionRange(0, formState.title.length)
input.focus()
}, 500)
})
})
</script>
<template>
<a-card :loading="isLoading" class="w-[500px] !mx-auto !mt-100px shadow-md">
<GeneralNocoIcon />
<h3 class="text-3xl text-center font-semibold mt-8 mb-2">{{ $t('activity.createProject') }}</h3>
<a-form ref="form" :model="formState" name="basic" layout="vertical" autocomplete="off" @finish="createProject">
<a-form-item :label="$t('labels.projName')" name="title" :rules="nameValidationRules" class="my-10 mx-10">
<a-input v-model:value="formState.title" name="title" class="nc-metadb-project-name" />
</a-form-item>
<a-form-item style="text-align: center" class="mt-2">
<a-button type="primary" html-type="submit">
<div class="flex items-center">
<MaterialSymbolsRocketLaunchOutline class="mr-1" />
{{ $t('general.create') }}
</div>
</a-button>
</a-form-item>
</a-form>
</a-card>
</template>

2
packages/nc-gui-v2/pages/signin.vue

@ -80,7 +80,7 @@ function resetError() {
<template> <template>
<NuxtLayout> <NuxtLayout>
<div class="md:bg-primary bg-opacity-5 signin h-full min-h-[600px] flex flex-col justify-center items-center nc-form-signup"> <div class="md:bg-primary bg-opacity-5 signin h-full min-h-[600px] flex flex-col justify-center items-center nc-form-signin">
<div <div
class="bg-white mt-[60px] relative flex flex-col justify-center gap-2 w-full max-w-[500px] mx-auto p-8 md:(rounded-lg border-1 border-gray-200 shadow-xl)" class="bg-white mt-[60px] relative flex flex-col justify-center gap-2 w-full max-w-[500px] mx-auto p-8 md:(rounded-lg border-1 border-gray-200 shadow-xl)"
> >

12
packages/nc-gui-v2/windi.config.ts

@ -57,6 +57,18 @@ export default defineConfig({
primary: 'rgba(var(--color-primary), var(--tw-text-opacity))', primary: 'rgba(var(--color-primary), var(--tw-text-opacity))',
accent: 'rgba(var(--color-accent), var(--tw-text-opacity))', accent: 'rgba(var(--color-accent), var(--tw-text-opacity))',
}, },
borderColor: {
primary: 'rgba(var(--color-primary), var(--tw-border-opacity))',
accent: 'rgba(var(--color-accent), var(--tw-border-opacity))',
},
backgroundColor: {
primary: 'rgba(var(--color-primary), var(--tw-bg-opacity))',
accent: 'rgba(var(--color-accent), var(--tw-bg-opacity))',
},
ringColor: {
primary: 'rgba(var(--color-primary), var(--tw-ring-opacity))',
accent: 'rgba(var(--color-accent), var(--tw-ring-opacity))',
},
colors: { colors: {
...windiColors, ...windiColors,
...themeColors, ...themeColors,

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

@ -1431,7 +1431,8 @@ export default {
let item = cookie; let item = cookie;
try { try {
await this.$api.dbTable.update(item.id, { await this.$api.dbTable.update(item.id, {
title, project_id: this.projectId,
table_name: title,
}); });
} catch (e) { } catch (e) {
this.$toast.error(await this._extractSdkResponseErrorMsg(e)).goAway(3000); this.$toast.error(await this._extractSdkResponseErrorMsg(e)).goAway(3000);

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

@ -121,6 +121,7 @@ export interface TableType {
columns?: ColumnType[]; columns?: ColumnType[];
columnsById?: object; columnsById?: object;
slug?: string; slug?: string;
project_id?: string;
} }
export interface ViewType { export interface ViewType {
@ -169,7 +170,7 @@ export interface TableReqType {
deleted?: boolean; deleted?: boolean;
order?: number; order?: number;
mm?: boolean; mm?: boolean;
columns?: ColumnType[]; columns: ColumnType[];
} }
export interface TableListType { export interface TableListType {
@ -1487,7 +1488,7 @@ export class Api<
*/ */
update: ( update: (
tableId: string, tableId: string,
data: { title?: string }, data: { table_name?: string; project_id?: string },
params: RequestParams = {} params: RequestParams = {}
) => ) =>
this.request<any, any>({ this.request<any, any>({

81
packages/nocodb/src/lib/meta/api/tableApis.ts

@ -2,6 +2,7 @@ import { Request, Response, Router } from 'express';
import Model from '../../models/Model'; import Model from '../../models/Model';
import { PagedResponseImpl } from '../helpers/PagedResponse'; import { PagedResponseImpl } from '../helpers/PagedResponse';
import { Tele } from 'nc-help'; import { Tele } from 'nc-help';
import DOMPurify from 'isomorphic-dompurify';
import { import {
AuditOperationSubTypes, AuditOperationSubTypes,
AuditOperationTypes, AuditOperationTypes,
@ -102,6 +103,8 @@ export async function tableCreate(req: Request<any, any, TableReqType>, res) {
} }
} }
req.body.table_name = DOMPurify.sanitize(req.body.table_name);
// validate table name // validate table name
if (/^\s+|\s+$/.test(req.body.table_name)) { if (/^\s+|\s+$/.test(req.body.table_name)) {
NcError.badRequest( NcError.badRequest(
@ -217,18 +220,86 @@ export async function tableCreate(req: Request<any, any, TableReqType>, res) {
export async function tableUpdate(req: Request<any, any>, res) { export async function tableUpdate(req: Request<any, any>, res) {
const model = await Model.get(req.params.tableId); const model = await Model.get(req.params.tableId);
const project = await Project.getWithInfo(req.body.project_id);
const base = project.bases[0];
if (!req.body.table_name) {
NcError.badRequest(
'Missing table name `table_name` property in request body'
);
}
if (project.prefix) {
if (!req.body.table_name.startsWith(project.prefix)) {
req.body.table_name = `${project.prefix}${req.body.table_name}`;
}
}
req.body.table_name = DOMPurify.sanitize(req.body.table_name);
// 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,
project_id: project.id,
base_id: base.id,
}))
) {
NcError.badRequest('Duplicate table name');
}
if (!req.body.title) {
req.body.title = getTableNameAlias(
req.body.table_name,
project.prefix,
base
);
}
if ( if (
!(await Model.checkAliasAvailable({ !(await Model.checkAliasAvailable({
title: req.body.title, title: req.body.title,
project_id: model.project_id, project_id: project.id,
base_id: model.base_id, base_id: base.id,
exclude_id: req.params.tableId,
})) }))
) { ) {
NcError.badRequest('Duplicate table name'); NcError.badRequest('Duplicate table alias');
} }
await Model.updateAlias(req.params.tableId, req.body.title); const sqlMgr = await ProjectMgrv2.getSqlMgr(project);
const sqlClient = NcConnectionMgrv2.getSqlClient(base);
let tableNameLengthLimit = 255;
const sqlClientType = sqlClient.clientType;
if (sqlClientType === 'mysql2' || sqlClientType === 'mysql') {
tableNameLengthLimit = 64;
} else if (sqlClientType === 'pg') {
tableNameLengthLimit = 63;
} else if (sqlClientType === 'mssql') {
tableNameLengthLimit = 128;
}
if (req.body.table_name.length > tableNameLengthLimit) {
NcError.badRequest(`Table name exceeds ${tableNameLengthLimit} characters`);
}
await Model.updateAliasAndTableName(
req.params.tableId,
req.body.title,
req.body.table_name
);
await sqlMgr.sqlOpPlus(base, 'tableRename', {
...req.body,
tn: req.body.table_name,
tn_old: model.table_name,
});
Tele.emit('evt', { evt_type: 'table:updated' }); Tele.emit('evt', { evt_type: 'table:updated' });

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

@ -412,14 +412,25 @@ export default class Model implements TableType {
return insertObj; return insertObj;
} }
static async updateAlias(tableId, title: string, ncMeta = Noco.ncMeta) { static async updateAliasAndTableName(
if (!title) NcError.badRequest("Missing 'title' property in body"); tableId,
title: string,
table_name: string,
ncMeta = Noco.ncMeta
) {
if (!title) {
NcError.badRequest("Missing 'title' property in body");
}
if (!table_name) {
NcError.badRequest("Missing 'table_name' property in body");
}
// get existing cache // get existing cache
const key = `${CacheScope.MODEL}:${tableId}`; const key = `${CacheScope.MODEL}:${tableId}`;
const o = await NocoCache.get(key, CacheGetType.TYPE_OBJECT); const o = await NocoCache.get(key, CacheGetType.TYPE_OBJECT);
// update alias // update alias
if (o) { if (o) {
o.title = title; o.title = title;
o.table_name = table_name;
// set cache // set cache
await NocoCache.set(key, o); await NocoCache.set(key, o);
} }
@ -430,6 +441,7 @@ export default class Model implements TableType {
MetaTable.MODELS, MetaTable.MODELS,
{ {
title, title,
table_name,
}, },
tableId tableId
); );

12
packages/nocodb/src/lib/plugins/smtp/SMTP.ts

@ -14,11 +14,12 @@ export default class SMTP implements IEmailAdapter {
public async init(): Promise<any> { public async init(): Promise<any> {
const config = { const config = {
// from: this.input.from,
// options: {
host: this.input?.host, host: this.input?.host,
port: parseInt(this.input?.port, 10), port: parseInt(this.input?.port, 10),
secure: this.input?.secure === 'true', secure:
typeof this.input?.secure === 'boolean'
? this.input?.secure
: this.input?.secure === 'true',
ignoreTLS: ignoreTLS:
typeof this.input?.ignoreTLS === 'boolean' typeof this.input?.ignoreTLS === 'boolean'
? this.input?.ignoreTLS ? this.input?.ignoreTLS
@ -27,8 +28,11 @@ export default class SMTP implements IEmailAdapter {
user: this.input?.username, user: this.input?.username,
pass: this.input?.password, pass: this.input?.password,
}, },
// } tls: {
rejectUnauthorized: this.input?.rejectUnauthorized,
},
}; };
this.transporter = nodemailer.createTransport(config); this.transporter = nodemailer.createTransport(config);
} }

13
packages/nocodb/src/lib/plugins/smtp/index.ts

@ -8,7 +8,7 @@ import SMTPPlugin from './SMTPPlugin';
const config: XcPluginConfig = { const config: XcPluginConfig = {
builder: SMTPPlugin, builder: SMTPPlugin,
title: 'SMTP', title: 'SMTP',
version: '0.0.1', version: '0.0.2',
// icon: 'mdi-email-outline', // icon: 'mdi-email-outline',
description: 'SMTP email client', description: 'SMTP email client',
price: 'Free', price: 'Free',
@ -42,8 +42,8 @@ const config: XcPluginConfig = {
key: 'secure', key: 'secure',
label: 'Secure', label: 'Secure',
placeholder: 'Secure', placeholder: 'Secure',
type: XcType.SingleLineText, type: XcType.Checkbox,
required: true, required: false,
}, },
{ {
key: 'ignoreTLS', key: 'ignoreTLS',
@ -52,6 +52,13 @@ const config: XcPluginConfig = {
type: XcType.Checkbox, type: XcType.Checkbox,
required: false, required: false,
}, },
{
key: 'rejectUnauthorized',
label: 'Reject Unauthorized',
placeholder: 'Reject Unauthorized',
type: XcType.Checkbox,
required: false,
},
{ {
key: 'username', key: 'username',
label: 'Username', label: 'Username',

2
packages/nocodb/tests/pg-cy-quick/01-cy-quick.sql

@ -3308,7 +3308,7 @@ nc_iyhlectialukbv Vultr Object Storage Using Vultr Object Storage can give flexi
nc_lr8pcvg64g5bho OvhCloud Object Storage Upload your files to a space that you can access via HTTPS using the OpenStack Swift API, or the S3 API. f \N 0.0.1 \N install \N plugins/ovhCloud.png \N Storage Storage {"title":"Configure OvhCloud Object Storage","items":[{"key":"bucket","label":"Bucket Name","placeholder":"Bucket Name","type":"SingleLineText","required":true},{"key":"region","label":"Region","placeholder":"Region","type":"SingleLineText","required":true},{"key":"access_key","label":"Access Key","placeholder":"Access Key","type":"SingleLineText","required":true},{"key":"access_secret","label":"Access Secret","placeholder":"Access Secret","type":"Password","required":true}],"actions":[{"label":"Test","placeholder":"Test","key":"test","actionType":"TEST","type":"Button"},{"label":"Save","placeholder":"Save","key":"save","actionType":"SUBMIT","type":"Button"}],"msgOnInstall":"Successfully installed and attachment will be stored in OvhCloud Object Storage","msgOnUninstall":""} \N \N \N \N 2022-06-13 07:00:02.60416+00 2022-06-13 07:00:02.60416+00 nc_lr8pcvg64g5bho OvhCloud Object Storage Upload your files to a space that you can access via HTTPS using the OpenStack Swift API, or the S3 API. f \N 0.0.1 \N install \N plugins/ovhCloud.png \N Storage Storage {"title":"Configure OvhCloud Object Storage","items":[{"key":"bucket","label":"Bucket Name","placeholder":"Bucket Name","type":"SingleLineText","required":true},{"key":"region","label":"Region","placeholder":"Region","type":"SingleLineText","required":true},{"key":"access_key","label":"Access Key","placeholder":"Access Key","type":"SingleLineText","required":true},{"key":"access_secret","label":"Access Secret","placeholder":"Access Secret","type":"Password","required":true}],"actions":[{"label":"Test","placeholder":"Test","key":"test","actionType":"TEST","type":"Button"},{"label":"Save","placeholder":"Save","key":"save","actionType":"SUBMIT","type":"Button"}],"msgOnInstall":"Successfully installed and attachment will be stored in OvhCloud Object Storage","msgOnUninstall":""} \N \N \N \N 2022-06-13 07:00:02.60416+00 2022-06-13 07:00:02.60416+00
nc_3f19m6v5iyudty Linode Object Storage S3-compatible Linode Object Storage makes it easy and more affordable to manage unstructured data such as content assets, as well as sophisticated and data-intensive storage challenges around artificial intelligence and machine learning. f \N 0.0.1 \N install \N plugins/linode.svg \N Storage Storage {"title":"Configure Linode Object Storage","items":[{"key":"bucket","label":"Bucket Name","placeholder":"Bucket Name","type":"SingleLineText","required":true},{"key":"region","label":"Region","placeholder":"Region","type":"SingleLineText","required":true},{"key":"access_key","label":"Access Key","placeholder":"Access Key","type":"SingleLineText","required":true},{"key":"access_secret","label":"Access Secret","placeholder":"Access Secret","type":"Password","required":true}],"actions":[{"label":"Test","placeholder":"Test","key":"test","actionType":"TEST","type":"Button"},{"label":"Save","placeholder":"Save","key":"save","actionType":"SUBMIT","type":"Button"}],"msgOnInstall":"Successfully installed and attachment will be stored in Linode Object Storage","msgOnUninstall":""} \N \N \N \N 2022-06-13 07:00:02.606391+00 2022-06-13 07:00:02.606391+00 nc_3f19m6v5iyudty Linode Object Storage S3-compatible Linode Object Storage makes it easy and more affordable to manage unstructured data such as content assets, as well as sophisticated and data-intensive storage challenges around artificial intelligence and machine learning. f \N 0.0.1 \N install \N plugins/linode.svg \N Storage Storage {"title":"Configure Linode Object Storage","items":[{"key":"bucket","label":"Bucket Name","placeholder":"Bucket Name","type":"SingleLineText","required":true},{"key":"region","label":"Region","placeholder":"Region","type":"SingleLineText","required":true},{"key":"access_key","label":"Access Key","placeholder":"Access Key","type":"SingleLineText","required":true},{"key":"access_secret","label":"Access Secret","placeholder":"Access Secret","type":"Password","required":true}],"actions":[{"label":"Test","placeholder":"Test","key":"test","actionType":"TEST","type":"Button"},{"label":"Save","placeholder":"Save","key":"save","actionType":"SUBMIT","type":"Button"}],"msgOnInstall":"Successfully installed and attachment will be stored in Linode Object Storage","msgOnUninstall":""} \N \N \N \N 2022-06-13 07:00:02.606391+00 2022-06-13 07:00:02.606391+00
nc_ikemr7ajwzfcr3 UpCloud Object Storage The perfect home for your data. Thanks to the S3-compatible programmable interface,\nyou have a host of options for existing tools and code implementations.\n f \N 0.0.1 \N install \N plugins/upcloud.png \N Storage Storage {"title":"Configure UpCloud Object Storage","items":[{"key":"bucket","label":"Bucket Name","placeholder":"Bucket Name","type":"SingleLineText","required":true},{"key":"endpoint","label":"Endpoint","placeholder":"Endpoint","type":"SingleLineText","required":true},{"key":"access_key","label":"Access Key","placeholder":"Access Key","type":"SingleLineText","required":true},{"key":"access_secret","label":"Access Secret","placeholder":"Access Secret","type":"Password","required":true}],"actions":[{"label":"Test","placeholder":"Test","key":"test","actionType":"TEST","type":"Button"},{"label":"Save","placeholder":"Save","key":"save","actionType":"SUBMIT","type":"Button"}],"msgOnInstall":"Successfully installed and attachment will be stored in UpCloud Object Storage","msgOnUninstall":""} \N \N \N \N 2022-06-13 07:00:02.6085+00 2022-06-13 07:00:02.6085+00 nc_ikemr7ajwzfcr3 UpCloud Object Storage The perfect home for your data. Thanks to the S3-compatible programmable interface,\nyou have a host of options for existing tools and code implementations.\n f \N 0.0.1 \N install \N plugins/upcloud.png \N Storage Storage {"title":"Configure UpCloud Object Storage","items":[{"key":"bucket","label":"Bucket Name","placeholder":"Bucket Name","type":"SingleLineText","required":true},{"key":"endpoint","label":"Endpoint","placeholder":"Endpoint","type":"SingleLineText","required":true},{"key":"access_key","label":"Access Key","placeholder":"Access Key","type":"SingleLineText","required":true},{"key":"access_secret","label":"Access Secret","placeholder":"Access Secret","type":"Password","required":true}],"actions":[{"label":"Test","placeholder":"Test","key":"test","actionType":"TEST","type":"Button"},{"label":"Save","placeholder":"Save","key":"save","actionType":"SUBMIT","type":"Button"}],"msgOnInstall":"Successfully installed and attachment will be stored in UpCloud Object Storage","msgOnUninstall":""} \N \N \N \N 2022-06-13 07:00:02.6085+00 2022-06-13 07:00:02.6085+00
nc_tv5fgn17fgvcwh SMTP SMTP email client f \N 0.0.1 \N install \N \N \N Email Email {"title":"Configure Email SMTP","items":[{"key":"from","label":"From","placeholder":"eg: admin@run.com","type":"SingleLineText","required":true},{"key":"host","label":"Host","placeholder":"eg: smtp.run.com","type":"SingleLineText","required":true},{"key":"port","label":"Port","placeholder":"Port","type":"SingleLineText","required":true},{"key":"secure","label":"Secure","placeholder":"Secure","type":"SingleLineText","required":true},{"key":"ignoreTLS","label":"Ignore TLS","placeholder":"Ignore TLS","type":"Checkbox","required":false},{"key":"username","label":"Username","placeholder":"Username","type":"SingleLineText","required":false},{"key":"password","label":"Password","placeholder":"Password","type":"Password","required":false}],"actions":[{"label":"Test","key":"test","actionType":"TEST","type":"Button"},{"label":"Save","key":"save","actionType":"SUBMIT","type":"Button"}],"msgOnInstall":"Successfully installed and email notification will use SMTP configuration","msgOnUninstall":""} \N \N \N \N 2022-06-13 07:00:02.61079+00 2022-06-13 07:00:02.61079+00 nc_tv5fgn17fgvcwh SMTP SMTP email client f \N 0.0.1 \N install \N \N \N Email Email {"title":"Configure Email SMTP","items":[{"key":"from","label":"From","placeholder":"eg: admin@run.com","type":"SingleLineText","required":true},{"key":"host","label":"Host","placeholder":"eg: smtp.run.com","type":"SingleLineText","required":true},{"key":"port","label":"Port","placeholder":"Port","type":"SingleLineText","required":true},{"key":"secure","label":"Secure","placeholder":"Secure","type":"Checkbox","required":false},{"key":"ignoreTLS","label":"Ignore TLS","placeholder":"Ignore TLS","type":"Checkbox","required":false},{"key":"username","label":"Username","placeholder":"Username","type":"SingleLineText","required":false},{"key":"password","label":"Password","placeholder":"Password","type":"Password","required":false}],"actions":[{"label":"Test","key":"test","actionType":"TEST","type":"Button"},{"label":"Save","key":"save","actionType":"SUBMIT","type":"Button"}],"msgOnInstall":"Successfully installed and email notification will use SMTP configuration","msgOnUninstall":""} \N \N \N \N 2022-06-13 07:00:02.61079+00 2022-06-13 07:00:02.61079+00
nc_hcnsq71s0tr8um MailerSend MailerSend email client f \N 0.0.1 \N install \N plugins/mailersend.svg \N Email Email {"title":"Configure MailerSend","items":[{"key":"api_key","label":"API KEy","placeholder":"eg: ***************","type":"Password","required":true},{"key":"from","label":"From","placeholder":"eg: admin@run.com","type":"SingleLineText","required":true},{"key":"from_name","label":"From Name","placeholder":"eg: Adam","type":"SingleLineText","required":true}],"actions":[{"label":"Test","key":"test","actionType":"TEST","type":"Button"},{"label":"Save","key":"save","actionType":"SUBMIT","type":"Button"}],"msgOnInstall":"Successfully installed and email notification will use MailerSend configuration","msgOnUninstall":""} \N \N \N \N 2022-06-13 07:00:02.612874+00 2022-06-13 07:00:02.612874+00 nc_hcnsq71s0tr8um MailerSend MailerSend email client f \N 0.0.1 \N install \N plugins/mailersend.svg \N Email Email {"title":"Configure MailerSend","items":[{"key":"api_key","label":"API KEy","placeholder":"eg: ***************","type":"Password","required":true},{"key":"from","label":"From","placeholder":"eg: admin@run.com","type":"SingleLineText","required":true},{"key":"from_name","label":"From Name","placeholder":"eg: Adam","type":"SingleLineText","required":true}],"actions":[{"label":"Test","key":"test","actionType":"TEST","type":"Button"},{"label":"Save","key":"save","actionType":"SUBMIT","type":"Button"}],"msgOnInstall":"Successfully installed and email notification will use MailerSend configuration","msgOnUninstall":""} \N \N \N \N 2022-06-13 07:00:02.612874+00 2022-06-13 07:00:02.612874+00
nc_z2zas0qdy0cz7e Scaleway Object Storage Scaleway Object Storage is an S3-compatible object store from Scaleway Cloud Platform. f \N 0.0.1 \N install \N plugins/scaleway.png \N Storage Storage {"title":"Setup Scaleway","items":[{"key":"bucket","label":"Bucket name","placeholder":"Bucket name","type":"SingleLineText","required":true},{"key":"region","label":"Region of bucket","placeholder":"Region of bucket","type":"SingleLineText","required":true},{"key":"access_key","label":"Access Key","placeholder":"Access Key","type":"SingleLineText","required":true},{"key":"access_secret","label":"Access Secret","placeholder":"Access Secret","type":"Password","required":true}],"actions":[{"label":"Test","placeholder":"Test","key":"test","actionType":"TEST","type":"Button"},{"label":"Save","placeholder":"Save","key":"save","actionType":"SUBMIT","type":"Button"}],"msgOnInstall":"Successfully installed Scaleway Object Storage","msgOnUninstall":""} \N \N \N \N 2022-06-13 07:00:02.61482+00 2022-06-13 07:00:02.61482+00 nc_z2zas0qdy0cz7e Scaleway Object Storage Scaleway Object Storage is an S3-compatible object store from Scaleway Cloud Platform. f \N 0.0.1 \N install \N plugins/scaleway.png \N Storage Storage {"title":"Setup Scaleway","items":[{"key":"bucket","label":"Bucket name","placeholder":"Bucket name","type":"SingleLineText","required":true},{"key":"region","label":"Region of bucket","placeholder":"Region of bucket","type":"SingleLineText","required":true},{"key":"access_key","label":"Access Key","placeholder":"Access Key","type":"SingleLineText","required":true},{"key":"access_secret","label":"Access Secret","placeholder":"Access Secret","type":"Password","required":true}],"actions":[{"label":"Test","placeholder":"Test","key":"test","actionType":"TEST","type":"Button"},{"label":"Save","placeholder":"Save","key":"save","actionType":"SUBMIT","type":"Button"}],"msgOnInstall":"Successfully installed Scaleway Object Storage","msgOnUninstall":""} \N \N \N \N 2022-06-13 07:00:02.61482+00 2022-06-13 07:00:02.61482+00
nc_96vkc0jdyw7los SES Amazon Simple Email Service (SES) is a cost-effective, flexible, and scalable email service that enables developers to send mail from within any application. f \N 0.0.1 \N install \N plugins/aws.png \N Email Email {"title":"Configure Amazon Simple Email Service (SES)","items":[{"key":"from","label":"From","placeholder":"From","type":"SingleLineText","required":true},{"key":"region","label":"Region","placeholder":"Region","type":"SingleLineText","required":true},{"key":"access_key","label":"Access Key","placeholder":"Access Key","type":"SingleLineText","required":true},{"key":"access_secret","label":"Access Secret","placeholder":"Access Secret","type":"Password","required":true}],"actions":[{"label":"Test","placeholder":"Test","key":"test","actionType":"TEST","type":"Button"},{"label":"Save","placeholder":"Save","key":"save","actionType":"SUBMIT","type":"Button"}],"msgOnInstall":"Successfully installed and email notification will use Amazon SES","msgOnUninstall":""} \N \N \N \N 2022-06-13 07:00:02.617114+00 2022-06-13 07:00:02.617114+00 nc_96vkc0jdyw7los SES Amazon Simple Email Service (SES) is a cost-effective, flexible, and scalable email service that enables developers to send mail from within any application. f \N 0.0.1 \N install \N plugins/aws.png \N Email Email {"title":"Configure Amazon Simple Email Service (SES)","items":[{"key":"from","label":"From","placeholder":"From","type":"SingleLineText","required":true},{"key":"region","label":"Region","placeholder":"Region","type":"SingleLineText","required":true},{"key":"access_key","label":"Access Key","placeholder":"Access Key","type":"SingleLineText","required":true},{"key":"access_secret","label":"Access Secret","placeholder":"Access Secret","type":"Password","required":true}],"actions":[{"label":"Test","placeholder":"Test","key":"test","actionType":"TEST","type":"Button"},{"label":"Save","placeholder":"Save","key":"save","actionType":"SUBMIT","type":"Button"}],"msgOnInstall":"Successfully installed and email notification will use Amazon SES","msgOnUninstall":""} \N \N \N \N 2022-06-13 07:00:02.617114+00 2022-06-13 07:00:02.617114+00

6
scripts/cypress/integration/common/1a_table_operations.js

@ -76,6 +76,9 @@ export const genTest = (apiType, dbType) => {
cy.closeTableTab("CityX"); cy.closeTableTab("CityX");
// revert re-name operation to not impact rest of test suite
cy.renameTable("CityX", "City");
// 4. verify linked contents in other table // 4. verify linked contents in other table
// 4a. Address table, has many field // 4a. Address table, has many field
cy.openTableTab("Address", 25); cy.openTableTab("Address", 25);
@ -97,9 +100,6 @@ export const genTest = (apiType, dbType) => {
.contains("Kabul") .contains("Kabul")
.should("exist"); .should("exist");
cy.closeTableTab("Country"); cy.closeTableTab("Country");
// revert re-name operation to not impact rest of test suite
cy.renameTable("CityX", "City");
}); });
}); });
}; };

3
scripts/cypress/support/page_objects/mainPage.js

@ -222,7 +222,8 @@ export class _mainPage {
.click() .click()
.type(host); .type(host);
cy.getActiveModal().find('[placeholder="Port"]').click().type(port); cy.getActiveModal().find('[placeholder="Port"]').click().type(port);
cy.getActiveModal().find('[placeholder="Secure"]').click().type(secure); // TODO: in v2, it would be a button
// if (secure) cy.getActiveModal().find('[placeholder="Secure"]').click();
cy.getActiveModal().find("button").contains("Save").click(); cy.getActiveModal().find("button").contains("Save").click();
cy.toastWait( cy.toastWait(
"Successfully installed and email notification will use SMTP configuration" "Successfully installed and email notification will use SMTP configuration"

8
scripts/sdk/swagger.json

@ -1309,7 +1309,10 @@
"schema": { "schema": {
"type": "object", "type": "object",
"properties": { "properties": {
"title": { "table_name": {
"type": "string"
},
"project_id": {
"type": "string" "type": "string"
} }
} }
@ -6050,6 +6053,9 @@
}, },
"slug": { "slug": {
"type": "string" "type": "string"
},
"project_id": {
"type": "string"
} }
}, },
"required": [ "required": [

Loading…
Cancel
Save