Browse Source

Nc feat/integrations (#8903)

* feat: integrations backend (WIP)

* feat: migration - source table

* feat: updated migration

* feat: integration APIs - WIP

* feat: integration - crud, acl, api tests

* feat: integration - crud, acl, api tests

* feat: integration - GUI integration

* feat: private integration config

* feat: integration GUI

* feat: delete api and source creation

* feat: add hint for input fields

* fix: source creation bugs

* refactor: placeholder text correction

* refactor: include context

* feat: integration delete with transaction

* refactor: permission scope correction and move ee logic

* refactor: migration correction and improvements

* feat: confirm dialog

* refactor: review comments

* refactor: meta service changes

* feat: add oss support - WIP

* feat: add oss support

* refactor: coderabbt suggestions

* refactor: exclude config from api response

* refactor: coderabbit review comments

* refactor: rename migration names

* fix: method name correction

* fix(nc-gui): integration ui changes

* fix(nc-gui): add edit integration ui changes

* fix(nc-gui): add shared badge in integrations list

* feat(nc-gui): duplicate integration

* fix(nocodb): add copy from id integration support in create integration api

* fix(nc-gui): update useIntegration store

* fix(nc-gui): test connection btn style update

* fix(nc-gui): update new integration modal

* feat(nc-gui): add sort integration list support

* fix(nc-gui): integration table to be center aligned

* fix(nc-gui): move form item required mark to right side

* fix: remove divider

* fix(nc-gui): add input shadow

* fix(nc-gui): base name validator error message

* fix(nc-gui): add border if search connection input has some value

* fix(nc-gui): add close btn in integration modal

* chore(nc-gui): lint

* fix(nc-gui): pr review changes

* chore(nc-gui): cleanup unused code

* chore(nc-gui): lint

* fix(nc-gui): integrationsType not found issue

* fix(nc-gui): update data source table

* fix(nc-gui): populate integration name only on input value change

* fix(nc-gui): create data source form update

* fix: type correction

* fix: label correction

* fix: font corrections

* fix: remove help text

* fix: grammar in help text

* fix(nc-gui): edit source ui changes

* fix(nc-gui): base settings modal changes & datasource search feat

* fix(nc-gui): update data source table

* fix(nc-gui): move integrations outside team & settings

* fix(nc-gui): make connections table full width

* fix(nc-gui): modal height issue in small screen

* fix(nc-gui): disable editing selected connection in edit data source

* fix(nc-gui): add data sources in base settings tab

* fix(nc-gui): ant design multiple warnings issue

* fix(nc-gui): create source page scrollbar issue

* feat(nc-gui): create connection from create source page

* chore(nc-gui): lint

* fix(nc-gui): update project members tab content margin

* chore: label text change

* fix: font changes

* chore: font corrections

* chore: integration => connection

* fix(nc-gui): disable auto editing database name on changing connection name

* fix(nc-gui): table header overflow issue

* fix(nc-gui): show connection crud operation messages in toast

* feat(nc-gui): request new integration ui

* fix(nc-gui): text area height adjust issue

* fix(nc-gui): add connection from source create issue

* fix(nc-gui): show data source details in modal

* fix(nc-gui): hide private connection option

* fix(nc-gui): user should able to edit & save connection without test connection if only title updated

* fix(nc-gui): add integration page in oss

* fix(nc-gui): typo currection

* fix(nc-gui): oss create base ui changes

* misc: minor formatting changes

* misc: formatting corrections

* fix(nc-gui): overlay close btn issue

* fix(nc-gui): some review changes

* fix(nc-gui): remove link beetween connection name & database name

* fix(nc-gui): update edit base/source modal oss

* fix(nc-gui): add db type icon in select connection

* chore(nc-gui): lint

* fix: integration list - allow access based on base level role

* fix(nc-gui): load integrations on creating integration from source create issue

* fix(nc-gui): add connection count in tab

* fix: correction in soft delete logic

* fix(nc-gui): reset use ssl on panel collapse

* fix(nc-gui): reduce select input font weight

* fix(nc-gui): update connection edit access control

* fix: integration read api correction

* fix(nc-gui): some review changes

* fix(nc-gui): labels update

* fix(nc-gui): udpate text in delete modal integration -> connection

* fix: remove permission from wrong scope

* refactor: swagger description correction

* fix(nc-gui): remove connection between source name & database name

* fix(nc-gui): test connection is not needed form source name. inflection field changes

* refactor: include integration title with source

* feat: integration pagination

* fix: remove unused prop

* fix(nc-gui): update all tables tab btns tooltip

* feat: new integration request

* refactor: replace delete statement and use assigning undefined for better performance

* feat(nc-gui): sync data support in project page

* fix(nc-gui): all sync data type list

* fix(nc-gui): close sync data modal issue

* fix(nc-gui): add bg gray color on db icon of tooltip

* fix(nc-gui): make connection as required field

* fix(nc-gui): show connection name if not found and reload page

* fix(nc-gui): show connection name in ds list

* fix(nc-gui): ssl related changes

* fix: oss permission

* fix(nc-gui): active tab issue on clicking source

* feat: include source count and sources in api response

* fix(nc-gui): add getIntegration fun in useIntegrationStore

* fix(nc-gui): source list udpate issue on updating source details

* fix(nc-gui): fix external source icon alignment

* feat: include base name and source count

* fix: query correction

* fix(nc-gui): show liked sources list in delete connection modal

* fix(nc-gui): display connection usage information in list

* fix(nc-gui): add sync data types icons

* fix(nc-gui): add pagination support in connection list

* fix(nc-gui): connection pagination issue

* fix(nc-gui): connection tab count update issue

* test(nc-gui): some of test cases updated

* fix(nc-gui): some minor review changes

* fix(nc-gui): minor ui changes

* fix(nc-gui): Cannot read properties of undefined (reading 'sub_type')

* fix(nc-gui): udpate all tables btn text

* fix(nc-gui): ui changes

* fix(nc-gui): overflow issue

* fix(nc-gui): add connection icon & back btn in modal

* fix(nc-gui): some minor ui changes

* test(nc-gui): update source restriction test cases

* chore(test): remove only from test

* fix(nc-gui): update style of delete connection modal

* test(nc-gui): update acl pw test cases

* fix(test): ws collaboration role accss test fail issue

* fix(nc-gui): add connection successfully added modal

* fix(nc-gui): update connection added modal

* fix(nc-gui): trigger sync request event on upvote

* chore(nc-gui): lint

* fix(nc-gui): add learn more btn in connection successfull modal

* fix(nc-gui): add integration docs link support

* fix(nc-gui): integration table name field text truncate issue

* fix: misc corrections

* misc: button width change

* fix(nc-gui): update icons

* fix(nc-gui): update test connection btn icons

* fix(nc-gui): all tables btn gap issue

* feat(nc-gui): search option in sync data modal

* feat(nc-gui): search connection through api

* fix(nc-gui): add base and source icon in delete connection modal

* fix: update sync request event

* fix(nc-gui): rebase conflict issue

* fix: connections text length

* fix(nc-gui): enable integration/create source supported docs option

* fix(nc-gui): update advanced option header style

---------

Co-authored-by: mertmit <mertmit99@gmail.com>
Co-authored-by: Ramesh Mane <101566080+rameshmane7218@users.noreply.github.com>
Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com>
pull/9152/head
Pranav C 4 months ago committed by GitHub
parent
commit
9de25471b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5
      packages/nc-gui/assets/nc-icons/alert-triangle-solid.svg
  2. 3
      packages/nc-gui/assets/nc-icons/apple_solid.svg
  3. 10
      packages/nc-gui/assets/nc-icons/asana.svg
  4. 4
      packages/nc-gui/assets/nc-icons/book-open.svg
  5. 4
      packages/nc-gui/assets/nc-icons/box.svg
  6. 4
      packages/nc-gui/assets/nc-icons/check-circle-solid.svg
  7. 3
      packages/nc-gui/assets/nc-icons/circle.svg
  8. 5
      packages/nc-gui/assets/nc-icons/git-commit.svg
  9. 17
      packages/nc-gui/assets/nc-icons/github_solid.svg
  10. 16
      packages/nc-gui/assets/nc-icons/gitlab.svg
  11. 15
      packages/nc-gui/assets/nc-icons/google-drive.svg
  12. 17
      packages/nc-gui/assets/nc-icons/google_calendar.svg
  13. 60
      packages/nc-gui/assets/nc-icons/google_sheet.svg
  14. 10
      packages/nc-gui/assets/nc-icons/hubspot.svg
  15. 6
      packages/nc-gui/assets/nc-icons/integration.svg
  16. 20
      packages/nc-gui/assets/nc-icons/jira.svg
  17. 11
      packages/nc-gui/assets/nc-icons/mailchimp.svg
  18. 19
      packages/nc-gui/assets/nc-icons/microsoft_access.svg
  19. 251
      packages/nc-gui/assets/nc-icons/microsoft_excel.svg
  20. 238
      packages/nc-gui/assets/nc-icons/microsoft_outlook.svg
  21. 11
      packages/nc-gui/assets/nc-icons/miro.svg
  22. 20
      packages/nc-gui/assets/nc-icons/salesforce.svg
  23. 6
      packages/nc-gui/assets/nc-icons/server1.svg
  24. 10
      packages/nc-gui/assets/nc-icons/snowflake.svg
  25. 11
      packages/nc-gui/assets/nc-icons/stripe.svg
  26. 32
      packages/nc-gui/assets/nc-icons/survey_monkey.svg
  27. 18
      packages/nc-gui/assets/nc-icons/tableau.svg
  28. 10
      packages/nc-gui/assets/nc-icons/thumbs-up-outline.svg
  29. 16
      packages/nc-gui/assets/nc-icons/trello.svg
  30. 11
      packages/nc-gui/assets/nc-icons/typeform.svg
  31. 12
      packages/nc-gui/assets/nc-icons/workday.svg
  32. 10
      packages/nc-gui/assets/nc-icons/zendesk.svg
  33. 5
      packages/nc-gui/components/account/Integration.vue
  34. 34
      packages/nc-gui/components/dashboard/Sidebar/TopSection.vue
  35. 6
      packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue
  36. 271
      packages/nc-gui/components/dashboard/settings/DataSources.vue
  37. 4
      packages/nc-gui/components/dashboard/settings/Metadata.vue
  38. 92
      packages/nc-gui/components/dashboard/settings/Modal.vue
  39. 6
      packages/nc-gui/components/dashboard/settings/UIAcl.vue
  40. 901
      packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue
  41. 732
      packages/nc-gui/components/dashboard/settings/data-sources/EditBase.vue
  42. 66
      packages/nc-gui/components/dashboard/settings/data-sources/SourceRestrictions.vue
  43. 38
      packages/nc-gui/components/dashboard/settings/data-sources/SupportedDocs.vue
  44. 2
      packages/nc-gui/components/general/BaseIconColorPicker.vue
  45. 12
      packages/nc-gui/components/general/DeleteModal.vue
  46. 17
      packages/nc-gui/components/nc/Button.vue
  47. 4
      packages/nc-gui/components/nc/Modal.vue
  48. 12
      packages/nc-gui/components/nc/Select.vue
  49. 7
      packages/nc-gui/components/nc/Tabs.vue
  50. 2
      packages/nc-gui/components/project/AccessSettings.vue
  51. 79
      packages/nc-gui/components/project/AllTables.vue
  52. 6
      packages/nc-gui/components/project/ImportModal.vue
  53. 153
      packages/nc-gui/components/project/SyncDataModal.vue
  54. 16
      packages/nc-gui/components/project/View.vue
  55. 2
      packages/nc-gui/components/smartsheet/Details.vue
  56. 8
      packages/nc-gui/components/workspace/AuditLogs.vue
  57. 2
      packages/nc-gui/components/workspace/CreateProjectDlg.vue
  58. 8
      packages/nc-gui/components/workspace/View.vue
  59. 60
      packages/nc-gui/components/workspace/integrations/Edit.vue
  60. 71
      packages/nc-gui/components/workspace/integrations/EditOrAdd.vue
  61. 70
      packages/nc-gui/components/workspace/integrations/Icon.vue
  62. 803
      packages/nc-gui/components/workspace/integrations/List.vue
  63. 55
      packages/nc-gui/components/workspace/integrations/Panel.vue
  64. 38
      packages/nc-gui/components/workspace/integrations/SupportedDocs.vue
  65. 1258
      packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue
  66. 15
      packages/nc-gui/components/workspace/integrations/forms/MySql.vue
  67. 157
      packages/nc-gui/components/workspace/integrations/forms/PostgreSql.vue
  68. 240
      packages/nc-gui/components/workspace/integrations/newAvailableList.vue
  69. 130
      packages/nc-gui/components/workspace/integrations/view.vue
  70. 4
      packages/nc-gui/composables/useApi/index.ts
  71. 5
      packages/nc-gui/composables/useGlobal/actions.ts
  72. 1
      packages/nc-gui/composables/useGlobal/state.ts
  73. 2
      packages/nc-gui/composables/useGlobal/types.ts
  74. 355
      packages/nc-gui/composables/useIntegrationsStore.ts
  75. 71
      packages/nc-gui/lang/en.json
  76. 1
      packages/nc-gui/lib/acl.ts
  77. 41
      packages/nc-gui/lib/enums.ts
  78. 1
      packages/nc-gui/pages/account/index.vue
  79. 5
      packages/nc-gui/pages/index.vue
  80. 3
      packages/nc-gui/pages/index/[typeOrId]/integrations.vue
  81. 15
      packages/nc-gui/store/workspace.ts
  82. 25
      packages/nc-gui/utils/baseCreateUtils.ts
  83. 67
      packages/nc-gui/utils/iconUtils.ts
  84. 38
      packages/nc-gui/utils/syncDataUtils.ts
  85. 26
      packages/nc-gui/utils/validation.ts
  86. 13
      packages/nocodb-sdk/src/lib/enums.ts
  87. 2
      packages/nocodb-sdk/src/lib/globals.ts
  88. 19
      packages/nocodb-sdk/src/lib/helperFunctions.ts
  89. 1
      packages/nocodb/src/controllers/bases.controller.ts
  90. 139
      packages/nocodb/src/controllers/integrations.controller.ts
  91. 4
      packages/nocodb/src/controllers/sources.controller.ts
  92. 74
      packages/nocodb/src/controllers/utils.controller.ts
  93. 42
      packages/nocodb/src/helpers/catchError.ts
  94. 7
      packages/nocodb/src/meta/meta.service.ts
  95. 4
      packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts
  96. 44
      packages/nocodb/src/meta/migrations/v2/nc_042_integrations.ts
  97. 105
      packages/nocodb/src/meta/migrations/v2/nc_056_integration.ts
  98. 45
      packages/nocodb/src/middlewares/extract-ids/extract-ids.middleware.ts
  99. 35
      packages/nocodb/src/models/Base.ts
  100. 2
      packages/nocodb/src/models/BaseUser.ts
  101. Some files were not shown because too many files have changed in this diff Show More

5
packages/nc-gui/assets/nc-icons/alert-triangle-solid.svg

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M8.57514 3.21635L1.51681 14.9997C1.37128 15.2517 1.29428 15.5374 1.29346 15.8284C1.29265 16.1195 1.36805 16.4056 1.51216 16.6585C1.65627 16.9113 1.86408 17.122 2.1149 17.2696C2.36571 17.4171 2.65081 17.4965 2.94181 17.4997H17.0585C17.3495 17.4965 17.6346 17.4171 17.8854 17.2696C18.1362 17.122 18.344 16.9113 18.4881 16.6585C18.6322 16.4056 18.7076 16.1195 18.7068 15.8284C18.706 15.5374 18.629 15.2517 18.4835 14.9997L11.4251 3.21635C11.2766 2.97144 11.0674 2.76895 10.8178 2.62842C10.5682 2.48789 10.2866 2.41406 10.0001 2.41406C9.71369 2.41406 9.43208 2.48789 9.18248 2.62842C8.93287 2.76895 8.7237 2.97144 8.57514 3.21635Z" fill="currentColor" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 7.5V10.8333" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 14.167H10.0083" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

3
packages/nc-gui/assets/nc-icons/apple_solid.svg

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="32" viewBox="0 0 28 32" fill="none">
<path d="M26.1914 10.9109C26.0394 11.0046 22.6963 12.951 22.7343 17.0001C22.7763 21.8434 26.98 23.4562 27.0283 23.4745C26.9907 23.5898 26.3562 25.7717 24.8124 28.025C23.4783 29.9771 22.0963 31.9181 19.9149 31.9586C17.7719 31.9979 17.0812 30.6871 14.6331 30.6871C12.1834 30.6871 11.4167 31.9181 9.39012 31.9979C7.2847 32.0775 5.68177 29.8899 4.33777 27.9485C1.59026 23.9755 -0.510428 16.7204 2.3103 11.8243C3.71067 9.39274 6.21367 7.85225 8.92948 7.81246C10.9978 7.77337 12.9483 9.20289 14.2135 9.20289C15.4605 9.20289 17.6956 7.53647 20.3301 7.73137C21.3626 7.80789 24.2937 8.11606 26.1914 10.9109ZM18.7045 5.10862C19.8234 3.75637 20.5762 1.87313 20.3689 0C18.7585 0.0638623 16.8109 1.07268 15.6558 2.42407C14.6215 3.62119 13.7131 5.53851 13.9597 7.37342C15.7546 7.51211 17.5865 6.46171 18.7045 5.10862Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 931 B

10
packages/nc-gui/assets/nc-icons/asana.svg

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="30" viewBox="0 0 32 30" fill="none">
<g clip-path="url(#clip0_881_27774)">
<path d="M25.0406 15.8463C21.197 15.8463 18.0812 18.9622 18.0812 22.8059C18.0812 26.6496 21.197 29.7655 25.0406 29.7655C28.8842 29.7655 32 26.6496 32 22.8059C32 18.9622 28.8842 15.8463 25.0406 15.8463ZM6.95943 15.8469C3.11588 15.8469 0 18.9622 0 22.8059C0 26.6496 3.11588 29.7655 6.95943 29.7655C10.8031 29.7655 13.9192 26.6496 13.9192 22.8059C13.9192 18.9622 10.8031 15.8469 6.95943 15.8469ZM22.9593 7.14678C22.9593 10.9906 19.8435 14.1068 16.0001 14.1068C12.1563 14.1068 9.04059 10.9906 9.04059 7.14678C9.04059 3.30368 12.1563 0.1875 16.0001 0.1875C19.8435 0.1875 22.9593 3.30368 22.9593 7.14678Z" fill="#F06A6A"/>
</g>
<defs>
<clipPath id="clip0_881_27774">
<rect width="32" height="29.625" fill="white" transform="translate(0 0.1875)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 927 B

4
packages/nc-gui/assets/nc-icons/book-open.svg

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M14.6667 2H10.6667C9.95942 2 9.28115 2.28095 8.78105 2.78105C8.28095 3.28115 8 3.95942 8 4.66667V14C8 13.4696 8.21071 12.9609 8.58579 12.5858C8.96086 12.2107 9.46957 12 10 12H14.6667V2Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1.33331 2H5.33331C6.04056 2 6.71883 2.28095 7.21893 2.78105C7.71903 3.28115 7.99998 3.95942 7.99998 4.66667V14C7.99998 13.4696 7.78927 12.9609 7.41419 12.5858C7.03912 12.2107 6.53041 12 5.99998 12H1.33331V2Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 709 B

4
packages/nc-gui/assets/nc-icons/box.svg

@ -0,0 +1,4 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="32" height="32" rx="4" fill="#0061D5"/>
<path d="M27.7884 21.0273C28.088 21.4467 28.0281 21.986 27.6686 22.2856C27.2491 22.5852 26.6499 22.5253 26.3503 22.1658L24.2531 19.4693L22.2158 22.1058C21.9162 22.5253 21.317 22.5253 20.8976 22.2257C20.4781 21.9261 20.4182 21.3868 20.7178 20.9673L23.1146 17.8515L20.7178 14.7356C20.4182 14.3162 20.538 13.717 20.8976 13.4174C21.317 13.1178 21.9162 13.2376 22.2158 13.5971L24.2531 16.2936L26.3503 13.717C26.6499 13.2975 27.1892 13.2376 27.6686 13.5372C28.088 13.8368 28.088 14.436 27.7884 14.8555L25.4515 17.9114L27.7884 21.0273ZM16.8829 20.6677C15.325 20.6677 14.0666 19.4693 14.0666 17.9114C14.0666 16.4134 15.325 15.1551 16.8829 15.1551C18.4408 15.1551 19.6991 16.4134 19.6991 17.9114C19.6392 19.4693 18.3809 20.6677 16.8829 20.6677ZM8.61387 20.6677C7.05594 20.6677 5.79761 19.4693 5.79761 17.9114C5.79761 16.4134 7.05594 15.1551 8.61387 15.1551C10.1718 15.1551 11.4301 16.4134 11.4301 17.9114C11.4301 19.4693 10.1718 20.6677 8.61387 20.6677ZM16.8829 13.3575C15.1452 13.3575 13.5873 14.3162 12.8083 15.7543C12.0293 14.3162 10.4714 13.3575 8.67379 13.3575C7.59522 13.3575 6.6365 13.717 5.85753 14.2563V10.4214C5.85753 9.942 5.43809 9.52255 4.95873 9.52255C4.41944 9.52255 4 9.942 4 10.4214V17.9713C4.05992 20.488 6.09721 22.4654 8.61387 22.4654C10.4115 22.4654 11.9694 21.4467 12.7484 20.0086C13.5273 21.4467 15.0853 22.4654 16.823 22.4654C19.3995 22.4654 21.4968 20.4281 21.4968 17.8515C21.5567 15.3948 19.4595 13.3575 16.8829 13.3575Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

4
packages/nc-gui/assets/nc-icons/check-circle-solid.svg

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
<path d="M9.99984 18.3337C14.6022 18.3337 18.3332 14.6027 18.3332 10.0003C18.3332 5.39795 14.6022 1.66699 9.99984 1.66699C5.39746 1.66699 1.6665 5.39795 1.6665 10.0003C1.6665 14.6027 5.39746 18.3337 9.99984 18.3337Z" fill="currentColor" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.5 7.5L8.6875 12.5L6.5 10.2273" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 562 B

3
packages/nc-gui/assets/nc-icons/circle.svg

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 316 B

5
packages/nc-gui/assets/nc-icons/git-commit.svg

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M11.3401 8H15.3068" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M0.699951 8H4.66662" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.99992 10.6663C9.47268 10.6663 10.6666 9.47243 10.6666 7.99967C10.6666 6.52692 9.47268 5.33301 7.99992 5.33301C6.52716 5.33301 5.33325 6.52692 5.33325 7.99967C5.33325 9.47243 6.52716 10.6663 7.99992 10.6663Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 669 B

17
packages/nc-gui/assets/nc-icons/github_solid.svg

@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<g clip-path="url(#clip0_881_27792)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.0003 0.666656C12.202 0.668627 8.52813 2.01393 5.63578 4.46202C2.74344 6.91011 0.821164 10.3013 0.212685 14.0293C-0.395793 17.7573 0.349195 21.5789 2.31445 24.8109C4.27971 28.0428 7.33708 30.4743 10.9399 31.6706C11.7347 31.8181 12.0341 31.3255 12.0341 30.9067C12.0341 30.4878 12.0182 29.2733 12.0129 27.9456C7.56182 28.9072 6.62127 26.0673 6.62127 26.0673C5.89532 24.2232 4.84614 23.7385 4.84614 23.7385C3.39425 22.7533 4.95478 22.7717 4.95478 22.7717C6.56299 22.885 7.40817 24.4129 7.40817 24.4129C8.83357 26.8444 11.1518 26.1411 12.0632 25.7301C12.2063 24.7001 12.6223 23.9993 13.0806 23.6015C9.52507 23.2011 5.78934 21.8365 5.78934 15.7406C5.76731 14.1596 6.35732 12.6307 7.43731 11.4702C7.27304 11.0698 6.7246 9.4523 7.59361 7.25523C7.59361 7.25523 8.9369 6.82846 11.9944 8.88591C14.6168 8.17282 17.3838 8.17282 20.0063 8.88591C23.0611 6.82846 24.4017 7.25523 24.4017 7.25523C25.2734 9.44703 24.7249 11.0645 24.5607 11.4702C25.6441 12.6309 26.2353 14.1625 26.2113 15.7458C26.2113 21.8549 22.4676 23.2011 18.9067 23.5936C19.479 24.0889 19.9904 25.0557 19.9904 26.5415C19.9904 28.6701 19.9718 30.3824 19.9718 30.9067C19.9718 31.3308 20.2606 31.826 21.0713 31.6706C24.6746 30.4742 27.7322 28.0423 29.6974 24.8098C31.6627 21.5773 32.4073 17.7551 31.7981 14.0267C31.1889 10.2984 29.2657 6.90713 26.3724 4.45946C23.4791 2.0118 19.8044 0.667378 16.0056 0.666656H16.0003Z" fill="#191717"/>
<path d="M10.7038 26.6337C10.7038 26.7628 10.5554 26.8734 10.3647 26.8761C10.1739 26.8787 10.0176 26.7733 10.0176 26.6442C10.0176 26.5152 10.1659 26.4045 10.3567 26.4019C10.5475 26.3992 10.7038 26.502 10.7038 26.6337Z" fill="#191717"/>
<path d="M11.9225 26.4309C11.9463 26.5599 11.8139 26.6943 11.6231 26.7259C11.4323 26.7575 11.2654 26.6811 11.2416 26.5547C11.2177 26.4282 11.3555 26.2912 11.541 26.257C11.7264 26.2227 11.8986 26.3018 11.9225 26.4309Z" fill="#191717"/>
<path d="M9.39494 26.5441C9.3552 26.6679 9.17503 26.7233 8.99487 26.6706C8.81471 26.6179 8.69548 26.4704 8.72993 26.3439C8.76437 26.2175 8.94718 26.1595 9.12999 26.2175C9.31281 26.2754 9.42938 26.415 9.39494 26.5441Z" fill="#191717"/>
<path d="M8.19474 26.0278C8.10731 26.1252 7.9298 26.0989 7.78408 25.9672C7.63836 25.8355 7.60391 25.6563 7.69134 25.5615C7.77877 25.4667 7.95629 25.493 8.1073 25.6221C8.25832 25.7512 8.28747 25.9329 8.19474 26.0278Z" fill="#191717"/>
<path d="M7.33903 25.1426C7.241 25.2111 7.07408 25.1426 6.98135 25.0057C6.95571 24.9811 6.93532 24.9516 6.92139 24.919C6.90746 24.8864 6.90027 24.8514 6.90027 24.816C6.90027 24.7806 6.90746 24.7455 6.92139 24.7129C6.93532 24.6803 6.95571 24.6509 6.98135 24.6263C7.07938 24.5605 7.2463 24.6263 7.33903 24.7607C7.43176 24.895 7.43441 25.0742 7.33903 25.1426Z" fill="#191717"/>
<path d="M6.70835 24.2285C6.65348 24.256 6.5907 24.2637 6.53077 24.2502C6.47084 24.2368 6.41748 24.203 6.37982 24.1547C6.27649 24.0441 6.25528 23.8913 6.33477 23.8228C6.41425 23.7543 6.55733 23.7859 6.66066 23.8966C6.76399 24.0072 6.78783 24.16 6.70835 24.2285Z" fill="#191717"/>
<path d="M6.05921 23.5093C6.02476 23.5884 5.89759 23.6121 5.79426 23.5568C5.69094 23.5014 5.61411 23.3987 5.6512 23.317C5.68829 23.2354 5.81282 23.2143 5.91614 23.2696C6.01947 23.3249 6.09895 23.4303 6.05921 23.5093Z" fill="#191717"/>
</g>
<defs>
<clipPath id="clip0_881_27792">
<rect width="32" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

16
packages/nc-gui/assets/nc-icons/gitlab.svg

@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="32" viewBox="0 0 36 32" fill="none">
<g clip-path="url(#clip0_881_27798)">
<path d="M18.0102 32.0101L24.3971 12.3531H11.6232L18.0102 32.0101Z" fill="#E24329"/>
<path d="M18.0102 32.0101L11.6232 12.3531H2.672L18.0102 32.0101Z" fill="#FC6D26"/>
<path d="M2.67197 12.3531L0.731078 18.3267C0.554047 18.8715 0.747938 19.4684 1.21142 19.8051L18.0101 32.0101L2.67197 12.3531Z" fill="#FCA326"/>
<path d="M2.672 12.3532H11.6232L7.77629 0.513932C7.57844 -0.0953436 6.71659 -0.0951783 6.51873 0.513932L2.672 12.3532Z" fill="#E24329"/>
<path d="M18.0101 32.0101L24.3971 12.3531H33.3483L18.0101 32.0101Z" fill="#FC6D26"/>
<path d="M33.3483 12.3531L35.2892 18.3267C35.4662 18.8715 35.2723 19.4684 34.8089 19.8051L18.0101 32.0101L33.3483 12.3531Z" fill="#FCA326"/>
<path d="M33.3483 12.3532H24.3971L28.244 0.513932C28.4419 -0.0953436 29.3037 -0.0951783 29.5016 0.513932L33.3483 12.3532Z" fill="#E24329"/>
</g>
<defs>
<clipPath id="clip0_881_27798">
<rect width="34.7119" height="32" fill="white" transform="translate(0.644043)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

15
packages/nc-gui/assets/nc-icons/google-drive.svg

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="30" viewBox="0 0 32 30" fill="none">
<g clip-path="url(#clip0_881_27829)">
<path d="M2.41928 25.1917L3.83052 27.6293C4.12375 28.1425 4.54528 28.5457 5.04014 28.839C6.45753 27.0398 7.44418 25.6591 8.00008 24.6969C8.56421 23.7204 9.25762 22.1931 10.0803 20.115C7.8633 19.8231 6.18325 19.6772 5.04014 19.6772C3.94318 19.6772 2.26314 19.8231 0 20.115C0 20.6831 0.146624 21.2513 0.439867 21.7644L2.41928 25.1917Z" fill="#0066DA"/>
<path d="M26.9602 28.839C27.455 28.5457 27.8766 28.1425 28.1698 27.6293L28.7563 26.6213L31.5604 21.7644C31.8537 21.2513 32.0003 20.6831 32.0003 20.115C29.7241 19.8231 28.0471 19.6772 26.9693 19.6772C25.8111 19.6772 24.1341 19.8231 21.9384 20.115C22.7513 22.2045 23.4355 23.7318 23.9911 24.6969C24.5515 25.6705 25.5412 27.0512 26.9602 28.839Z" fill="#EA4335"/>
<path d="M16.0001 9.85139C17.6401 7.87069 18.7703 6.34338 19.3908 5.26945C19.8904 4.4047 20.4402 3.02401 21.0402 1.12737C20.5454 0.83412 19.9772 0.6875 19.3908 0.6875H12.6095C12.023 0.6875 11.4548 0.852448 10.96 1.12737C11.7232 3.30263 12.3709 4.85073 12.903 5.77167C13.4909 6.7894 14.5233 8.14931 16.0001 9.85139Z" fill="#00832D"/>
<path d="M21.92 20.115H10.0803L5.04016 28.839C5.53502 29.1323 6.10318 29.2789 6.68968 29.2789H25.3107C25.8972 29.2789 26.4653 29.1139 26.9602 28.839L21.92 20.115Z" fill="#2684FC"/>
<path d="M16.0002 9.85141L10.96 1.12738C10.4652 1.42062 10.0436 1.82382 9.75038 2.33702L0.439867 18.4655C0.146624 18.9787 0 19.5468 0 20.115H10.0803L16.0002 9.85141Z" fill="#00AC47"/>
<path d="M26.9052 10.4013L22.2499 2.33702C21.9567 1.82382 21.5351 1.42062 21.0402 1.12738L16.0001 9.85141L21.92 20.115H31.982C31.982 19.5468 31.8353 18.9787 31.5421 18.4655L26.9052 10.4013Z" fill="#FFBA00"/>
</g>
<defs>
<clipPath id="clip0_881_27829">
<rect width="32" height="28.625" fill="white" transform="translate(0 0.6875)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

17
packages/nc-gui/assets/nc-icons/google_calendar.svg

@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<g clip-path="url(#clip0_881_27812)">
<path d="M24.421 7.57895H7.57886V24.4211H24.421V7.57895Z" fill="white"/>
<path d="M24.4212 32L32.0001 24.4211L28.2106 23.7745L24.4212 24.4211L23.7295 27.8873L24.4212 32Z" fill="#EA4335"/>
<path d="M0 24.4211V29.4737C0 30.8695 1.13053 32 2.52632 32H7.57895L8.35711 28.2105L7.57895 24.4211L3.4499 23.7745L0 24.4211Z" fill="#188038"/>
<path d="M32.0001 7.57895V2.52632C32.0001 1.13053 30.8696 0 29.4738 0H24.4212C23.96 1.87948 23.7295 3.26264 23.7295 4.14947C23.7295 5.03629 23.96 6.17945 24.4212 7.57895C26.0973 8.05894 27.3605 8.29893 28.2106 8.29893C29.0607 8.29893 30.3239 8.05894 32.0001 7.57895Z" fill="#1967D2"/>
<path d="M32.0001 7.57895H24.4211V24.4211H32.0001V7.57895Z" fill="#FBBC04"/>
<path d="M24.421 24.4211H7.57886V32H24.421V24.4211Z" fill="#34A853"/>
<path d="M24.4211 0H2.52632C1.13053 0 0 1.13053 0 2.52632V24.4211H7.57895V7.57895H24.4211V0Z" fill="#4285F4"/>
<path d="M11.0336 20.6442C10.4042 20.219 9.96836 19.5979 9.73047 18.7768L11.1915 18.1747C11.3242 18.68 11.5557 19.0716 11.8863 19.3495C12.2147 19.6274 12.6147 19.7642 13.082 19.7642C13.5599 19.7642 13.9705 19.619 14.3136 19.3284C14.6568 19.0379 14.8294 18.6674 14.8294 18.219C14.8294 17.76 14.6484 17.3853 14.2863 17.0947C13.9242 16.8042 13.4694 16.659 12.9263 16.659H12.082V15.2126H12.8399C13.3073 15.2126 13.701 15.0863 14.021 14.8337C14.341 14.5811 14.501 14.2358 14.501 13.7958C14.501 13.4042 14.3578 13.0926 14.0715 12.859C13.7852 12.6253 13.4231 12.5074 12.9831 12.5074C12.5536 12.5074 12.2126 12.6211 11.9599 12.8505C11.7075 13.0806 11.5177 13.3711 11.4084 13.6947L9.96205 13.0926C10.1536 12.5495 10.5052 12.0695 11.021 11.6547C11.5368 11.24 12.1957 11.0316 12.9957 11.0316C13.5873 11.0316 14.1199 11.1453 14.5915 11.3747C15.0631 11.6042 15.4336 11.9221 15.701 12.3263C15.9684 12.7326 16.101 13.1874 16.101 13.6926C16.101 14.2084 15.9768 14.6442 15.7284 15.0021C15.4799 15.36 15.1747 15.6337 14.8126 15.8253V15.9116C15.2801 16.1044 15.6863 16.421 15.9873 16.8274C16.2926 17.2379 16.4463 17.7284 16.4463 18.3011C16.4463 18.8737 16.301 19.3853 16.0105 19.8337C15.7199 20.2821 15.3178 20.6358 14.8084 20.8926C14.2968 21.1495 13.722 21.28 13.0842 21.28C12.3452 21.2821 11.6631 21.0695 11.0336 20.6442ZM20.0084 13.3937L18.4042 14.5537L17.602 13.3368L20.4799 11.2611H21.5831V21.0526H20.0084V13.3937Z" fill="#4285F4"/>
</g>
<defs>
<clipPath id="clip0_881_27812">
<rect width="32" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

60
packages/nc-gui/assets/nc-icons/google_sheet.svg

@ -0,0 +1,60 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="32" viewBox="0 0 24 32" fill="none">
<g clip-path="url(#clip0_881_27842)">
<mask id="mask0_881_27842" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="32">
<path d="M14.8258 0.457703H2.81581C1.65014 0.457703 0.696411 1.41143 0.696411 2.57711V29.4229C0.696411 30.5885 1.65014 31.5423 2.81581 31.5423H21.184C22.3496 31.5423 23.3034 30.5885 23.3034 29.4229V8.93531L14.8258 0.457703Z" fill="white"/>
</mask>
<g mask="url(#mask0_881_27842)">
<path d="M14.8258 0.457703H2.81581C1.65014 0.457703 0.696411 1.41143 0.696411 2.57711V29.4229C0.696411 30.5885 1.65014 31.5423 2.81581 31.5423H21.184C22.3496 31.5423 23.3034 30.5885 23.3034 29.4229V8.93531L18.3581 5.40298L14.8258 0.457703Z" fill="#0F9D58"/>
</g>
<mask id="mask1_881_27842" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="32">
<path d="M14.8258 0.457703H2.81581C1.65014 0.457703 0.696411 1.41143 0.696411 2.57711V29.4229C0.696411 30.5885 1.65014 31.5423 2.81581 31.5423H21.184C22.3496 31.5423 23.3034 30.5885 23.3034 29.4229V8.93531L14.8258 0.457703Z" fill="white"/>
</mask>
<g mask="url(#mask1_881_27842)">
<path d="M6.34814 15.6467V25.8905H17.6516V15.6467H6.34814ZM11.2934 24.4776H7.76108V22.7114H11.2934V24.4776ZM11.2934 21.6517H7.76108V19.8855H11.2934V21.6517ZM11.2934 18.8258H7.76108V17.0597H11.2934V18.8258ZM16.2387 24.4776H12.7064V22.7114H16.2387V24.4776ZM16.2387 21.6517H12.7064V19.8855H16.2387V21.6517ZM16.2387 18.8258H12.7064V17.0597H16.2387V18.8258Z" fill="#F1F1F1"/>
</g>
<mask id="mask2_881_27842" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="32">
<path d="M14.8258 0.457703H2.81581C1.65014 0.457703 0.696411 1.41143 0.696411 2.57711V29.4229C0.696411 30.5885 1.65014 31.5423 2.81581 31.5423H21.184C22.3496 31.5423 23.3034 30.5885 23.3034 29.4229V8.93531L14.8258 0.457703Z" fill="white"/>
</mask>
<g mask="url(#mask2_881_27842)">
<path d="M15.4457 8.31537L23.3034 16.1713V8.93529L15.4457 8.31537Z" fill="url(#paint0_linear_881_27842)"/>
</g>
<mask id="mask3_881_27842" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="32">
<path d="M14.8258 0.457703H2.81581C1.65014 0.457703 0.696411 1.41143 0.696411 2.57711V29.4229C0.696411 30.5885 1.65014 31.5423 2.81581 31.5423H21.184C22.3496 31.5423 23.3034 30.5885 23.3034 29.4229V8.93531L14.8258 0.457703Z" fill="white"/>
</mask>
<g mask="url(#mask3_881_27842)">
<path d="M14.8258 0.457703V6.81591C14.8258 7.98688 15.7742 8.93531 16.9452 8.93531H23.3034L14.8258 0.457703Z" fill="#87CEAC"/>
</g>
<mask id="mask4_881_27842" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="32">
<path d="M14.8258 0.457703H2.81581C1.65014 0.457703 0.696411 1.41143 0.696411 2.57711V29.4229C0.696411 30.5885 1.65014 31.5423 2.81581 31.5423H21.184C22.3496 31.5423 23.3034 30.5885 23.3034 29.4229V8.93531L14.8258 0.457703Z" fill="white"/>
</mask>
<g mask="url(#mask4_881_27842)">
<path d="M2.81581 0.457703C1.65014 0.457703 0.696411 1.41143 0.696411 2.57711V2.75372C0.696411 1.58805 1.65014 0.63432 2.81581 0.63432H14.8258V0.457703H2.81581Z" fill="white" fill-opacity="0.2"/>
</g>
<mask id="mask5_881_27842" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="32">
<path d="M14.8258 0.457703H2.81581C1.65014 0.457703 0.696411 1.41143 0.696411 2.57711V29.4229C0.696411 30.5885 1.65014 31.5423 2.81581 31.5423H21.184C22.3496 31.5423 23.3034 30.5885 23.3034 29.4229V8.93531L14.8258 0.457703Z" fill="white"/>
</mask>
<g mask="url(#mask5_881_27842)">
<path d="M21.184 31.3657H2.81581C1.65014 31.3657 0.696411 30.4119 0.696411 29.2463V29.4229C0.696411 30.5886 1.65014 31.5423 2.81581 31.5423H21.184C22.3496 31.5423 23.3034 30.5886 23.3034 29.4229V29.2463C23.3034 30.4119 22.3496 31.3657 21.184 31.3657Z" fill="#263238" fill-opacity="0.2"/>
</g>
<mask id="mask6_881_27842" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="24" height="32">
<path d="M14.8258 0.457703H2.81581C1.65014 0.457703 0.696411 1.41143 0.696411 2.57711V29.4229C0.696411 30.5885 1.65014 31.5423 2.81581 31.5423H21.184C22.3496 31.5423 23.3034 30.5885 23.3034 29.4229V8.93531L14.8258 0.457703Z" fill="white"/>
</mask>
<g mask="url(#mask6_881_27842)">
<path d="M16.9452 8.93532C15.7742 8.93532 14.8258 7.98689 14.8258 6.81592V6.99254C14.8258 8.16351 15.7742 9.11194 16.9452 9.11194H23.3034V8.93532H16.9452Z" fill="#263238" fill-opacity="0.1"/>
</g>
<path d="M14.8258 0.457703H2.81581C1.65014 0.457703 0.696411 1.41143 0.696411 2.57711V29.4229C0.696411 30.5885 1.65014 31.5423 2.81581 31.5423H21.184C22.3496 31.5423 23.3034 30.5885 23.3034 29.4229V8.93531L14.8258 0.457703Z" fill="url(#paint1_radial_881_27842)"/>
</g>
<defs>
<linearGradient id="paint0_linear_881_27842" x1="408.372" y1="75.7671" x2="408.372" y2="794.017" gradientUnits="userSpaceOnUse">
<stop stop-color="#263238" stop-opacity="0.2"/>
<stop offset="1" stop-color="#263238" stop-opacity="0.02"/>
</linearGradient>
<radialGradient id="paint1_radial_881_27842" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(72.3164 61.8945) scale(3645.34 3645.34)">
<stop stop-color="white" stop-opacity="0.1"/>
<stop offset="1" stop-color="white" stop-opacity="0"/>
</radialGradient>
<clipPath id="clip0_881_27842">
<rect width="23.403" height="32" fill="white" transform="translate(0.298462)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

10
packages/nc-gui/assets/nc-icons/hubspot.svg

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<g clip-path="url(#clip0_881_27892)">
<path d="M29.3632 15.2004C28.6503 13.9836 27.6331 12.9731 26.4115 12.2683C25.4789 11.7263 24.5145 11.382 23.4256 11.2331V7.3341C23.9537 7.11528 24.404 6.74307 24.7182 6.26561C25.0324 5.78814 25.1962 5.22738 25.1883 4.65583C25.1903 4.26986 25.1159 3.88732 24.9696 3.53017C24.8232 3.17303 24.6077 2.84833 24.3354 2.57472C24.0632 2.30112 23.7396 2.08401 23.3832 1.93586C23.0268 1.78771 22.6446 1.71145 22.2586 1.71145C21.4768 1.71145 20.7269 2.02135 20.1732 2.57325C19.6194 3.12515 19.3071 3.87404 19.3045 4.65583C19.3045 5.85214 19.9392 6.87266 21.0306 7.3341V11.2111C20.1266 11.3419 19.2495 11.6173 18.4329 12.0266L7.9957 4.1236C8.06895 3.84771 8.13242 3.56207 8.13242 3.26177C8.13242 1.45998 6.67244 0 4.87066 0C3.06887 0 1.61133 1.45998 1.61133 3.26177C1.61133 5.06355 3.07131 6.52354 4.8731 6.52354C5.48834 6.52354 6.0572 6.34287 6.54793 6.04746L7.23153 6.56504L16.5945 13.3156C16.0989 13.7697 15.6374 14.2873 15.2688 14.8684C14.5217 16.0525 14.0651 17.3538 14.0651 18.7747V19.0677C14.0684 20.0313 14.242 20.9868 14.5778 21.89C14.861 22.6615 15.2761 23.3646 15.7912 23.9994L12.6808 27.1171C11.7604 26.7753 10.7277 27.0024 10.0319 27.6982C9.55823 28.1694 9.29211 28.8115 9.29455 29.4804C9.29699 30.1494 9.55823 30.7866 10.0343 31.2627C10.5104 31.7388 11.1476 32.0024 11.8166 32.0024C12.4855 32.0024 13.1276 31.7388 13.5988 31.2627C14.07 30.7866 14.3386 30.1494 14.3361 29.4804C14.3359 29.2236 14.2964 28.9684 14.2189 28.7236L17.4319 25.5106C17.8713 25.8158 18.3474 26.0722 18.8601 26.2919C19.9139 26.7541 21.0518 26.9935 22.2025 26.995H22.4222C23.7699 26.995 25.0419 26.6777 26.2357 26.0307C27.4629 25.3726 28.4939 24.4014 29.2241 23.2157C29.9736 22.0194 30.3837 20.6937 30.3837 19.2361V19.1629C30.3837 17.7298 30.0517 16.4089 29.3583 15.198L29.3632 15.2004ZM25.4398 21.9437C24.5682 22.9129 23.5648 23.5111 22.432 23.5111H22.2171C21.5701 23.5111 20.9354 23.3329 20.3177 23.0057C19.6404 22.6535 19.0665 22.131 18.6526 21.4896C18.2034 20.8548 17.9592 20.1614 17.9592 19.4266V19.2068C17.9592 18.4842 18.0984 17.7981 18.4475 17.1511C18.8211 16.4187 19.3264 15.8938 19.9978 15.4568C20.6692 15.0198 21.3602 14.8098 22.1463 14.8098H22.2196C22.9276 14.8098 23.6039 14.949 24.2386 15.2737C24.8817 15.6119 25.4279 16.1085 25.8256 16.7166C26.2193 17.3264 26.4628 18.0208 26.536 18.743L26.5531 19.1995C26.5531 20.1932 26.1723 21.1136 25.413 21.9486L25.4398 21.9437Z" fill="#F8761F"/>
</g>
<defs>
<clipPath id="clip0_881_27892">
<rect width="32" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

6
packages/nc-gui/assets/nc-icons/integration.svg

@ -0,0 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.99307 13.3778C4.49298 12.8777 4.21203 12.1995 4.21203 11.4922C4.21203 10.785 4.49298 10.1067 4.99308 9.6066L5.46448 9.13519" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.64292 2.29989L12.2998 7.95669L10.1785 10.078C9.67838 10.5781 9.0001 10.859 8.29286 10.859C7.58561 10.859 6.90733 10.5781 6.40724 10.078L4.52162 8.19242C4.02153 7.69233 3.74058 7.01405 3.74058 6.3068C3.74058 5.59956 4.02153 4.92128 4.52162 4.42119L6.64292 2.29989Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.2426 4.18542L11.1212 6.30675" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.4142 1.35699L8.29286 3.47831" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 978 B

20
packages/nc-gui/assets/nc-icons/jira.svg

@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<g clip-path="url(#clip0_881_27899)">
<path d="M30.5823 0H15.2134C15.2134 1.84001 15.9443 3.60466 17.2454 4.90575C18.5465 6.20684 20.3111 6.93778 22.1512 6.93778H24.9823V9.67111C24.9847 13.4993 28.0874 16.602 31.9156 16.6044V1.33333C31.9156 0.596954 31.3186 0 30.5823 0Z" fill="#2684FF"/>
<path d="M22.9778 7.65778H7.60889C7.61134 11.4859 10.7141 14.5887 14.5422 14.5911H17.3733V17.3333C17.3782 21.1615 20.4829 24.2622 24.3111 24.2622V8.99111C24.3111 8.25473 23.7142 7.65778 22.9778 7.65778Z" fill="url(#paint0_linear_881_27899)"/>
<path d="M15.3689 15.3111H0C0 19.1427 3.10615 22.2489 6.93778 22.2489H9.77778V24.9822C9.78022 28.8069 12.8775 31.9082 16.7022 31.9155V16.6444C16.7022 15.9081 16.1053 15.3111 15.3689 15.3111Z" fill="url(#paint1_linear_881_27899)"/>
</g>
<defs>
<linearGradient id="paint0_linear_881_27899" x1="1644.94" y1="10.3245" x2="995.143" y2="688.359" gradientUnits="userSpaceOnUse">
<stop offset="0.18" stop-color="#0052CC"/>
<stop offset="1" stop-color="#2684FF"/>
</linearGradient>
<linearGradient id="paint1_linear_881_27899" x1="1681.33" y1="22.8665" x2="929.674" y2="762.39" gradientUnits="userSpaceOnUse">
<stop offset="0.18" stop-color="#0052CC"/>
<stop offset="1" stop-color="#2684FF"/>
</linearGradient>
<clipPath id="clip0_881_27899">
<rect width="32" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

11
packages/nc-gui/assets/nc-icons/mailchimp.svg

@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<g clip-path="url(#clip0_881_27909)">
<path d="M0 0H32V32H0" fill="#FFDD22"/>
<path d="M26.125 19.125L25.75 18.0625C25.75 18.0625 27.3125 15.6875 23.4375 14.875C23.4375 14.875 23.6875 11.9375 22.3125 10.5625C25.3125 7.62497 24.625 3.18747 17.8125 6.06247C14.3125 -0.625031 0.812511 15.0625 6.43751 17.5625C5.87501 18.3125 5.87501 22.0625 9.75001 22.4375C12.375 28.0625 18.75 28.4375 22.4375 26.75C26.125 25.0625 28.25 19.6875 26.125 19.125ZM9.68751 21.625C6.50001 21.3125 6.18751 16.9375 8.93751 16.5C11.6875 16.0625 12.375 21.875 9.68751 21.625ZM8.75001 15.6875C7.87501 15.6875 6.81251 16.875 6.81251 16.875C2.56251 14.8125 14.5 1.12497 17.0625 6.43747C17.0625 6.43747 10.8125 9.43747 8.75001 15.6875ZM21.25 21C21.25 20.75 19.9375 21.375 17.5625 20.5625C17.75 19.25 20.5625 21.6875 25.25 18.5L25.625 19.8125C27.375 19.5 25.625 25.4375 20 25.375C15.4375 25.3125 14 20.625 16.5 18.0625C17 17.5625 14.6875 16.5625 15.125 14.375C15.3125 13.4375 16.125 12.0625 18.1875 12.4375C20.25 12.8125 20.6875 10.9375 22.0625 11.625C23.4375 12.3125 22.625 14.9375 22.8125 15.3125C23 15.6875 25 15.75 25.375 16.8125C25.75 17.875 22.8125 20.1875 18.25 19.5625C17.1875 19.4375 16.5625 20.8125 17.25 21.6875C18.625 23.6875 24.25 22.375 25.1875 20.4375C22.8125 22.25 17.9375 22.9375 17.5625 21C18.9375 21.625 21.25 21.25 21.25 21ZM13.0625 11.125C14.4375 9.43747 16.25 8.43747 16.25 8.43747L15.875 9.37497C15.875 9.37497 17.1875 8.37497 18.625 8.37497L18.125 8.87497C19.75 8.93747 20.4375 9.56247 20.4375 9.56247C20.4375 9.56247 16.625 8.43747 13.0625 11.125ZM21.5 13.5625C22.3125 13.5 22.0625 15.375 22.0625 15.375H21.5625C21.5625 15.375 20.6875 13.625 21.5 13.5625ZM17.8125 15.625C17.25 15.6875 16.625 16 16.6875 15.75C16.9375 14.75 19.25 15 19.1875 15.875C19.125 16.75 18.625 15.5 17.8125 15.625ZM19.125 16.375C19.1875 16.5 18.6875 16.375 18.3125 16.4375C17.9375 16.5 17.5625 16.6875 17.5625 16.5625C17.5625 16.4375 19 15.875 19.125 16.375ZM20.375 16.5625C20.5625 16.1875 21.3125 16.5625 21.125 16.9375C20.9375 17.3125 20.1875 16.9375 20.375 16.5625ZM21.9375 16.6875C21.5625 16.6875 21.5625 15.875 21.9375 15.875C22.3125 15.875 22.3125 16.75 21.9375 16.75V16.6875ZM10.6875 20C10.875 20.1875 10.3125 20.5625 9.87501 20.25C9.43751 19.9375 10.375 18.4375 9.25001 18.0625C8.12501 17.6875 8.43751 19.125 8.12501 18.9375C7.81251 18.75 8.56251 16.75 9.87501 17.5625C11.1875 18.375 9.50001 19.625 10.25 20C11 20.375 10.5625 19.875 10.6875 20Z" fill="#222222"/>
</g>
<defs>
<clipPath id="clip0_881_27909">
<rect width="32" height="32" rx="4" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

19
packages/nc-gui/assets/nc-icons/microsoft_access.svg

@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="32" viewBox="0 0 28 32" fill="none">
<g clip-path="url(#clip0_881_27919)">
<path d="M4.33203 19.5188V26.5564C4.33203 28.4998 9.42183 30.0752 15.7004 30.0752C21.9791 30.0752 27.0689 28.4998 27.0689 26.5564V19.5188H4.33203Z" fill="#881421"/>
<path d="M4.33203 12.4812V19.5188C4.33203 21.4622 9.42183 23.0376 15.7004 23.0376C21.9791 23.0376 27.0689 21.4622 27.0689 19.5188V12.4812H4.33203Z" fill="#AF2031"/>
<path d="M4.33203 5.4436V12.4812C4.33203 14.4246 9.42183 16 15.7004 16C21.9791 16 27.0689 14.4246 27.0689 12.4812V5.4436H4.33203Z" fill="#C94F60"/>
<path d="M15.7004 8.96239C21.979 8.96239 27.0689 7.38697 27.0689 5.4436C27.0689 3.50022 21.979 1.9248 15.7004 1.9248C9.42184 1.9248 4.33203 3.50022 4.33203 5.4436C4.33203 7.38697 9.42184 8.96239 15.7004 8.96239Z" fill="#E08095"/>
<path opacity="0.2" d="M12.9937 9.68244V23.9417C12.9949 24.0144 12.9876 24.0871 12.972 24.1582C12.9227 24.451 12.7711 24.7168 12.544 24.9082C12.3169 25.0996 12.0293 25.2041 11.7323 25.203H4.33203V8.42108H11.7323C11.898 8.4208 12.0622 8.45323 12.2154 8.51652C12.3685 8.5798 12.5077 8.6727 12.6249 8.78989C12.7421 8.90707 12.835 9.04624 12.8982 9.1994C12.9615 9.35256 12.994 9.51671 12.9937 9.68244Z" fill="black"/>
<path opacity="0.1" d="M13.535 9.6824V22.8589C13.5305 23.3356 13.3391 23.7915 13.002 24.1286C12.6649 24.4657 12.209 24.6571 11.7323 24.6616H4.33203V7.8797H11.7323C12.2099 7.88141 12.6674 8.07189 13.0051 8.40959C13.3428 8.74729 13.5333 9.20482 13.535 9.6824Z" fill="black"/>
<path opacity="0.2" d="M12.9937 9.68244V22.859C12.994 23.0247 12.9615 23.1888 12.8982 23.342C12.835 23.4952 12.7421 23.6343 12.6249 23.7515C12.5077 23.8687 12.3685 23.9616 12.2154 24.0249C12.0622 24.0882 11.898 24.1206 11.7323 24.1203H4.33203V8.42108H11.7323C11.898 8.4208 12.0622 8.45323 12.2154 8.51652C12.3685 8.5798 12.5077 8.6727 12.6249 8.78989C12.7421 8.90707 12.835 9.04624 12.8982 9.1994C12.9615 9.35256 12.994 9.51671 12.9937 9.68244Z" fill="black"/>
<path opacity="0.1" d="M12.4523 9.68244V22.859C12.4526 23.0247 12.4202 23.1888 12.3569 23.342C12.2936 23.4952 12.2007 23.6343 12.0835 23.7515C11.9663 23.8687 11.8272 23.9616 11.674 24.0249C11.5208 24.0882 11.3567 24.1206 11.191 24.1203H4.33203V8.42108H11.191C11.3567 8.4208 11.5208 8.45323 11.674 8.51652C11.8272 8.5798 11.9663 8.6727 12.0835 8.78989C12.2007 8.90707 12.2936 9.04624 12.3569 9.1994C12.4202 9.35256 12.4526 9.51671 12.4523 9.68244Z" fill="black"/>
<path d="M11.1893 8.42108H-1.44259C-2.14011 8.42108 -2.70557 8.98654 -2.70557 9.68406V22.316C-2.70557 23.0135 -2.14011 23.579 -1.44259 23.579H11.1893C11.8869 23.579 12.4523 23.0135 12.4523 22.316V9.68406C12.4523 8.98654 11.8869 8.42108 11.1893 8.42108Z" fill="#AF2031"/>
<path d="M3.96598 11.8944H5.82661L8.81541 20.1051H7.05764L6.39936 18.147H3.32447L2.67756 20.1051H0.931152L3.96598 11.8944ZM6.00417 16.8819L5.04814 13.9099C4.96944 13.6923 4.91002 13.4682 4.87058 13.2402H4.83593C4.80028 13.4742 4.74282 13.7044 4.66432 13.9277L3.69692 16.8819H6.00417Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_881_27919">
<rect width="26.1376" height="32" fill="white" transform="translate(0.931152)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

251
packages/nc-gui/assets/nc-icons/microsoft_excel.svg

@ -0,0 +1,251 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<g clip-path="url(#clip0_877_12918)">
<path d="M9.32544 1.3506H30.6561C31.0148 1.34873 31.3567 1.49623 31.6011 1.75623C31.8511 2.02373 31.9904 2.37623 31.9904 2.74248V29.1887C31.9898 29.555 31.8504 29.9069 31.6011 30.175C31.3567 30.4356 31.0136 30.5825 30.6561 30.5806H9.32544C8.57919 30.5675 7.98606 29.9512 7.99919 29.205V29.1962V2.74373C7.98106 1.9956 8.56919 1.3731 9.31669 1.3506H9.32544Z" fill="#21A366"/>
<path d="M7.99998 15.9669V29.2306C7.9631 29.9769 8.53623 30.6106 9.28248 30.6475L9.29372 30.6481H30.6587C31.41 30.635 32.0081 30.015 31.9931 29.2644V29.2556V15.9912L7.99998 15.9669Z" fill="#185C37"/>
<path d="M8 8.66248H31.9925V15.975H8V8.66248Z" fill="#107C41"/>
<path d="M30.6581 1.35059H19.9968V23.2787H31.9931V2.74372C31.9925 2.37747 31.8531 2.02559 31.6037 1.75747C31.3575 1.49622 31.015 1.34934 30.6581 1.35059Z" fill="#33C481"/>
<path d="M32.0005 8.66248H19.9961V15.9743H32.0005V8.66248Z" fill="#21A366"/>
<path d="M32.0005 15.9669H19.9961V23.2787H32.0005V15.9669Z" fill="#107C41"/>
<path d="M17.483 15.975H17.1017V15.9769H17.483V15.975ZM16.488 7.32062H15.8074C15.8711 7.42062 15.918 7.53249 15.943 7.65187H16.098V7.65124H16.1061C16.1149 7.65124 16.123 7.65187 16.1317 7.65187C16.6774 7.66999 17.1067 8.12062 17.1011 8.66249H17.4817V8.36437C17.498 7.80187 17.053 7.33437 16.488 7.32062Z" fill="#209D63"/>
<path d="M17.1017 15.9762V23.9087V23.9175C17.1036 24.0212 17.0892 24.12 17.0624 24.215C17.0624 24.2981 17.0511 24.38 17.0317 24.4568C17.3105 24.2693 17.4911 23.9475 17.483 23.5856V23.585V15.9756L17.1017 15.9762Z" fill="#175935"/>
<path d="M17.4831 8.66248H17.1024C17.1024 8.6706 17.1024 8.67935 17.1018 8.68685V15.9743H17.4831V8.66248Z" fill="#0F783F"/>
<path d="M8.0249 24.4906V26.8437H14.6574C15.3887 26.8394 15.9812 26.2537 15.9999 25.5268C15.9537 25.5487 15.9043 25.5681 15.8543 25.5831C15.6324 26.02 15.1799 26.3187 14.6574 26.3225H8.0249V25.8581V24.4906Z" fill="#175633"/>
<path d="M17.1018 15.975H17.0606V15.9769H17.1018V15.975ZM16.1075 7.65125H16.0993V7.65187H15.9443C15.9612 7.72875 15.9687 7.80937 15.9662 7.89187V8.07437H16.0918C16.4843 8.09 16.8162 8.3275 16.9706 8.6625H17.1037C17.1087 8.12 16.6793 7.67 16.1343 7.65187C16.1243 7.65187 16.1162 7.65125 16.1075 7.65125Z" fill="#1E935C"/>
<path d="M17.0618 15.9763V24.1506L17.0624 24.17C17.063 24.185 17.063 24.2006 17.063 24.2156C17.0899 24.1213 17.1036 24.0219 17.1024 23.9181V23.9094V15.9775L17.0618 15.9763Z" fill="#165332"/>
<path d="M17.1031 8.66248H16.97C17.0337 8.80123 17.0675 8.95623 17.0618 9.1181V15.9743H17.1031V8.68748C17.1031 8.67935 17.1031 8.67123 17.1031 8.66248Z" fill="#0E703B"/>
<path d="M17.0324 24.4581C16.9936 24.4838 16.9542 24.5075 16.9124 24.5275C16.8211 24.6556 16.7005 24.7619 16.5605 24.8363C16.5336 24.9488 16.4886 25.0538 16.428 25.1494C16.7267 25.0288 16.9536 24.7706 17.0324 24.4581Z" fill="#175633"/>
<path d="M17.063 24.215C17.0305 24.3281 16.9799 24.4331 16.9124 24.5268C16.9542 24.5068 16.9936 24.4831 17.0324 24.4575C17.0517 24.38 17.0624 24.2993 17.063 24.215Z" fill="#165332"/>
<path d="M16.9123 24.5275C16.8123 24.5768 16.7036 24.61 16.5879 24.6237C16.5861 24.6962 16.5767 24.7675 16.5598 24.8356C16.7011 24.7612 16.8211 24.655 16.9123 24.5275Z" fill="#155030"/>
<path d="M17.0618 15.975H16.92V15.9756H17.0618V15.975ZM16.0912 8.07562H15.9656V8.13062C15.9656 8.14812 15.9662 8.16562 15.9656 8.18312V8.39625C15.9656 8.41375 15.9662 8.43125 15.9656 8.44875V8.60812C16.0637 8.61312 16.1575 8.6325 16.2462 8.66437H16.9687C16.815 8.32875 16.4837 8.09 16.0912 8.07562Z" fill="#1C8A56"/>
<path d="M16.9192 15.9762V23.835V23.8356C16.9261 24.1425 16.798 24.4206 16.5886 24.6125C16.5886 24.6162 16.5886 24.6193 16.5886 24.6231C16.7036 24.6093 16.813 24.5756 16.913 24.5268C16.9799 24.4331 17.0311 24.3281 17.0636 24.215C17.0636 24.2 17.0636 24.185 17.063 24.1693L17.0624 24.15V15.9756L16.9192 15.9762Z" fill="#154E2E"/>
<path d="M16.9693 8.66248H16.2468C16.6368 8.80123 16.9168 9.17185 16.9193 9.60748V15.975H17.0612V9.11872C17.0668 8.95622 17.0337 8.80123 16.9693 8.66248Z" fill="#0D6937"/>
<path d="M14.9725 6.84808H14.9637H8V7.79246V7.13871H14.9637H14.9725C15.18 7.14371 15.3712 7.21058 15.53 7.32121H15.8081C15.6319 7.04371 15.325 6.85683 14.9725 6.84808Z" fill="#21A265"/>
<path d="M15.8079 7.32062H15.5298C15.6542 7.40749 15.7579 7.52062 15.8329 7.65187H15.9442C15.9179 7.53249 15.8717 7.42062 15.8079 7.32062Z" fill="#209C62"/>
<path d="M15.9438 7.65186H15.8325C15.905 7.77873 15.9506 7.92186 15.9631 8.07436H15.9656V7.89186C15.9681 7.80998 15.9606 7.72936 15.9438 7.65186Z" fill="#1E925B"/>
<path d="M15.9669 8.07562H15.9644C15.9662 8.09375 15.9669 8.1125 15.9669 8.13062V8.07562Z" fill="#1C8855"/>
<path d="M15.8535 25.5825C15.771 25.6068 15.6835 25.6212 15.5929 25.6243H15.406C15.1923 25.77 14.9342 25.855 14.6567 25.8568H8.02417V26.3212H14.6567C15.1798 26.3181 15.6317 26.0187 15.8535 25.5825Z" fill="#144B2D"/>
<path d="M8.0249 24.4906V25.8568H14.6574C14.9355 25.855 15.193 25.77 15.4068 25.6243H8.6224H8.62178C8.4999 25.6206 8.38865 25.5744 8.30365 25.5H8.02678V25.185V24.4906H8.0249Z" fill="#114227"/>
<path d="M8.12928 25.1856H8.0249V25.5006H8.30178C8.2099 25.4212 8.14803 25.31 8.12928 25.1856Z" fill="#0F3A23"/>
<path d="M8.0249 24.4906V25.1856H8.12928C8.1249 25.1556 8.12303 25.125 8.12428 25.0944V24.4956C8.11803 24.4956 8.11303 24.4956 8.10678 24.4956C8.07928 24.4956 8.0524 24.4937 8.0249 24.4906Z" fill="#0D321E"/>
<path d="M16.9193 15.975H16.5887V15.9756H16.9193V15.975ZM15.9668 8.60687V8.65062C15.9718 8.655 15.978 8.65875 15.9837 8.66312H16.2474C16.1587 8.63125 16.0637 8.61187 15.9668 8.60687Z" fill="#1A8050"/>
<path d="M16.5894 15.9756V24.5875C16.5894 24.5956 16.5894 24.6043 16.5894 24.6118C16.7987 24.42 16.9275 24.1418 16.92 23.835V23.8343V15.9756H16.5894Z" fill="#13492B"/>
<path d="M16.2467 8.66248H15.9829C16.1679 8.80435 16.2992 8.96873 16.2992 9.15935V9.61435C16.4848 9.80435 16.5967 10.0656 16.5892 10.3525V15.9731H16.9198V9.60685C16.9167 9.17185 16.6373 8.80123 16.2467 8.66248Z" fill="#0D6233"/>
<path d="M8.01562 23.8293V24.4893C8.01812 24.4893 8.02187 24.49 8.02437 24.49V23.8362C8.02187 23.8343 8.01875 23.8318 8.01562 23.8293Z" fill="#155030"/>
<path d="M8.03303 24.4393H8.0249V24.49C8.05178 24.4937 8.07928 24.495 8.1074 24.495C8.11365 24.495 8.11865 24.495 8.1249 24.495V24.4887H8.03365V24.4387" fill="#0C2E1C"/>
<path d="M8.12421 24.4393H8.03296V24.4893H8.12421V24.4393Z" fill="#0B2919"/>
<path d="M8.0249 23.8375V24.4406H8.03303V23.8443C8.03053 23.8406 8.02803 23.8387 8.0249 23.8375Z" fill="#0B2919"/>
<path d="M8.03296 23.8431V24.4393H8.12421V23.8993C8.09171 23.8843 8.06108 23.865 8.03296 23.8431Z" fill="#0A2617"/>
<path d="M7.99857 7.7912C7.89482 7.88495 7.83107 8.02182 7.83357 8.17245V8.1812V23.4356C7.83357 23.5843 7.89669 23.7193 7.99857 23.8137V23.4887C7.93294 23.3999 7.89482 23.2899 7.89919 23.1706V22.9631C7.89919 22.9524 7.89919 22.9412 7.89919 22.9306V22.6481C7.89919 22.6431 7.89919 22.6374 7.89919 22.6312V20.4918H7.99857V7.7912Z" fill="#CBCBCB"/>
<path d="M7.99854 23.4894V23.8144L7.99916 23.815V23.4906C7.99978 23.49 7.99979 23.49 7.99854 23.4894Z" fill="#1A8251"/>
<path d="M8 23.4906V23.815C8.005 23.8194 8.01063 23.8244 8.01562 23.8287V23.5106C8.01 23.5037 8.005 23.4981 8 23.4906Z" fill="#13492C"/>
<path d="M7.99855 20.4931H7.89917V22.6325C7.89917 22.6375 7.89917 22.6431 7.89917 22.6494V20.7925H7.99855V20.4931Z" fill="#CACACA"/>
<path d="M15.9668 8.65057V8.66307H15.9837C15.9768 8.65807 15.9718 8.65495 15.9668 8.65057Z" fill="#156A42"/>
<path d="M15.9825 8.66248H15.9656V8.96998C16.0781 9.11998 16.1487 9.30247 16.1631 9.49872C16.2112 9.53372 16.2568 9.57248 16.2981 9.6156V9.1606C16.2987 8.96935 16.1675 8.80498 15.9825 8.66248Z" fill="#0A512A"/>
<path d="M15.9668 8.97058V9.13371C15.9868 9.22058 15.998 9.31058 15.9999 9.40308C16.058 9.42996 16.113 9.46183 16.1637 9.49871C16.1487 9.30246 16.078 9.12058 15.9668 8.97058Z" fill="#094926"/>
<path d="M15.9668 9.13434C15.9868 9.22121 15.998 9.31121 15.9999 9.40371C15.9974 9.31059 15.9855 9.22121 15.9668 9.13434Z" fill="#083F21"/>
<path d="M15.9668 9.13434V9.38871C15.978 9.39309 15.9887 9.39809 15.9999 9.40371C15.9974 9.31184 15.9855 9.22121 15.9668 9.13434Z" fill="#07381E"/>
<path d="M8.01562 23.5112V23.8294C8.01812 23.8319 8.02187 23.8344 8.02437 23.8362V23.5206C8.02187 23.5181 8.01875 23.5144 8.01562 23.5112Z" fill="#114127"/>
<path d="M8.0249 23.5219V23.8375C8.0274 23.8393 8.0299 23.8419 8.03303 23.8437V23.5306C8.03053 23.5275 8.02803 23.5244 8.0249 23.5219Z" fill="#0A2617"/>
<path d="M8.03296 23.5306V23.8437C8.06108 23.8656 8.09171 23.885 8.12421 23.9006V23.6093C8.09108 23.5862 8.05983 23.56 8.03296 23.5306Z" fill="#092415"/>
<path d="M16.4287 25.1481C16.3244 25.19 16.2106 25.2156 16.0912 25.22H16V25.4925C16 25.5038 16 25.515 16 25.5269C16.1762 25.4431 16.3244 25.3113 16.4287 25.1481Z" fill="#175633"/>
<path d="M16 25.22H15.9775C15.9538 25.3481 15.9119 25.47 15.8538 25.5831C15.9038 25.5681 15.9531 25.55 15.9994 25.5268C15.9994 25.5156 15.9994 25.5043 15.9994 25.4925V25.22" fill="#155030"/>
<path d="M16.5613 24.8356C16.4238 24.9094 16.2669 24.9513 16.1 24.9538H16V24.9781V25.2194H16.0912C16.2106 25.215 16.3237 25.1894 16.4287 25.1475C16.4894 25.0531 16.5338 24.9481 16.5613 24.8356Z" fill="#155030"/>
<path d="M15.9998 24.9788C15.9992 25.0613 15.991 25.1419 15.9773 25.22H15.9998V24.9788Z" fill="#144B2D"/>
<path d="M16.5881 24.6225C16.5838 24.6231 16.5794 24.6231 16.575 24.6244C16.4188 24.7619 16.22 24.8531 16 24.875V24.9537H16.0994C16.2662 24.9512 16.4231 24.9081 16.5606 24.8356C16.5769 24.7675 16.5863 24.6962 16.5881 24.6225Z" fill="#144B2D"/>
<path d="M16.5892 24.6125C16.5848 24.6169 16.5804 24.6206 16.5754 24.625C16.5798 24.6244 16.5842 24.6244 16.5886 24.6231C16.5892 24.6194 16.5892 24.6156 16.5892 24.6125Z" fill="#13492B"/>
<path d="M15.9767 25.22H15.7961C15.6961 25.3806 15.563 25.5181 15.4067 25.6243H15.5936C15.6836 25.6218 15.7705 25.6068 15.8542 25.5825C15.9111 25.4693 15.9536 25.3475 15.9767 25.22Z" fill="#12462A"/>
<path d="M15.7962 25.22H15.4675C15.2431 25.3925 14.9625 25.4968 14.6575 25.5006H8.30249C8.38812 25.575 8.49874 25.6206 8.62062 25.625H8.62124H15.4062C15.5631 25.5187 15.6956 25.38 15.7962 25.22Z" fill="#113E25"/>
<path d="M15.9998 24.9543H15.9235C15.8904 25.0481 15.8473 25.1375 15.7954 25.22H15.976C15.991 25.1418 15.9985 25.0612 15.9985 24.9787V24.9543" fill="#114227"/>
<path d="M15.9236 24.9543H15.7299C15.6555 25.0543 15.5668 25.1443 15.468 25.22H15.7968C15.848 25.1362 15.8911 25.0481 15.9236 24.9543Z" fill="#103B23"/>
<path d="M15.9999 24.875C15.9824 24.8769 15.9656 24.8775 15.9481 24.8787C15.9412 24.9044 15.9331 24.9294 15.9243 24.9544H15.9999V24.875Z" fill="#113E25"/>
<path d="M15.948 24.8787C15.9399 24.8787 15.9324 24.8793 15.9242 24.8793H15.7811C15.7642 24.905 15.7467 24.9293 15.7292 24.9537H15.923C15.9324 24.9293 15.9399 24.9043 15.948 24.8787Z" fill="#0F3821"/>
<path d="M8.95551 25.1856H8.12988C8.14801 25.31 8.21113 25.4212 8.30238 25.5006H14.658C14.963 25.4969 15.2436 25.3925 15.468 25.22H9.11926C9.06113 25.2169 9.00613 25.205 8.95551 25.1856Z" fill="#0E3821"/>
<path d="M15.7298 24.9543H15.4167C15.203 25.1006 14.9436 25.1856 14.6655 25.1856C14.663 25.1856 14.6605 25.1856 14.6573 25.1856H8.95483C9.00546 25.2056 9.06108 25.2168 9.11858 25.22H15.4667C15.5667 25.1437 15.6555 25.0543 15.7298 24.9543Z" fill="#0E3520"/>
<path d="M15.7817 24.8793H15.5161C15.4842 24.9056 15.4511 24.93 15.4167 24.9537H15.7299C15.748 24.9293 15.7655 24.905 15.7817 24.8793Z" fill="#0D331E"/>
<path d="M8.46991 24.4894H8.19928C8.17491 24.4931 8.14928 24.4944 8.12491 24.4956V25.0944C8.12428 25.125 8.12553 25.1556 8.12991 25.1856H8.95553C8.77428 25.1156 8.64241 24.9469 8.62428 24.7475C8.54928 24.6788 8.49491 24.5906 8.46991 24.4894Z" fill="#0D301D"/>
<path d="M8.62354 24.7462C8.64166 24.9462 8.77354 25.1143 8.95479 25.1843H14.6573C14.6598 25.1843 14.6623 25.1843 14.6654 25.1843C14.9442 25.1843 15.2029 25.0993 15.4167 24.9531H9.13479H9.12604C9.02791 24.9493 8.93666 24.9187 8.86104 24.8675C8.77104 24.8487 8.68979 24.8062 8.62354 24.7462Z" fill="#0C2E1C"/>
<path d="M8.8606 24.8681C8.93685 24.9187 9.02747 24.9506 9.1256 24.9537H9.13435H15.4162C15.4506 24.93 15.4837 24.9056 15.5156 24.8794H8.95997C8.92622 24.8787 8.8931 24.875 8.8606 24.8681Z" fill="#0C2D1B"/>
<path d="M16.575 24.6243C16.5463 24.6281 16.5181 24.6293 16.4881 24.6293H16V24.8737C16.22 24.8531 16.4188 24.7631 16.575 24.6243Z" fill="#13462A"/>
<path d="M16.5892 15.975H16.2986V15.9756H16.5892V15.975Z" fill="#18794C"/>
<path d="M16.2987 15.9756V22.9137C16.3012 23.1031 16.2531 23.2812 16.1656 23.4343V23.4437V23.4443C16.1706 23.6593 16.1094 23.86 16 24.0262V24.1393V24.6287H16.4881C16.5169 24.6281 16.5463 24.6262 16.575 24.6237C16.5794 24.6193 16.5838 24.6156 16.5888 24.6112C16.5888 24.6031 16.5888 24.5943 16.5888 24.5868V15.9756H16.2987Z" fill="#124429"/>
<path d="M16.2986 9.6156V15.975H16.5886V10.3544C16.5961 10.0675 16.4848 9.80623 16.2986 9.6156Z" fill="#0C5C30"/>
<path d="M16 24.6306H15.995C15.9869 24.7156 15.9712 24.7987 15.9487 24.8787C15.9662 24.8781 15.9837 24.8769 16.0006 24.875V24.6306" fill="#103B23"/>
<path d="M15.9999 24.5137C15.9999 24.5531 15.998 24.5919 15.9949 24.6306H15.9999V24.5137Z" fill="#103A23"/>
<path d="M15.9943 24.6306H15.9093C15.875 24.7181 15.8331 24.8019 15.7825 24.8794H15.9256C15.9337 24.8794 15.9412 24.8787 15.9493 24.8787C15.9712 24.7987 15.9868 24.7156 15.9943 24.6306Z" fill="#0E3520"/>
<path d="M15.9999 24.1406C15.9999 24.3131 15.9674 24.4781 15.9087 24.63H15.9937C15.9974 24.5912 15.9987 24.5531 15.9987 24.5131V24.1406" fill="#0E341F"/>
<path d="M15.9094 24.6306H15.7487C15.6819 24.7237 15.6031 24.8069 15.5156 24.8794H15.7812C15.8325 24.8019 15.8756 24.7181 15.9094 24.6306Z" fill="#0D311D"/>
<path d="M15.9999 24.0281C15.9955 24.035 15.9905 24.0431 15.9849 24.0499C15.9524 24.2649 15.8699 24.4618 15.7493 24.6306H15.9099C15.9686 24.4787 16.0011 24.3131 16.0011 24.1412V24.0281" fill="#0D301D"/>
<path d="M8.62111 24.4894H8.46924C8.49361 24.59 8.54924 24.6788 8.62361 24.7469C8.62174 24.7313 8.62111 24.7144 8.62111 24.6975V24.4894Z" fill="#0C2E1C"/>
<path d="M8.63859 24.4894H8.62109V24.6981C8.62109 24.715 8.62172 24.7306 8.62359 24.7475C8.68922 24.8069 8.77109 24.85 8.86109 24.8694C8.73609 24.7844 8.65172 24.6463 8.63859 24.4894Z" fill="#0C2D1B"/>
<path d="M9.17805 24.4894H8.63867C8.65117 24.6463 8.73617 24.785 8.86055 24.8681C8.89305 24.875 8.92555 24.8788 8.95992 24.8794H15.5149C15.6024 24.8069 15.6818 24.7231 15.748 24.6306H9.51555H9.51492C9.3843 24.6263 9.2668 24.5725 9.17805 24.4894Z" fill="#0B2B1A"/>
<path d="M15.9849 24.05C15.8061 24.3087 15.5099 24.4812 15.1705 24.4894H14.6649H9.17798C9.26548 24.5737 9.38423 24.6262 9.51548 24.6306H9.5161H15.7486C15.8699 24.4625 15.9536 24.2644 15.9849 24.05Z" fill="#0B2B1A"/>
<path d="M8.19864 24.4894H8.12427V24.4956C8.14864 24.495 8.17302 24.4931 8.19864 24.4894Z" fill="#0C2D1B"/>
<path d="M8.45989 24.4393H8.12427V24.4893H8.19864H8.46927C8.46614 24.4731 8.46239 24.4562 8.45989 24.4393Z" fill="#0A2818"/>
<path d="M8.12427 23.9006V24.4406H8.45989C8.45614 24.4138 8.45364 24.3863 8.45489 24.3581V23.9525H8.33114C8.25739 23.9488 8.18739 23.93 8.12427 23.9006Z" fill="#0A2516"/>
<path d="M16.1656 23.435C16.12 23.515 16.0637 23.5875 16 23.6525V24.0275C16.1094 23.8606 16.1712 23.6594 16.1656 23.4456V23.445V23.435Z" fill="#103D24"/>
<path d="M15.9999 23.8594C15.9999 23.9244 15.9949 23.9881 15.9849 24.0506C15.9899 24.0437 15.9942 24.0356 15.9999 24.0287V23.8594Z" fill="#0C2C1B"/>
<path d="M15.9999 23.6525C15.9505 23.7019 15.8974 23.7456 15.8393 23.7831C15.6137 24.2006 15.173 24.4856 14.6655 24.4887H15.1712C15.5105 24.4806 15.8068 24.3081 15.9855 24.0494C15.9949 23.9869 15.9993 23.9231 16.0005 23.8581V23.6519" fill="#0A2818"/>
<path d="M8.62121 24.4393H8.45996C8.46246 24.4562 8.46496 24.4725 8.46934 24.4893H8.62121V24.4393Z" fill="#0A2718"/>
<path d="M8.63672 24.4393H8.62109V24.4893H8.63859C8.63797 24.4731 8.63672 24.4562 8.63672 24.4393Z" fill="#0A2617"/>
<path d="M9.13234 24.4393H8.63672C8.63672 24.4562 8.63672 24.4725 8.63859 24.4893H9.17797C9.16234 24.4737 9.14609 24.4568 9.13234 24.4393Z" fill="#0A2616"/>
<path d="M15.8392 23.7838C15.8242 23.7931 15.8092 23.8031 15.7936 23.8119C15.5561 24.1888 15.1361 24.4394 14.6573 24.4394H9.13232C9.14607 24.4569 9.16232 24.4738 9.17795 24.4894H14.6648C15.1729 24.4863 15.6142 24.2013 15.8392 23.7838Z" fill="#0A2516"/>
<path d="M8.62122 23.9512H8.45435V24.3569C8.45435 24.385 8.45622 24.4119 8.45935 24.4394H8.62059V23.9512" fill="#0A2416"/>
<path d="M8.63672 23.9512H8.62109V24.4394H8.63672C8.63672 24.4369 8.63672 24.4344 8.63672 24.4312V23.9512Z" fill="#092415"/>
<path d="M9.0187 23.9512H8.63745V24.4319C8.63745 24.4344 8.63745 24.4369 8.63745 24.44H9.13308C9.0587 24.3469 9.01558 24.2287 9.01933 24.1006L9.0187 23.9512Z" fill="#092315"/>
<path d="M15.7936 23.8118C15.6499 23.8962 15.4824 23.9468 15.3036 23.9512H9.01861V24.1C9.01424 24.2281 9.05799 24.3475 9.13236 24.4393H14.6574C15.1361 24.4393 15.5561 24.1887 15.7936 23.8118Z" fill="#092315"/>
<path d="M16.2987 15.975H16.1655V15.9756H16.2987V15.975Z" fill="#14653F"/>
<path d="M16.1655 15.9756V23.4356C16.253 23.2825 16.3012 23.1043 16.2987 22.915V15.9762L16.1655 15.9756Z" fill="#0F3922"/>
<path d="M16.1636 9.49933C16.1661 9.53558 16.1673 9.5712 16.1654 9.60808V15.975H16.2986V9.61558C16.2573 9.57308 16.2117 9.53433 16.1636 9.49933Z" fill="#0A4D28"/>
<path d="M8.12427 23.6081V23.8993C8.18739 23.93 8.25739 23.9475 8.33177 23.95H8.45552V23.6818L8.39739 23.6931H8.38864C8.28989 23.6906 8.19927 23.6593 8.12427 23.6081Z" fill="#092315"/>
<path d="M16.1656 15.975H16V15.9756H16.1656V15.975Z" fill="#135C39"/>
<path d="M16 15.9756V23.6531C16.065 23.5881 16.12 23.5156 16.1656 23.4356V15.9756H16Z" fill="#0E341F"/>
<path d="M16 9.40375C16 9.41437 16 9.425 16 9.43437V9.74937V10.0475V15.975H16.1656V9.60812C16.1662 9.57125 16.1662 9.53562 16.1637 9.49937C16.1119 9.4625 16.0575 9.43062 16 9.40375Z" fill="#094624"/>
<path d="M15.9668 9.38873V9.47186C15.9868 9.56123 15.998 9.65436 15.9999 9.74873V9.43373C15.9999 9.42311 15.9999 9.41248 15.9999 9.40311C15.9887 9.39811 15.9768 9.39311 15.9668 9.38873Z" fill="#07361D"/>
<path d="M15.9668 9.47186V9.77874C15.9868 9.86561 15.998 9.95499 15.9999 10.0469V9.74874C15.998 9.65436 15.9868 9.56124 15.9668 9.47186Z" fill="#07331B"/>
<path d="M16 23.1462C16 23.3769 15.9419 23.5931 15.8394 23.7837C15.8975 23.745 15.9506 23.7012 16 23.6531V23.1462Z" fill="#0A2516"/>
<path d="M16 23.0975C16 23.3606 15.9243 23.605 15.7937 23.8131C15.8093 23.8043 15.8243 23.795 15.8393 23.785C15.9418 23.5956 16 23.3781 16 23.1475V23.0975Z" fill="#092315"/>
<path d="M15.9668 9.77875V10.1437C15.9868 10.2331 15.998 10.3262 15.9999 10.4206V10.885V15.9744V10.0469C15.998 9.955 15.9855 9.86562 15.9668 9.77875Z" fill="#063119"/>
<path d="M15.9668 10.1437V10.6081C15.9868 10.6975 15.998 10.7906 15.9999 10.885V10.4206C15.998 10.325 15.9868 10.2331 15.9668 10.1437Z" fill="#062F19"/>
<path d="M8.62108 23.6506L8.45483 23.6831V23.9513H8.62108V23.6506Z" fill="#092315"/>
<path d="M8.63672 23.6481L8.62109 23.6506V23.9513H8.63672V23.6481Z" fill="#092214"/>
<path d="M15.9668 10.6081V11.1137C15.988 11.2056 15.9993 11.3006 15.9999 11.3987V15.9743V10.885C15.998 10.79 15.9868 10.6975 15.9668 10.6081Z" fill="#062E18"/>
<path d="M9.01859 23.575L8.63672 23.6481V23.9512H9.01859V23.575Z" fill="#092214"/>
<path d="M15.9999 15.975H15.9668L15.9999 15.9756V15.975Z" fill="#0C3B25"/>
<path d="M15.9668 15.975V21.5625C15.9668 22.1344 14.4912 22.6319 13.9362 22.6319L9.01929 23.5744V23.9506H15.3043C15.483 23.9463 15.6499 23.8963 15.7943 23.8113C15.9249 23.6044 16.0005 23.3588 16.0005 23.0956V15.9738L15.9668 15.975Z" fill="#092214"/>
<path d="M15.9668 11.1144V15.975H15.9999V11.3994C15.9987 11.3006 15.9874 11.205 15.9668 11.1144Z" fill="#062D18"/>
<path d="M14.9725 7.13745H14.9637H8V7.7912V7.40183H9.25375C9.33 7.35245 9.42063 7.32183 9.51688 7.31933H15.5306C15.3713 7.20995 15.18 7.14245 14.9725 7.13745Z" fill="#1F975F"/>
<path d="M15.5298 7.32062H9.51605C9.41855 7.32437 9.32855 7.35374 9.25293 7.40312H14.9629H14.9717C15.2179 7.40937 15.4423 7.50249 15.6154 7.65187H15.8329C15.7579 7.52062 15.6542 7.40749 15.5298 7.32062Z" fill="#1E925B"/>
<path d="M15.833 7.65186H15.6155C15.7455 7.76498 15.8467 7.91061 15.9067 8.07436H15.9636C15.9517 7.92186 15.9049 7.77873 15.833 7.65186Z" fill="#1C8855"/>
<path d="M15.9631 8.07562H15.9062C15.9425 8.17625 15.9631 8.28312 15.9669 8.39562V8.13062C15.9656 8.11125 15.965 8.09312 15.9631 8.07562Z" fill="#1A8050"/>
<path d="M7.99865 20.7925H7.8999V22.6481V21.0406H7.99865V20.7925Z" fill="#BEBEBE"/>
<path d="M7.89992 22.9637V23.1712C7.89617 23.2906 7.93304 23.4012 7.99929 23.4894V23.25C7.93929 23.1694 7.90304 23.07 7.89992 22.9637Z" fill="#B9B9B9"/>
<path d="M7.99854 23.2487V23.4887L7.99916 23.4893V23.25C7.99978 23.25 7.99979 23.25 7.99854 23.2487Z" fill="#18774A"/>
<path d="M8 23.2506V23.4906C8.005 23.4975 8.01063 23.5044 8.01562 23.5106V23.2706C8.01 23.2637 8.005 23.2575 8 23.2506Z" fill="#114328"/>
<path d="M8.01562 23.2706V23.5106C8.01812 23.5144 8.02187 23.5175 8.02437 23.5213V23.2813C8.02187 23.2781 8.01875 23.2744 8.01562 23.2706Z" fill="#103C24"/>
<path d="M8.0249 23.2812V23.5212C8.0274 23.5237 8.0299 23.5275 8.03303 23.53V23.29C8.03053 23.2869 8.02803 23.2837 8.0249 23.2812Z" fill="#0A2516"/>
<path d="M8.03296 23.29V23.53C8.05983 23.56 8.09108 23.5862 8.12421 23.6081V23.3681C8.09108 23.3462 8.05983 23.32 8.03296 23.29Z" fill="#092315"/>
<path d="M8.12427 23.3681V23.6081C8.19989 23.6587 8.29114 23.6894 8.38802 23.6931H8.39677L8.45489 23.6819V23.4419L8.39677 23.4531H8.38802C8.28989 23.45 8.19927 23.4187 8.12427 23.3681Z" fill="#092214"/>
<path d="M8.62108 23.41L8.45483 23.4425V23.6831L8.62108 23.6506V23.41Z" fill="#092214"/>
<path d="M8.63672 23.4069L8.62109 23.41V23.6506L8.63672 23.6481V23.4069Z" fill="#092214"/>
<path d="M9.01859 23.3331L8.63672 23.4069V23.6481L9.01859 23.575V23.3331Z" fill="#092114"/>
<path d="M15.9668 15.975V21.3144C15.9668 21.8862 14.4912 22.3837 13.9362 22.3837L9.01929 23.3331V23.5744L13.9362 22.6318C14.4912 22.6318 15.9668 22.135 15.9668 21.5625V15.975Z" fill="#092114"/>
<path d="M9.25375 7.40308H8V7.79245C8.0875 7.71245 8.20438 7.66245 8.3325 7.6612H9.04937C9.0875 7.5537 9.15937 7.46308 9.25375 7.40308Z" fill="#1C8855"/>
<path d="M14.9724 7.40308H14.9637H9.25369C9.15994 7.4637 9.08807 7.5537 9.04932 7.65995H9.05994C9.08432 7.65558 9.10994 7.65308 9.13557 7.65183H15.6156C15.4424 7.50183 15.2187 7.4087 14.9724 7.40308Z" fill="#1B8453"/>
<path d="M15.615 7.65186H9.13495C9.10933 7.65248 9.08433 7.65561 9.05933 7.65998H13.4056C13.8912 7.66248 14.3706 7.78561 14.7987 8.01686C14.8125 8.03498 14.8381 8.05436 14.8712 8.07561H15.9056C15.8462 7.91061 15.7456 7.76498 15.615 7.65186Z" fill="#197C4E"/>
<path d="M15.9062 8.07562H14.8718C14.8981 8.09125 14.9287 8.10875 14.965 8.12625C15.0518 8.14625 15.135 8.17562 15.215 8.21125C15.3025 8.25187 15.385 8.30062 15.4612 8.3575C15.6118 8.43 15.7681 8.5125 15.9031 8.605H15.9256C15.9393 8.605 15.9525 8.60562 15.9668 8.60687V8.39562C15.9631 8.28312 15.9431 8.17562 15.9062 8.07562Z" fill="#187549"/>
<path d="M15.2144 8.21124C15.3019 8.25187 15.3844 8.30062 15.4606 8.35749C15.4606 8.35749 15.4606 8.35749 15.4612 8.35749C15.3844 8.30062 15.3019 8.25187 15.2144 8.21124Z" fill="#14623D"/>
<path d="M14.9644 8.12622C15.0912 8.19122 15.2719 8.26747 15.4606 8.35747C15.3844 8.3006 15.3019 8.25122 15.2144 8.21122C15.1344 8.1756 15.0512 8.14622 14.9644 8.12622Z" fill="#115535"/>
<path d="M15.9256 8.60498H15.9031C15.925 8.61998 15.9462 8.63498 15.9662 8.65061V8.60686C15.9525 8.60561 15.9393 8.60561 15.9256 8.60498Z" fill="#166F45"/>
<path d="M7.8999 22.6481V22.9638C7.90365 23.07 7.93928 23.1688 7.99928 23.2488V22.9413C7.93803 22.8594 7.90178 22.7581 7.8999 22.6481Z" fill="#AFAFAF"/>
<path d="M7.99854 22.9412V23.2487L7.99978 23.25V22.9425C7.99978 22.9418 7.99979 22.9418 7.99854 22.9412Z" fill="#177046"/>
<path d="M8 22.9431V23.2506C8.005 23.2575 8.01063 23.2644 8.01562 23.2706V22.9631C8.01 22.9562 8.005 22.95 8 22.9431Z" fill="#103F26"/>
<path d="M7.99855 21.0406H7.89917V22.6487C7.90104 22.7581 7.93792 22.8593 7.99855 22.9418V21.0406Z" fill="#AEAEAE"/>
<path d="M9.04937 7.65997H8.3325C8.20438 7.66185 8.08812 7.71185 8 7.79122V21.0406V22.9412L8.00062 22.9418V15.9656V8.66185H8.01625V8.60372C8.01875 8.6031 8.0225 8.60185 8.025 8.60122V8.08997H8.64312C8.67437 7.87622 8.8375 7.70435 9.04813 7.66122C9.04813 7.66122 9.04937 7.6606 9.04937 7.65997Z" fill="#176F46"/>
<path d="M8 15.9669V22.9431C8.005 22.95 8.01063 22.9569 8.01562 22.9631V15.975H8V15.9669Z" fill="#103F26"/>
<path d="M8.01562 8.66248H8V15.9669V15.975H8.01562V8.66248Z" fill="#0B552C"/>
<path d="M9.05983 7.65997H9.04921C9.04921 7.6606 9.04858 7.66185 9.04858 7.66185C9.05171 7.66122 9.05608 7.6606 9.05983 7.65997Z" fill="#166D44"/>
<path d="M9.04807 7.66248C8.83682 7.7056 8.67369 7.87748 8.64307 8.09123H9.00682C9.01119 8.0906 9.01494 8.08935 9.01932 8.08873V7.8431C9.01744 7.77873 9.02807 7.71873 9.04807 7.66248Z" fill="#156942"/>
<path d="M13.4049 7.65997H9.05861C9.05486 7.6606 9.05173 7.66185 9.04736 7.66185C9.02736 7.7181 9.01674 7.77935 9.01736 7.84247V8.0881C9.04986 8.07997 9.08299 8.0756 9.11736 8.07497H14.8699C14.8367 8.05372 14.8111 8.03435 14.7974 8.01622C14.3699 7.78497 13.8911 7.66247 13.4049 7.65997Z" fill="#156640"/>
<path d="M9.0186 8.08746C9.01423 8.08809 9.01048 8.08934 9.0061 8.08996H9.0186V8.08746Z" fill="#14643F"/>
<path d="M14.8718 8.07562H9.11929C9.08491 8.07625 9.05179 8.08062 9.01929 8.08875V8.09125H14.6568H14.6655C14.768 8.09187 14.868 8.10437 14.9643 8.12625C14.9287 8.10812 14.8968 8.09125 14.8718 8.07562Z" fill="#14623D"/>
<path d="M14.6655 8.09125H14.6567C14.763 8.09125 14.8655 8.10375 14.9642 8.12625C14.868 8.10312 14.768 8.09125 14.6655 8.09125Z" fill="#115535"/>
<path d="M15.4604 8.35748C15.5548 8.42873 15.6404 8.51186 15.7136 8.60498H15.9029C15.7673 8.51311 15.6111 8.43061 15.4604 8.35748Z" fill="#14623D"/>
<path d="M8.64303 8.09125H8.0249V8.6025V8.4225H8.03303V8.09125H8.64303Z" fill="#135E3B"/>
<path d="M15.46 8.35748C15.5543 8.42873 15.6406 8.51186 15.7131 8.60498C15.6406 8.51186 15.5556 8.42873 15.46 8.35748C15.4606 8.35748 15.4606 8.35748 15.46 8.35748Z" fill="#115535"/>
<path d="M8.64296 8.09125H8.03296V8.4225H8.63796V8.16625C8.63671 8.14125 8.63983 8.11562 8.64296 8.09125Z" fill="#115233"/>
<path d="M9.00669 8.09125H8.64294C8.63919 8.11562 8.63794 8.14125 8.63794 8.16687V8.42312H8.64919C8.70419 8.26062 8.83856 8.13437 9.00669 8.09125Z" fill="#104F32"/>
<path d="M9.01868 8.09125H9.00618C8.83806 8.13437 8.70368 8.26125 8.64868 8.4225H9.01868V8.09125Z" fill="#104D30"/>
<path d="M14.6568 8.09125H9.01929V8.4225H14.6568C14.8743 8.42312 15.0805 8.47687 15.2618 8.56875C15.3243 8.575 15.3843 8.58687 15.443 8.605H15.7137C15.6412 8.51187 15.5549 8.42875 15.4605 8.3575C15.273 8.2675 15.0912 8.19062 14.9643 8.12625C14.8655 8.10312 14.763 8.09125 14.6568 8.09125Z" fill="#0F4C2F"/>
<path d="M8.03303 8.42249H8.0249V8.60249C8.0274 8.60186 8.0299 8.60061 8.03303 8.59999V8.42249Z" fill="#115233"/>
<path d="M8.63674 8.42249H8.03174V8.59936C8.05611 8.59249 8.08111 8.58874 8.10611 8.58874C8.13674 8.58874 8.16736 8.59374 8.19736 8.60374H8.61986C8.61799 8.55374 8.62424 8.50561 8.63549 8.45936V8.42249" fill="#0F4A2E"/>
<path d="M8.6487 8.42249H8.63745V8.45999C8.64058 8.44811 8.64495 8.43499 8.6487 8.42249Z" fill="#0E472C"/>
<path d="M8.63686 8.45996C8.62436 8.50621 8.61936 8.55434 8.62123 8.60434H8.63686V8.45996Z" fill="#0E472C"/>
<path d="M9.0187 8.42249H8.6487C8.64433 8.43499 8.64058 8.44811 8.63745 8.45999V8.60436H9.0187V8.42249Z" fill="#0E462C"/>
<path d="M14.6568 8.42249H9.01929V8.60499H15.1549L15.1724 8.56374H15.1893C15.2137 8.56436 15.2387 8.56624 15.2618 8.56874C15.0793 8.47686 14.8743 8.42436 14.6568 8.42249Z" fill="#0E442B"/>
<path d="M15.9025 8.60498H15.7131C15.7281 8.62311 15.7419 8.64248 15.7563 8.66311H15.9656V8.65061C15.9456 8.63561 15.9244 8.61998 15.9025 8.60498Z" fill="#135D3A"/>
<path d="M15.9667 8.66248H15.7573C15.8011 8.72498 15.8398 8.79123 15.8729 8.86123C15.9073 8.8956 15.9386 8.93123 15.9667 8.96998V8.66248Z" fill="#094725"/>
<path d="M15.7144 8.60498C15.7294 8.62311 15.7431 8.64248 15.7575 8.66311C15.7431 8.64373 15.7287 8.62436 15.7144 8.60498Z" fill="#105133"/>
<path d="M15.7568 8.66248C15.8006 8.72498 15.8393 8.79123 15.8718 8.8606C15.8393 8.79185 15.8006 8.72498 15.7568 8.66248Z" fill="#083E20"/>
<path d="M15.7142 8.60498H15.4436C15.4955 8.61998 15.5455 8.63998 15.5936 8.66311H15.7574C15.743 8.64373 15.728 8.62436 15.7142 8.60498Z" fill="#0F492E"/>
<path d="M15.756 8.66248H15.5923C15.6967 8.71248 15.7904 8.77998 15.871 8.8606C15.8392 8.79185 15.7998 8.72498 15.756 8.66248Z" fill="#07371D"/>
<path d="M8.01562 22.9631V23.2706C8.01812 23.2744 8.02187 23.2775 8.02437 23.2813V22.9738C8.02187 22.97 8.01875 22.9669 8.01562 22.9631Z" fill="#0F3922"/>
<path d="M8.02486 8.60248C8.02236 8.6031 8.01861 8.60435 8.01611 8.60498V8.6631H8.02486V8.60248Z" fill="#14643E"/>
<path d="M8.02486 15.975H8.01611V22.9631C8.01861 22.9669 8.02236 22.97 8.02486 22.9737V15.975Z" fill="#0F3822"/>
<path d="M8.025 8.66248H8.01562V15.975H8.025V10.0637V9.09435V8.66248Z" fill="#0A4C28"/>
<path d="M15.2617 8.57001C15.2836 8.58126 15.3055 8.59376 15.3273 8.60626H15.443C15.3842 8.58813 15.3236 8.57626 15.2617 8.57001Z" fill="#0E472C"/>
<path d="M8.03303 8.59937C8.03053 8.59999 8.02803 8.60124 8.0249 8.60187V8.66249H8.03303V8.59937Z" fill="#0F4C30"/>
<path d="M8.03303 8.66248H8.0249V9.09435V8.73748H8.03303V8.66248Z" fill="#073A1E"/>
<path d="M8.10733 8.58997C8.08171 8.58997 8.05733 8.59372 8.03296 8.60059V8.66372H8.62233V8.60684V8.60622H8.19983C8.16858 8.59497 8.13796 8.58997 8.10733 8.58997Z" fill="#0E462C"/>
<path d="M8.62111 8.66248H8.03174V8.73685H8.61924C8.61986 8.73685 8.61986 8.73623 8.62111 8.73623V8.66248Z" fill="#07361C"/>
<path d="M8.63672 8.60498H8.62109V8.60561V8.66248H8.63672V8.60498Z" fill="#0E442A"/>
<path d="M8.63672 8.66248H8.62109V8.73623C8.62609 8.73185 8.63172 8.72685 8.63672 8.72248V8.66248Z" fill="#07341B"/>
<path d="M9.0187 8.60498H8.63745V8.66311H8.7262C8.7962 8.62623 8.8762 8.60498 8.9612 8.60498H9.0187Z" fill="#0D4229"/>
<path d="M8.72669 8.66248H8.63794V8.72185C8.66481 8.69935 8.69481 8.67935 8.72669 8.66248Z" fill="#06321A"/>
<path d="M15.1886 8.56433H15.1717L15.1542 8.60558H9.01855H15.3267C15.3048 8.59308 15.2829 8.58121 15.2611 8.56933C15.2367 8.56746 15.2136 8.56558 15.1886 8.56433Z" fill="#0D4229"/>
<path d="M8.03303 8.73749H8.0249V9.09436H8.03303V8.73749Z" fill="#07361C"/>
<path d="M8.03303 9.09436H8.0249V9.55874H8.03303V9.09436Z" fill="#07341B"/>
<path d="M8.0249 22.9738V23.2813C8.0274 23.2838 8.0299 23.2875 8.03303 23.29V22.9825C8.03053 22.98 8.02803 22.9763 8.0249 22.9738Z" fill="#0A2416"/>
<path d="M8.03303 9.55872H8.0249V10.0637H8.03303V9.55872Z" fill="#06311A"/>
<path d="M8.03303 15.975H8.0249V22.9737C8.0274 22.9762 8.0299 22.98 8.03303 22.9825V15.975Z" fill="#0A2416"/>
<path d="M8.03303 10.0637H8.0249V15.975H8.03303V10.0637Z" fill="#06311A"/>
<path d="M8.62046 8.73749H8.03296V9.09436H8.45546C8.45983 8.95311 8.52296 8.82686 8.62046 8.73749Z" fill="#07321A"/>
<path d="M8.45497 9.09436H8.03247V9.55874H8.2006C8.25935 9.46311 8.3481 9.38686 8.45497 9.34499V9.12749V9.11061C8.45497 9.10561 8.45497 9.09936 8.45497 9.09436Z" fill="#063019"/>
<path d="M8.03296 22.9825V23.29C8.05983 23.32 8.09108 23.3462 8.12421 23.3681V23.0606C8.09108 23.0387 8.05983 23.0125 8.03296 22.9825Z" fill="#092214"/>
<path d="M8.2006 9.55872H8.03247V10.0643H8.12372V9.83184C8.12435 9.73122 8.15247 9.63872 8.2006 9.55872Z" fill="#062F19"/>
<path d="M8.12421 15.975H8.03296V22.9825C8.05983 23.0125 8.09108 23.0387 8.12421 23.0612V15.975Z" fill="#092214"/>
<path d="M8.12421 10.0637H8.03296V15.975H8.12421V10.0637Z" fill="#062E18"/>
<path d="M15.8718 8.86188C15.9125 8.9475 15.9443 9.03875 15.9656 9.13438V8.97125C15.9375 8.93188 15.9062 8.89625 15.8718 8.86188Z" fill="#084122"/>
<path d="M15.8718 8.8606C15.9131 8.94622 15.9443 9.03872 15.9662 9.1331C15.9443 9.03872 15.9131 8.94747 15.8718 8.8606C15.8718 8.86185 15.8718 8.86185 15.8718 8.8606Z" fill="#073A1E"/>
<path d="M15.443 8.60498H15.3274C15.358 8.62311 15.3886 8.64248 15.4174 8.66311H15.5918C15.5449 8.63998 15.4949 8.62061 15.443 8.60498Z" fill="#0E462B"/>
<path d="M15.5931 8.66248H15.4187C15.665 8.83373 15.8525 9.08435 15.9418 9.3781C15.9506 9.38185 15.9587 9.38498 15.9662 9.38873V9.13435C15.9443 9.03872 15.9125 8.94747 15.8718 8.86185C15.7912 8.77997 15.6975 8.71248 15.5931 8.66248Z" fill="#07351C"/>
<path d="M8.62126 8.73621C8.62063 8.73621 8.62063 8.73683 8.61938 8.73683H8.62126V8.73621Z" fill="#07341B"/>
<path d="M8.63672 8.72247C8.63172 8.72685 8.62609 8.73122 8.62109 8.73622V8.73685H8.63672V8.72247Z" fill="#06321A"/>
<path d="M9.01871 8.60498H8.96059C8.87559 8.60498 8.79559 8.62623 8.72559 8.66311H9.01871V8.60498Z" fill="#0D4128"/>
<path d="M9.01859 8.66248H8.72547C8.69422 8.67935 8.66422 8.69935 8.63672 8.72185V8.73685H9.01797L9.01859 8.66248Z" fill="#06311A"/>
<path d="M15.3274 8.60498H9.01929V8.66311H15.4187C15.3887 8.64248 15.358 8.62311 15.3274 8.60498Z" fill="#0D4128"/>
<path d="M15.4173 8.66248H9.01855V8.73685H14.6561C15.1179 8.73685 15.5273 8.97123 15.7698 9.32748C15.8286 9.33873 15.8867 9.35622 15.9411 9.37747C15.8517 9.08435 15.6648 8.83373 15.4173 8.66248Z" fill="#06311A"/>
<path d="M8.62122 8.73749H8.61935C8.52185 8.82624 8.45997 8.95311 8.45435 9.09436H8.62122V8.73749Z" fill="#06311A"/>
<path d="M8.62122 9.09436H8.45435C8.45435 9.09936 8.45435 9.10561 8.45435 9.11124V9.12811V9.34561C8.50622 9.32561 8.56247 9.31249 8.62122 9.30936V9.09436Z" fill="#062F19"/>
<path d="M8.63672 8.73749H8.62109V9.09436H8.63672V8.73749Z" fill="#063119"/>
<path d="M8.63672 9.09436H8.62109V9.30999H8.63672V9.09436Z" fill="#062F19"/>
<path d="M9.01859 8.73749H8.63672V9.09436H9.01859V8.73749Z" fill="#062F19"/>
<path d="M14.6568 8.73749H9.01929V9.09436H14.6568C14.923 9.09624 15.1724 9.17499 15.3812 9.30999H15.5943H15.603C15.6599 9.31186 15.7162 9.31811 15.7705 9.32936C15.528 8.97124 15.1187 8.73749 14.6568 8.73749Z" fill="#062F19"/>
<path d="M9.01859 9.09436H8.63672V9.30999H9.01859V9.09436Z" fill="#062E18"/>
<path d="M14.6568 9.09436H9.01929V9.30999H15.3812C15.1712 9.17499 14.923 9.09561 14.6568 9.09436Z" fill="#062E18"/>
<path d="M8.45481 9.34497C8.34856 9.38685 8.25919 9.46247 8.20044 9.55872H8.45481V9.34497Z" fill="#062F19"/>
<path d="M8.12427 23.0606V23.3681C8.19989 23.4187 8.29114 23.4494 8.38802 23.4531H8.39677L8.45489 23.4419V23.135L8.39677 23.1462C8.29614 23.1444 8.20239 23.1131 8.12427 23.0606Z" fill="#092214"/>
<path d="M8.45479 9.55872H8.20041C8.15104 9.63872 8.12354 9.73247 8.12354 9.83247V10.065H8.45416V9.55934" fill="#062E18"/>
<path d="M8.45489 15.975H8.12427V23.0606C8.20239 23.1131 8.29614 23.145 8.39802 23.1462L8.45614 23.135V15.975" fill="#092214"/>
<path d="M8.45489 10.0637H8.12427V15.975H8.45489V10.0637Z" fill="#062E18"/>
<path d="M15.9412 9.37933C15.9505 9.40996 15.9587 9.44058 15.9655 9.47246V9.38933C15.958 9.38496 15.9499 9.38183 15.9412 9.37933Z" fill="#07331B"/>
<path d="M15.77 9.32812C15.8613 9.4625 15.9288 9.615 15.9663 9.77812V9.47125C15.9594 9.43937 15.9506 9.40875 15.9419 9.37813C15.8869 9.35688 15.8288 9.34063 15.77 9.32812Z" fill="#063019"/>
<path d="M8.62122 9.31C8.56247 9.3125 8.50622 9.325 8.45435 9.34625V9.56H8.62122V9.31Z" fill="#062F19"/>
<path d="M8.63672 9.31H8.62109V9.55875H8.63672V9.31Z" fill="#062E18"/>
<path d="M15.6025 9.31H15.5937H15.3806C15.6719 9.49875 15.8862 9.795 15.9662 10.1437V9.77875C15.9287 9.61562 15.8612 9.46312 15.77 9.32875C15.7156 9.3175 15.66 9.31187 15.6025 9.31Z" fill="#062F19"/>
<path d="M9.01859 9.31H8.63672V9.55875H9.01859V9.31Z" fill="#062E18"/>
<path d="M15.3812 9.31H9.01929V9.55875H14.6568C15.2949 9.5625 15.8299 10.0094 15.9668 10.6081V10.1437C15.8868 9.79437 15.6718 9.4975 15.3812 9.31Z" fill="#062D18"/>
<path d="M8.62108 23.1031L8.45483 23.1356V23.4425L8.62108 23.41V23.1031Z" fill="#092214"/>
<path d="M8.63672 23.1006L8.62109 23.1031V23.41L8.63672 23.4068V23.1006Z" fill="#092114"/>
<path d="M9.01859 23.0269L8.63672 23.1006V23.4069L9.01859 23.3331V23.0269Z" fill="#092114"/>
<path d="M15.9668 15.975V21.0156C15.9668 21.5875 14.4912 22.085 13.9362 22.085L9.01929 23.0275V23.3337L13.9362 22.3843C14.4912 22.3843 15.9668 21.8862 15.9668 21.315V15.975Z" fill="#092114"/>
<path d="M8.62108 9.55872H8.45483V10.0637H8.62108V9.55872Z" fill="#062E18"/>
<path d="M8.62108 15.975H8.45483V23.1356L8.62108 23.1031V15.975Z" fill="#092214"/>
<path d="M8.62108 10.0637H8.45483V15.975H8.62108V10.0637Z" fill="#062D18"/>
<path d="M8.63672 9.55872H8.62109V10.0637H8.63672V9.55872Z" fill="#062D18"/>
<path d="M8.63672 15.975H8.62109V23.1031L8.63672 23.1006V15.975Z" fill="#092114"/>
<path d="M8.63672 10.0637H8.62109V15.975H8.63672V10.0637Z" fill="#062D17"/>
<path d="M9.01859 9.55872H8.63672V10.0637H9.01859V9.55872Z" fill="#062D17"/>
<path d="M14.6568 9.55872H9.01929V10.0643H14.6568C15.2962 10.0681 15.8299 10.515 15.9668 11.1137V10.6081C15.8299 10.0087 15.2949 9.56247 14.6568 9.55872Z" fill="#062C17"/>
<path d="M9.01859 15.975H8.63672V23.1006L9.01859 23.0268V15.975Z" fill="#092114"/>
<path d="M9.01859 10.0637H8.63672V15.975H9.01859V10.0637Z" fill="#062C17"/>
<path d="M15.6868 15.975H9.01929V23.0275L13.9362 22.085C14.4912 22.085 15.9668 21.5869 15.9668 21.0156V15.975H15.6868Z" fill="#092114"/>
<path d="M14.6568 10.0637H9.01929V15.9743H15.6868H15.9662V11.1137C15.8299 10.5156 15.2955 10.0681 14.6568 10.0637Z" fill="#062C17"/>
<path d="M1.3349 7.60999H14.6655C15.4193 7.63186 16.0149 8.25686 15.9999 9.01123V22.9312C16.028 23.685 15.4443 24.3219 14.6899 24.3575H1.3349C0.583647 24.3437 -0.0144784 23.7244 0.000521623 22.9737V22.965V9.01186C-0.0144784 8.25749 0.580522 7.63249 1.3349 7.60999Z" fill="#107C41"/>
<path d="M3.62988 21.1906L6.77238 16.0669L3.88676 11.0087H6.20863L7.77551 14.2419C7.88676 14.4744 7.98676 14.7125 8.07426 14.9544C8.17363 14.7144 8.28988 14.465 8.40551 14.2419L10.0643 11.0331H12.1955L9.26926 16.0644L12.2949 21.1794H10.0243L8.19988 17.6556C8.11676 17.5025 8.04238 17.3444 7.97613 17.1831C7.92113 17.3425 7.84926 17.495 7.76051 17.6387L5.89551 21.195L3.62988 21.1906Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_877_12918">
<rect width="32" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 38 KiB

238
packages/nc-gui/assets/nc-icons/microsoft_outlook.svg

@ -0,0 +1,238 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<g clip-path="url(#clip0_881_28190)">
<path d="M31.1368 15.2737C31.5962 15.2737 31.9687 15.6462 31.9687 16.1056C31.9687 16.565 31.5962 16.9375 31.1368 16.9375C30.6774 16.9375 30.3049 16.565 30.3049 16.1056C30.3049 15.6469 30.6774 15.2737 31.1368 15.2737Z" fill="#123B6D"/>
<path d="M8.66251 14.9769C9.21563 14.9769 9.66375 15.425 9.66375 15.9781C9.66375 16.5306 9.21563 16.9794 8.66251 16.9794C8.11001 16.9794 7.66125 16.5312 7.66125 15.9781C7.66125 15.425 8.10938 14.9769 8.66251 14.9769Z" fill="#123B6D"/>
<path d="M19.9844 15.9769H8.3269V16.5344L19.1744 22.4931L19.32 22.5694C19.74 22.7719 20.2294 22.7719 20.6494 22.5694L20.795 22.4931L31.6425 16.5344V15.9769H19.9844Z" fill="#123B6D"/>
<path d="M19.9844 15.9769H8.3269V15.4194L19.1744 9.46061L19.32 9.38436C19.74 9.18186 20.2294 9.18186 20.6494 9.38436L20.795 9.46061L31.5969 15.4275V15.9775H19.9844" fill="#123B6D"/>
<path d="M11.4275 2.53998H28.5618C29.3375 2.53998 29.9675 3.16935 29.9675 3.9456V26.0456C29.9675 26.8212 29.3381 27.4512 28.5618 27.4512H11.4275C10.6518 27.4512 10.0219 26.8219 10.0219 26.0456V3.94498C10.0225 3.16935 10.6518 2.53998 11.4275 2.53998Z" fill="#28A8EA"/>
<path d="M10.0225 16.94H17.9831V22.9444H10.0225V16.94Z" fill="#14447D"/>
<path d="M16.9906 16.94H23.9794V22.9444H16.9906V16.94Z" fill="#0364B8"/>
<path d="M10.0225 11.1569H16.9887V16.94H10.0225V11.1569Z" fill="#0664B2"/>
<path d="M23.9793 16.94H29.9762V22.9438H23.9793V16.94ZM16.9906 11.1569H23.9806V16.9394H16.9906V11.1569ZM10.0225 5.3819H16.9887V11.1569H10.0225V5.3819Z" fill="#0078D4"/>
<path d="M29.9762 5.3819H23.9794V11.1569H29.9762V5.3819Z" fill="#50D9FF"/>
<path d="M11.4275 2.54H28.54C28.9093 2.54 29.265 2.68188 29.5325 2.93688C29.7931 3.18563 29.9418 3.53126 29.9456 3.89188V5.41875H10.0225V3.89188C10.04 3.13063 10.6668 2.52688 11.4275 2.54Z" fill="#0358A7"/>
<path d="M19.205 22.5069C19.2418 22.5306 19.2806 22.5519 19.32 22.57L19.3075 22.5631C19.2768 22.5481 19.2462 22.5319 19.2162 22.5131L19.205 22.5069ZM32 15.9813C32 16.0506 31.9875 16.1188 31.9668 16.1819C31.9168 16.3294 31.8125 16.4556 31.6712 16.5331H31.6425L29.9775 17.4469L23.9806 20.7363L23.0243 21.2613L20.7337 22.5244C20.7181 22.5338 20.7025 22.5425 20.6856 22.5506L20.6506 22.57C20.4431 22.6694 20.2156 22.7219 19.9862 22.7219C19.7568 22.7219 19.5293 22.6694 19.3218 22.5694L31.36 29.1844C31.7306 28.9319 31.9818 28.5138 32.0006 28.0313C32.0012 28.0181 32.0012 28.005 32.0012 27.9919V15.9813M32 15.9763V15.9813C32 15.98 32 15.9781 32 15.9763Z" fill="url(#paint0_linear_881_28190)"/>
<path d="M31.2807 15.9769H30.6438L29.9675 16.3487V16.94H29.9769V17.4281L31.5963 16.5356L31.2807 15.9769Z" fill="#0E316A"/>
<path d="M31.1299 15.71L30.6437 15.9769H31.2806L31.1299 15.71Z" fill="#0E316A"/>
<path d="M29.9675 16.3488L28.8918 16.94H29.9675V16.3488Z" fill="#1967A8"/>
<path d="M23.9794 19.64L20.205 21.7144C20.1381 21.7431 20.0662 21.7575 19.9956 21.7575C19.925 21.7575 19.8525 21.7438 19.7862 21.7144C19.7269 21.69 18.7931 21.1813 17.4956 20.4694V21.5619L19.1756 22.485C19.1894 22.4944 19.2037 22.5044 19.2175 22.5131L19.3087 22.5631C19.3131 22.565 19.3169 22.5669 19.3212 22.5694C19.5306 22.6713 19.7587 22.7213 19.9856 22.7213C20.2125 22.7213 20.44 22.6706 20.65 22.5694C20.6625 22.5644 20.6737 22.5581 20.6862 22.5519L20.7344 22.5256C20.7556 22.5131 20.7756 22.4994 20.7956 22.485L23.025 21.2619L23.9812 20.7344V19.64" fill="#07458F"/>
<path d="M29.9769 16.94H29.9675H28.8919L23.9794 19.64V20.7344L29.9769 17.4281V16.94Z" fill="#054F9D"/>
<path d="M20.6838 22.5519C20.6725 22.5582 20.66 22.5632 20.6475 22.5694C20.4381 22.6713 20.21 22.7213 19.9831 22.7213C19.7563 22.7213 19.5288 22.6707 19.3188 22.5694C19.3144 22.5675 19.3106 22.5657 19.3063 22.5632L19.3188 22.57C19.5263 22.67 19.7538 22.7225 19.9831 22.7225C20.2125 22.7225 20.44 22.67 20.6475 22.5707L20.6838 22.5519ZM17.4944 21.5619L19.1744 22.4857C19.1838 22.4925 19.1944 22.4994 19.205 22.5069L19.2163 22.5132C19.2025 22.5044 19.1881 22.495 19.1744 22.485L17.4944 21.5619ZM23.0231 21.2619L20.7938 22.485C20.7738 22.4988 20.7531 22.5119 20.7325 22.5257L23.0231 21.2619Z" fill="url(#paint1_linear_881_28190)"/>
<path d="M9.49564 29.4613H30.4725C30.7881 29.4606 31.0969 29.365 31.3581 29.1863L19.3181 22.57C19.2675 22.5475 19.2181 22.5181 19.1725 22.4856L8.34002 16.5338L8.00439 16.3506V27.9925C8.00439 28.8025 8.66127 29.4594 9.47127 29.4594L9.49564 29.4613Z" fill="#28A8EA"/>
<path d="M17.1806 8.26874H16.9888V8.86686C17.045 8.98686 17.0781 9.11999 17.0819 9.26124V11.1556H17.495V8.96311C17.4925 8.69749 17.3788 8.44624 17.1806 8.26874Z" fill="#27A2E2"/>
<path d="M17.4943 16.94H17.0812V20.2438C17.2224 20.3206 17.3599 20.3963 17.4943 20.47V16.94Z" fill="#0361B2"/>
<path d="M17.4944 11.1569H17.0813V16.9394H17.4944V11.1569ZM16.9888 8.26874H15.9207C15.9244 8.27811 15.9275 8.28874 15.93 8.29936H16.0957C16.4894 8.29936 16.8313 8.53061 16.9882 8.86686V8.26874" fill="#0074CD"/>
<path d="M17.0818 20.2431V21.3356L17.4949 21.5625V20.47C17.3605 20.3956 17.2224 20.3206 17.0818 20.2431Z" fill="#07438A"/>
<path d="M17.0818 21.3356V21.3363L17.4943 21.5625V21.5619L17.0818 21.3356Z" fill="url(#paint2_linear_881_28190)"/>
<path d="M17.0819 21.3362V23.3037C17.0819 23.3125 17.0812 23.3212 17.0812 23.33V23.5481C17.0725 23.6444 17.0525 23.7369 17.0219 23.825C17.3006 23.655 17.4894 23.35 17.495 22.9994V21.5644L17.0819 21.3362Z" fill="#27A2E2"/>
<path d="M7.99878 23.6844V25.9919H8.00565V25.5256H7.99878V25.0981V23.6844Z" fill="#EEEEEE"/>
<path d="M15.9869 24.7919C15.9388 24.8144 15.8888 24.8331 15.8363 24.8481C15.6181 25.2537 15.1881 25.5275 14.6994 25.5275C14.6863 25.5275 14.6731 25.5275 14.66 25.5269H8.00439V25.9931H14.6594C14.6725 25.9931 14.6856 25.9937 14.6988 25.9937C15.3781 25.9925 15.9413 25.4675 15.9869 24.7919Z" fill="#259DDA"/>
<path d="M16.9888 8.86688V9.26501C17.045 9.38501 17.0775 9.51813 17.0806 9.65938V11.1569H17.0813V9.26251C17.0781 9.12126 17.0456 8.98688 16.9888 8.86688Z" fill="#2498D3"/>
<path d="M17.0818 16.94H17.0806V20.2425L17.0818 20.2431V16.94Z" fill="#035AA6"/>
<path d="M17.0818 11.1569H17.0812V16.9394H17.0818V11.1569ZM16.0962 8.29999H15.9305C15.9543 8.37436 15.968 8.45061 15.9743 8.52874V8.69686H16.0805C16.0855 8.69686 16.0918 8.69686 16.0974 8.69686C16.4912 8.69686 16.8318 8.92811 16.9899 9.26499V8.86686C16.8305 8.53124 16.4899 8.29999 16.0962 8.29999Z" fill="#006CC0"/>
<path d="M17.0806 20.2425V21.335L17.0818 21.3356V20.2431L17.0806 20.2425Z" fill="#063E81"/>
<path d="M17.0806 21.335V21.3356L17.0818 21.3363V21.3356L17.0806 21.335Z" fill="url(#paint3_linear_881_28190)"/>
<path d="M17.0806 21.3356V23.3294C17.0812 23.3206 17.0812 23.3119 17.0812 23.3031V21.3356H17.0806Z" fill="#2498D3"/>
<path d="M17.0212 23.8244C16.9706 23.8557 16.9162 23.8825 16.8587 23.9044C16.7656 24.0175 16.6481 24.1094 16.5149 24.1725C16.4731 24.3182 16.3981 24.4488 16.2999 24.5582C16.6387 24.4357 16.9043 24.165 17.0212 23.8244Z" fill="#259DDA"/>
<path d="M17.0806 23.3294C17.0694 23.5469 16.9887 23.7457 16.86 23.9038C16.9169 23.8819 16.9706 23.8557 17.0225 23.8238C17.0525 23.7363 17.0725 23.6432 17.0819 23.5469V23.3294" fill="#2498D3"/>
<path d="M16.86 23.9037C16.7656 23.94 16.6625 23.9619 16.5569 23.9669C16.555 23.9675 16.5544 23.9694 16.5525 23.9706C16.5475 24.04 16.535 24.1075 16.5162 24.1725C16.6494 24.1094 16.7669 24.0175 16.86 23.9037Z" fill="#2392CC"/>
<path d="M16.9887 9.26501V11.1569V16.9394H16.9131H16.99V11.1569H17.0812V9.65939C17.0775 9.51814 17.045 9.38501 16.9887 9.26501Z" fill="#228EC5"/>
<path d="M16.9906 16.94H16.9137V20.1506C16.9399 20.1644 16.9643 20.1788 16.9906 20.1925V16.94Z" fill="#11396A"/>
<path d="M17.0806 16.94H16.9894V20.1925C17.02 20.2094 17.05 20.2256 17.0806 20.2425V16.94Z" fill="#03549B"/>
<path d="M16.9887 11.1569H16.9131V16.94H16.9887V11.1569Z" fill="#055497"/>
<path d="M17.0806 11.1569H16.9894V16.9394H17.0806V11.1569ZM16.0969 8.6969C16.0919 8.6969 16.0856 8.6969 16.08 8.6969H15.9738V9.14065C16.4869 9.16315 16.9006 9.5819 16.9125 10.1025V11.1569H16.9881V9.26503C16.8319 8.92815 16.4906 8.6969 16.0969 8.6969Z" fill="#0065B3"/>
<path d="M16.9131 20.15V21.2425L16.99 21.2844V20.1919C16.9643 20.1782 16.9393 20.165 16.9131 20.15Z" fill="#0D2D60"/>
<path d="M16.9906 20.1925V21.285L17.0819 21.335V20.2425C17.0506 20.2256 17.02 20.2088 16.9906 20.1925Z" fill="#063A79"/>
<path d="M16.9131 21.2425V21.2437L17.0806 21.3356V21.335L16.9906 21.285L16.9131 21.2425Z" fill="url(#paint4_linear_881_28190)"/>
<path d="M16.9131 21.2438V23.2269V23.235C16.9063 23.5313 16.7688 23.7938 16.5563 23.9681C16.6625 23.9631 16.765 23.9413 16.8594 23.905C16.9881 23.7475 17.07 23.5482 17.08 23.3307V21.3369L16.9131 21.2438Z" fill="#228EC5"/>
<path d="M15.9206 8.26874H15.7906C15.7975 8.27811 15.8038 8.28874 15.81 8.29936H15.93C15.9269 8.28936 15.9244 8.27936 15.9206 8.26874Z" fill="#0073CB"/>
<path d="M15.9305 8.29999H15.8105C15.8843 8.41811 15.933 8.55249 15.9518 8.69686H15.9743V8.52874C15.9687 8.45061 15.9543 8.37311 15.9305 8.29999Z" fill="#006BBE"/>
<path d="M15.9744 8.6969H15.9519C15.9563 8.7294 15.9582 8.76253 15.9588 8.79628V8.88752C15.9675 8.93752 15.9719 8.98815 15.9725 9.04065V9.14127H15.9732V8.6969" fill="#0064B1"/>
<path d="M8.00565 25.0981H7.99878V25.5256H8.00565V25.0981Z" fill="#CFCFCF"/>
<path d="M15.8369 24.8475C15.7519 24.8713 15.6613 24.8844 15.5688 24.8844C15.5638 24.8844 15.5594 24.8844 15.5538 24.8844H15.4125C15.2075 25.02 14.9625 25.0994 14.7 25.0994C14.6869 25.0994 14.6738 25.0994 14.6606 25.0988H8.005V25.5269H14.66C14.6731 25.5269 14.6863 25.5275 14.6994 25.5275C15.1888 25.5263 15.6175 25.2538 15.8369 24.8475Z" fill="#2189BE"/>
<path d="M7.99878 23.6844V25.0981H8.00565V24.7688H7.99878V24.4794V23.845V23.6844Z" fill="#B6B6B6"/>
<path d="M8.29437 24.7687H8.00562V25.0975H14.6606C14.6737 25.0975 14.6869 25.0981 14.7 25.0981C14.9619 25.0981 15.2075 25.0194 15.4125 24.8831H8.61812C8.61562 24.8831 8.61312 24.8831 8.60999 24.8831C8.48874 24.8844 8.37937 24.8406 8.29437 24.7687Z" fill="#1D78A7"/>
<path d="M8.00565 24.4794H7.99878V24.7687H8.00565V24.4794Z" fill="#A2A2A2"/>
<path d="M8.12749 24.4794H8.00562V24.7687H8.29437C8.20812 24.6969 8.14812 24.595 8.12749 24.4794Z" fill="#196B95"/>
<path d="M8.00565 23.845H7.99878V24.4794H8.00565V23.845Z" fill="#8B8B8B"/>
<path d="M8.05435 23.845H8.00623V24.48H8.1281C8.12373 24.4557 8.12123 24.4294 8.12123 24.4038V23.8463C8.1106 23.8469 8.10123 23.8469 8.0906 23.8469C8.07748 23.8463 8.06622 23.8457 8.05435 23.845Z" fill="#165C80"/>
<path d="M7.99878 23.7687V23.845H8.00565V23.8387C8.00315 23.8387 8.00128 23.8381 7.99878 23.8381V23.7687Z" fill="#7A7A7A"/>
<path d="M8.00562 23.8394V23.8456H8.05374C8.03749 23.8437 8.02187 23.8419 8.00562 23.8394Z" fill="#135070"/>
<path d="M16.5563 23.9669C16.5544 23.9669 16.5538 23.9669 16.5519 23.9669C16.5519 23.9675 16.5519 23.9694 16.5519 23.9706C16.5531 23.9694 16.555 23.9681 16.5563 23.9669Z" fill="#2189BF"/>
<path d="M16.9131 16.94H16.5537V19.9544C16.6756 20.0213 16.795 20.0869 16.9131 20.1519V16.94Z" fill="#103663"/>
<path d="M16.9131 11.1569H16.5537V16.94H16.9131V11.1569Z" fill="#054F8D"/>
<path d="M15.9744 9.14062V9.21625C16.1744 9.35188 16.3169 9.51063 16.3169 9.6975V10.1781C16.46 10.345 16.5481 10.5606 16.5537 10.7969V11.1562H16.9131V10.1019C16.9012 9.58125 16.4862 9.16437 15.9744 9.14062Z" fill="#005FA7"/>
<path d="M16.5537 19.9537V21.0462L16.9131 21.2437V20.1512C16.7956 20.0862 16.6756 20.0206 16.5537 19.9537Z" fill="#0C2A5A"/>
<path d="M16.5537 21.0457V21.0463L16.9131 21.2438V21.2425L16.5537 21.0457Z" fill="url(#paint5_linear_881_28190)"/>
<path d="M16.5538 21.0463V23.9219C16.5538 23.9369 16.5531 23.9525 16.5519 23.9675C16.5538 23.9675 16.5544 23.9675 16.5563 23.9675C16.7681 23.7931 16.9063 23.5306 16.9131 23.2344V23.2263V21.2438L16.5538 21.0463Z" fill="#2084B9"/>
<path d="M15.9738 9.14062V9.21562L15.9744 9.21688V9.14062H15.9738Z" fill="#005EA7"/>
<path d="M8.12059 23.845H8.05371C8.06621 23.8457 8.07809 23.8457 8.08996 23.8457C8.10059 23.8463 8.10996 23.8457 8.12059 23.845Z" fill="#145475"/>
<path d="M8.00565 23.7687H7.99878V23.8381C8.00128 23.8381 8.00315 23.8387 8.00565 23.8387V23.7687Z" fill="#737373"/>
<path d="M8.12062 23.7687H8.00562V23.84C8.02124 23.8425 8.03812 23.8444 8.05374 23.8462H8.12062V23.7687Z" fill="#124C6A"/>
<path d="M8.00565 23.6844H7.99878V23.7688H8.00565V23.6844Z" fill="#696969"/>
<path d="M8.12062 23.6844H8.00562V23.7688H8.12062V23.6844Z" fill="#104560"/>
<path d="M15.9744 9.21686V9.38061C15.9831 9.43686 15.9875 9.49373 15.9881 9.55248V9.59123C16.0681 9.71561 16.1194 9.85936 16.1362 10.0137C16.2031 10.0612 16.2644 10.1162 16.3169 10.1775V9.69686C16.3169 9.51123 16.1731 9.35186 15.9744 9.21686Z" fill="#004E8A"/>
<path d="M15.9738 9.21564V9.37439C15.9738 9.37626 15.9744 9.37876 15.9744 9.38064V9.21689C15.9738 9.21564 15.9738 9.21564 15.9738 9.21564Z" fill="#004E89"/>
<path d="M15.988 9.59125V9.92687C15.988 9.92687 15.988 9.92687 15.988 9.9275C16.0399 9.95187 16.0899 9.98125 16.1362 10.0131C16.1199 9.85937 16.068 9.71562 15.988 9.59125Z" fill="#00477D"/>
<path d="M16.3012 24.5575C16.2049 24.5925 16.1024 24.6156 15.9962 24.625H15.9893V24.7469C15.9887 24.7619 15.9887 24.7769 15.9868 24.7919C16.1068 24.7344 16.2137 24.655 16.3012 24.5575Z" fill="#259DDA"/>
<path d="M15.9306 24.6244C15.9062 24.7025 15.8743 24.7769 15.8362 24.8481C15.8881 24.8331 15.9387 24.815 15.9868 24.7919C15.9874 24.7769 15.9887 24.7619 15.9893 24.7469V24.625L15.9306 24.6244Z" fill="#2392CC"/>
<path d="M16.5163 24.1725C16.3894 24.2319 16.2463 24.2662 16.0963 24.2662H15.9894V24.2812V24.625H15.9963C16.1025 24.6162 16.205 24.5925 16.3013 24.5575C16.3994 24.4487 16.4738 24.3175 16.5163 24.1725Z" fill="#2392CC"/>
<path d="M15.9893 24.2806C15.9849 24.4 15.9649 24.515 15.9305 24.6231L15.9893 24.6238V24.2806Z" fill="#2189BF"/>
<path d="M16.5519 23.9713C16.3969 24.0975 16.2013 24.1775 15.9888 24.1888V24.2669H16.0956C16.2456 24.2669 16.3875 24.2338 16.5156 24.1731C16.5344 24.1075 16.5469 24.04 16.5519 23.9713Z" fill="#2189BF"/>
<path d="M15.7031 24.62C15.62 24.7225 15.5219 24.8112 15.4119 24.8837H15.5531C15.5581 24.8837 15.5625 24.8837 15.5681 24.8837C15.6612 24.8837 15.7512 24.8706 15.8362 24.8469C15.8737 24.7756 15.9062 24.7012 15.9306 24.6231L15.7031 24.62Z" fill="#1E80B2"/>
<path d="M15.315 24.6138C15.1318 24.7138 14.9212 24.77 14.6993 24.77C14.6862 24.77 14.6731 24.77 14.66 24.7694H8.2937C8.3787 24.8413 8.48933 24.8844 8.60933 24.8844C8.61183 24.8844 8.61433 24.8844 8.61745 24.8844H15.4125C15.5218 24.8119 15.62 24.7219 15.7037 24.6206L15.315 24.6138Z" fill="#1B729E"/>
<path d="M15.9894 24.2656H15.9069C15.8575 24.3956 15.7887 24.5144 15.7037 24.62L15.9319 24.6238C15.9662 24.515 15.9862 24.4 15.9906 24.2812V24.2663" fill="#1D79A8"/>
<path d="M15.9068 24.2656H15.7224C15.6131 24.4087 15.4737 24.5269 15.3149 24.6138L15.7024 24.62C15.7887 24.5144 15.8574 24.395 15.9068 24.2656Z" fill="#1A6B96"/>
<path d="M15.9894 24.1875C15.9719 24.1881 15.9544 24.1894 15.9369 24.1894C15.9362 24.1894 15.9344 24.1894 15.9331 24.1894C15.925 24.215 15.9162 24.2413 15.9069 24.2656H15.9894V24.1875Z" fill="#1B729F"/>
<path d="M15.9118 24.1894H15.7768C15.7593 24.2156 15.7405 24.2413 15.7224 24.2663H15.9068C15.9162 24.2406 15.9249 24.2156 15.933 24.19C15.9262 24.1894 15.9193 24.1894 15.9118 24.1894Z" fill="#18668F"/>
<path d="M8.94119 24.4794H8.12744C8.14744 24.595 8.20807 24.6969 8.29432 24.7687H14.6612C14.6743 24.7687 14.6874 24.7694 14.7006 24.7694C14.9231 24.7694 15.1331 24.7131 15.3162 24.6131L9.12119 24.5094C9.11869 24.5094 9.11619 24.5094 9.11307 24.5094C9.05244 24.51 8.99494 24.4987 8.94119 24.4794Z" fill="#18668D"/>
<path d="M15.7225 24.2656H15.4063C15.2013 24.4013 14.955 24.4794 14.6919 24.4794C14.6813 24.4794 14.6706 24.4794 14.6606 24.4794H8.94189C8.99564 24.4988 9.05314 24.51 9.11377 24.51C9.11627 24.51 9.11877 24.51 9.12189 24.51L15.3169 24.6138C15.4744 24.5269 15.6131 24.4087 15.7225 24.2656Z" fill="#176187"/>
<path d="M15.7781 24.1894H15.5118C15.4787 24.2163 15.4437 24.2419 15.4075 24.2663H15.7237C15.7418 24.2413 15.7606 24.2156 15.7781 24.1894Z" fill="#165C81"/>
<path d="M8.47811 23.845H8.12686C8.12498 23.845 8.12248 23.845 8.12061 23.845V24.4025C8.12061 24.4288 8.12311 24.4544 8.12748 24.4788H8.94123C8.76686 24.415 8.63811 24.2563 8.61873 24.0632C8.55373 24.005 8.50498 23.9307 8.47811 23.845Z" fill="#15587B"/>
<path d="M8.61804 24.0638C8.63742 24.2569 8.76679 24.415 8.94054 24.4794H14.6593C14.6699 24.4794 14.6805 24.4794 14.6905 24.4794C14.9537 24.4794 15.1993 24.4007 15.4049 24.2657H9.13492C9.13242 24.2657 9.12992 24.2657 9.12804 24.2657C9.02429 24.2657 8.92804 24.2344 8.84742 24.18C8.76179 24.1619 8.68179 24.1207 8.61804 24.0638Z" fill="#145475"/>
<path d="M8.84937 24.1794C8.92937 24.2338 9.02562 24.265 9.12999 24.265C9.13249 24.265 9.13499 24.265 9.13687 24.265H15.4069C15.4431 24.2413 15.4781 24.2156 15.5112 24.1881H8.95374C8.95124 24.1881 8.94874 24.1881 8.94562 24.1881C8.91249 24.1894 8.87999 24.1856 8.84937 24.1794Z" fill="#135272"/>
<path d="M8.47558 23.8375H8.18183C8.16371 23.8413 8.14496 23.8425 8.12683 23.8444H8.47808C8.47746 23.8432 8.47683 23.8406 8.47558 23.8375Z" fill="#134E6D"/>
<path d="M16.5519 23.9675C16.5381 23.9682 16.5231 23.9682 16.5081 23.9682C16.5031 23.9682 16.4988 23.9682 16.4931 23.9682H15.9888V24.1882C16.2006 24.1769 16.3956 24.0982 16.5519 23.9707C16.5519 23.9694 16.5519 23.9682 16.5519 23.9675Z" fill="#1F81B3"/>
<path d="M16.5538 16.94H16.3169V19.8238C16.3969 19.8675 16.4756 19.9113 16.5538 19.9538V16.94Z" fill="#0F325D"/>
<path d="M16.5538 11.1569H16.3169V16.94H16.5538V11.1569Z" fill="#044A84"/>
<path d="M16.3169 10.1788V11.1569H16.5538V10.7975C16.5488 10.5606 16.46 10.345 16.3169 10.1788Z" fill="#00599D"/>
<path d="M16.3169 19.8231V20.9156L16.5538 21.0462V19.9537C16.4756 19.9106 16.3969 19.8669 16.3169 19.8231Z" fill="#0B2754"/>
<path d="M16.3169 20.9156V20.9163L16.5538 21.0463V21.0457L16.3169 20.9156Z" fill="url(#paint6_linear_881_28190)"/>
<path d="M16.3169 20.9163V22.3944C16.3169 22.405 16.3163 22.4156 16.3163 22.4256C16.3069 22.6188 16.2425 22.7963 16.1381 22.9438C16.125 23.1063 16.0713 23.2556 15.9894 23.3856V23.525V23.9688H16.4938C16.4988 23.9688 16.5031 23.9688 16.5088 23.9688C16.5238 23.9688 16.5375 23.9688 16.5525 23.9681C16.5531 23.9531 16.5544 23.9375 16.5544 23.9225V21.0469L16.3169 20.9163Z" fill="#1E7DAE"/>
<path d="M15.9894 23.9681H15.9806C15.9712 24.0444 15.955 24.1181 15.9331 24.1888C15.9337 24.1888 15.9356 24.1888 15.9369 24.1888C15.9544 24.1888 15.9719 24.1881 15.9894 24.1869V23.9681Z" fill="#1A6C97"/>
<path d="M15.9893 23.8594C15.9875 23.8962 15.985 23.9319 15.9806 23.9675H15.9893V23.8594Z" fill="#196993"/>
<path d="M15.9806 23.9681H15.895C15.8637 24.0463 15.8237 24.12 15.7781 24.1888H15.9125C15.9193 24.1888 15.9262 24.1888 15.9337 24.1888C15.955 24.1181 15.9706 24.0438 15.9806 23.9681Z" fill="#176288"/>
<path d="M15.9894 23.5237C15.9844 23.6806 15.9506 23.8306 15.895 23.9675H15.9806C15.985 23.9312 15.9875 23.8956 15.9894 23.8594V23.5237Z" fill="#176085"/>
<path d="M15.8944 23.9681H15.725C15.6625 24.0494 15.5906 24.1244 15.5112 24.1888H15.7775C15.8237 24.12 15.8631 24.0456 15.8944 23.9681Z" fill="#15587B"/>
<path d="M15.9893 23.3844C15.9831 23.3938 15.9762 23.4044 15.97 23.4144C15.9331 23.6207 15.8475 23.8088 15.7256 23.9675H15.895C15.9512 23.83 15.9843 23.6807 15.9893 23.5238V23.3844Z" fill="#15587A"/>
<path d="M8.61626 23.845H8.47876C8.50501 23.93 8.55438 24.0057 8.61876 24.0644C8.61813 24.0532 8.61688 24.0407 8.61626 24.0294V23.845Z" fill="#145475"/>
<path d="M8.63809 23.845H8.61621V24.0282C8.61621 24.0407 8.61684 24.0519 8.61871 24.0632C8.68246 24.1213 8.76184 24.1613 8.84934 24.1788C8.73809 24.1032 8.65871 23.9844 8.63809 23.845Z" fill="#135272"/>
<path d="M9.2037 23.845H8.63745C8.6587 23.9844 8.73745 24.1038 8.8487 24.18C8.87995 24.1863 8.91183 24.1894 8.94495 24.1894C8.94745 24.1894 8.94995 24.1894 8.95308 24.1894H15.5106C15.5906 24.1244 15.6625 24.05 15.7243 23.9688H9.53308C9.40745 23.9682 9.29308 23.9219 9.2037 23.845Z" fill="#134F6E"/>
<path d="M15.97 23.4144C15.7931 23.6706 15.4975 23.8381 15.1644 23.8381C15.1594 23.8381 15.155 23.8381 15.1494 23.8381H14.8319C14.7862 23.8431 14.74 23.845 14.6931 23.845C14.6825 23.845 14.6719 23.845 14.6606 23.845H9.20374C9.29249 23.9219 9.40811 23.9675 9.53436 23.9675H15.7256C15.8475 23.8087 15.9325 23.62 15.97 23.4144Z" fill="#134E6C"/>
<path d="M8.6162 23.8375H8.4762C8.47682 23.84 8.47807 23.8419 8.47807 23.8444H8.61557V23.8375" fill="#124C6A"/>
<path d="M8.63624 23.8375H8.61499V23.8444H8.63687C8.63687 23.8432 8.63687 23.8406 8.63624 23.8375Z" fill="#114A66"/>
<path d="M9.19561 23.8375H8.63623C8.63623 23.84 8.63686 23.8419 8.63686 23.8444H9.20311C9.20061 23.8432 9.19811 23.8406 9.19561 23.8375Z" fill="#114864"/>
<path d="M14.8312 23.8375H9.19556C9.19806 23.84 9.20056 23.8419 9.20368 23.8444H14.6606C14.6712 23.8444 14.6824 23.8444 14.6931 23.8444C14.7387 23.8457 14.7856 23.8431 14.8312 23.8375Z" fill="#114662"/>
<path d="M8.46061 23.7687H8.12061V23.845H8.12686C8.14498 23.8431 8.16373 23.8412 8.18186 23.8381H8.47561C8.46936 23.815 8.46436 23.7919 8.46061 23.7687Z" fill="#114966"/>
<path d="M8.45623 23.6844H8.12061V23.7688H8.46061C8.45811 23.7488 8.45623 23.7281 8.45623 23.7081V23.6844Z" fill="#10445F"/>
<path d="M16.1381 22.9437C16.095 23.005 16.045 23.0612 15.9894 23.1106V23.385C16.0719 23.2556 16.1244 23.105 16.1381 22.9437Z" fill="#1A6F9A"/>
<path d="M15.9893 23.2344C15.9868 23.2956 15.9806 23.3556 15.97 23.4144C15.9768 23.405 15.9831 23.395 15.9893 23.3844V23.2344Z" fill="#135171"/>
<path d="M15.9893 23.11C15.9268 23.165 15.8581 23.2125 15.7831 23.2512C15.5756 23.5731 15.2287 23.7956 14.8312 23.8375H15.1487C15.1537 23.8375 15.1581 23.8375 15.1637 23.8375C15.4968 23.8375 15.7931 23.67 15.9693 23.4137C15.9799 23.355 15.9868 23.2956 15.9887 23.2337V23.1094" fill="#114966"/>
<path d="M8.61618 23.7687H8.46118C8.46493 23.7925 8.46931 23.8162 8.47618 23.8381H8.61618V23.7687Z" fill="#114864"/>
<path d="M8.63184 23.7687H8.61621V23.8381H8.63746C8.63496 23.8206 8.63371 23.8031 8.63309 23.7844V23.7687" fill="#114661"/>
<path d="M9.13184 23.7687H8.63184V23.7844C8.63246 23.8025 8.63371 23.8206 8.63621 23.8381H9.19559C9.17246 23.8169 9.15059 23.7931 9.13184 23.7687Z" fill="#10455F"/>
<path d="M15.7831 23.2513C15.7575 23.2644 15.7325 23.2757 15.7062 23.2863C15.4687 23.5819 15.105 23.7688 14.7 23.7688C14.6868 23.7688 14.6737 23.7688 14.6606 23.7682H9.13184C9.15121 23.7938 9.17246 23.8163 9.19559 23.8375H14.8312C15.2293 23.7957 15.5756 23.5732 15.7831 23.2513Z" fill="#10445F"/>
<path d="M8.61616 23.6844H8.45679V23.7081C8.45679 23.7281 8.45866 23.7488 8.46116 23.7688H8.61616V23.6844Z" fill="#10435D"/>
<path d="M8.63184 23.6844H8.61621V23.7688H8.63184V23.6844Z" fill="#10415B"/>
<path d="M9.07996 23.6844H8.63184V23.7688H9.13184C9.11246 23.7419 9.09496 23.7144 9.07996 23.6844Z" fill="#0F415A"/>
<path d="M15.7062 23.2863C15.6612 23.3044 15.6162 23.3194 15.5687 23.33C15.32 23.5563 14.9962 23.6831 14.6593 23.6844H9.07996C9.09496 23.7144 9.11121 23.7425 9.13183 23.7688H14.6606C14.6737 23.7688 14.6868 23.7694 14.7 23.7694C15.1043 23.7688 15.4687 23.5819 15.7062 23.2863Z" fill="#0F4059"/>
<path d="M16.3169 16.94H16.1412V19.7269C16.2 19.7594 16.2587 19.7919 16.3169 19.8231V16.94Z" fill="#0C2B4D"/>
<path d="M16.3168 11.1569H16.1418V16.94H16.3168V11.1569Z" fill="#043E6E"/>
<path d="M16.1362 10.0137C16.1387 10.0425 16.1412 10.0725 16.1412 10.1025V11.1569H16.3169V10.1787C16.2637 10.1162 16.2031 10.0612 16.1362 10.0137Z" fill="#004A83"/>
<path d="M16.1418 19.7269V20.8181L16.3175 20.9144V19.8219C16.2593 19.7919 16.2006 19.7594 16.1418 19.7269Z" fill="#092147"/>
<path d="M16.1418 20.8194L16.3168 20.9163V20.9156L16.1418 20.8194Z" fill="url(#paint7_linear_881_28190)"/>
<path d="M16.1418 20.8194V22.875C16.1418 22.8975 16.1399 22.9206 16.1381 22.9425C16.2418 22.795 16.3062 22.6181 16.3162 22.4244C16.3162 22.4138 16.3168 22.4031 16.3168 22.3931V20.915L16.1418 20.8194Z" fill="#196891"/>
<path d="M16.1419 16.94H15.9894V19.6438C16.04 19.6719 16.0913 19.7 16.1419 19.7269V16.94Z" fill="#0B2646"/>
<path d="M16.1419 11.1569H15.9894V16.94H16.1419V11.1569Z" fill="#033864"/>
<path d="M15.988 9.92749C15.988 9.93437 15.9887 9.94124 15.9887 9.94874V10.2475V10.5369V11.1569H16.1412V10.1025C16.1405 10.0725 16.1387 10.0431 16.1362 10.0137C16.0899 9.98124 16.0399 9.95249 15.988 9.92749Z" fill="#004377"/>
<path d="M15.9894 19.6437V20.735L16.1419 20.8181V19.7269C16.0906 19.6987 16.04 19.6719 15.9894 19.6437Z" fill="#081E40"/>
<path d="M15.9894 20.735V20.7356L16.1419 20.8194L15.9894 20.735Z" fill="url(#paint8_linear_881_28190)"/>
<path d="M15.9894 20.7357V23.11C16.0456 23.06 16.0956 23.005 16.1381 22.9432C16.14 22.9207 16.1406 22.8982 16.1419 22.8757V20.82L15.9894 20.7357Z" fill="#165E83"/>
<path d="M15.988 9.92749V10.2275C15.988 10.2337 15.9887 10.2406 15.9887 10.2469V9.94812C15.9893 9.94187 15.988 9.93499 15.988 9.92749Z" fill="#00355E"/>
<path d="M15.988 10.2275V10.5169C15.988 10.5231 15.9887 10.53 15.9887 10.5362V10.2469C15.9893 10.2406 15.988 10.2344 15.988 10.2275Z" fill="#003156"/>
<path d="M15.9893 22.6C15.98 22.84 15.905 23.0631 15.7831 23.2512C15.8575 23.2125 15.9268 23.1656 15.9893 23.11V22.6Z" fill="#10445F"/>
<path d="M15.9893 22.5237C15.9799 22.8131 15.8743 23.0769 15.7062 23.2862C15.7324 23.2756 15.7581 23.2637 15.7831 23.2512C15.9049 23.0625 15.9793 22.84 15.9893 22.6V22.5237Z" fill="#0F4059"/>
<path d="M15.988 10.5181V10.8581C15.988 10.8631 15.9887 10.8694 15.9887 10.875V11.1575V10.5375C15.9893 10.5313 15.988 10.5238 15.988 10.5181Z" fill="#002F53"/>
<path d="M15.9894 11.1569H15.9888V11.2744C15.9888 11.2806 15.9894 11.2875 15.9894 11.2937V11.1569Z" fill="#022644"/>
<path d="M15.988 10.8582V11.1569H15.9887V10.8744C15.9893 10.8688 15.988 10.8632 15.988 10.8582Z" fill="#002E51"/>
<path d="M15.988 11.2744V11.7406C15.988 11.7469 15.9887 11.7537 15.9887 11.76V16.9394V11.2937C15.9893 11.2875 15.988 11.2806 15.988 11.2744Z" fill="#022542"/>
<path d="M15.9893 16.94H15.988V19.6425L15.9893 19.6438V16.94Z" fill="#07192E"/>
<path d="M15.988 11.74V16.94H15.9887V11.7606C15.9893 11.7531 15.988 11.7469 15.988 11.74Z" fill="#022441"/>
<path d="M15.988 19.6425V20.735H15.9893V19.6438L15.988 19.6425Z" fill="#05132A"/>
<path d="M15.988 20.735L15.9893 20.7356V20.735H15.988Z" fill="url(#paint9_linear_881_28190)"/>
<path d="M15.9881 20.735V22.4006C15.9881 22.7419 15.8469 23.0687 15.5987 23.3019C15.5894 23.3112 15.5787 23.3212 15.5681 23.33C15.6156 23.3187 15.6612 23.3037 15.7056 23.2862C15.8737 23.0769 15.9794 22.8131 15.9887 22.5237V20.7362L15.9881 20.735Z" fill="#0F3D56"/>
<path d="M10.0225 7.83313H9.9906V8.26875V8.07H10.0225V7.83313Z" fill="#EEEEEE"/>
<path d="M14.9725 7.83313C14.9675 7.83313 14.9631 7.83313 14.9575 7.83313H10.0225V8.07H14.9575L14.9731 8.06938C14.9781 8.06938 14.9825 8.06938 14.9881 8.06938C15.2106 8.06938 15.4168 8.14375 15.5825 8.26938H15.7912C15.6131 8.00625 15.3125 7.83313 14.9725 7.83313Z" fill="#0070C6"/>
<path d="M15.7913 8.26874H15.5825C15.5956 8.27811 15.6081 8.28874 15.62 8.29936H15.81C15.8044 8.28936 15.7981 8.27874 15.7913 8.26874Z" fill="#006BBE"/>
<path d="M15.8106 8.29999H15.6206C15.7462 8.40499 15.8462 8.54186 15.9062 8.69686H15.9519C15.9331 8.55249 15.8837 8.41749 15.8106 8.29999Z" fill="#0065B2"/>
<path d="M15.9513 8.6969H15.9056C15.9294 8.75752 15.9469 8.82065 15.9581 8.88627V8.79502C15.9575 8.76252 15.9556 8.7294 15.9513 8.6969Z" fill="#005EA7"/>
<path d="M15.9587 8.88623V9.13936C15.9631 9.13936 15.9681 9.13936 15.9725 9.13998V9.03936C15.9725 8.98811 15.9675 8.93623 15.9587 8.88623Z" fill="#005BA2"/>
<path d="M15.9587 9.14063V9.2075C15.9631 9.21125 15.9681 9.21375 15.9725 9.21688V9.14125C15.9681 9.14062 15.9637 9.14063 15.9587 9.14063Z" fill="#005698"/>
<path d="M15.9587 9.20624V9.29749C15.9637 9.32311 15.9694 9.34811 15.9725 9.37436V9.21561C15.9681 9.21249 15.9637 9.20999 15.9587 9.20624Z" fill="#00487F"/>
<path d="M10.0225 8.07001H9.9906V8.26876H10.0225V8.07001Z" fill="#D7D7D7"/>
<path d="M14.9881 8.0694C14.9831 8.0694 14.9787 8.0694 14.9731 8.0694L14.9575 8.07002H10.0225V8.26877H14.6593C14.6675 8.26877 14.6762 8.26877 14.6837 8.26877C14.6918 8.26877 14.6993 8.26877 14.7081 8.26877H15.5831C15.4168 8.14377 15.2106 8.0694 14.9881 8.0694Z" fill="#0065B3"/>
<path d="M15.5825 8.26874H14.7075C14.795 8.27061 14.8813 8.28124 14.9644 8.29936H15.62C15.6081 8.28936 15.5956 8.27874 15.5825 8.26874Z" fill="#0061AC"/>
<path d="M15.62 8.29999H14.9644C15.2325 8.35874 15.47 8.50061 15.6494 8.69686H15.9062C15.8456 8.54061 15.7462 8.40499 15.62 8.29999Z" fill="#005BA1"/>
<path d="M15.9062 8.6969H15.6493C15.7649 8.82377 15.8555 8.97377 15.9143 9.1394C15.9193 9.1394 15.9237 9.1394 15.9293 9.1394C15.9387 9.1394 15.9493 9.1394 15.9593 9.14003V8.8869C15.9468 8.82065 15.9293 8.75752 15.9062 8.6969Z" fill="#005698"/>
<path d="M15.9293 9.13936C15.9243 9.13936 15.9199 9.13936 15.9143 9.13936C15.9193 9.15498 15.9249 9.17061 15.9299 9.18748C15.9393 9.19373 15.9493 9.19998 15.9587 9.20561V9.13873C15.9487 9.13936 15.9393 9.13936 15.9293 9.13936Z" fill="#005290"/>
<path d="M15.9305 9.18811C15.9418 9.22436 15.9518 9.26061 15.9593 9.29749V9.20624C15.9499 9.19999 15.9399 9.19374 15.9305 9.18811Z" fill="#004579"/>
<path d="M1.33685 8.26878H14.6593C15.3806 8.25565 15.9762 8.83065 15.9887 9.55253V22.4007C15.9887 22.7419 15.8475 23.0688 15.5993 23.3019C15.3468 23.5456 15.01 23.6832 14.6606 23.6844H1.33685C0.986221 23.685 0.647471 23.5488 0.398096 23.3019C0.148096 23.0688 0.0037207 22.7425 0.0012207 22.4007V9.55253C0.0174707 8.8294 0.614346 8.25565 1.33685 8.26878Z" fill="#0078D4"/>
<path d="M3.83429 13.5694C4.19616 12.8256 4.77491 12.2088 5.49241 11.7969C6.30366 11.3631 7.21616 11.1525 8.13554 11.1856C8.99054 11.1675 9.83491 11.3788 10.5799 11.7969C11.2705 12.1881 11.8337 12.7725 12.1993 13.4775C12.5862 14.2488 12.7805 15.1038 12.7637 15.9675C12.7793 16.8706 12.578 17.765 12.1755 18.5725C11.8074 19.3088 11.2268 19.9181 10.5099 20.3225C8.93179 21.1413 7.05304 21.1413 5.47491 20.3225C4.77991 19.9238 4.21241 19.3356 3.84054 18.6263C3.45054 17.865 3.25304 17.0213 3.26741 16.1663C3.25241 15.2738 3.44616 14.3894 3.83179 13.5844L3.83429 13.5694ZM5.62179 17.7263C5.81366 18.195 6.13741 18.5988 6.55366 18.8881C6.98304 19.1813 7.49429 19.33 8.01241 19.3163C8.55491 19.3344 9.08929 19.1819 9.53991 18.8806C9.95366 18.5913 10.2693 18.1838 10.4493 17.7119C10.648 17.1919 10.7455 16.6406 10.7393 16.085C10.7474 15.5231 10.6537 14.9638 10.4643 14.435C10.2987 13.9556 9.99616 13.5363 9.59366 13.2288C9.14804 12.9131 8.61179 12.7519 8.06616 12.7713C7.53741 12.7563 7.01679 12.9056 6.57679 13.1994C6.15429 13.4913 5.82304 13.8956 5.62179 14.3681C5.19054 15.4431 5.19054 16.6469 5.62179 17.7263Z" fill="white"/>
<path d="M8.06746 12.7694C8.09621 12.7687 8.12558 12.7675 8.15433 12.7675C8.66996 12.7675 9.17308 12.9281 9.59496 13.2269C9.99746 13.5344 10.3 13.9544 10.4656 14.4331C10.6481 14.9406 10.7418 15.4775 10.7418 16.0162C10.7418 16.0381 10.7418 16.06 10.7412 16.0819C10.7412 16.0969 10.7412 16.1125 10.7412 16.1275C10.7412 16.6675 10.6437 17.2044 10.45 17.7094C10.27 18.18 9.95433 18.5881 9.54058 18.8781C9.11371 19.1637 8.61308 19.3156 8.09996 19.3156C8.07121 19.3156 8.04183 19.315 8.01308 19.3137C7.99058 19.3144 7.96808 19.3144 7.94558 19.3144C7.44996 19.3144 6.96558 19.1656 6.55433 18.8856C6.13808 18.5969 5.81371 18.1937 5.62246 17.7237C5.40683 17.1844 5.29871 16.6137 5.29871 16.0431C5.29871 15.4725 5.40746 14.9019 5.62308 14.3625C5.82371 13.89 6.15433 13.4856 6.57808 13.1937C6.99808 12.9144 7.49058 12.765 7.99371 12.765C8.01808 12.7675 8.04246 12.7681 8.06746 12.7694ZM7.94683 11.1825C7.09121 11.1825 6.24746 11.3931 5.49183 11.7969C4.77496 12.2087 4.19621 12.8262 3.83371 13.5694L3.83308 13.5844C3.46121 14.3594 3.26746 15.2081 3.26746 16.0675C3.26746 16.1006 3.26746 16.1344 3.26808 16.1669C3.26746 16.195 3.26746 16.2237 3.26746 16.2519C3.26746 17.0775 3.46371 17.8912 3.84058 18.6269C4.21308 19.3362 4.77933 19.9244 5.47496 20.3231C6.26371 20.7325 7.12871 20.9375 7.99183 20.9375C8.85496 20.9375 9.71996 20.7325 10.5087 20.3231C11.2256 19.92 11.8062 19.3106 12.1743 18.5731C12.5618 17.7944 12.7637 16.9369 12.7637 16.0681C12.7637 16.035 12.7637 16.0012 12.7631 15.9675C12.7637 15.9344 12.7637 15.9 12.7637 15.8669C12.7637 15.0375 12.5693 14.2194 12.1975 13.4781C11.8318 12.7731 11.2693 12.1887 10.5781 11.7975C9.86183 11.395 9.05496 11.1844 8.23558 11.1844C8.20121 11.1844 8.16808 11.1844 8.13371 11.185C8.07246 11.1831 8.00996 11.1825 7.94683 11.1825Z" fill="white"/>
<path d="M3.83429 13.5694C4.19616 12.8256 4.77491 12.2088 5.49241 11.7969C6.30366 11.3631 7.21616 11.1525 8.13554 11.1856C8.99054 11.1675 9.83491 11.3788 10.5799 11.7969C11.2705 12.1881 11.8337 12.7725 12.1993 13.4775C12.5862 14.2488 12.7805 15.1038 12.7637 15.9675C12.7793 16.8706 12.578 17.765 12.1755 18.5725C11.8074 19.3088 11.2268 19.9181 10.5099 20.3225C8.93179 21.1413 7.05304 21.1413 5.47491 20.3225C4.77991 19.9238 4.21241 19.3356 3.84054 18.6263C3.45054 17.865 3.25304 17.0213 3.26741 16.1663C3.25241 15.2738 3.44616 14.3894 3.83179 13.5844L3.83429 13.5694ZM5.62179 17.7263C5.81366 18.195 6.13741 18.5988 6.55366 18.8881C6.98304 19.1813 7.49429 19.33 8.01241 19.3163C8.55491 19.3344 9.08929 19.1819 9.53991 18.8806C9.95366 18.5913 10.2693 18.1838 10.4493 17.7119C10.648 17.1919 10.7455 16.6406 10.7393 16.085C10.7474 15.5231 10.6537 14.9638 10.4643 14.435C10.2987 13.9556 9.99616 13.5363 9.59366 13.2288C9.14804 12.9131 8.61179 12.7519 8.06616 12.7713C7.53741 12.7563 7.01679 12.9056 6.57679 13.1994C6.15429 13.4913 5.82304 13.8956 5.62179 14.3681C5.19054 15.4431 5.19054 16.6469 5.62179 17.7263Z" fill="url(#paint10_linear_881_28190)"/>
<path d="M3.83429 13.5694C4.19616 12.8256 4.77491 12.2088 5.49241 11.7969C6.30366 11.3631 7.21616 11.1525 8.13554 11.1856C8.99054 11.1675 9.83491 11.3788 10.5799 11.7969C11.2705 12.1881 11.8337 12.7725 12.1993 13.4775C12.5862 14.2488 12.7805 15.1038 12.7637 15.9675C12.7793 16.8706 12.578 17.765 12.1755 18.5725C11.8074 19.3088 11.2268 19.9181 10.5099 20.3225C8.93179 21.1413 7.05304 21.1413 5.47491 20.3225C4.77991 19.9238 4.21241 19.3356 3.84054 18.6263C3.45054 17.865 3.25304 17.0213 3.26741 16.1663C3.25241 15.2738 3.44616 14.3894 3.83179 13.5844L3.83429 13.5694ZM5.62179 17.7263C5.81366 18.195 6.13741 18.5988 6.55366 18.8881C6.98304 19.1813 7.49429 19.33 8.01241 19.3163C8.55491 19.3344 9.08929 19.1819 9.53991 18.8806C9.95366 18.5913 10.2693 18.1838 10.4493 17.7119C10.648 17.1919 10.7455 16.6406 10.7393 16.085C10.7474 15.5231 10.6537 14.9638 10.4643 14.435C10.2987 13.9556 9.99616 13.5363 9.59366 13.2288C9.14804 12.9131 8.61179 12.7519 8.06616 12.7713C7.53741 12.7563 7.01679 12.9056 6.57679 13.1994C6.15429 13.4913 5.82304 13.8956 5.62179 14.3681C5.19054 15.4431 5.19054 16.6469 5.62179 17.7263Z" fill="url(#paint11_linear_881_28190)"/>
</g>
<defs>
<linearGradient id="paint0_linear_881_28190" x1="31.6856" y1="22.5783" x2="21.5863" y2="34.1488" gradientUnits="userSpaceOnUse">
<stop stop-color="#35B8F1"/>
<stop offset="0.75" stop-color="#0D64AD"/>
<stop offset="1" stop-color="#0D64AD"/>
</linearGradient>
<linearGradient id="paint1_linear_881_28190" x1="27.5733" y1="18.9888" x2="17.4739" y2="30.5593" gradientUnits="userSpaceOnUse">
<stop stop-color="#1F6FAC"/>
<stop offset="0.75" stop-color="#0B458A"/>
<stop offset="1" stop-color="#0B458A"/>
</linearGradient>
<linearGradient id="paint2_linear_881_28190" x1="26.5469" y1="18.093" x2="16.4475" y2="29.6634" gradientUnits="userSpaceOnUse">
<stop stop-color="#1E6BA6"/>
<stop offset="0.75" stop-color="#0B4385"/>
<stop offset="1" stop-color="#0B4385"/>
</linearGradient>
<linearGradient id="paint3_linear_881_28190" x1="26.389" y1="17.9563" x2="22.4751" y2="22.4334" gradientUnits="userSpaceOnUse">
<stop stop-color="#1C649B"/>
<stop offset="0.75" stop-color="#0A3E7D"/>
<stop offset="1" stop-color="#0A3E7D"/>
</linearGradient>
<linearGradient id="paint4_linear_881_28190" x1="26.3323" y1="17.9055" x2="16.233" y2="29.4761" gradientUnits="userSpaceOnUse">
<stop stop-color="#1A5E91"/>
<stop offset="0.75" stop-color="#093A75"/>
<stop offset="1" stop-color="#093A75"/>
</linearGradient>
<linearGradient id="paint5_linear_881_28190" x1="26.1379" y1="17.736" x2="16.0385" y2="29.3064" gradientUnits="userSpaceOnUse">
<stop stop-color="#185788"/>
<stop offset="0.75" stop-color="#09366D"/>
<stop offset="1" stop-color="#09366D"/>
</linearGradient>
<linearGradient id="paint6_linear_881_28190" x1="25.9178" y1="17.5439" x2="15.8185" y2="29.1143" gradientUnits="userSpaceOnUse">
<stop stop-color="#17527F"/>
<stop offset="0.75" stop-color="#083366"/>
<stop offset="1" stop-color="#083366"/>
</linearGradient>
<linearGradient id="paint7_linear_881_28190" x1="25.7658" y1="17.411" x2="15.6663" y2="28.9816" gradientUnits="userSpaceOnUse">
<stop stop-color="#13456B"/>
<stop offset="0.75" stop-color="#072B56"/>
<stop offset="1" stop-color="#072B56"/>
</linearGradient>
<linearGradient id="paint8_linear_881_28190" x1="25.6445" y1="17.3053" x2="15.5453" y2="28.8757" gradientUnits="userSpaceOnUse">
<stop stop-color="#113E60"/>
<stop offset="0.75" stop-color="#06274D"/>
<stop offset="1" stop-color="#06274D"/>
</linearGradient>
<linearGradient id="paint9_linear_881_28190" x1="18.8226" y1="19.7105" x2="15.3633" y2="23.6828" gradientUnits="userSpaceOnUse">
<stop stop-color="#0B283F"/>
<stop offset="0.75" stop-color="#041932"/>
<stop offset="1" stop-color="#041932"/>
</linearGradient>
<linearGradient id="paint10_linear_881_28190" x1="8.01577" y1="12.7522" x2="8.01577" y2="22.5296" gradientUnits="userSpaceOnUse">
<stop stop-color="#F2F2F2"/>
<stop offset="1" stop-color="white"/>
</linearGradient>
<linearGradient id="paint11_linear_881_28190" x1="8.01577" y1="12.7522" x2="8.01577" y2="22.5296" gradientUnits="userSpaceOnUse">
<stop stop-color="#F2F2F2"/>
<stop offset="1" stop-color="white"/>
</linearGradient>
<clipPath id="clip0_881_28190">
<rect width="32" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 38 KiB

11
packages/nc-gui/assets/nc-icons/miro.svg

@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<g clip-path="url(#clip0_881_28372)">
<path d="M0 0H32V32H0V0Z" fill="#FFD02F"/>
<path d="M21.8054 4.26666H18.3894L21.422 8.96666L15.0107 4.26666H11.5947L14.6274 10.4253L8.21605 4.26666H4.80005L7.78272 12.2173L4.80005 27.7333H8.21605L14.6274 11.0967L11.5947 27.7333H15.0107L21.422 9.63866L18.3894 27.7333H21.8054L28.2667 7.85666L21.8054 4.26666Z" fill="#050038"/>
</g>
<defs>
<clipPath id="clip0_881_28372">
<rect width="32" height="32" rx="4" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 611 B

20
packages/nc-gui/assets/nc-icons/salesforce.svg

@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="24" viewBox="0 0 32 24" fill="none">
<g clip-path="url(#clip0_881_28380)">
<path d="M13.3191 3.20641C14.3501 2.13216 15.7855 1.46593 17.373 1.46593C19.4832 1.46593 21.3243 2.64265 22.3048 4.3895C23.1568 4.0088 24.0999 3.79705 25.0922 3.79705C28.8983 3.79705 31.984 6.9096 31.984 10.7489C31.984 14.5888 28.8983 17.7013 25.0922 17.7013C24.6277 17.7013 24.1737 17.6549 23.7347 17.5661C22.8713 19.1062 21.226 20.1467 19.3375 20.1467C18.547 20.1467 17.7992 19.9641 17.1334 19.6394C16.2582 21.6982 14.219 23.1418 11.8423 23.1418C9.36731 23.1418 7.25797 21.5757 6.4483 19.3794C6.09447 19.4545 5.72788 19.4937 5.35173 19.4937C2.40494 19.4937 0.0159912 17.0802 0.0159912 14.1024C0.0159912 12.1069 1.08933 10.3646 2.68409 9.43243C2.35576 8.67695 2.17315 7.84314 2.17315 6.96652C2.17315 3.54203 4.95327 0.766006 8.38232 0.766006C10.3956 0.766006 12.1848 1.72322 13.3191 3.20641Z" fill="#00A1E0"/>
<path d="M4.64626 12.3695C4.62623 12.4219 4.65355 12.4328 4.65992 12.4419C4.72004 12.4856 4.78106 12.517 4.84253 12.5521C5.16859 12.7251 5.47643 12.7757 5.79839 12.7757C6.45414 12.7757 6.86125 12.4269 6.86125 11.8654V11.8545C6.86125 11.3353 6.40177 11.1468 5.97052 11.0106L5.91451 10.9924C5.58936 10.8868 5.30885 10.7957 5.30885 10.5817V10.5703C5.30885 10.3872 5.47279 10.2524 5.72689 10.2524C6.00923 10.2524 6.34439 10.3462 6.56024 10.4655C6.56024 10.4655 6.62354 10.5065 6.64677 10.445C6.65952 10.4122 6.76881 10.1181 6.78019 10.0862C6.79249 10.0516 6.77063 10.0261 6.74832 10.0124C6.50196 9.8626 6.16133 9.76014 5.80886 9.76014L5.74328 9.76059C5.14309 9.76059 4.72413 10.1231 4.72413 10.6427V10.6536C4.72413 11.2014 5.18635 11.379 5.61942 11.5029L5.68909 11.5243C6.00468 11.6213 6.27654 11.7046 6.27654 11.9269V11.9378C6.27654 12.1409 6.09985 12.2921 5.81478 12.2921C5.70412 12.2921 5.3512 12.2898 4.97004 12.0489C4.92405 12.022 4.89718 12.0025 4.86166 11.981C4.84299 11.9692 4.79608 11.9487 4.77559 12.0106L4.64626 12.3695Z" fill="white"/>
<path d="M14.2462 12.3695C14.2262 12.4219 14.2535 12.4328 14.2599 12.4419C14.32 12.4856 14.381 12.517 14.4425 12.5521C14.7686 12.7251 15.0764 12.7757 15.3984 12.7757C16.0541 12.7757 16.4612 12.4269 16.4612 11.8654V11.8545C16.4612 11.3353 16.0017 11.1468 15.5705 11.0106L15.5145 10.9924C15.1893 10.8868 14.9088 10.7957 14.9088 10.5817V10.5703C14.9088 10.3872 15.0728 10.2524 15.3269 10.2524C15.6092 10.2524 15.9444 10.3462 16.1602 10.4655C16.1602 10.4655 16.2235 10.5065 16.2467 10.445C16.2595 10.4122 16.3688 10.1181 16.3802 10.0862C16.3925 10.0516 16.3706 10.0261 16.3483 10.0124C16.1019 9.8626 15.7613 9.76014 15.4088 9.76014L15.3433 9.76059C14.7431 9.76059 14.3241 10.1231 14.3241 10.6427V10.6536C14.3241 11.2014 14.7863 11.379 15.2194 11.5029L15.2891 11.5243C15.6047 11.6213 15.877 11.7046 15.877 11.9269V11.9378C15.877 12.1409 15.6998 12.2921 15.4148 12.2921C15.3041 12.2921 14.9512 12.2898 14.57 12.0489C14.524 12.022 14.4967 12.0034 14.4621 11.981C14.4503 11.9733 14.3947 11.9519 14.3756 12.0106L14.2462 12.3695Z" fill="white"/>
<path d="M20.7999 11.2697C20.7999 11.5871 20.7407 11.8371 20.6241 12.0138C20.5089 12.1886 20.3345 12.2738 20.0913 12.2738C19.8477 12.2738 19.6741 12.1891 19.5608 12.0138C19.446 11.8375 19.3877 11.5871 19.3877 11.2697C19.3877 10.9527 19.446 10.7032 19.5608 10.5283C19.6741 10.3552 19.8477 10.271 20.0913 10.271C20.3345 10.271 20.5089 10.3552 20.6245 10.5283C20.7407 10.7032 20.7999 10.9527 20.7999 11.2697ZM21.3472 10.6813C21.2935 10.4996 21.2097 10.3393 21.0981 10.2059C20.9866 10.072 20.8454 9.96452 20.6778 9.8862C20.5107 9.80832 20.3131 9.76871 20.0913 9.76871C19.8691 9.76871 19.6714 9.80832 19.5043 9.8862C19.3367 9.96452 19.1955 10.072 19.0835 10.2059C18.9724 10.3398 18.8886 10.5001 18.8344 10.6813C18.7811 10.8621 18.7543 11.0597 18.7543 11.2697C18.7543 11.4796 18.7811 11.6777 18.8344 11.858C18.8886 12.0393 18.9719 12.1995 19.084 12.3334C19.1955 12.4673 19.3372 12.5743 19.5043 12.6504C19.6719 12.7264 19.8691 12.7651 20.0913 12.7651C20.3131 12.7651 20.5102 12.7264 20.6778 12.6504C20.8449 12.5743 20.9866 12.4673 21.0981 12.3334C21.2097 12.2 21.2935 12.0397 21.3472 11.858C21.401 11.6772 21.4278 11.4791 21.4278 11.2697C21.4278 11.0602 21.401 10.8621 21.3472 10.6813Z" fill="white"/>
<path d="M25.8422 12.189C25.824 12.1358 25.7726 12.1558 25.7726 12.1558C25.6929 12.1863 25.6082 12.2145 25.518 12.2287C25.4265 12.2428 25.3258 12.2501 25.2179 12.2501C24.9529 12.2501 24.7425 12.1713 24.5917 12.0155C24.4406 11.8598 24.3559 11.608 24.3568 11.2673C24.3577 10.9572 24.4324 10.7241 24.5667 10.5465C24.7001 10.3698 24.9032 10.2792 25.1742 10.2792C25.4 10.2792 25.5722 10.3051 25.7525 10.362C25.7525 10.362 25.7958 10.3807 25.8163 10.3242C25.8641 10.1913 25.8996 10.0961 25.9506 9.94991C25.9652 9.90847 25.9297 9.89071 25.9169 9.8857C25.8459 9.85792 25.6783 9.81284 25.5517 9.79371C25.4333 9.7755 25.2949 9.76594 25.1409 9.76594C24.911 9.76594 24.706 9.8051 24.5307 9.88342C24.3559 9.9613 24.2074 10.0688 24.0899 10.2027C23.9724 10.3365 23.8832 10.4968 23.8235 10.6781C23.7643 10.8589 23.7343 11.0574 23.7343 11.2673C23.7343 11.7214 23.8568 12.0884 24.0986 12.3571C24.3408 12.6267 24.7047 12.7637 25.1792 12.7637C25.4597 12.7637 25.7475 12.7068 25.9543 12.6253C25.9543 12.6253 25.9939 12.6062 25.9766 12.5602L25.8422 12.189Z" fill="white"/>
<path d="M26.7999 10.9656C26.8259 10.7894 26.8746 10.6427 26.9497 10.5284C27.0631 10.3549 27.2362 10.2598 27.4793 10.2598C27.7225 10.2598 27.8833 10.3554 27.9985 10.5284C28.075 10.6427 28.1082 10.7957 28.1214 10.9656H26.7999ZM28.6428 10.5781C28.5964 10.4028 28.4812 10.2256 28.4056 10.1445C28.2863 10.0161 28.1697 9.92642 28.054 9.87633C27.9028 9.81166 27.7216 9.76886 27.5231 9.76886C27.2917 9.76886 27.0818 9.80756 26.9115 9.88771C26.7407 9.96786 26.5973 10.0772 26.4848 10.2133C26.3723 10.349 26.2876 10.5107 26.2339 10.6942C26.1797 10.8768 26.1523 11.0758 26.1523 11.2857C26.1523 11.4993 26.1806 11.6983 26.2366 11.8773C26.2931 12.0576 26.3832 12.2165 26.5053 12.3482C26.6269 12.4807 26.7835 12.5845 26.9711 12.6569C27.1574 12.7289 27.3837 12.7662 27.6437 12.7657C28.1788 12.7639 28.4607 12.6446 28.5768 12.5804C28.5973 12.569 28.6169 12.549 28.5923 12.4916L28.4712 12.1523C28.4529 12.1018 28.4015 12.1205 28.4015 12.1205C28.269 12.1696 28.0804 12.258 27.641 12.2571C27.3536 12.2566 27.1405 12.1719 27.0071 12.0394C26.87 11.9037 26.8031 11.7042 26.7912 11.4228L28.6442 11.4246C28.6442 11.4246 28.6929 11.4237 28.6979 11.3764C28.6998 11.3563 28.7617 10.9957 28.6428 10.5781Z" fill="white"/>
<path d="M11.9604 10.9656C11.9868 10.7894 12.0351 10.6427 12.1102 10.5284C12.2236 10.3549 12.3967 10.2598 12.6399 10.2598C12.883 10.2598 13.0438 10.3554 13.1595 10.5284C13.2355 10.6427 13.2687 10.7957 13.2819 10.9656H11.9604ZM13.8029 10.5781C13.7565 10.4028 13.6417 10.2256 13.5661 10.1445C13.4468 10.0161 13.3302 9.92642 13.2146 9.87633C13.0634 9.81166 12.8821 9.76886 12.6836 9.76886C12.4527 9.76886 12.2423 9.80756 12.072 9.88771C11.9012 9.96786 11.7578 10.0772 11.6453 10.2133C11.5328 10.349 11.4481 10.5107 11.3944 10.6942C11.3406 10.8768 11.3129 11.0758 11.3129 11.2857C11.3129 11.4993 11.3411 11.6983 11.3971 11.8773C11.4536 12.0576 11.5437 12.2165 11.6658 12.3482C11.7874 12.4807 11.944 12.5845 12.1316 12.6569C12.3179 12.7289 12.5442 12.7662 12.8043 12.7657C13.3393 12.7639 13.6212 12.6446 13.7373 12.5804C13.7578 12.569 13.7774 12.549 13.7528 12.4916L13.6321 12.1523C13.6135 12.1018 13.562 12.1205 13.562 12.1205C13.4295 12.1696 13.2414 12.258 12.8011 12.2571C12.5142 12.2566 12.3011 12.1719 12.1676 12.0394C12.0306 11.9037 11.9636 11.7042 11.9518 11.4228L13.8047 11.4246C13.8047 11.4246 13.8535 11.4237 13.8585 11.3764C13.8603 11.3563 13.9222 10.9957 13.8029 10.5781Z" fill="white"/>
<path d="M7.95515 12.1789C7.88275 12.1211 7.87273 12.1065 7.84814 12.0692C7.81171 12.0122 7.79304 11.9312 7.79304 11.8283C7.79304 11.6652 7.84677 11.5482 7.95834 11.4694C7.95697 11.4699 8.11772 11.3305 8.49569 11.3355C8.76118 11.3392 8.99844 11.3783 8.99844 11.3783V12.2208H8.99889C8.99889 12.2208 8.76346 12.2713 8.49843 12.2873C8.12137 12.3101 7.95379 12.1785 7.95515 12.1789ZM8.69242 10.877C8.61728 10.8715 8.51983 10.8683 8.40325 10.8683C8.24432 10.8683 8.09086 10.8883 7.94695 10.9271C7.80214 10.9658 7.6719 11.0263 7.55988 11.1065C7.4474 11.1871 7.35678 11.29 7.2912 11.412C7.22563 11.5341 7.19238 11.678 7.19238 11.8392C7.19238 12.0031 7.22062 12.1457 7.27708 12.2622C7.33355 12.3793 7.41507 12.4767 7.51889 12.5519C7.62181 12.627 7.74886 12.6821 7.89641 12.7153C8.04167 12.7486 8.20652 12.7654 8.38686 12.7654C8.57675 12.7654 8.76619 12.75 8.94971 12.7185C9.13141 12.6876 9.35455 12.6425 9.41648 12.6284C9.47796 12.6138 9.54627 12.5951 9.54627 12.5951C9.59226 12.5837 9.58862 12.5346 9.58862 12.5346L9.58771 10.8401C9.58771 10.4685 9.48843 10.193 9.29307 10.0222C9.09862 9.85189 8.81219 9.76582 8.44196 9.76582C8.30307 9.76582 8.07947 9.78495 7.94559 9.81182C7.94559 9.81182 7.54075 9.89014 7.37408 10.0204C7.37408 10.0204 7.33765 10.0432 7.35769 10.0942L7.48884 10.4466C7.50523 10.4922 7.5494 10.4767 7.5494 10.4767C7.5494 10.4767 7.56352 10.4712 7.57992 10.4617C7.93648 10.2677 8.38731 10.2736 8.38731 10.2736C8.58768 10.2736 8.7416 10.3137 8.84543 10.3933C8.94652 10.4708 8.99798 10.5878 8.99798 10.8346V10.9129C8.8386 10.8902 8.69242 10.877 8.69242 10.877Z" fill="white"/>
<path d="M23.6374 9.92216C23.6515 9.88027 23.6219 9.86023 23.6096 9.85568C23.5782 9.84338 23.4206 9.81014 23.299 9.8024C23.0663 9.78828 22.937 9.82744 22.8213 9.87936C22.7066 9.93127 22.5791 10.0151 22.508 10.1102V9.88482C22.508 9.8534 22.4857 9.82835 22.4548 9.82835H21.9798C21.9488 9.82835 21.9265 9.8534 21.9265 9.88482V12.6486C21.9265 12.6795 21.952 12.705 21.983 12.705H22.4698C22.5008 12.705 22.5258 12.6795 22.5258 12.6486V11.2678C22.5258 11.0825 22.5463 10.8976 22.5873 10.7815C22.6274 10.6667 22.682 10.5747 22.7494 10.5087C22.8172 10.4431 22.8942 10.3971 22.9785 10.3712C23.0645 10.3448 23.1597 10.3361 23.2271 10.3361C23.3241 10.3361 23.4306 10.3612 23.4306 10.3612C23.4662 10.3653 23.4862 10.3434 23.498 10.3111C23.5299 10.2264 23.6201 9.97271 23.6374 9.92216Z" fill="white"/>
<path d="M19.068 8.64127C19.0088 8.62305 18.955 8.61076 18.8849 8.59755C18.8139 8.5848 18.7292 8.57842 18.6331 8.57842C18.2979 8.57842 18.0338 8.67314 17.8484 8.85985C17.664 9.04565 17.5388 9.32844 17.4759 9.70049L17.4532 9.82572H17.0324C17.0324 9.82572 16.9814 9.8239 16.9705 9.87946L16.9017 10.2652C16.8967 10.3016 16.9126 10.3248 16.9618 10.3248H17.3712L16.9559 12.6436C16.9236 12.8303 16.8862 12.9838 16.8448 13.1004C16.8042 13.2151 16.7646 13.3012 16.7154 13.3641C16.6681 13.4242 16.6235 13.4688 16.546 13.4948C16.4823 13.5162 16.4085 13.5262 16.3279 13.5262C16.2833 13.5262 16.2236 13.5189 16.1795 13.5098C16.1357 13.5011 16.1125 13.4916 16.0793 13.4775C16.0793 13.4775 16.0315 13.4592 16.0123 13.5071C15.9973 13.5467 15.888 13.8468 15.8748 13.8837C15.8621 13.9205 15.8803 13.9492 15.9035 13.9579C15.9581 13.977 15.9987 13.9898 16.0729 14.0075C16.1758 14.0317 16.2628 14.033 16.3443 14.033C16.5146 14.033 16.6704 14.0089 16.7992 13.9624C16.9286 13.9155 17.0415 13.834 17.1417 13.7238C17.2496 13.6045 17.3175 13.4797 17.3821 13.309C17.4463 13.1405 17.5014 12.931 17.5452 12.6869L17.9627 10.3248H18.573C18.573 10.3248 18.6244 10.3266 18.6349 10.2706L18.7041 9.88538C18.7087 9.84849 18.6932 9.82572 18.6435 9.82572H18.0511C18.0543 9.81252 18.0811 9.60395 18.149 9.40768C18.1781 9.32434 18.2328 9.25649 18.2788 9.21004C18.3243 9.1645 18.3767 9.13217 18.4341 9.1135C18.4928 9.09437 18.5598 9.08527 18.6331 9.08527C18.6886 9.08527 18.7437 9.09164 18.7852 9.10029C18.8425 9.11259 18.8649 9.11896 18.8799 9.12352C18.9405 9.14173 18.9487 9.12397 18.9605 9.09483L19.1021 8.70593C19.1167 8.66404 19.0807 8.64628 19.068 8.64127Z" fill="white"/>
<path d="M10.7902 12.6487C10.7902 12.6797 10.7679 12.7047 10.7369 12.7047H10.2456C10.2146 12.7047 10.1927 12.6797 10.1927 12.6487V8.69414C10.1927 8.66317 10.2146 8.63813 10.2456 8.63813H10.7369C10.7679 8.63813 10.7902 8.66317 10.7902 8.69414V12.6487Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_881_28380">
<rect width="32" height="22.5" fill="white" transform="translate(0 0.75)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

6
packages/nc-gui/assets/nc-icons/server1.svg

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M20 14H4C2.89543 14 2 14.8954 2 16V20C2 21.1046 2.89543 22 4 22H20C21.1046 22 22 21.1046 22 20V16C22 14.8954 21.1046 14 20 14Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 18H6.01" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M20 2H4C2.89543 2 2 2.89543 2 4V8C2 9.10457 2.89543 10 4 10H20C21.1046 10 22 9.10457 22 8V4C22 2.89543 21.1046 2 20 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 6H6.01" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 769 B

10
packages/nc-gui/assets/nc-icons/snowflake.svg

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<g clip-path="url(#clip0_881_28398)">
<path d="M12.5265 20.2112C13.5452 20.2836 14.3524 21.118 14.3735 22.1415L14.3739 22.1825V29.9444C14.3739 31.0383 13.4782 31.9246 12.3783 31.9246C11.2864 31.9246 10.4017 31.0595 10.3805 29.9838L10.3801 29.9444V25.6025L6.57913 27.7754C5.62605 28.325 4.40713 27.9997 3.85373 27.0549C3.31002 26.12 3.62049 24.925 4.54641 24.3681L4.58327 24.3465L11.3676 20.4697C11.732 20.2594 12.137 20.1783 12.5265 20.2112ZM20.583 20.443L20.6308 20.4697L27.4138 24.3465C28.3696 24.8935 28.6939 26.1077 28.1445 27.0549C27.601 27.9874 26.4051 28.3164 25.4566 27.7964L25.4193 27.7754L21.6208 25.6025V29.9444C21.6208 31.0383 20.7279 31.9246 19.6227 31.9246C18.5308 31.9246 17.6483 31.0595 17.6272 29.9838L17.6268 29.9444V22.1825C17.6268 21.1404 18.4396 20.2846 19.4743 20.2112C19.8457 20.1797 20.2314 20.2525 20.583 20.443ZM2.95893 10.3762L2.99635 10.397L9.77563 14.2763C10.2523 14.5487 10.5704 14.9808 10.7047 15.4644C10.7493 15.6201 10.7696 15.7757 10.7775 15.9315C10.7826 16.1466 10.7544 16.3644 10.6866 16.5759C10.5483 17.0201 10.2536 17.4173 9.82089 17.6789L9.77563 17.7055L2.99635 21.5874C2.03934 22.1333 0.819264 21.8103 0.270023 20.8657C-0.276284 19.9284 0.0343468 18.7392 0.963074 18.1814L0.99957 18.16L4.78785 15.9947L0.99957 13.8234C0.0425596 13.2761 -0.284535 12.0684 0.270023 11.1227C0.812131 10.188 2.00776 9.85963 2.95893 10.3762ZM31.7309 11.1227C32.2841 12.0684 31.957 13.2761 31.0002 13.8234L27.2117 15.9947L31.0002 18.16C31.957 18.7082 32.2841 19.9162 31.7309 20.8657C31.1789 21.8103 29.9577 22.1333 29.0044 21.5874L22.22 17.7055C21.7676 17.4446 21.4535 17.0354 21.3141 16.5759C21.2477 16.3644 21.217 16.1466 21.2246 15.9315C21.2286 15.7757 21.2514 15.6201 21.2949 15.4644C21.4306 14.9808 21.7487 14.5489 22.22 14.2763L29.0044 10.397C29.9577 9.85109 31.1789 10.1757 31.7309 11.1227ZM16.4132 12.2925C16.5281 12.2925 16.6865 12.3571 16.7671 12.4393L19.5868 15.23C19.6673 15.3097 19.7325 15.468 19.7325 15.5821V16.405C19.7325 16.5166 19.6673 16.6746 19.5868 16.7532L16.7671 19.5442C16.6865 19.6262 16.5306 19.6933 16.4132 19.6933H15.584C15.4701 19.6933 15.3117 19.6262 15.2299 19.5442L12.4116 16.7532C12.3309 16.6746 12.2657 16.5166 12.2657 16.405V15.5821C12.2657 15.468 12.3309 15.3097 12.4116 15.23L15.2299 12.4393C15.3117 12.3571 15.4701 12.2925 15.584 12.2925H16.4132ZM16.0142 14.6743H15.9826C15.8688 14.6743 15.7105 14.7402 15.6312 14.8197L14.8147 15.6252C14.7329 15.7086 14.6703 15.8657 14.6703 15.9784V16.0101C14.6703 16.1214 14.7329 16.2772 14.8147 16.3583L15.6312 17.166C15.7116 17.2457 15.8688 17.3115 15.9826 17.3115H16.0142C16.1282 17.3115 16.2868 17.2457 16.3672 17.166L17.1837 16.3583C17.2641 16.2772 17.333 16.1214 17.333 16.0101V15.9784C17.333 15.8657 17.2641 15.7086 17.1837 15.6252L16.3672 14.8197C16.2868 14.7402 16.1282 14.6743 16.0142 14.6743ZM19.6227 0.0625C20.7145 0.0625 21.5993 0.927826 21.6204 2.00063L21.6208 2.03997V6.3832L25.4193 4.2078C26.3738 3.66097 27.5938 3.98536 28.1445 4.93079C28.6868 5.86825 28.3779 7.05833 27.4507 7.61532L27.4138 7.63698L20.6308 11.5175C20.2665 11.7252 19.8626 11.8075 19.4743 11.7756C18.4532 11.701 17.6482 10.8652 17.6272 9.84283L17.6268 9.80184V2.03997C17.6268 0.94906 18.5175 0.0625 19.6227 0.0625ZM12.3783 0.0625C13.4649 0.0625 14.3523 0.927826 14.3735 2.00063L14.3739 2.03997V9.80184C14.3739 10.8428 13.5588 11.7 12.5265 11.7756C12.1539 11.8061 11.7671 11.7322 11.4154 11.5438L11.3676 11.5175L4.58327 7.63698C3.62881 7.08969 3.30287 5.88058 3.85373 4.93079C4.39994 3.99764 5.5945 3.6695 6.54186 4.18694L6.57913 4.2078L10.3801 6.3832V2.03997C10.3801 0.94906 11.2731 0.0625 12.3783 0.0625Z" fill="#29B5E8"/>
</g>
<defs>
<clipPath id="clip0_881_28398">
<rect width="32" height="31.875" fill="white" transform="translate(0 0.0625)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

11
packages/nc-gui/assets/nc-icons/stripe.svg

@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<g clip-path="url(#clip0_881_28406)">
<path d="M28.8 0H3.2C1.43269 0 0 1.43269 0 3.2V28.8C0 30.5673 1.43269 32 3.2 32H28.8C30.5673 32 32 30.5673 32 28.8V3.2C32 1.43269 30.5673 0 28.8 0Z" fill="#6772E5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.5707 11.968C14.5707 11.136 15.2533 10.816 16.384 10.816C18.0053 10.816 20.0533 11.3066 21.6747 12.1813V7.16798C19.904 6.46398 18.1547 6.18665 16.384 6.18665C12.0533 6.18665 9.17334 8.44798 9.17334 12.224C9.17334 18.112 17.28 17.1733 17.28 19.712C17.28 20.6933 16.4267 21.0133 15.232 21.0133C13.4613 21.0133 11.2 20.288 9.40801 19.3066V24.384C11.392 25.2373 13.3973 25.6 15.232 25.6C19.6693 25.6 22.72 23.4026 22.72 19.584C22.6987 13.2266 14.5707 14.3573 14.5707 11.968Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_881_28406">
<rect width="32" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 983 B

32
packages/nc-gui/assets/nc-icons/survey_monkey.svg

@ -0,0 +1,32 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<g clip-path="url(#clip0_881_28414)">
<mask id="mask0_881_28414" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="-1" y="0" width="33" height="33">
<path d="M15.9442 32.0173C24.7691 32.0173 31.923 24.8633 31.923 16.0385C31.923 7.21364 24.7691 0.0596924 15.9442 0.0596924C7.1194 0.0596924 -0.0345459 7.21364 -0.0345459 16.0385C-0.0345459 24.8633 7.1194 32.0173 15.9442 32.0173Z" fill="white"/>
</mask>
<g mask="url(#mask0_881_28414)">
<mask id="mask1_881_28414" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="-1" y="0" width="33" height="33">
<path d="M-0.017334 0.0596924H31.9395V32.0165H-0.0169744L-0.017334 0.0596924Z" fill="white"/>
</mask>
<g mask="url(#mask1_881_28414)">
<path d="M15.9615 32.0173C24.7863 32.0173 31.9399 24.8633 31.9399 16.0389C31.9399 7.21404 24.7863 0.0604248 15.9615 0.0604248C7.13665 0.0604248 -0.0169678 7.21368 -0.0169678 16.0385C-0.0169678 24.8626 7.13665 32.0173 15.9615 32.0173Z" fill="#DDDDDC"/>
</g>
</g>
<mask id="mask2_881_28414" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="-1" y="0" width="33" height="33">
<path d="M15.9442 32.0173C24.7691 32.0173 31.923 24.8633 31.923 16.0385C31.923 7.21364 24.7691 0.0596924 15.9442 0.0596924C7.1194 0.0596924 -0.0345459 7.21364 -0.0345459 16.0385C-0.0345459 24.8633 7.1194 32.0173 15.9442 32.0173Z" fill="white"/>
</mask>
<g mask="url(#mask2_881_28414)">
<path d="M-25.2682 -17.4803H57.3015V44.4476H-25.2678L-25.2682 -17.4803Z" fill="#BDCF31"/>
</g>
<mask id="mask3_881_28414" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="-1" y="0" width="33" height="33">
<path d="M15.9442 32.0173C24.7691 32.0173 31.923 24.8633 31.923 16.0385C31.923 7.21364 24.7691 0.0596924 15.9442 0.0596924C7.1194 0.0596924 -0.0345459 7.21364 -0.0345459 16.0385C-0.0345459 24.8633 7.1194 32.0173 15.9442 32.0173Z" fill="white"/>
</mask>
<g mask="url(#mask3_881_28414)">
<path d="M25.9084 14.4205C25.6553 14.4205 25.405 14.4554 25.1692 14.5125C24.2379 10.8257 21.1559 7.99964 17.3382 7.43478C17.2806 6.79119 17.5269 5.78193 19.1366 4.80934L19.0885 4.71478C19.0885 4.71478 16.3925 5.56764 15.7831 7.25105C15.8097 6.72539 15.6184 5.93582 14.4477 5.3742C14.4477 5.3742 14.3553 5.40584 14.4021 5.44827C14.6343 5.66292 15.3739 6.37842 15.0601 7.37474C11.0007 7.744 7.67848 10.661 6.70482 14.5133C6.46298 14.4531 6.21479 14.4222 5.96558 14.4212C4.26203 14.4212 2.87524 15.8048 2.87524 17.5133C2.87524 19.2187 4.26239 20.6044 5.96558 20.6044C6.36468 20.6044 6.74509 20.5253 7.09457 20.3879C7.35804 21.0465 7.6949 21.6732 8.09879 22.2562L10.6753 20.5188L10.6423 20.498C9.96378 19.6261 9.54922 18.3421 9.47947 17.0643C9.40252 15.6548 9.74626 14.2551 10.6462 13.4321C12.5015 11.8526 14.5214 12.5728 15.7878 14.0865H16.1258C17.3921 12.6026 19.3894 11.913 21.2299 13.4752C22.118 14.2928 22.4654 15.6685 22.3978 17.0592C22.3366 18.3493 21.9196 19.6537 21.2382 20.5318L23.7781 22.2558C24.1801 21.672 24.5161 21.0453 24.7798 20.3872C25.1293 20.5239 25.509 20.6037 25.9077 20.6037C27.6156 20.6037 28.9987 19.2176 28.9987 17.5115C28.9989 17.1056 28.9191 16.7036 28.7639 16.3285C28.6086 15.9533 28.381 15.6125 28.094 15.3254C27.8069 15.0383 27.4662 14.8106 27.0911 14.6553C26.716 14.4999 26.314 14.42 25.9081 14.4201M6.07093 18.1307C5.73079 18.1307 5.4525 17.8549 5.4525 17.5108C5.45219 17.3979 5.48274 17.287 5.54087 17.1901C5.59899 17.0933 5.68248 17.0141 5.78231 16.9613C5.88214 16.9084 5.99451 16.8838 6.10729 16.8902C6.22007 16.8966 6.32896 16.9336 6.42221 16.9974C6.42581 17.3257 6.45097 17.6489 6.48873 17.9682C6.37473 18.0728 6.22563 18.1307 6.07093 18.1307ZM25.8013 18.1321C25.6487 18.1319 25.5015 18.0756 25.3878 17.9739C25.4248 17.6525 25.45 17.3278 25.4539 16.9981C25.5535 16.9312 25.6733 16.891 25.802 16.891C26.1436 16.891 26.4226 17.1696 26.4226 17.513C26.4226 17.8574 26.1421 18.1325 25.8016 18.1325" fill="white"/>
</g>
</g>
<defs>
<clipPath id="clip0_881_28414">
<rect width="32" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

18
packages/nc-gui/assets/nc-icons/tableau.svg

@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<g clip-path="url(#clip0_881_28442)">
<path d="M15.4912 1.76166V3.21673H12.8887V4.16311H15.4912V7.06142H16.5086V4.16311H19.1762V3.21673H16.5086V0.3125H15.4912V1.76166Z" fill="#7099A6"/>
<path d="M6.98548 5.45257V7.5642H3.04614V8.88914H6.98548V13.1775H8.44647V8.88914H12.445V7.5642H8.44647V3.34094H6.98548V5.45257Z" fill="#EB912C"/>
<path d="M23.494 5.45257V7.5642H19.5547V8.9542H23.494V13.1775H25.0142V8.9542H28.9535V7.5642H25.0142V3.34094H23.494V5.45257Z" fill="#59879B"/>
<path d="M15.1126 12.6451V15.0111H10.7296V16.7737H15.1126V21.5056H16.887V16.7737H21.27V15.0111H16.887V10.2791H15.1126V12.6451Z" fill="#E8762C"/>
<path d="M28.0012 13.8399V15.3246H25.3336V16.5253H28.0012V19.4887H29.3321V16.5253H31.9997V15.3246H29.3321V12.3612H28.0012V13.8399Z" fill="#5B6591"/>
<path d="M2.60257 13.9701V15.3896H0V16.3952H2.60257V19.2343H3.61993V16.3952L6.28756 16.3005V15.3896H3.61993V12.5505H2.60257V13.9701Z" fill="#7099A6"/>
<path d="M6.98548 20.6539V22.7656H3.04614V24.1556H6.98548V28.3788H8.50561V24.1556H12.445V22.7656H8.50561V18.5423H6.98548V20.6539Z" fill="#C72035"/>
<path d="M23.494 20.6539V22.7656H19.5547V24.0905H23.494V28.3788H25.0142V24.0905H28.9535V22.7656H25.0142V18.5423H23.494V20.6539Z" fill="#1F447E"/>
<path d="M15.367 26.0188V27.4975H12.6993V28.6982H15.367V31.6616H16.6978V28.6982H19.3655V27.4975H16.6978V24.5341H15.367V26.0188Z" fill="#5B6591"/>
</g>
<defs>
<clipPath id="clip0_881_28442">
<rect width="32" height="31.375" fill="white" transform="translate(0 0.3125)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

10
packages/nc-gui/assets/nc-icons/thumbs-up-outline.svg

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<g clip-path="url(#clip0_910_11614)">
<path d="M4.66671 14.6666H2.66671C2.31309 14.6666 1.97395 14.5262 1.7239 14.2761C1.47385 14.0261 1.33337 13.6869 1.33337 13.3333V8.66665C1.33337 8.31302 1.47385 7.97389 1.7239 7.72384C1.97395 7.47379 2.31309 7.33331 2.66671 7.33331H4.66671M9.33337 5.99998V3.33331C9.33337 2.80288 9.12266 2.29417 8.74759 1.9191C8.37252 1.54403 7.86381 1.33331 7.33337 1.33331L4.66671 7.33331V14.6666H12.1867C12.5083 14.6703 12.8203 14.5576 13.0653 14.3493C13.3103 14.1411 13.4718 13.8513 13.52 13.5333L14.44 7.53331C14.469 7.34222 14.4562 7.1471 14.4023 6.96148C14.3484 6.77586 14.2548 6.60418 14.1279 6.45832C14.0011 6.31247 13.8441 6.19593 13.6678 6.11679C13.4914 6.03765 13.3 5.99779 13.1067 5.99998H9.33337Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_910_11614">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

16
packages/nc-gui/assets/nc-icons/trello.svg

@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<g clip-path="url(#clip0_881_28458)">
<path d="M28.875 0H3.125C1.39911 0 0 1.39911 0 3.125V28.875C0 30.6009 1.39911 32 3.125 32H28.875C30.6009 32 32 30.6009 32 28.875V3.125C32 1.39911 30.6009 0 28.875 0Z" fill="url(#paint0_linear_881_28458)"/>
<path d="M26.3401 4.15997H19.5801C18.7517 4.15997 18.0801 4.83155 18.0801 5.65997V16.66C18.0801 17.4884 18.7517 18.16 19.5801 18.16H26.3401C27.1685 18.16 27.8401 17.4884 27.8401 16.66V5.65997C27.8401 4.83155 27.1685 4.15997 26.3401 4.15997Z" fill="white"/>
<path d="M12.4199 4.15997H5.65991C4.83148 4.15997 4.15991 4.83155 4.15991 5.65997V24.66C4.15991 25.4884 4.83148 26.16 5.65991 26.16H12.4199C13.2483 26.16 13.9199 25.4884 13.9199 24.66V5.65997C13.9199 4.83155 13.2483 4.15997 12.4199 4.15997Z" fill="white"/>
</g>
<defs>
<linearGradient id="paint0_linear_881_28458" x1="1600" y1="0" x2="1600" y2="3200" gradientUnits="userSpaceOnUse">
<stop stop-color="#0091E6"/>
<stop offset="1" stop-color="#0079BF"/>
</linearGradient>
<clipPath id="clip0_881_28458">
<rect width="32" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

11
packages/nc-gui/assets/nc-icons/typeform.svg

@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="22" viewBox="0 0 32 22" fill="none">
<g clip-path="url(#clip0_881_28469)">
<path d="M26.626 0.496216H15.145C12.177 0.496216 9.771 2.90226 9.771 5.87026V16.1298C9.771 19.0978 12.177 21.5038 15.145 21.5038H26.626C29.594 21.5038 32 19.0978 32 16.1298V5.87026C32 2.90226 29.594 0.496216 26.626 0.496216Z" fill="#1A1A18"/>
<path d="M7.32824 4.16034C7.32824 2.1367 5.68776 0.496216 3.66412 0.496216C1.64048 0.496216 0 2.1367 0 4.16034V17.8397C0 19.8634 1.64048 21.5038 3.66412 21.5038C5.68776 21.5038 7.32824 19.8634 7.32824 17.8397V4.16034Z" fill="#1A1A18"/>
</g>
<defs>
<clipPath id="clip0_881_28469">
<rect width="32" height="21.0076" fill="white" transform="translate(0 0.496216)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 796 B

12
packages/nc-gui/assets/nc-icons/workday.svg

@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
<g clip-path="url(#clip0_881_28477)">
<path d="M16 32C24.8366 32 32 24.8366 32 16C32 7.16344 24.8366 0 16 0C7.16344 0 0 7.16344 0 16C0 24.8366 7.16344 32 16 32Z" fill="#005CB9"/>
<path d="M8.48 11.36C8.52 11.28 8.52 11.2 8.56 11.12C9.6 7.8 12.6 5.56 16.04 5.56C19.48 5.56 22.48 7.8 23.52 11.08C23.56 11.16 23.56 11.24 23.6 11.32C23.76 11.84 23.44 12.4 22.92 12.56C22.4 12.72 21.84 12.4 21.68 11.88C21.64 11.84 21.64 11.76 21.64 11.72C20.8 9.24 18.56 7.6 16 7.6C13.44 7.6 11.2 9.28 10.44 11.72C10.4 11.8 10.4 11.84 10.4 11.88C10.28 12.32 9.88 12.6 9.44 12.6C9.36 12.6 9.28 12.6 9.16 12.56C8.64 12.44 8.32 11.88 8.48 11.36Z" fill="#F38B00"/>
<path d="M23.5601 15.88L21.0401 25.4C21.0001 25.56 20.8401 25.72 20.6401 25.72H18.5601C18.5601 25.72 18.2001 25.68 18.1601 25.48C17.8001 24.16 15.9601 17.48 15.9601 17.48C15.9601 17.48 14.2001 24.16 13.8801 25.48C13.8401 25.64 13.4801 25.8 13.4801 25.8H11.4001C11.2001 25.8 11.0401 25.68 11.0001 25.48L8.48006 15.96C8.40006 15.72 8.60006 15.44 8.88006 15.44H10.2401C10.4401 15.44 10.6001 15.56 10.6401 15.76C10.6401 15.76 12.4401 23.32 12.5201 23.36L14.5601 15.76C14.6001 15.6 14.8001 15.44 14.9601 15.44H17.0801C17.2801 15.44 17.4401 15.56 17.5201 15.76C17.5201 15.76 19.5201 23.32 19.5601 23.32L21.4001 15.76C21.4401 15.56 21.6001 15.44 21.8001 15.44H23.1601C23.4401 15.44 23.6401 15.68 23.5601 15.96" fill="white"/>
</g>
<defs>
<clipPath id="clip0_881_28477">
<rect width="32" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

10
packages/nc-gui/assets/nc-icons/zendesk.svg

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="26" viewBox="0 0 32 26" fill="none">
<g clip-path="url(#clip0_881_28488)">
<path d="M14.7811 7.21658V25.0632H0L14.7811 7.21658ZM14.7811 0.8125C14.7811 4.89301 11.4711 8.20307 7.39057 8.20307C3.31006 8.20307 0 4.89301 0 0.8125H14.7811ZM17.2189 25.0632C17.2189 20.9786 20.5248 17.6726 24.6094 17.6726C28.694 17.6726 32 20.9827 32 25.0632H17.2189ZM17.2189 18.655V0.8125H32L17.2189 18.655Z" fill="#03363D"/>
</g>
<defs>
<clipPath id="clip0_881_28488">
<rect width="32" height="24.375" fill="white" transform="translate(0 0.8125)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 639 B

5
packages/nc-gui/components/account/Integration.vue

@ -0,0 +1,5 @@
<script lang="ts" setup></script>
<template>
<WorkspaceIntegrationsView />
</template>

34
packages/nc-gui/components/dashboard/Sidebar/TopSection.vue

@ -8,9 +8,9 @@ const { appInfo } = useGlobal()
const { meta: metaKey, control } = useMagicKeys() const { meta: metaKey, control } = useMagicKeys()
const { isWorkspaceLoading, isWorkspaceSettingsPageOpened } = storeToRefs(workspaceStore) const { isWorkspaceLoading, isWorkspaceSettingsPageOpened, isIntegrationsPageOpened } = storeToRefs(workspaceStore)
const { navigateToWorkspaceSettings } = workspaceStore const { navigateToWorkspaceSettings, navigateToIntegrations: _navigateToIntegrations } = workspaceStore
const { isSharedBase } = storeToRefs(baseStore) const { isSharedBase } = storeToRefs(baseStore)
@ -27,6 +27,12 @@ const navigateToSettings = () => {
// } else { // } else {
// } // }
} }
const navigateToIntegrations = () => {
const cmdOrCtrl = isMac() ? metaKey.value : control.value
_navigateToIntegrations('', cmdOrCtrl)
}
</script> </script>
<template> <template>
@ -74,6 +80,30 @@ const navigateToSettings = () => {
<div>{{ $t('title.teamAndSettings') }}</div> <div>{{ $t('title.teamAndSettings') }}</div>
</div> </div>
</NcButton> </NcButton>
<NcButton
v-if="isUIAllowed('workspaceSettings')"
v-e="['c:integrations']"
type="text"
size="xsmall"
class="nc-sidebar-top-button !xs:hidden my-0.5 !h-7"
data-testid="nc-sidebar-integrations-btn"
:centered="false"
:class="{
'!text-brand-600 !bg-brand-50 !hover:bg-brand-50': isIntegrationsPageOpened,
'!hover:(bg-gray-200 text-gray-700)': !isIntegrationsPageOpened,
}"
@click="navigateToIntegrations"
>
<div
class="flex items-center gap-2"
:class="{
'font-semibold': isIntegrationsPageOpened,
}"
>
<GeneralIcon icon="integration" class="!h-4" />
<div>{{ $t('general.integrations') }}</div>
</div>
</NcButton>
<WorkspaceCreateProjectBtn <WorkspaceCreateProjectBtn
v-model:is-open="isCreateProjectOpen" v-model:is-open="isCreateProjectOpen"
modal modal

6
packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue

@ -771,7 +771,7 @@ async function openAudit(source: SourceType) {
</div> </div>
<div <div
v-else v-else
class="source-context flex flex-grow items-center gap-1.75 text-gray-800 min-w-1/20 max-w-full" class="source-context flex flex-grow items-center gap-1 text-gray-800 min-w-1/20 max-w-full"
@contextmenu="setMenuContext('source', source)" @contextmenu="setMenuContext('source', source)"
> >
<NcTooltip <NcTooltip
@ -785,10 +785,12 @@ async function openAudit(source: SourceType) {
<template #title> <template #title>
<component :is="getSourceTooltip(source)" /> <component :is="getSourceTooltip(source)" />
</template> </template>
<div class="flex-none w-6 flex items-center justify-center">
<GeneralBaseLogo <GeneralBaseLogo
:color="getSourceIconColor(source)" :color="getSourceIconColor(source)"
class="flex-none min-w-4 !xs:(min-w-4.25 w-4.25 text-sm)" class="flex-none min-w-4 !xs:(min-w-4.25 w-4.25 text-sm)"
/> />
</div>
</NcTooltip> </NcTooltip>
<input <input
v-if="source.id && sourceRenameHelpers[source.id]?.editMode" v-if="source.id && sourceRenameHelpers[source.id]?.editMode"
@ -991,7 +993,7 @@ async function openAudit(source: SourceType) {
<style lang="scss" scoped> <style lang="scss" scoped>
:deep(.ant-collapse-header) { :deep(.ant-collapse-header) {
@apply !mx-0 !pl-8.75 h-7 !xs:(pl-7 h-[3rem]) !pr-0.5 !py-0 hover:bg-gray-200 xs:(hover:bg-gray-50) !rounded-md; @apply !mx-0 !pl-7.5 h-7 !xs:(pl-6 h-[3rem]) !pr-0.5 !py-0 hover:bg-gray-200 xs:(hover:bg-gray-50) !rounded-md;
.ant-collapse-arrow { .ant-collapse-arrow {
@apply !right-1 !xs:(flex-none border-1 border-gray-200 w-6.5 h-6.5 mr-1); @apply !right-1 !xs:(flex-none border-1 border-gray-200 w-6.5 h-6.5 mr-1);

271
packages/nc-gui/components/dashboard/settings/DataSources.vue

@ -42,6 +42,8 @@ const isReloading = ref(false)
const isDeleteBaseModalOpen = ref(false) const isDeleteBaseModalOpen = ref(false)
const toBeDeletedBase = ref<SourceType | undefined>() const toBeDeletedBase = ref<SourceType | undefined>()
const searchQuery = ref<string>('')
async function updateIfSourceOrderIsNullOrDuplicate() { async function updateIfSourceOrderIsNullOrDuplicate() {
const sourceOrderSet = new Set() const sourceOrderSet = new Set()
let hasNullOrDuplicates = false let hasNullOrDuplicates = false
@ -263,24 +265,51 @@ const isNewBaseModalOpen = computed({
}, },
}) })
const activeSource = ref<SourceType>(null) const activeSource = ref<SourceType | null>(null)
const openedTab = ref('erd') const openedTab = ref('erd')
const isSearchResultAvailable = () => {
return (
sources.value.filter((s) => s?.alias?.toLowerCase()?.includes(searchQuery.value?.toLowerCase())).length ||
'default'.includes(searchQuery.value?.toLowerCase())
)
}
const isOpenModal = computed({
get: () => !!activeSource.value,
set: (value) => {
if (!value) {
activeSource.value = null
}
},
})
const handleClickRow = (source: SourceType, tab?: string) => {
if (tab && tab !== openedTab.value) {
openedTab.value = tab
}
activeSource.value = source
}
</script> </script>
<template> <template>
<div class="flex flex-col h-full" data-testid="nc-settings-datasources-tab"> <div class="flex flex-col h-full" data-testid="nc-settings-datasources-tab">
<div class="px-4 py-2 flex justify-between"> <div class="px-1 pt-3 mb-6 flex items-center justify-between gap-3">
<a-breadcrumb separator=">" class="w-full cursor-pointer font-weight-bold"> <a-input
<a-breadcrumb-item @click="activeSource = null"> v-model:value="searchQuery"
<a class="!no-underline">Data Sources</a> type="text"
</a-breadcrumb-item> class="nc-search-data-source-input !max-w-90 nc-input-sm"
<a-breadcrumb-item v-if="activeSource"> placeholder="Search data source"
<span class="capitalize">{{ activeSource.alias || 'Default Source' }}</span> allow-clear
</a-breadcrumb-item> >
</a-breadcrumb> <template #prefix>
<GeneralIcon icon="search" class="mr-2 h-4 w-4 text-gray-500" />
</template>
</a-input>
<NcButton <NcButton
v-if="!isDataSourceLimitReached && !activeSource && isUIAllowed('sourceCreate')" v-if="!isDataSourceLimitReached && isUIAllowed('sourceCreate')"
size="large" size="large"
class="z-10 !px-2" class="z-10 !px-2"
type="primary" type="primary"
@ -292,19 +321,47 @@ const openedTab = ref('erd')
</div> </div>
</NcButton> </NcButton>
</div> </div>
<div data-testid="nc-settings-datasources" class="flex flex-row w-full nc-data-sources-view flex-grow min-h-0"> <div
<template v-if="activeSource"> data-testid="nc-settings-datasources"
<NcTabs v-model:activeKey="openedTab" class="nc-source-tab w-full"> class="flex flex-row w-full nc-data-sources-view flex-grow min-h-0"
:style="{
maxHeight: isNewBaseModalOpen ? '100%' : activeSource ? 'calc(100% - 46px)' : 'calc(100% - 66px)',
}"
>
<NcModal
v-model:visible="isOpenModal"
centered
size="large"
wrap-class-name="nc-active-data-sources-view"
@keydown.esc="activeSource = null"
>
<div v-if="activeSource" class="h-full">
<div class="px-4 pt-4 pb-2 flex items-center justify-between gap-3">
<a-breadcrumb separator=">" class="flex-1 cursor-pointer font-weight-bold">
<a-breadcrumb-item @click="activeSource = null">
<a class="!no-underline">Data Sources</a>
</a-breadcrumb-item>
<a-breadcrumb-item v-if="activeSource">
<span class="capitalize">{{ activeSource.alias || 'Default Source' }}</span>
</a-breadcrumb-item>
</a-breadcrumb>
<NcButton size="small" type="text" class="nc-close-btn" @click="isOpenModal = false">
<GeneralIcon icon="close" class="text-gray-600" />
</NcButton>
</div>
<NcTabs v-model:activeKey="openedTab" class="nc-source-tab w-full h-[calc(100%_-_58px)] max-h-[calc(100%_-_58px)]">
<a-tab-pane key="erd"> <a-tab-pane key="erd">
<template #tab> <template #tab>
<div class="tab" data-testid="nc-erd-tab"> <div class="tab" data-testid="nc-erd-tab">
<div>{{ $t('title.erdView') }}</div> <div>{{ $t('title.erdView') }}</div>
</div> </div>
</template> </template>
<div class="h-full pt-4"> <div class="h-full p-6">
<LazyDashboardSettingsErd <LazyDashboardSettingsErd
class="h-full overflow-auto" class="h-full overflow-auto"
:base-id="baseId" :base-id="base.id"
:source-id="activeSource.id" :source-id="activeSource.id"
:show-all-columns="false" :show-all-columns="false"
/> />
@ -316,7 +373,7 @@ const openedTab = ref('erd')
<div>{{ $t('title.auditLogs') }}</div> <div>{{ $t('title.auditLogs') }}</div>
</div> </div>
</template> </template>
<div class="p-4 h-full"> <div class="p-6 h-full">
<LazyDashboardSettingsBaseAudit :source-id="activeSource.id" /> <LazyDashboardSettingsBaseAudit :source-id="activeSource.id" />
</div> </div>
</a-tab-pane> </a-tab-pane>
@ -326,9 +383,8 @@ const openedTab = ref('erd')
<div>{{ $t('labels.connectionDetails') }}</div> <div>{{ $t('labels.connectionDetails') }}</div>
</div> </div>
</template> </template>
<div class="p-6 mt-4 h-full overflow-auto"> <div class="h-full">
<LazyDashboardSettingsDataSourcesEditBase <LazyDashboardSettingsDataSourcesEditBase
class="w-760px pr-5"
:source-id="activeSource.id" :source-id="activeSource.id"
@source-updated="loadBases(true)" @source-updated="loadBases(true)"
@close="activeSource = null" @close="activeSource = null"
@ -343,7 +399,7 @@ const openedTab = ref('erd')
</div> </div>
</template> </template>
<div class="pt-4 h-full"> <div class="p-6 h-full">
<LazyDashboardSettingsUIAcl :source-id="activeSource.id" /> <LazyDashboardSettingsUIAcl :source-id="activeSource.id" />
</div> </div>
</a-tab-pane> </a-tab-pane>
@ -353,35 +409,49 @@ const openedTab = ref('erd')
<div>{{ $t('labels.metaSync') }}</div> <div>{{ $t('labels.metaSync') }}</div>
</div> </div>
</template> </template>
<div class="pt-4 h-full"> <div class="p-6 h-full">
<LazyDashboardSettingsMetadata :source-id="activeSource.id" @source-synced="loadBases(true)" /> <LazyDashboardSettingsMetadata :source-id="activeSource.id" @source-synced="loadBases(true)" />
</div> </div>
</a-tab-pane> </a-tab-pane>
</NcTabs> </NcTabs>
</template> </div>
<div v-else class="flex flex-col w-full overflow-auto mt-1"> </NcModal>
<div <div
class="overflow-y-auto nc-scrollbar-md" class="flex flex-col w-full"
:style="{ :class="{
maxHeight: 'calc(100vh - 200px)', 'overflow-auto': !isNewBaseModalOpen,
}" }"
> >
<div class="ds-table-head"> <template v-if="isNewBaseModalOpen">
<div class="ds-table-row"> <DashboardSettingsDataSourcesCreateBase
v-model:open="isNewBaseModalOpen"
:connection-type="clientType"
is-modal
@source-created="loadBases(true)"
/>
</template>
<div v-else class="ds-table overflow-y-auto nc-scrollbar-thin relative max-h-full mx-1 mb-4">
<div class="ds-table-head sticky top-0 bg-white z-10">
<div class="ds-table-row !border-0">
<div class="ds-table-col ds-table-enabled cursor-pointer">{{ $t('general.visibility') }}</div> <div class="ds-table-col ds-table-enabled cursor-pointer">{{ $t('general.visibility') }}</div>
<div class="ds-table-col ds-table-name">{{ $t('general.name') }}</div> <div class="ds-table-col ds-table-name">{{ $t('general.name') }}</div>
<div class="ds-table-col ds-table-integration-name">{{ $t('general.connection') }} {{ $t('general.name') }}</div>
<div class="ds-table-col ds-table-type">{{ $t('general.type') }}</div> <div class="ds-table-col ds-table-type">{{ $t('general.type') }}</div>
<div class="ds-table-col ds-table-actions">{{ $t('labels.actions') }}</div> <div class="ds-table-col ds-table-actions">{{ $t('labels.actions') }}</div>
</div> </div>
</div> </div>
<div class="ds-table-body"> <div class="ds-table-body relative">
<Draggable :list="sources" item-key="id" handle=".ds-table-handle" @end="moveBase"> <Draggable :list="sources" item-key="id" handle=".ds-table-handle" @end="moveBase">
<template #header> <template v-if="'default'.includes(searchQuery.toLowerCase())" #header>
<div v-if="sources[0]" class="ds-table-row border-gray-200 cursor-pointer" @click="activeSource = sources[0]"> <div
v-if="sources[0]"
class="ds-table-row border-gray-200 cursor-pointer"
@click="handleClickRow(sources[0], 'erd')"
>
<div class="ds-table-col ds-table-enabled"> <div class="ds-table-col ds-table-enabled">
<div class="flex items-center gap-1" @click.stop> <div class="flex items-center gap-1" @click.stop>
<div v-if="sources.length > 2" class="ds-table-handle" /> <div v-if="sources.length > 2" class="ds-table-handle" />
<a-tooltip> <NcTooltip>
<template #title> <template #title>
<template v-if="sources[0].enabled">{{ $t('activity.hideInUI') }}</template> <template v-if="sources[0].enabled">{{ $t('activity.hideInUI') }}</template>
<template v-else>{{ $t('activity.showInUI') }}</template> <template v-else>{{ $t('activity.showInUI') }}</template>
@ -392,7 +462,7 @@ const openedTab = ref('erd')
size="small" size="small"
@change="toggleBase(sources[0], $event)" @change="toggleBase(sources[0], $event)"
/> />
</a-tooltip> </NcTooltip>
</div> </div>
</div> </div>
<div class="ds-table-col ds-table-name font-medium"> <div class="ds-table-col ds-table-name font-medium">
@ -402,6 +472,9 @@ const openedTab = ref('erd')
</div> </div>
</div> </div>
<div class="ds-table-col ds-table-integration-name">
<div class="flex items-center gap-1">-</div>
</div>
<div class="ds-table-col ds-table-type"> <div class="ds-table-col ds-table-type">
<div class="flex items-center gap-1">-</div> <div class="flex items-center gap-1">-</div>
</div> </div>
@ -420,11 +493,18 @@ const openedTab = ref('erd')
</div> </div>
</template> </template>
<template #item="{ element: source, index }"> <template #item="{ element: source, index }">
<div v-if="index !== 0" class="ds-table-row border-gray-200 cursor-pointer" @click="activeSource = source"> <div
v-if="index !== 0"
class="ds-table-row border-gray-200 cursor-pointer"
:class="{
'!hidden': !source?.alias?.toLowerCase()?.includes(searchQuery.toLowerCase()),
}"
@click="activeSource = source"
>
<div class="ds-table-col ds-table-enabled"> <div class="ds-table-col ds-table-enabled">
<div class="flex items-center gap-1" @click.stop> <div class="flex items-center gap-1" @click.stop>
<GeneralIcon v-if="sources.length > 2" icon="dragVertical" small class="ds-table-handle" /> <GeneralIcon v-if="sources.length > 2" icon="dragVertical" small class="ds-table-handle" />
<a-tooltip> <NcTooltip>
<template #title> <template #title>
<template v-if="source.enabled">{{ $t('activity.hideInUI') }}</template> <template v-if="source.enabled">{{ $t('activity.hideInUI') }}</template>
<template v-else>{{ $t('activity.showInUI') }}</template> <template v-else>{{ $t('activity.showInUI') }}</template>
@ -435,48 +515,91 @@ const openedTab = ref('erd')
size="small" size="small"
@change="toggleBase(source, $event)" @change="toggleBase(source, $event)"
/> />
</a-tooltip> </NcTooltip>
</div> </div>
</div> </div>
<div class="ds-table-col ds-table-name font-medium w-full"> <div class="ds-table-col ds-table-name font-medium w-full">
<div v-if="source.is_meta || source.is_local" class="h-8 w-1">-</div> <div v-if="source.is_meta || source.is_local" class="h-8 w-1">-</div>
<span v-else class="truncate">
<NcTooltip v-else class="truncate" show-on-truncate-only>
<template #title>
{{ source.is_meta || source.is_local ? $t('general.base') : source.alias }}
</template>
{{ source.is_meta || source.is_local ? $t('general.base') : source.alias }} {{ source.is_meta || source.is_local ? $t('general.base') : source.alias }}
</span> </NcTooltip>
</div>
<div class="ds-table-col ds-table-integration-name font-medium w-full">
<NcTooltip class="truncate" show-on-truncate-only>
<template #title>
{{ source?.integration_title || '-' }}
</template>
{{ source?.integration_title || '-' }}
</NcTooltip>
</div> </div>
<div class="ds-table-col ds-table-type"> <div class="ds-table-col ds-table-type">
<div class="flex items-center gap-2"> <NcBadge rounded="lg" class="flex items-center gap-2 px-2 py-1 !h-7 truncate">
<GeneralBaseLogo :source-type="source.type" /> <GeneralBaseLogo :source-type="source.type" class="flex-none" />
<span class="text-gray-700 capitalize">{{ source.type }}</span> <NcTooltip placement="bottom" show-on-truncate-only class="text-sm truncate">
</div> <template #title> {{ clientTypesMap[source.type]?.text || source.type }}</template>
{{ source.type && clientTypesMap[source.type] ? clientTypesMap[source.type]?.text : source.type }}
</NcTooltip>
</NcBadge>
</div> </div>
<div class="ds-table-col justify-end gap-x-1 ds-table-actions"> <div class="ds-table-col justify-end gap-x-1 ds-table-actions" @click.stop>
<NcTooltip> <div class="flex justify-end">
<template #title> <NcDropdown v-if="!source.is_meta && !source.is_local" placement="bottomRight">
<NcButton size="small" type="secondary">
<GeneralIcon icon="threeDotVertical" />
</NcButton>
<template #overlay>
<NcMenu>
<NcMenuItem @click="handleClickRow(source, 'edit')">
<GeneralIcon class="text-gray-800" icon="edit" />
<span>{{ $t('general.edit') }}</span>
</NcMenuItem>
<NcDivider />
<NcMenuItem class="!text-red-500 !hover:bg-red-50" @click.stop="openDeleteBase(source)">
<GeneralIcon icon="delete" />
{{ $t('general.remove') }} {{ $t('general.remove') }}
</NcMenuItem>
</NcMenu>
</template> </template>
<NcButton </NcDropdown>
v-if="!source.is_meta && !source.is_local" </div>
size="small"
class="nc-action-btn nc-delete-base cursor-pointer outline-0 !w-8 !px-1 !rounded-lg"
type="text"
@click.stop="openDeleteBase(source)"
>
<GeneralIcon icon="delete" class="text-red-500" />
</NcButton>
</NcTooltip>
</div> </div>
</div> </div>
</template> </template>
</Draggable> </Draggable>
<div
v-if="!isReloading && sources?.length && !isSearchResultAvailable()"
class="flex-none integration-table-empty flex items-center justify-center py-8 px-6"
>
<div class="px-2 py-6 text-gray-500 flex flex-col items-center gap-6 text-center">
<img
src="~assets/img/placeholder/no-search-result-found.png"
class="!w-[164px] flex-none"
alt="No search results found"
/>
{{ $t('title.noResultsMatchedYourSearch') }}
</div> </div>
</div> </div>
<LazyDashboardSettingsDataSourcesCreateBase </div>
v-model:open="isNewBaseModalOpen" <div
:connection-type="clientType" v-show="isReloading"
@source-created="loadBases(true)" class="flex items-center justify-center absolute left-0 top-0 w-full h-[calc(100%_-_45px)] z-10 pb-10 pointer-events-none"
/> >
<div class="flex flex-col justify-center items-center gap-2">
<GeneralLoader size="xlarge" />
<span class="text-center">{{ $t('general.loading') }}</span>
</div>
</div>
</div>
<GeneralDeleteModal <GeneralDeleteModal
v-model:visible="isDeleteBaseModalOpen" v-model:visible="isDeleteBaseModalOpen"
:entity-name="$t('general.datasource')" :entity-name="$t('general.datasource')"
@ -501,8 +624,11 @@ const openedTab = ref('erd')
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
.ds-table {
@apply border-1 border-gray-200 rounded-lg h-full;
}
.ds-table-head { .ds-table-head {
@apply flex items-center border-0 text-gray-500; @apply flex items-center border-b-1 text-gray-500 bg-gray-50 text-sm font-weight-500;
} }
.ds-table-body { .ds-table-body {
@ -522,15 +648,19 @@ const openedTab = ref('erd')
} }
.ds-table-name { .ds-table-name {
@apply col-span-9 items-center capitalize; @apply col-span-6 items-center capitalize;
}
.ds-table-integration-name {
@apply col-span-5 items-center capitalize;
} }
.ds-table-type { .ds-table-type {
@apply col-span-2 items-center; @apply col-span-3 items-center;
} }
.ds-table-actions { .ds-table-actions {
@apply col-span-5 flex w-full justify-center; @apply col-span-2 flex w-full justify-center;
} }
.ds-table-col:last-child { .ds-table-col:last-child {
@ -552,3 +682,16 @@ const openedTab = ref('erd')
@apply !min-h-0 !flex-shrink; @apply !min-h-0 !flex-shrink;
} }
</style> </style>
<style lang="scss">
.nc-active-data-sources-view {
.ant-modal-content {
@apply overflow-hidden;
}
.nc-modal {
@apply !p-0;
height: min(calc(100vh - 100px), 1024px);
max-height: min(calc(100vh - 100px), 1024px) !important;
}
}
</style>

4
packages/nc-gui/components/dashboard/settings/Metadata.vue

@ -128,7 +128,7 @@ const customRow = (record: Record<string, any>) => ({
<div v-else> <div v-else>
<!-- Tables metadata is in sync --> <!-- Tables metadata is in sync -->
<span> <span>
<a-alert :message="$t('msg.info.tablesMetadataInSync')" type="success" show-icon /> <a-alert :message="$t('msg.info.tablesMetadataInSync')" type="success" show-icon class="!rounded-md"/>
</span> </span>
</div> </div>
</div> </div>
@ -151,7 +151,7 @@ const customRow = (record: Record<string, any>) => ({
header-row-height="44px" header-row-height="44px"
:is-data-loading="isLoading" :is-data-loading="isLoading"
:custom-row="customRow" :custom-row="customRow"
class="nc-metasync-table h-[calc(100%_-_72px)] w-full" class="nc-metasync-table h-[calc(100%_-_58px)] w-full"
> >
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'table_name'"> <template v-if="column.key === 'table_name'">

92
packages/nc-gui/components/dashboard/settings/Modal.vue

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { FunctionalComponent, SVGAttributes } from 'vue' import type { FunctionalComponent, SVGAttributes } from 'vue'
import Misc from './Misc.vue' import Misc from './Misc.vue'
import DataSources from '~/components/dashboard/settings/DataSources.vue' // import DataSources from '~/components/dashboard/settings/DataSources.vue'
interface Props { interface Props {
modelValue?: boolean modelValue?: boolean
@ -110,21 +110,21 @@ const tabsInfo: TabGroup = {
}, },
}, },
dataSources: { // dataSources: {
// Data Sources // // Data Sources
title: 'Data Sources', // title: 'Data Sources',
icon: iconMap.database, // icon: iconMap.database,
subTabs: { // subTabs: {
dataSources: { // dataSources: {
title: 'Data Sources', // title: 'Data Sources',
body: DataSources, // body: DataSources,
}, // },
}, // },
onClick: () => { // onClick: () => {
vDataState.value = '' // vDataState.value = ''
$e('c:settings:data-sources') // $e('c:settings:data-sources')
}, // },
}, // },
} }
const firstKeyOfObject = (obj: object) => Object.keys(obj)[0] const firstKeyOfObject = (obj: object) => Object.keys(obj)[0]
@ -160,36 +160,30 @@ watch(
wrap-class-name="nc-modal-settings" wrap-class-name="nc-modal-settings"
@cancel="emits('update:modelValue', false)" @cancel="emits('update:modelValue', false)"
> >
<div class="nc-modal-settings-content">
<!-- Settings --> <!-- Settings -->
<div class="flex flex-row justify-between w-full items-center mb-1"> <div class="flex flex-row justify-between w-full items-center p-4 border-b-1 border-gray-200">
<a-typography-title class="ml-4 select-none" type="secondary" :level="5"> <h5 class="!my-0 text-2xl font-bold">{{ $t('objects.project') }} {{ $t('activity.settings') }}</h5>
{{ $t('activity.settings') }}
</a-typography-title> <NcButton type="text" size="small" data-testid="settings-modal-close-button" @click="vModel = false">
<a-button
type="text"
class="!rounded-md border-none !px-1.5"
data-testid="settings-modal-close-button"
@click="vModel = false"
>
<component :is="iconMap.close" class="cursor-pointer nc-modal-close w-4" /> <component :is="iconMap.close" class="cursor-pointer nc-modal-close w-4" />
</a-button> </NcButton>
</div> </div>
<a-layout class="mt-3 overflow-y-auto flex"> <a-layout class="overflow-y-auto flex !h-[calc(100%_-_66px)]">
<!-- Side tabs --> <!-- Side tabs -->
<a-layout-sider> <a-layout-sider class="!bg-white">
<a-menu v-model:selected-keys="selectedTabKeys" class="tabs-menu h-full" :open-keys="[]"> <a-menu v-model:selected-keys="selectedTabKeys" class="tabs-menu h-full" :open-keys="[]">
<template v-for="(tab, key) of tabsInfo" :key="key"> <template v-for="(tab, key) of tabsInfo" :key="key">
<a-menu-item <a-menu-item
v-if="key !== 'dataSources' || isUIAllowed('sourceCreate')" v-if="key !== 'dataSources' || isUIAllowed('sourceCreate')"
:key="key" :key="key"
class="active:(!ring-0) hover:(!bg-primary !bg-opacity-25)" class="active:(!ring-0) hover:(!bg-[#F0F3FF])"
> >
<div class="flex items-center space-x-2" @click="tab.onClick"> <div class="flex items-center space-x-3 min-h-10" @click="tab.onClick">
<component :is="tab.icon" /> <component :is="tab.icon" class="flex-none" />
<div class="select-none"> <div class="select-none text-sm">
{{ tab.title }} {{ tab.title }}
</div> </div>
</div> </div>
@ -199,12 +193,13 @@ watch(
</a-layout-sider> </a-layout-sider>
<!-- Sub Tabs --> <!-- Sub Tabs -->
<a-layout-content class="h-auto h-80vh px-4 scrollbar-thumb-gray-500"> <a-layout-content class="h-full scrollbar-thumb-gray-500">
<a-menu <a-menu
v-if="selectedTabKeys[0] !== 'dataSources'" v-if="selectedTabKeys[0] !== 'dataSources'"
v-model:selectedKeys="selectedSubTabKeys" v-model:selectedKeys="selectedSubTabKeys"
:open-keys="[]" :open-keys="[]"
mode="horizontal" mode="horizontal"
class="px-4"
> >
<a-menu-item <a-menu-item
v-for="(tab, key) of selectedTab.subTabs" v-for="(tab, key) of selectedTab.subTabs"
@ -220,6 +215,7 @@ watch(
class="overflow-auto" class="overflow-auto"
:class="{ :class="{
'h-full': selectedSubTabKeys[0] === 'dataSources', 'h-full': selectedSubTabKeys[0] === 'dataSources',
'px-4': selectedTabKeys[0] !== 'dataSources',
}" }"
> >
<component <component
@ -227,7 +223,7 @@ watch(
v-if="selectedSubTabKeys[0] === 'dataSources'" v-if="selectedSubTabKeys[0] === 'dataSources'"
v-model:state="vDataState" v-model:state="vDataState"
v-model:reload="dataSourcesReload" v-model:reload="dataSourcesReload"
class="px-2 pb-2 h-full" class="h-full"
:data-testid="`nc-settings-subtab-${selectedSubTab.key}`" :data-testid="`nc-settings-subtab-${selectedSubTab.key}`"
:base-id="baseId" :base-id="baseId"
/> />
@ -241,13 +237,33 @@ watch(
</div> </div>
</a-layout-content> </a-layout-content>
</a-layout> </a-layout>
</div>
</a-modal> </a-modal>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.tabs-menu { .tabs-menu {
:deep(.ant-menu-item-selected) { @apply !p-3;
@apply border-r-3 border-primary bg-primary !bg-opacity-25;
:deep(.ant-menu-item) {
@apply rounded-lg first:!mt-0 !mb-1 font-weight-500;
&.ant-menu-item-selected {
@apply bg-[#F0F3FF] font-weight-600;
}
}
}
</style>
<style lang="scss">
.nc-modal-settings {
.ant-modal-content {
@apply !p-0 overflow-hidden;
}
.nc-modal-settings-content {
height: min(calc(100vh - 100px), 1124px);
max-height: min(calc(100vh - 100px), 1124px) !important;
} }
} }
</style> </style>

6
packages/nc-gui/components/dashboard/settings/UIAcl.vue

@ -153,9 +153,9 @@ const columns = [
<span> UI ACL : {{ base.title }} </span> <span> UI ACL : {{ base.title }} </span>
</NcTooltip> </NcTooltip>
<div class="flex flex-row items-center w-full mb-4 gap-2 justify-between"> <div class="flex flex-row items-center w-full mb-4 gap-2 justify-between">
<a-input v-model:value="searchInput" :placeholder="$t('placeholder.searchModels')" class="nc-acl-search !w-[400px]"> <a-input v-model:value="searchInput" :placeholder="$t('placeholder.searchModels')" allow-clear class="nc-acl-search nc-input-border-on-value !w-[400px] nc-input-sm">
<template #prefix> <template #prefix>
<component :is="iconMap.search" /> <component :is="iconMap.search" class="text-gray-600"/>
</template> </template>
</a-input> </a-input>
<div class="flex"> <div class="flex">
@ -180,7 +180,7 @@ const columns = [
:data="filteredTables" :data="filteredTables"
row-height="44px" row-height="44px"
header-row-height="44px" header-row-height="44px"
class="h-[calc(100%_-_102px)] w-full" class="h-[calc(100%_-_88px)] w-full"
> >
<template #headerCell="{ column }"> <template #headerCell="{ column }">
<template v-if="column.key === 'name'"> <template v-if="column.key === 'name'">

901
packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue

File diff suppressed because it is too large Load Diff

732
packages/nc-gui/components/dashboard/settings/data-sources/EditBase.vue

@ -1,14 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import { type SourceType, validateAndExtractSSLProp } from 'nocodb-sdk' import { type SourceType, validateAndExtractSSLProp } from 'nocodb-sdk'
import { Form, message } from 'ant-design-vue' import { Form, message } from 'ant-design-vue'
import type { SelectHandler } from 'ant-design-vue/es/vc-select/Select'
import { import {
type CertTypes,
ClientType, ClientType,
type DatabricksConnection, type DatabricksConnection,
type DefaultConnection, type DefaultConnection,
type ProjectCreateForm, type ProjectCreateForm,
type SQLiteConnection,
SSLUsage, SSLUsage,
type SnowflakeConnection, type SnowflakeConnection,
clientTypes as _clientTypes, clientTypes as _clientTypes,
@ -18,12 +15,14 @@ const props = defineProps<{
sourceId: string sourceId: string
}>() }>()
const emit = defineEmits(['baseUpdated', 'close']) const emit = defineEmits(['sourceUpdated', 'close'])
const baseStore = useBase() const baseStore = useBase()
const basesStore = useBases() const basesStore = useBases()
const { base } = storeToRefs(baseStore) const { base } = storeToRefs(baseStore)
const { integrations, loadIntegrations } = useIntegrationStore()
const _projectId = inject(ProjectIdInj, undefined) const _projectId = inject(ProjectIdInj, undefined)
const baseId = computed(() => _projectId?.value ?? base.value?.id) const baseId = computed(() => _projectId?.value ?? base.value?.id)
@ -56,15 +55,10 @@ const onEasterEgg = () => {
} }
} }
const clientTypes = computed(() => { const defaultFormState = (client = ClientType.MYSQL) => {
return _clientTypes.filter((type) => { return {
return ![ClientType.SNOWFLAKE, ClientType.DATABRICKS, ...(easterEgg.value ? [] : [ClientType.MSSQL])].includes(type.value)
})
})
const formState = ref<ProjectCreateForm>({
title: '', title: '',
dataSource: { ...getDefaultConnectionConfig(ClientType.MYSQL) }, dataSource: { ...getDefaultConnectionConfig(client) },
inflection: { inflection: {
inflectionColumn: 'none', inflectionColumn: 'none',
inflectionTable: 'none', inflectionTable: 'none',
@ -73,49 +67,50 @@ const formState = ref<ProjectCreateForm>({
extraParameters: [], extraParameters: [],
is_schema_readonly: true, is_schema_readonly: true,
is_data_readonly: false, is_data_readonly: false,
}) }
}
const customFormState = ref<ProjectCreateForm>({ const formState = ref<ProjectCreateForm>(defaultFormState())
title: '',
dataSource: { ...getDefaultConnectionConfig(ClientType.MYSQL) }, const selectedIntegration = computed(() => {
inflection: { return formState.value.fk_integration_id && integrations.value.find((i) => i.id === formState.value.fk_integration_id)
inflectionColumn: 'none', })
inflectionTable: 'none', const selectedIntegrationDb = computed(() => {
}, return selectedIntegration.value?.config?.connection?.database
sslUse: SSLUsage.No, })
extraParameters: [], const selectedIntegrationSchema = computed(() => {
is_schema_readonly: true, return selectedIntegration.value?.config?.searchPath?.[0]
is_data_readonly: false,
}) })
const getDataSourceValue = (field: 'database' | 'schema') => {
if (field === 'database') {
return selectedIntegrationDb.value
}
if (field === 'schema') {
return selectedIntegrationSchema.value
}
}
const validators = computed(() => { const validators = computed(() => {
return { return {
'title': [baseTitleValidator], 'title': [baseTitleValidator()],
'extraParameters': [extraParameterValidator], 'extraParameters': [extraParameterValidator],
'dataSource.client': [fieldRequiredValidator()], 'dataSource.client': [fieldRequiredValidator()],
...(formState.value.dataSource.client === ClientType.SQLITE ...(formState.value.dataSource.client === ClientType.SQLITE
? { ? {}
'dataSource.connection.connection.filename': [fieldRequiredValidator()],
}
: formState.value.dataSource.client === ClientType.SNOWFLAKE : formState.value.dataSource.client === ClientType.SNOWFLAKE
? { ? {
'dataSource.connection.account': [fieldRequiredValidator()],
'dataSource.connection.username': [fieldRequiredValidator()],
'dataSource.connection.password': [fieldRequiredValidator()],
'dataSource.connection.warehouse': [fieldRequiredValidator()],
'dataSource.connection.database': [fieldRequiredValidator()], 'dataSource.connection.database': [fieldRequiredValidator()],
'dataSource.connection.schema': [fieldRequiredValidator()], 'dataSource.connection.schema': [fieldRequiredValidator()],
} }
: { : {
'dataSource.connection.host': [fieldRequiredValidator()], 'dataSource.connection.database':
'dataSource.connection.port': [fieldRequiredValidator()], selectedIntegration.value && getDataSourceValue('database') ? [] : [fieldRequiredValidator()],
'dataSource.connection.user': [fieldRequiredValidator()],
'dataSource.connection.password': [fieldRequiredValidator()],
'dataSource.connection.database': [fieldRequiredValidator()],
...([ClientType.PG, ClientType.MSSQL].includes(formState.value.dataSource.client) && ...([ClientType.PG, ClientType.MSSQL].includes(formState.value.dataSource.client) &&
formState.value.dataSource.searchPath formState.value.dataSource.searchPath
? { ? {
'dataSource.searchPath.0': [fieldRequiredValidator()], 'dataSource.searchPath.0':
selectedIntegration.value && getDataSourceValue('schema') ? [] : [fieldRequiredValidator()],
} }
: {}), : {}),
}), }),
@ -124,31 +119,6 @@ const validators = computed(() => {
const { validate, validateInfos } = useForm(formState, validators) const { validate, validateInfos } = useForm(formState, validators)
const onClientChange = () => {
formState.value.dataSource = { ...getDefaultConnectionConfig(formState.value.dataSource.client) }
}
const onSSLModeChange = ((mode: SSLUsage) => {
if (formState.value.dataSource.client !== ClientType.SQLITE) {
const connection = formState.value.dataSource.connection as DefaultConnection
switch (mode) {
case SSLUsage.No:
connection.ssl = undefined
break
case SSLUsage.Allowed:
connection.ssl = 'true'
break
default:
connection.ssl = {
ca: '',
cert: '',
key: '',
}
break
}
}
}) as SelectHandler
const updateSSLUse = () => { const updateSSLUse = () => {
if (formState.value.dataSource.client !== ClientType.SQLITE) { if (formState.value.dataSource.client !== ClientType.SQLITE) {
const connection = formState.value.dataSource.connection as DefaultConnection const connection = formState.value.dataSource.connection as DefaultConnection
@ -164,35 +134,7 @@ const updateSSLUse = () => {
} }
} }
const addNewParam = () => {
formState.value.extraParameters.push({ key: '', value: '' })
}
const removeParam = (index: number) => {
formState.value.extraParameters.splice(index, 1)
}
const inflectionTypes = ['camelize', 'none'] const inflectionTypes = ['camelize', 'none']
const importURL = ref('')
const configEditDlg = ref(false)
const importURLDlg = ref(false)
const caFileInput = ref<HTMLInputElement>()
const keyFileInput = ref<HTMLInputElement>()
const certFileInput = ref<HTMLInputElement>()
const onFileSelect = (key: CertTypes, el?: HTMLInputElement) => {
if (!el) return
readFile(el, (content) => {
if ('ssl' in formState.value.dataSource.connection && typeof formState.value.dataSource.connection.ssl === 'object')
formState.value.dataSource.connection.ssl[key] = content ?? ''
})
}
const sslFilesRequired = computed(
() => !!formState.value.sslUse && formState.value.sslUse !== SSLUsage.No && formState.value.sslUse !== SSLUsage.Allowed,
)
function getConnectionConfig() { function getConnectionConfig() {
const extraParameters = Object.fromEntries(new Map(formState.value.extraParameters.map((object) => [object.key, object.value]))) const extraParameters = Object.fromEntries(new Map(formState.value.extraParameters.map((object) => [object.key, object.value])))
@ -231,6 +173,16 @@ const editBase = async () => {
config.connection.filename = config.connection.connection.filename config.connection.filename = config.connection.connection.filename
} }
// if integration is selected and database/schema is empty, set it to `undefined` to use default from integration
if (selectedIntegration.value) {
if (config.connection?.database === '') {
config.connection.database = undefined
}
if (config.searchPath?.[0] === '') {
config.searchPath = undefined
}
}
await api.source.update(base.value?.id, props.sourceId, { await api.source.update(base.value?.id, props.sourceId, {
alias: formState.value.title, alias: formState.value.title,
type: formState.value.dataSource.client, type: formState.value.dataSource.client,
@ -244,7 +196,7 @@ const editBase = async () => {
$e('a:source:edit:extdb') $e('a:source:edit:extdb')
await basesStore.loadProject(baseId.value!, true) await basesStore.loadProject(baseId.value!, true)
emit('baseUpdated') emit('sourceUpdated')
emit('close') emit('close')
} catch (e: any) { } catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))
@ -253,6 +205,8 @@ const editBase = async () => {
} }
} }
const testConnectionError = ref()
const testConnection = async () => { const testConnection = async () => {
try { try {
await validate() await validate()
@ -273,9 +227,22 @@ const testConnection = async () => {
connection.database = getTestDatabaseName(formState.value.dataSource)! connection.database = getTestDatabaseName(formState.value.dataSource)!
let searchPath = formState.value.dataSource.searchPath
// if integration is selected and database/schema is empty, set it to `undefined` to use default from integration
if (selectedIntegration.value) {
if (connection?.database === '') {
connection.database = undefined
}
if (searchPath?.[0] === '') {
searchPath = undefined
}
}
const testConnectionConfig = { const testConnectionConfig = {
...formState.value.dataSource, ...formState.value.dataSource,
connection, connection,
searchPath,
fk_integration_id: formState.value.fk_integration_id,
} }
const result = await api.utils.testConnection(testConnectionConfig) const result = await api.utils.testConnection(testConnectionConfig)
@ -290,60 +257,37 @@ const testConnection = async () => {
} }
} catch (e: any) { } catch (e: any) {
testSuccess.value = false testSuccess.value = false
testConnectionError.value = await extractSdkResponseErrorMsg(e)
message.error(await extractSdkResponseErrorMsg(e))
} }
testingConnection.value = false testingConnection.value = false
} }
const handleImportURL = async () => {
if (!importURL.value || importURL.value === '') return
const connectionConfig = await api.utils.urlToConfig({ url: importURL.value })
if (connectionConfig) {
formState.value.dataSource.client = connectionConfig.client
formState.value.dataSource.connection = { ...connectionConfig.connection }
} else {
message.error(t('msg.error.invalidURL'))
}
importURLDlg.value = false
updateSSLUse()
}
const handleEditJSON = () => {
customFormState.value = formState.value
configEditDlg.value = true
}
const handleOk = () => {
formState.value = customFormState.value
configEditDlg.value = false
updateSSLUse()
}
// reset test status on config change // reset test status on config change
watch( watch(
() => formState.value.dataSource, () => formState.value.dataSource,
() => (testSuccess.value = false), () => {
testSuccess.value = false
testConnectionError.value = null
},
{ deep: true }, { deep: true },
) )
// load source config // load source config
onMounted(async () => { onMounted(async () => {
await loadIntegrations(true, base.value?.id)
if (base.value?.id) { if (base.value?.id) {
const definedParameters = ['host', 'port', 'user', 'password', 'database'] const definedParameters = ['host', 'port', 'user', 'password', 'database']
const activeBase = (await api.source.read(base.value?.id, props.sourceId)) as SourceType const activeBase = (await api.source.read(base.value?.id, props.sourceId)) as SourceType
const tempParameters = Object.entries(activeBase.config.connection) const tempParameters = Object.entries(activeBase.config.connection || {})
.filter(([key]) => !definedParameters.includes(key)) .filter(([key]) => !definedParameters.includes(key))
.map(([key, value]) => ({ key: key as string, value: value as string })) .map(([key, value]) => ({ key: key as string, value: value as string }))
formState.value = { formState.value = {
title: activeBase.alias || '', title: activeBase.alias || '',
dataSource: activeBase.config, dataSource: { connection: {}, ...(activeBase.config || {}), searchPath: activeBase.config?.searchPath || [] },
inflection: { inflection: {
inflectionColumn: activeBase.inflection_column, inflectionColumn: activeBase.inflection_column,
inflectionTable: activeBase.inflection_table, inflectionTable: activeBase.inflection_table,
@ -352,6 +296,7 @@ onMounted(async () => {
sslUse: SSLUsage.No, sslUse: SSLUsage.No,
is_schema_readonly: activeBase.is_schema_readonly, is_schema_readonly: activeBase.is_schema_readonly,
is_data_readonly: activeBase.is_data_readonly, is_data_readonly: activeBase.is_data_readonly,
fk_integration_id: activeBase.fk_integration_id,
} }
updateSSLUse() updateSSLUse()
} }
@ -389,77 +334,100 @@ const allowDataWrite = computed({
$e('c:source:data-write-toggle', { allowed: !v, edit: true }) $e('c:source:data-write-toggle', { allowed: !v, edit: true })
}, },
}) })
let timer: any
const handleAutoScroll = (scroll: boolean, className: string) => {
if (scroll) {
if (timer) {
clearTimeout(timer)
}
nextTick(() => {
const el = document.querySelector(`.edit-source .${className}`)
if (!el) return
// wait for transition complete
timer = setTimeout(() => {
el.scrollIntoView({ block: 'center', behavior: 'smooth' })
}, 400)
})
}
}
</script> </script>
<template> <template>
<div class="edit-source bg-white relative flex flex-col justify-start gap-2 w-full p-2"> <div class="edit-source bg-white relative h-full flex flex-col w-full">
<h1 class="prose-2xl font-bold self-start">{{ $t('activity.editSource') }}</h1> <div class="h-full max-h-[calc(100%_-_65px)] nc-scrollbar-thin">
<div class="h-full max-w-[992px] p-6 mx-auto">
<a-form ref="form" :model="formState" name="external-base-create-form" layout="horizontal" no-style :label-col="{ span: 5 }"> <a-form
<div ref="form"
class="nc-scrollbar-md" :model="formState"
:style="{ hide-required-mark
maxHeight: '60vh', name="external-base-create-form"
}" layout="vertical"
no-style
class="flex flex-col gap-5.5"
> >
<a-form-item label="Source Name" v-bind="validateInfos.title"> <div class="nc-form-section">
<div class="nc-form-section-title">Source details</div>
<div class="nc-form-section-body">
<a-row :gutter="24">
<a-col :span="12">
<a-form-item label="Source name" v-bind="validateInfos.title">
<a-input v-model:value="formState.title" 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-col>
<a-select </a-row>
v-model:value="formState.dataSource.client" <a-row :gutter="24">
class="nc-extdb-db-type" <a-col :span="12">
<a-form-item label="Select connection">
<NcSelect
:value="formState.fk_integration_id"
disabled
class="nc-extdb-db-type nc-select-shadow"
dropdown-class-name="nc-dropdown-ext-db-type" dropdown-class-name="nc-dropdown-ext-db-type"
@change="onClientChange" placeholder="Select connection"
> allow-clear
<a-select-option v-for="client in clientTypes" :key="client.value" :value="client.value" show-search
>{{ client.text }} dropdown-match-select-width
</a-select-option>
</a-select>
</a-form-item>
<!-- SQLite File -->
<a-form-item
v-if="formState.dataSource.client === ClientType.SQLITE"
:label="$t('labels.sqliteFile')"
v-bind="validateInfos['dataSource.connection.connection.filename']"
> >
<a-input v-model:value="(formState.dataSource.connection as SQLiteConnection).connection.filename" /> <a-select-option v-for="integration in integrations" :key="integration.id" :value="integration.id">
</a-form-item> <div class="w-full flex gap-2 items-center" :data-testid="integration.title">
<GeneralBaseLogo
<template v-else-if="formState.dataSource.client === ClientType.SNOWFLAKE"> v-if="integration.type"
<!-- Account --> :source-type="integration.sub_type"
<a-form-item :label="$t('labels.account')" v-bind="validateInfos['dataSource.connection.account']"> class="flex-none h-4 w-4"
<a-input
v-model:value="(formState.dataSource.connection as SnowflakeConnection).account"
class="nc-extdb-host-address"
/> />
</a-form-item> <NcTooltip class="flex-1 truncate" show-on-truncate-only>
<template #title>
<!-- Username --> {{ integration.title }}
<a-form-item :label="$t('labels.username')" v-bind="validateInfos['dataSource.connection.username']"> </template>
<a-input {{ integration.title }}
v-model:value="(formState.dataSource.connection as SnowflakeConnection).username" </NcTooltip>
class="nc-extdb-host-user" <component
/> :is="iconMap.check"
</a-form-item> v-if="formState.fk_integration_id === integration.id"
id="nc-selected-item-icon"
<!-- Password --> class="text-primary w-4 h-4"
<a-form-item :label="$t('labels.password')" v-bind="validateInfos['dataSource.connection.password']">
<a-input-password
v-model:value="(formState.dataSource.connection as SnowflakeConnection).password"
class="nc-extdb-host-password"
/> />
</div>
</a-select-option>
</NcSelect>
</a-form-item> </a-form-item>
</a-col>
</a-row>
</div>
</div>
<!-- Warehouse --> <div class="nc-form-section">
<a-form-item label="Warehouse" v-bind="validateInfos['dataSource.connection.warehouse']"> <div class="nc-form-section-title">Connection details</div>
<a-input
v-model:value="(formState.dataSource.connection as SnowflakeConnection).warehouse"
class="nc-extdb-host-database"
/>
</a-form-item>
<div class="nc-form-section-body">
<!-- SQLite File -->
<template v-if="formState.dataSource.client === ClientType.SQLITE"> </template>
<template v-else-if="formState.dataSource.client === ClientType.SNOWFLAKE">
<a-row :gutter="24">
<a-col :span="12">
<!-- Database --> <!-- Database -->
<a-form-item :label="$t('labels.database')" v-bind="validateInfos['dataSource.connection.database']"> <a-form-item :label="$t('labels.database')" v-bind="validateInfos['dataSource.connection.database']">
<a-input <a-input
@ -467,7 +435,8 @@ const allowDataWrite = computed({
class="nc-extdb-host-database" class="nc-extdb-host-database"
/> />
</a-form-item> </a-form-item>
</a-col>
<a-col :span="12">
<!-- Schema --> <!-- Schema -->
<a-form-item label="Schema" v-bind="validateInfos['dataSource.connection.schema']"> <a-form-item label="Schema" v-bind="validateInfos['dataSource.connection.schema']">
<a-input <a-input
@ -475,69 +444,33 @@ const allowDataWrite = computed({
class="nc-extdb-host-database" class="nc-extdb-host-database"
/> />
</a-form-item> </a-form-item>
</a-col>
</a-row>
</template> </template>
<template v-else-if="formState.dataSource.client === ClientType.DATABRICKS"> <template v-else-if="formState.dataSource.client === ClientType.DATABRICKS">
<a-form-item label="Token" v-bind="validateInfos['dataSource.connection.token']"> <a-row :gutter="24">
<a-input <a-col :span="12">
v-model:value="(formState.dataSource.connection as DatabricksConnection).token"
class="nc-extdb-host-token"
/>
</a-form-item>
<a-form-item label="Host" v-bind="validateInfos['dataSource.connection.host']">
<a-input
v-model:value="(formState.dataSource.connection as DatabricksConnection).host"
class="nc-extdb-host-address"
/>
</a-form-item>
<a-form-item label="Path" v-bind="validateInfos['dataSource.connection.path']">
<a-input v-model:value="(formState.dataSource.connection as DatabricksConnection).path" class="nc-extdb-host-path" />
</a-form-item>
<a-form-item label="Database" v-bind="validateInfos['dataSource.connection.database']"> <a-form-item label="Database" v-bind="validateInfos['dataSource.connection.database']">
<a-input <a-input
v-model:value="(formState.dataSource.connection as DatabricksConnection).database" v-model:value="(formState.dataSource.connection as DatabricksConnection).database"
class="nc-extdb-host-database" class="nc-extdb-host-database"
/> />
</a-form-item> </a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="Schema" v-bind="validateInfos['dataSource.connection.schema']"> <a-form-item label="Schema" v-bind="validateInfos['dataSource.connection.schema']">
<a-input <a-input
v-model:value="(formState.dataSource.connection as DatabricksConnection).schema" v-model:value="(formState.dataSource.connection as DatabricksConnection).schema"
class="nc-extdb-host-schema" class="nc-extdb-host-schema"
/> />
</a-form-item> </a-form-item>
</a-col>
</a-row>
</template> </template>
<template v-else> <template v-else>
<!-- Host Address --> <a-row :gutter="24">
<a-form-item :label="$t('labels.hostAddress')" v-bind="validateInfos['dataSource.connection.host']"> <a-col :span="12">
<a-input v-model:value="(formState.dataSource.connection as DefaultConnection).host" class="nc-extdb-host-address" />
</a-form-item>
<!-- Port Number -->
<a-form-item :label="$t('labels.port')" v-bind="validateInfos['dataSource.connection.port']">
<a-input-number
v-model:value="(formState.dataSource.connection as DefaultConnection).port"
class="!w-full nc-extdb-host-port"
/>
</a-form-item>
<!-- Username -->
<a-form-item :label="$t('labels.username')" v-bind="validateInfos['dataSource.connection.user']">
<a-input v-model:value="(formState.dataSource.connection as DefaultConnection).user" class="nc-extdb-host-user" />
</a-form-item>
<!-- Password -->
<a-form-item :label="$t('labels.password')">
<a-input-password
v-model:value="(formState.dataSource.connection as DefaultConnection).password"
class="nc-extdb-host-password"
/>
</a-form-item>
<!-- Database --> <!-- Database -->
<a-form-item :label="$t('labels.database')" v-bind="validateInfos['dataSource.connection.database']"> <a-form-item :label="$t('labels.database')" v-bind="validateInfos['dataSource.connection.database']">
<!-- Database : create if not exists --> <!-- Database : create if not exists -->
@ -547,209 +480,165 @@ const allowDataWrite = computed({
class="nc-extdb-host-database" class="nc-extdb-host-database"
/> />
</a-form-item> </a-form-item>
</a-col>
<a-col :span="12">
<!-- 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) ||
[ClientType.MSSQL, ClientType.PG].includes(selectedIntegration?.sub_type)) &&
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]" /> <a-input
v-model:value="formState.dataSource.searchPath[0]"
:placeholder="selectedIntegrationSchema && `${selectedIntegrationSchema} (default)`"
/>
</a-form-item> </a-form-item>
</a-col>
</a-row>
</template> </template>
</div>
</div>
<div class="nc-form-section">
<div class="nc-form-section-title">Permissions</div>
<div class="nc-form-section-body">
<DashboardSettingsDataSourcesSourceRestrictions <DashboardSettingsDataSourcesSourceRestrictions
v-model:allowMetaWrite="allowMetaWrite" v-model:allowMetaWrite="allowMetaWrite"
v-model:allowDataWrite="allowDataWrite" v-model:allowDataWrite="allowDataWrite"
/> />
</div>
</div>
<template <template
v-if=" v-if="![ClientType.SQLITE, ClientType.SNOWFLAKE, ClientType.DATABRICKS].includes(formState.dataSource.client)"
formState.dataSource.client !== ClientType.SQLITE && >
formState.dataSource.client !== ClientType.DATABRICKS && <a-collapse
formState.dataSource.client !== ClientType.SNOWFLAKE ghost
" expand-icon-position="right"
class="nc-source-advanced-options !mt-4"
@change="handleAutoScroll(!!$event?.length, 'nc-source-advanced-options')"
> >
<!-- Use Connection URL --> <template #expandIcon="{ isActive }">
<div class="flex justify-end gap-2"> <NcButton type="text" size="xsmall">
<NcButton size="small" type="ghost" class="nc-extdb-btn-import-url !rounded-md" @click.stop="importURLDlg = true"> <GeneralIcon
{{ $t('activity.useConnectionUrl') }} icon="chevronDown"
class="flex-none cursor-pointer transform transition-transform duration-500"
:class="{ '!rotate-180': isActive }"
/>
</NcButton> </NcButton>
</div> </template>
<a-collapse ghost expand-icon-position="right" class="!mt-6">
<a-collapse-panel key="1"> <a-collapse-panel key="1">
<template #header> <template #header>
<div class="flex items-center gap-2"> <div class="flex">
<span>{{ $t('title.advancedParameters') }}</span> <div class="nc-form-section-title">Advanced options</div>
</div> </div>
</template> </template>
<a-form-item label="SSL mode">
<a-select v-model:value="formState.sslUse" dropdown-class-name="nc-dropdown-ssl-mode" @select="onSSLModeChange">
<a-select-option v-for="opt in Object.values(SSLUsage)" :key="opt" :value="opt">{{ opt }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="SSL keys">
<div class="flex gap-2">
<a-tooltip placement="top">
<!-- Select .cert file -->
<template #title>
<span>{{ $t('tooltip.clientCert') }}</span>
</template>
<NcButton size="small" :disabled="!sslFilesRequired" class="shadow" @click="certFileInput?.click()">
{{ $t('labels.clientCert') }}
</NcButton>
</a-tooltip>
<a-tooltip placement="top">
<!-- Select .key file -->
<template #title>
<span>{{ $t('tooltip.clientKey') }}</span>
</template>
<NcButton size="small" :disabled="!sslFilesRequired" class="shadow" @click="keyFileInput?.click()">
{{ $t('labels.clientKey') }}
</NcButton>
</a-tooltip>
<a-tooltip placement="top">
<!-- Select CA file -->
<template #title>
<span>{{ $t('tooltip.clientCA') }}</span>
</template>
<NcButton size="small" :disabled="!sslFilesRequired" class="shadow" @click="caFileInput?.click()">
{{ $t('labels.serverCA') }}
</NcButton>
</a-tooltip>
</div>
</a-form-item>
<input ref="caFileInput" type="file" class="!hidden" @change="onFileSelect(CertTypes.ca, caFileInput)" />
<input ref="certFileInput" type="file" class="!hidden" @change="onFileSelect(CertTypes.cert, certFileInput)" />
<input ref="keyFileInput" type="file" class="!hidden" @change="onFileSelect(CertTypes.key, keyFileInput)" />
<a-divider />
<!-- Extra connection parameters -->
<a-form-item class="mb-2" :label="$t('labels.extraConnectionParameters')" v-bind="validateInfos.extraParameters">
<a-card>
<div v-for="(item, index) of formState.extraParameters" :key="index">
<div class="flex py-1 items-center gap-1">
<a-input v-model:value="item.key" />
<span>:</span>
<a-input v-model:value="item.value" />
<component
:is="iconMap.close"
:style="{ 'font-size': '1.5em', 'color': 'red' }"
@click="removeParam(index)"
/>
</div>
</div>
<NcButton size="small" type="dashed" class="w-full caption mt-2" @click="addNewParam">
<div class="flex items-center justify-center"><component :is="iconMap.plus" /></div>
</NcButton>
</a-card>
</a-form-item>
<a-divider />
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-4">
<a-row :gutter="24">
<a-col :span="12">
<a-form-item :label="$t('labels.inflection.tableName')"> <a-form-item :label="$t('labels.inflection.tableName')">
<a-select <NcSelect
v-model:value="formState.inflection.inflectionTable" v-model:value="formState.inflection.inflectionTable"
class="nc-select-shadow"
dropdown-class-name="nc-dropdown-inflection-table-name" dropdown-class-name="nc-dropdown-inflection-table-name"
> >
<a-select-option v-for="tp in inflectionTypes" :key="tp" :value="tp">{{ tp }}</a-select-option> <a-select-option v-for="tp in inflectionTypes" :key="tp" :value="tp">{{ tp }}</a-select-option>
</a-select> </NcSelect>
</a-form-item> </a-form-item>
</a-col>
<a-col :span="12">
<a-form-item :label="$t('labels.inflection.columnName')"> <a-form-item :label="$t('labels.inflection.columnName')">
<a-select <NcSelect
v-model:value="formState.inflection.inflectionColumn" v-model:value="formState.inflection.inflectionColumn"
class="nc-select-shadow"
dropdown-class-name="nc-dropdown-inflection-column-name" dropdown-class-name="nc-dropdown-inflection-column-name"
> >
<a-select-option v-for="tp in inflectionTypes" :key="tp" :value="tp">{{ tp }}</a-select-option> <a-select-option v-for="tp in inflectionTypes" :key="tp" :value="tp">{{ tp }}</a-select-option>
</a-select> </NcSelect>
</a-form-item> </a-form-item>
</a-col>
<div class="flex justify-end"> </a-row>
<NcButton size="small" type="primary" class="!rounded-md" @click="handleEditJSON()"> </div>
<!-- Edit connection JSON -->
{{ $t('activity.editConnJson') }}
</NcButton>
</div> </div>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
</template> </template>
<div>
<!-- For spacing -->
</div> </div>
</a-form>
<a-form-item class="flex justify-end !mt-5"> </div>
<div class="flex justify-end gap-2"> </div>
<div class="p-4 w-full flex items-center justify-between gap-3 border-t-1 border-gray-200">
<div class="flex-1 flex items-center gap-3">
<div class="flex-1 flex items-center gap-3 text-[#C86827]">
<GeneralIcon icon="alertTriangle" class="flex-none" />
<div>{{ $t('msg.warning.dbValid') }}</div>
</div>
</div>
<div class="flex items-center gap-3">
<div class="w-[15px] h-[15px] cursor-pointer" @dblclick="onEasterEgg"></div> <div class="w-[15px] h-[15px] cursor-pointer" @dblclick="onEasterEgg"></div>
<NcTooltip :disabled="!testConnectionError">
<template #title>
{{ testConnectionError }}
</template>
<NcButton <NcButton
:type="testSuccess ? 'ghost' : 'primary'" type="secondary"
size="small" size="small"
class="nc-extdb-btn-test-connection !rounded-md" class="nc-extdb-btn-test-connection"
:class="{ 'pointer-events-none': testSuccess }"
:loading="testingConnection" :loading="testingConnection"
icon-position="right"
@click="testConnection" @click="testConnection"
> >
<GeneralIcon v-if="testSuccess" icon="circleCheck" class="text-primary mr-2" /> <template #icon>
{{ $t('activity.testDbConn') }} <GeneralIcon v-if="testSuccess" icon="circleCheckSolid" class="!text-green-700 w-4 h-4" />
<GeneralIcon v-else-if="testConnectionError" icon="alertTriangleSolid" class="!text-red-700 w-4 h-4" />
</template>
<span>
{{ testSuccess ? 'Test successful' : 'Test connection' }}
</span>
</NcButton> </NcButton>
</NcTooltip>
<NcButton <NcButton
class="nc-extdb-btn-submit !rounded-md"
size="small" size="small"
type="primary" type="primary"
:disabled="!testSuccess" :disabled="!testSuccess"
:loading="editingSource" :loading="editingSource"
class="nc-extdb-btn-submit"
@click="editBase" @click="editBase"
> >
{{ $t('general.submit') }} {{ $t('general.submit') }}
</NcButton> </NcButton>
</div> </div>
</a-form-item>
<div class="w-full flex items-center mt-2 text-[#e65100]">
<component :is="iconMap.warning" class="mr-2 mb-5.9" />
<div>{{ $t('msg.warning.dbValid') }}</div>
</div> </div>
</a-form>
<a-modal
v-model:visible="configEditDlg"
:title="$t('activity.editConnJson')"
width="500px"
wrap-class-name="nc-modal-edit-connection-json"
@ok="handleOk"
>
<MonacoEditor v-if="configEditDlg" v-model="customFormState" class="h-[400px] w-full" />
</a-modal>
<!-- Use Connection URL -->
<a-modal
v-model:visible="importURLDlg"
:title="$t('activity.useConnectionUrl')"
width="600px"
:ok-text="$t('general.ok')"
:cancel-text="$t('general.cancel')"
wrap-class-name="nc-modal-connection-url"
@ok="handleImportURL"
>
<a-input v-model:value="importURL" />
</a-modal>
</div> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.nc-add-source-left-panel {
@apply p-6 flex-1 flex justify-center;
}
.nc-add-source-right-panel {
@apply p-5 w-[320px] border-l-1 border-gray-200 flex flex-col gap-4 bg-gray-50 rounded-br-2xl;
}
:deep(.ant-collapse-header) { :deep(.ant-collapse-header) {
@apply !pr-10 !-mt-4 text-right justify-end; @apply !-mt-4 !p-0 flex items-center;
}
:deep(.ant-collapse-icon-position-right > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow) {
@apply !right-0;
} }
:deep(.ant-collapse-content-box) { :deep(.ant-collapse-content-box) {
@apply !px-0; @apply !px-0 !pb-0 !pt-3;
} }
:deep(.ant-form-item-explain-error) { :deep(.ant-form-item-explain-error) {
@ -757,18 +646,26 @@ const allowDataWrite = computed({
} }
:deep(.ant-form-item) { :deep(.ant-form-item) {
@apply mb-2; @apply mb-0;
}
:deep(.ant-divider) {
@apply m-0;
} }
:deep(.ant-form-item-with-help .ant-form-item-explain) { :deep(.ant-form-item-with-help .ant-form-item-explain) {
@apply !min-h-0; @apply !min-h-0;
} }
:deep(.ant-select .ant-select-selector .ant-select-selection-item) {
@apply font-weight-400;
}
.edit-source { .edit-source {
:deep(.ant-input-affix-wrapper), :deep(.ant-input-affix-wrapper),
:deep(.ant-input), :deep(.ant-input),
:deep(.ant-select) { :deep(.ant-select) {
@apply !appearance-none border-solid rounded; @apply !appearance-none border-solid rounded-md;
} }
:deep(.ant-input-password) { :deep(.ant-input-password) {
@ -776,5 +673,100 @@ const allowDataWrite = computed({
@apply !border-none my-0; @apply !border-none my-0;
} }
} }
.nc-form-section {
@apply flex flex-col gap-3;
}
.nc-form-section-title {
@apply text-sm font-bold text-gray-800;
}
.nc-form-section-body {
@apply flex flex-col gap-3;
}
.nc-connection-json-editor {
@apply min-h-[300px] max-h-[600px];
resize: vertical;
overflow-y: auto;
}
:deep(.ant-form-item-label > label.ant-form-item-required:after) {
@apply content-['*'] inline-block text-inherit text-red-500 ml-1;
}
.nc-form-extra-connectin-parameters {
:deep(.ant-input) {
&:not(:hover):not(:focus):not(:disabled) {
@apply !shadow-default !border-gray-200;
}
&:hover:not(:focus):not(:disabled) {
@apply !border-gray-200 !shadow-hover;
}
&:focus {
@apply !shadow-selected !ring-0;
border-color: var(--ant-primary-color-hover) !important;
}
}
}
:deep(.ant-form-item) {
&.ant-form-item-has-error {
&:not(:has(.ant-input-password)) .ant-input {
&:not(:hover):not(:focus):not(:disabled) {
@apply shadow-default;
}
&:hover:not(:focus):not(:disabled) {
@apply shadow-hover;
}
&:focus {
@apply shadow-error ring-0;
}
}
.ant-input-number,
.ant-input-affix-wrapper.ant-input-password {
&:not(:hover):not(:focus-within):not(:disabled) {
@apply shadow-default;
}
&:hover:not(:focus-within):not(:disabled) {
@apply shadow-hover;
}
&:focus-within {
@apply shadow-error ring-0;
}
}
}
&:not(.ant-form-item-has-error) {
&:not(:has(.ant-input-password)) .ant-input {
&:not(:hover):not(:focus):not(:disabled) {
@apply shadow-default border-gray-200;
}
&:hover:not(:focus):not(:disabled) {
@apply border-gray-200 shadow-hover;
}
&:focus {
@apply shadow-selected ring-0;
}
}
.ant-input-number,
.ant-input-affix-wrapper.ant-input-password {
&:not(:hover):not(:focus-within):not(:disabled) {
@apply shadow-default border-gray-200;
}
&:hover:not(:focus-within):not(:disabled) {
@apply border-gray-200 shadow-hover;
}
&:focus-within {
@apply shadow-selected ring-0;
}
}
}
}
:deep(.ant-row:not(.ant-form-item)) {
@apply !-mx-1.5;
& > .ant-col {
@apply !px-1.5;
}
}
} }
</style> </style>

66
packages/nc-gui/components/dashboard/settings/data-sources/SourceRestrictions.vue

@ -11,54 +11,56 @@ const metaWrite = useVModel(props, 'allowMetaWrite', emits)
</script> </script>
<template> <template>
<a-form-item> <a-form-item class="nc-source-restictions-card">
<template #help> <div class="flex flex-col gap-2">
<span class="text-small"> <div class="flex items-center gap-3">
{{ $t('tooltip.allowDataWrite') }} <NcTooltip :disabled="!metaWrite" placement="topLeft" class="flex">
</span>
</template>
<template #label>
<div class="flex gap-1 justify-end">
<span>
{{ $t('labels.allowDataWrite') }}
</span>
</div>
</template>
<div class="flex justify-start">
<NcTooltip :disabled="!metaWrite" placement="topLeft">
<template #title> <template #title>
{{ $t('tooltip.dataWriteOptionDisabled') }} {{ $t('tooltip.dataWriteOptionDisabled') }}
</template> </template>
<a-switch v-model:checked="dataWrite" :disabled="metaWrite" data-testid="nc-allow-data-write" size="small"></a-switch> <a-switch v-model:checked="dataWrite" :disabled="metaWrite" data-testid="nc-allow-data-write" size="small"></a-switch>
</NcTooltip> </NcTooltip>
<span class="cursor-pointer" @click="!metaWrite ? (dataWrite = !dataWrite) : undefined">
{{ $t('labels.allowDataWrite') }}
</span>
</div>
<div class="ml-10 text-small leading-[18px] text-gray-500">
{{ $t('tooltip.allowDataWrite') }}
</div>
</div> </div>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item class="nc-source-restictions-card">
<template #help> <div class="flex flex-col gap-2">
<span class="text-small"> <div class="flex items-center gap-3">
<span class="font-weight-medium" :class="{ 'nc-allow-meta-write-help': metaWrite }"> <a-switch
{{ $t('labels.notRecommended') }}: v-model:checked="metaWrite"
</span> data-testid="nc-allow-meta-write"
{{ $t('tooltip.allowMetaWrite') }} class="nc-allow-meta-write"
</span> size="small"
</template> ></a-switch>
<template #label>
<div class="flex gap-1 justify-end"> <span class="cursor-pointer" @click="metaWrite = !metaWrite">
<span>
{{ $t('labels.allowMetaWrite') }} {{ $t('labels.allowMetaWrite') }}
</span> </span>
</div> </div>
</template> <div class="ml-10 text-small leading-[18px] text-gray-500" :class="{ 'nc-allow-meta-write-help': metaWrite }">
<a-switch v-model:checked="metaWrite" data-testid="nc-allow-meta-write" class="nc-allow-meta-write" size="small"></a-switch> {{ $t('labels.notRecommended') }}:
{{ $t('tooltip.allowMetaWrite') }}
</div>
</div>
</a-form-item> </a-form-item>
</template> </template>
<style scoped> <style lang="scss" scoped>
.nc-allow-meta-write.ant-switch-checked { .nc-allow-meta-write.ant-switch-checked {
background: #b33870; background: #b33771;
} }
.nc-allow-meta-write-help { .nc-allow-meta-write-help {
color: #b33870; color: #b33771;
}
.nc-source-restictions-card {
@apply border-1 border-gray-200 rounded-lg px-3 py-2;
} }
</style> </style>

38
packages/nc-gui/components/dashboard/settings/data-sources/SupportedDocs.vue

@ -0,0 +1,38 @@
<script lang="ts" setup>
const supportedDocs = [
{
title: 'Configure a PostgreSQL Integration',
href: '',
},
{
title: 'Getting started with Integrations',
href: '',
},
{
title: 'Troubleshoot database connection',
href: '',
},
] as {
title: string
href: string
}[]
</script>
<template>
<div>
<div class="w-full flex flex-col gap-3">
<div class="text-sm text-gray-800 font-semibold">Supported Docs</div>
<div>
<div v-for="doc of supportedDocs" class="flex items-center gap-1">
<div class="h-7 w-7 flex items-center justify-center">
<GeneralIcon icon="bookOpen" class="flex-none w-4 h-4 text-gray-600"/>
</div>
<a :href="doc.href" target="_blank" rel="noopener noreferrer" class="!text-gray-700 text-sm !no-underline !hover:underline">
{{ doc.title }}
</a>
</div>
</div>
</div>
</div>
</template>

2
packages/nc-gui/components/general/BaseIconColorPicker.vue

@ -3,7 +3,7 @@ import tinycolor from 'tinycolor2'
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
type?: NcProjectType | string type?: typeof NcProjectType | string
modelValue?: string modelValue?: string
size?: 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' size?: 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge'
readonly?: boolean readonly?: boolean

12
packages/nc-gui/components/general/DeleteModal.vue

@ -1,10 +1,16 @@
<script lang="ts" setup> <script lang="ts" setup>
const props = defineProps<{ const props = withDefaults(
defineProps<{
visible: boolean visible: boolean
entityName: string entityName: string
onDelete: () => Promise<void> onDelete: () => Promise<void>
deleteLabel?: string | undefined deleteLabel?: string | undefined
}>() showDefaultDeleteMsg?: boolean
}>(),
{
showDefaultDeleteMsg: true,
},
)
const emits = defineEmits(['update:visible']) const emits = defineEmits(['update:visible'])
const visible = useVModel(props, 'visible', emits) const visible = useVModel(props, 'visible', emits)
@ -57,7 +63,7 @@ watch(visible, (value) => {
<div ref="modalRef" class="flex flex-col p-6"> <div ref="modalRef" class="flex flex-col p-6">
<div class="flex flex-row pb-2 mb-3 font-medium text-lg text-gray-800">{{ deleteLabel }} {{ props.entityName }}</div> <div class="flex flex-row pb-2 mb-3 font-medium text-lg text-gray-800">{{ deleteLabel }} {{ props.entityName }}</div>
<div class="mb-3 text-gray-800"> <div v-if="showDefaultDeleteMsg" class="mb-3 text-gray-800">
{{ {{
$t('msg.areYouSureUWantToDeleteLabel', { $t('msg.areYouSureUWantToDeleteLabel', {
deleteLabel: deleteLabel.toLowerCase(), deleteLabel: deleteLabel.toLowerCase(),

17
packages/nc-gui/components/nc/Button.vue

@ -21,6 +21,7 @@ interface Props {
centered?: boolean centered?: boolean
fullWidth?: boolean fullWidth?: boolean
iconOnly?: boolean iconOnly?: boolean
iconPosition?: 'left' | 'right'
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
@ -29,6 +30,7 @@ const props = withDefaults(defineProps<Props>(), {
type: 'primary', type: 'primary',
fullWidth: false, fullWidth: false,
centered: true, centered: true,
iconPosition: 'left',
}) })
const emits = defineEmits(['update:loading']) const emits = defineEmits(['update:loading'])
@ -97,6 +99,7 @@ useEventListener(NcButton, 'mousedown', () => {
}" }"
class="flex flex-row gap-x-2.5 w-full" class="flex flex-row gap-x-2.5 w-full"
> >
<template v-if="iconPosition === 'left'">
<GeneralLoader <GeneralLoader
v-if="loading" v-if="loading"
:class="{ :class="{
@ -108,6 +111,7 @@ useEventListener(NcButton, 'mousedown', () => {
/> />
<slot v-else name="icon" /> <slot v-else name="icon" />
</template>
<div <div
v-if="!(size === 'xxsmall' && loading) && !props.iconOnly" v-if="!(size === 'xxsmall' && loading) && !props.iconOnly"
:class="{ :class="{
@ -120,6 +124,19 @@ useEventListener(NcButton, 'mousedown', () => {
<slot v-else /> <slot v-else />
</div> </div>
<template v-if="iconPosition === 'right'">
<GeneralLoader
v-if="loading"
:class="{
'!text-white': type === 'primary' || type === 'danger',
'!text-gray-800': type !== 'primary' && type !== 'danger',
}"
class="flex !bg-inherit"
size="medium"
/>
<slot v-else name="icon" />
</template>
</div> </div>
</a-button> </a-button>
</template> </template>

4
packages/nc-gui/components/nc/Modal.vue

@ -20,7 +20,9 @@ const props = withDefaults(
const emits = defineEmits(['update:visible']) const emits = defineEmits(['update:visible'])
const { width: propWidth, destroyOnClose, maskClosable, wrapClassName: _wrapClassName, showSeparator } = props const { width: propWidth, destroyOnClose, wrapClassName: _wrapClassName, showSeparator } = props
const { maskClosable } = toRefs(props)
const { isMobileMode } = useGlobal() const { isMobileMode } = useGlobal()

12
packages/nc-gui/components/nc/Select.vue

@ -62,6 +62,10 @@ const onChange = (value: string) => {
<GeneralLoader v-if="loading" /> <GeneralLoader v-if="loading" />
<GeneralIcon v-else class="text-gray-800 nc-select-expand-btn" :icon="suffixIcon" /> <GeneralIcon v-else class="text-gray-800 nc-select-expand-btn" :icon="suffixIcon" />
</template> </template>
<template v-if="$slots.dropdownRender" #dropdownRender="{ menuNode }">
<slot name="dropdownRender" :menu-node="menuNode" />
</template>
<slot /> <slot />
</a-select> </a-select>
</template> </template>
@ -97,6 +101,14 @@ const onChange = (value: string) => {
.ant-select-selection-item-remove { .ant-select-selection-item-remove {
@apply text-gray-800 !pb-1; @apply text-gray-800 !pb-1;
} }
.ant-select-clear {
@apply flex;
svg {
@apply flex-none;
}
}
} }
.nc-select.ant-select-focused:not(.ant-select-disabled).ant-select:not(.ant-select-customize-input) .ant-select-selector { .nc-select.ant-select-focused:not(.ant-select-disabled).ant-select:not(.ant-select-customize-input) .ant-select-selector {
box-shadow: none; box-shadow: none;

7
packages/nc-gui/components/nc/Tabs.vue

@ -13,6 +13,13 @@ const props = defineProps<{
}" }"
> >
<slot /> <slot />
<template v-if="$slots.leftExtra" #leftExtra>
<slot name="leftExtra" />
</template>
<template v-if="$slots.rightExtra" #rightExtra>
<slot name="rightExtra" />
</template>
</a-tabs> </a-tabs>
</template> </template>

2
packages/nc-gui/components/project/AccessSettings.vue

@ -280,7 +280,7 @@ const customRow = (record: Record<string, any>) => ({
</div> </div>
</div> </div>
<div class="w-full flex justify-between items-center max-w-350 gap-3"> <div v-else class="w-full flex justify-between items-center max-w-350 gap-3">
<a-input <a-input
v-model:value="userSearchText" v-model:value="userSearchText"
:placeholder="$t('title.searchMembers')" :placeholder="$t('title.searchMembers')"

79
packages/nc-gui/components/project/AllTables.vue

@ -1,13 +1,16 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { SourceType, TableType } from 'nocodb-sdk' import type { SourceType, TableType } from 'nocodb-sdk'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import NcTooltip from '~/components/nc/Tooltip.vue'
const { activeTables } = storeToRefs(useTablesStore()) const { activeTables } = storeToRefs(useTablesStore())
const { openTable } = useTablesStore() const { openTable } = useTablesStore()
const { openedProject } = storeToRefs(useBases()) const { openedProject, isDataSourceLimitReached } = storeToRefs(useBases())
const { base } = storeToRefs(useBase()) const { base } = storeToRefs(useBase())
const isNewBaseModalOpen = ref(false)
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRoles()
const { $e } = useNuxtApp() const { $e } = useNuxtApp()
@ -16,6 +19,8 @@ const { t } = useI18n()
const isImportModalOpen = ref(false) const isImportModalOpen = ref(false)
const syncDataModalOpen = ref(false)
const defaultBase = computed(() => { const defaultBase = computed(() => {
return openedProject.value?.sources?.[0] return openedProject.value?.sources?.[0]
}) })
@ -99,12 +104,18 @@ const customRow = (record: Record<string, any>) => ({
openTable(record as TableType) openTable(record as TableType)
}, },
}) })
const onCreateBaseClick = () => {
if (isDataSourceLimitReached.value) return
isNewBaseModalOpen.value = true
}
</script> </script>
<template> <template>
<div class="nc-all-tables-view"> <div class="nc-all-tables-view">
<div <div
class="flex flex-row gap-x-6 pb-3 pt-6" class="flex flex-row gap-x-6 pt-6 pb-2 overflow-x-auto nc-scrollbar-thin"
:class="{ :class="{
'pointer-events-none': base?.isLoading, 'pointer-events-none': base?.isLoading,
}" }"
@ -116,9 +127,14 @@ const customRow = (record: Record<string, any>) => ({
data-testid="proj-view-btn__add-new-table" data-testid="proj-view-btn__add-new-table"
@click="openTableCreateDialog()" @click="openTableCreateDialog()"
> >
<GeneralIcon icon="addOutlineBox" /> <div class="flex items-center gap-3">
<div class="label">{{ $t('general.new') }} {{ $t('objects.table') }}</div> <GeneralIcon icon="addOutlineBox" class="!text-brand-500 !h-5 !w-5" />
<div class="label">{{ $t('general.create') }} {{ $t('general.new') }} {{ $t('objects.table') }}</div>
</div>
<div class="subtext">Start from scratch by creating a new table.</div>
</div> </div>
<div <div
v-if="isUIAllowed('tableCreate', { source: base?.sources?.[0] })" v-if="isUIAllowed('tableCreate', { source: base?.sources?.[0] })"
v-e="['c:table:import']" v-e="['c:table:import']"
@ -127,14 +143,15 @@ const customRow = (record: Record<string, any>) => ({
data-testid="proj-view-btn__import-data" data-testid="proj-view-btn__import-data"
@click="isImportModalOpen = true" @click="isImportModalOpen = true"
> >
<GeneralIcon icon="download" /> <div class="flex items-center gap-3">
<GeneralIcon icon="download" class="!text-orange-700 !h-5 !w-5" />
<div class="label">{{ $t('activity.import') }} {{ $t('general.data') }}</div> <div class="label">{{ $t('activity.import') }} {{ $t('general.data') }}</div>
</div> </div>
<!-- <component :is="isDataSourceLimitReached ? NcTooltip : 'div'" v-if="isUIAllowed('sourceCreate')"> <div class="subtext">Quickly bring in existing data from various files & external sources.</div>
</div>
<NcTooltip v-if="isUIAllowed('sourceCreate')" placement="bottom" :disabled="!isDataSourceLimitReached" class="flex-none flex">
<template #title> <template #title>
<div>
{{ $t('tooltip.reachedSourceLimit') }} {{ $t('tooltip.reachedSourceLimit') }}
</div>
</template> </template>
<div <div
v-e="['c:table:create-source']" v-e="['c:table:create-source']"
@ -146,14 +163,32 @@ const customRow = (record: Record<string, any>) => ({
}" }"
@click="onCreateBaseClick" @click="onCreateBaseClick"
> >
<GeneralIcon icon="dataSource" /> <div class="flex items-center gap-3">
<GeneralIcon icon="server1" class="!text-green-700 !h-5 !w-5" />
<div class="label">{{ $t('labels.connectDataSource') }}</div> <div class="label">{{ $t('labels.connectDataSource') }}</div>
</div> </div>
</component> --> <div class="subtext">Connect directly in realtime to external databases.</div>
</div>
</NcTooltip>
<div
v-if="isUIAllowed('tableCreate', { source: base?.sources?.[0] })"
v-e="['c:table:create-source']"
role="button"
class="nc-base-view-all-table-btn"
data-testid="proj-view-btn__create-source"
@click="syncDataModalOpen = true"
>
<div class="flex items-center gap-3">
<GeneralIcon icon="refresh" class="!text-blue-700 !h-5 !w-5" />
<div class="label capitalize">{{ $t('labels.syncData') }}</div>
</div>
<div class="subtext">Keep your data updated and in sync across multiple sources.</div>
</div>
</div> </div>
<div <div
v-if="base?.isLoading" v-if="base?.isLoading"
class="flex items-center justify-center text-center" class="flex items-center justify-center text-center mt-4"
:style="{ :style="{
height: 'calc(100vh - var(--topbar-height) - 15.2rem)', height: 'calc(100vh - var(--topbar-height) - 15.2rem)',
}" }"
@ -168,7 +203,7 @@ const customRow = (record: Record<string, any>) => ({
<div <div
v-else-if="activeTables.length" v-else-if="activeTables.length"
class="flex mt-2" class="flex mt-4"
:style="{ :style="{
height: 'calc(100vh - var(--topbar-height) - 15.2rem)', height: 'calc(100vh - var(--topbar-height) - 15.2rem)',
}" }"
@ -238,24 +273,36 @@ const customRow = (record: Record<string, any>) => ({
</div> </div>
<ProjectImportModal v-if="defaultBase" v-model:visible="isImportModalOpen" :source="defaultBase" /> <ProjectImportModal v-if="defaultBase" v-model:visible="isImportModalOpen" :source="defaultBase" />
<!-- <LazyDashboardSettingsDataSourcesCreateBase v-model:open="isNewBaseModalOpen" /> --> <ProjectSyncDataModal v-if="defaultBase" v-model:open="syncDataModalOpen" />
<LazyDashboardSettingsDataSourcesCreateBase v-if="isNewBaseModalOpen" v-model:open="isNewBaseModalOpen" is-modal />
</div> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.nc-base-view-all-table-btn { .nc-base-view-all-table-btn {
@apply flex flex-col gap-y-6 p-4 bg-gray-100 rounded-xl w-56 cursor-pointer text-gray-600 hover:(bg-gray-200 text-black); @apply flex-none flex flex-col gap-y-3 px-3 py-5 bg-gray-50 rounded-xl border-1 border-gray-100 min-w-[230px] max-w-[245px] cursor-pointer text-gray-800 hover:(bg-gray-100 border-gray-200) transition-all duration-300;
&:hover {
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.08);
}
.nc-icon { .nc-icon {
@apply h-10 w-10; @apply h-6 w-6;
} }
.label { .label {
@apply text-base font-medium; @apply text-base font-bold whitespace-nowrap text-gray-800;
}
.subtext {
@apply text-xs text-gray-600;
} }
} }
.nc-base-view-all-table-btn.disabled { .nc-base-view-all-table-btn.disabled {
@apply bg-gray-50 text-gray-400 hover:(bg-gray-50 text-gray-400) cursor-not-allowed; @apply bg-gray-50 text-gray-400 hover:(bg-gray-50 text-gray-400) cursor-not-allowed;
} }
.nc-text-icon {
@apply flex-none w-5 h-5 rounded bg-white text-gray-800 text-[6px] leading-4 font-weight-800 flex items-center justify-center;
}
</style> </style>

6
packages/nc-gui/components/project/ImportModal.vue

@ -71,7 +71,11 @@ const onClick = (type: 'airtable' | 'csv' | 'excel' | 'json') => {
<template> <template>
<GeneralModal v-model:visible="visible" width="35rem"> <GeneralModal v-model:visible="visible" width="35rem">
<div class="flex flex-col px-8 pt-6 pb-9"> <div class="flex flex-col px-8 pt-6 pb-9">
<div class="text-lg font-medium mb-6">{{ $t('general.import') }}</div> <div class="flex items-center gap-3 mb-6">
<GeneralIcon icon="download" class="flex-none !text-orange-700 !w-4 !h-4" />
<div class="text-base font-weight-700">{{ $t('general.import') }}</div>
</div>
<div class="row mb-10"> <div class="row mb-10">
<div class="nc-base-view-import-sub-btn" @click="onClick('airtable')"> <div class="nc-base-view-import-sub-btn" @click="onClick('airtable')">
<GeneralIcon icon="airtable" /> <GeneralIcon icon="airtable" />

153
packages/nc-gui/components/project/SyncDataModal.vue

@ -0,0 +1,153 @@
<script lang="ts" setup>
import type { SyncDataType } from '../../lib/enums'
const props = defineProps<{ open: boolean }>()
const emit = defineEmits(['update:open'])
const vOpen = useVModel(props, 'open', emit)
const { syncDataUpvotes, updateSyncDataUpvotes } = useGlobal()
const { $e } = useNuxtApp()
const searchQuery = ref('')
const filteredSyncDataTypes = computed(() =>
syncDataTypes.filter((s) => s.title.toLowerCase().includes(searchQuery.value.toLowerCase())),
)
const upvotesData = computed(() => {
return new Set(syncDataUpvotes.value)
})
const handleUpvote = (syncDataType: SyncDataType) => {
if (upvotesData.value.has(syncDataType)) return
$e(`a:sync-request:${syncDataType}`)
updateSyncDataUpvotes([...syncDataUpvotes.value, syncDataType])
}
</script>
<template>
<NcModal
v-model:visible="vOpen"
centered
size="large"
wrap-class-name="nc-project-sync-data-modal-wrapper"
nc-modal-class-name="!p-0 h-80vh max-h-[864px]"
@keydown.esc="vOpen = false"
>
<div class="h-full flex flex-col overflow-hidden">
<div class="flex items-center justify-between gap-4 p-4 border-b-1 border-gray-200">
<GeneralIcon icon="refresh" class="flex-none h-5 w-5 !text-blue-700" />
<div class="flex-1">
<div class="flex-1 flex items-center gap-3">
<h3 class="my-0 capitalize text-base font-weight-700">
{{ $t('labels.syncData') }}
</h3>
<NcBadge :border="false" class="text-brand-500 !h-5.5 bg-brand-50 text-sm px-2">{{
$t('msg.toast.futureRelease')
}}</NcBadge>
</div>
<div class="text-xs text-gray-600">{{ $t('labels.syncDataModalSubtitle') }}</div>
</div>
<NcButton type="text" size="xs" class="!px-0" @click="vOpen = false">
<GeneralIcon icon="close" />
</NcButton>
</div>
<div class="p-6">
<div class="flex items-center justify-end gap-3 max-w-[918px] mx-auto">
<a-input
v-model:value="searchQuery"
type="text"
class="nc-search-sync-data-input !min-w-[300px] !max-w-[300px] nc-input-sm flex-none"
placeholder="Search service"
allow-clear
>
<template #prefix>
<GeneralIcon icon="search" class="mr-2 h-4 w-4 text-gray-500" />
</template>
</a-input>
</div>
</div>
<div class="flex-1 px-6 flex overflow-auto nc-scrollbar-thin">
<div
class="flex flex-col gap-6 w-full max-w-[918px] mx-auto"
:class="{
'flex-1': !filteredSyncDataTypes.length,
}"
>
<div v-if="filteredSyncDataTypes.length" class="flex items-start gap-3 flex-wrap pb-6">
<div v-for="syncData of filteredSyncDataTypes" :key="syncData.value" class="nc-sync-data-card">
<div class="card-icon-wrapper">
<component :is="syncData.icon" class="flex-none stroke-transparent" />
</div>
<div class="card-title flex-1">
{{ $t(syncData.title) }}
</div>
<div>
<NcButton
type="secondary"
size="xsmall"
class="nc-sync-data-upvote-btn !rounded-lg !px-2"
:class="{
selected: upvotesData.has(syncData.value),
}"
@click="handleUpvote(syncData.value)"
>
<div class="flex items-center gap-2">
<GeneralIcon icon="thumbsUpOutline" />
</div>
</NcButton>
</div>
</div>
</div>
<div v-else class="pt-8 flex-1 flex items-center justify-center">
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" class="!my-0" />
</div>
</div>
</div>
</div>
</NcModal>
</template>
<style lang="scss">
.nc-project-sync-data-modal-wrapper {
.nc-modal {
@apply !p-0;
height: min(90vh, 1024px);
max-height: min(90vh, 1024px) !important;
}
.nc-sync-data-title {
@apply text-xl font-semibold;
}
.ant-input-affix-wrapper.nc-search-sync-data-input {
&:not(:has(.ant-input-clear-icon-hidden)):has(.ant-input-clear-icon) {
@apply border-[var(--ant-primary-5)];
}
}
.nc-sync-data-card {
@apply p-3 flex items-center gap-4 rounded-xl border-1 border-gray-200 w-[298px] h-[76px];
.card-icon-wrapper {
@apply w-[52px] h-[52px] p-1 flex items-center justify-center bg-gray-100 rounded-lg;
.card-icon {
}
}
.card-title {
@apply text-base font-weight-700 text-gray-800;
}
.nc-sync-data-upvote-btn {
&.selected {
@apply shadow-selected !text-brand-500 !border-brand-500 !cursor-not-allowed pointer-events-none;
}
}
}
}
</style>

16
packages/nc-gui/components/project/View.vue

@ -6,6 +6,8 @@ const props = defineProps<{
baseId?: string baseId?: string
}>() }>()
useProvideIntegrationViewStore()
const basesStore = useBases() const basesStore = useBases()
const { openedProject, activeProjectId, basesUser, bases } = storeToRefs(basesStore) const { openedProject, activeProjectId, basesUser, bases } = storeToRefs(basesStore)
@ -35,10 +37,14 @@ const currentBase = computedAsync(async () => {
const { isUIAllowed, baseRoles } = useRoles() const { isUIAllowed, baseRoles } = useRoles()
const { base } = storeToRefs(useBase())
const { projectPageTab } = storeToRefs(useConfigStore()) const { projectPageTab } = storeToRefs(useConfigStore())
const { isMobileMode } = useGlobal() const { isMobileMode } = useGlobal()
const baseSettingsState = ref('')
const userCount = computed(() => const userCount = computed(() =>
activeProjectId.value ? basesUser.value.get(activeProjectId.value)?.filter((user) => !user?.deleted)?.length : 0, activeProjectId.value ? basesUser.value.get(activeProjectId.value)?.filter((user) => !user?.deleted)?.length : 0,
) )
@ -158,7 +164,7 @@ watch(
</template> </template>
<ProjectAccessSettings :base-id="currentBase?.id" /> <ProjectAccessSettings :base-id="currentBase?.id" />
</a-tab-pane> </a-tab-pane>
<!-- <a-tab-pane v-if="isUIAllowed('sourceCreate')" key="data-source"> <a-tab-pane v-if="isUIAllowed('sourceCreate') && base.id" key="data-source">
<template #tab> <template #tab>
<div class="tab-title" data-testid="proj-view-tab__data-sources"> <div class="tab-title" data-testid="proj-view-tab__data-sources">
<GeneralIcon icon="database" /> <GeneralIcon icon="database" />
@ -175,8 +181,12 @@ watch(
</div> </div>
</div> </div>
</template> </template>
<DashboardSettingsDataSources v-model:state="baseSettingsState" /> <DashboardSettingsDataSources
</a-tab-pane> --> v-model:state="baseSettingsState"
:base-id="base.id"
class="max-h-[calc(100%_-_36px)] pt-3"
/>
</a-tab-pane>
</a-tabs> </a-tabs>
</div> </div>
</div> </div>

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

@ -8,7 +8,7 @@ const { isLeftSidebarOpen } = storeToRefs(useSidebarStore())
const { $e } = useNuxtApp() const { $e } = useNuxtApp()
const { isUIAllowed, isDataReadOnly } = useRoles() const { isUIAllowed } = useRoles()
const { base } = storeToRefs(useBase()) const { base } = storeToRefs(useBase())
const meta = inject(MetaInj, ref()) const meta = inject(MetaInj, ref())

8
packages/nc-gui/components/workspace/AuditLogs.vue

@ -263,7 +263,7 @@ onKeyStroke('ArrowDown', onDown)
ref="tableWrapper" ref="tableWrapper"
class="nc-audit-logs-table h-full max-h-[calc(100%_-_40px)] relative nc-scrollbar-thin !overflow-auto" class="nc-audit-logs-table h-full max-h-[calc(100%_-_40px)] relative nc-scrollbar-thin !overflow-auto"
> >
<table class="!sticky top-0 z-10"> <table class="!sticky top-0 z-5">
<thead> <thead>
<tr> <tr>
<th <th
@ -630,11 +630,9 @@ onKeyStroke('ArrowDown', onDown)
tr { tr {
@apply cursor-pointer; @apply cursor-pointer;
.td {
@apply text-small leading-[18px] text-gray-600;
}
td { td {
@apply text-sm text-gray-600;
&.cell-user { &.cell-user {
@apply sticky left-0 z-4 bg-white; @apply sticky left-0 z-4 bg-white;
} }

2
packages/nc-gui/components/workspace/CreateProjectDlg.vue

@ -30,7 +30,7 @@ const nameValidationRules = [
required: true, required: true,
message: t('msg.info.dbNameRequired'), message: t('msg.info.dbNameRequired'),
}, },
baseTitleValidator, baseTitleValidator(),
] as RuleObject[] ] as RuleObject[]
const form = ref<typeof Form>() const form = ref<typeof Form>()

8
packages/nc-gui/components/workspace/View.vue

@ -71,7 +71,7 @@ onMounted(() => {
<div v-if="currentWorkspace" class="flex w-full px-6 max-w-[97.5rem] flex-col nc-workspace-settings"> <div v-if="currentWorkspace" class="flex w-full px-6 max-w-[97.5rem] flex-col nc-workspace-settings">
<div v-if="!props.workspaceId" class="flex gap-2 items-center min-w-0 py-4"> <div v-if="!props.workspaceId" class="flex gap-2 items-center min-w-0 py-4">
<h1 class="text-base capitalize font-weight-bold tracking-[0.5px] mb-0 nc-workspace-title truncate min-w-10 capitalize"> <h1 class="text-base capitalize font-weight-bold tracking-[0.5px] mb-0 nc-workspace-title truncate min-w-10 capitalize">
{{ currentWorkspace?.title }} > {{ $t('title.teamAndSettings') }} <span class="text-gray-500">{{ currentWorkspace?.title }} ></span> {{ $t('title.teamAndSettings') }}
</h1> </h1>
</div> </div>
<div v-else> <div v-else>
@ -99,7 +99,7 @@ onMounted(() => {
<template v-if="isUIAllowed('workspaceSettings')"> <template v-if="isUIAllowed('workspaceSettings')">
<a-tab-pane key="collaborators" class="w-full"> <a-tab-pane key="collaborators" class="w-full">
<template #tab> <template #tab>
<div class="flex flex-row items-center px-2 pb-1 gap-x-1.5"> <div class="flex flex-row items-center pb-1 gap-x-1.5">
<GeneralIcon icon="users" class="!h-3.5 !w-3.5" /> <GeneralIcon icon="users" class="!h-3.5 !w-3.5" />
Members Members
</div> </div>
@ -111,7 +111,7 @@ onMounted(() => {
<template v-if="isUIAllowed('workspaceManage')"> <template v-if="isUIAllowed('workspaceManage')">
<a-tab-pane key="settings" class="w-full"> <a-tab-pane key="settings" class="w-full">
<template #tab> <template #tab>
<div class="flex flex-row items-center px-2 pb-1 gap-x-1.5" data-testid="nc-workspace-settings-tab-settings"> <div class="flex flex-row items-center pb-1 gap-x-1.5" data-testid="nc-workspace-settings-tab-settings">
<GeneralIcon icon="settings" /> <GeneralIcon icon="settings" />
Settings Settings
</div> </div>
@ -123,7 +123,7 @@ onMounted(() => {
<template v-if="isUIAllowed('workspaceAuditList') && !props.workspaceId"> <template v-if="isUIAllowed('workspaceAuditList') && !props.workspaceId">
<a-tab-pane key="audit" class="w-full"> <a-tab-pane key="audit" class="w-full">
<template #tab> <template #tab>
<div class="flex flex-row items-center px-2 pb-1 gap-x-1.5"> <div class="flex flex-row items-center pb-1 gap-x-1.5">
<GeneralIcon icon="audit" class="!h-3.5 !w-3.5" /> <GeneralIcon icon="audit" class="!h-3.5 !w-3.5" />
Audit Logs Audit Logs
</div> </div>

60
packages/nc-gui/components/workspace/integrations/Edit.vue

@ -0,0 +1,60 @@
<script lang="ts" setup>
const { pageMode, IntegrationsPageMode, activeIntegration, categories, activeCategory } = useIntegrationStore()
</script>
<template>
<div class="flex flex-col nc-workspace-settings-integrations-new">
<div class="flex flex-col">
<div class="flex items-center p-6">
<div class="cursor-pointer text-primary mr-4" @click="pageMode = IntegrationsPageMode.LIST">
<GeneralIcon icon="arrowLeft" />
Back
</div>
<WorkspaceIntegrationsIcon :integration-type="activeIntegration.type" size="sm" />
<div class="text-md font-bold">New {{ activeIntegration.title }}</div>
</div>
<div class="border-b-1 border-gray-200 mx-4"></div>
</div>
<div class="panel-view">
<div class="panel-indices">
<div v-for="ct of categories" :key="ct.label" class="panel-index">
<div class="flex items-center gap-2" :class="{ 'text-primary': activeCategory?.label === ct.label }">
<div class="logo-wrapper">
<GeneralIcon :icon="ct.icon" />
</div>
<div class="text-sm">{{ ct.label }}</div>
</div>
</div>
</div>
<div v-if="activeIntegration" class="panel">
<WorkspaceIntegrationsFormsEditDatabase />
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.logo-wrapper {
@apply bg-gray-200 p-2 mr-2 rounded-lg flex items-center justify-center;
width: 32px;
height: 32px;
font-size: 2rem;
}
.panel-view {
@apply flex gap-2 mx-4 mt-6;
max-width: 1024px;
.panel {
@apply w-3/4;
}
.panel-indices {
@apply mr-4 flex flex-col cursor-default;
.panel-index {
@apply p-2 rounded-lg;
}
}
}
</style>

71
packages/nc-gui/components/workspace/integrations/EditOrAdd.vue

@ -0,0 +1,71 @@
<script lang="ts" setup>
const props = defineProps<{ loadDatasourceInfo?: boolean; baseId?: string }>()
const { loadDatasourceInfo, baseId } = toRefs(props)
const { pageMode, IntegrationsPageMode, integrationType, activeIntegration } = useIntegrationStore()
const isEditOrAddIntegrationModalOpen = computed({
get: () => {
return pageMode.value === IntegrationsPageMode.ADD || pageMode.value === IntegrationsPageMode.EDIT
},
set: (value: boolean) => {
if (!value) {
pageMode.value = null
}
},
})
const connectionType = computed(() => {
switch (
pageMode.value === IntegrationsPageMode.EDIT
? activeIntegration.value?.sub_type || activeIntegration.value?.config?.client
: activeIntegration.value?.type
) {
case integrationType.PostgreSQL:
return ClientType.PG
case integrationType.MySQL:
return ClientType.MYSQL
default: {
return undefined
}
}
})
</script>
<template>
<NcModal
v-model:visible="isEditOrAddIntegrationModalOpen"
size="large"
wrap-class-name="nc-modal-edit-or-add-integration"
@keydown.esc="isEditOrAddIntegrationModalOpen = false"
>
<div v-if="connectionType" class="h-full">
<WorkspaceIntegrationsFormsEditOrAddDatabase
v-model:open="isEditOrAddIntegrationModalOpen"
:connection-type="connectionType"
:load-datasource-info="loadDatasourceInfo"
:base-id="baseId"
/>
</div>
</NcModal>
</template>
<style lang="scss" scoped></style>
<style lang="scss">
.nc-modal-edit-or-add-integration {
.nc-modal {
@apply !p-0;
height: min(calc(100vh - 100px), 1024px);
max-height: min(calc(100vh - 100px), 1024px) !important;
.nc-edit-or-add-integration-left-panel {
@apply w-full p-6 flex-1 flex justify-center;
}
.nc-edit-or-add-integration-right-panel {
@apply p-5 w-[320px] border-l-1 border-gray-200 flex flex-col gap-4 bg-gray-50 rounded-br-2xl;
}
}
}
</style>

70
packages/nc-gui/components/workspace/integrations/Icon.vue

@ -0,0 +1,70 @@
<script lang="ts" setup>
const props = withDefaults(
defineProps<{
integrationType: string
size?: 'xs' | 'sm' | 'md' | 'lg'
}>(),
{
size: 'md',
},
)
const { integrationType: integrationTypeOrigin } = useIntegrationStore()
const { size, integrationType } = toRefs(props)
const pxSize = computed(() => {
switch (size.value) {
case 'xs':
return '16px'
case 'sm':
return '24px'
case 'md':
return '32px'
case 'lg':
return '48px'
}
})
const pxWrapperPadding = computed(() => {
switch (size.value) {
case 'xs':
return '8px'
case 'sm':
return '8px'
default:
return '10px'
}
})
</script>
<template>
<div
class="logo-wrapper"
:style="{
padding: pxWrapperPadding,
}"
>
<GeneralBaseLogo
v-if="integrationType === integrationTypeOrigin.MySQL"
source-type="mysql2"
:style="{ width: pxSize, height: pxSize }"
/>
<GeneralBaseLogo
v-else-if="integrationType === integrationTypeOrigin.PostgreSQL"
source-type="pg"
:style="{ width: pxSize, height: pxSize }"
/>
<GeneralIcon
v-else-if="integrationType === 'request'"
icon="plusSquare"
class="text-gray-700"
:style="{ width: pxSize, height: pxSize }"
/>
</div>
</template>
<style lang="scss" scoped>
.logo-wrapper {
@apply bg-gray-200 rounded-lg flex items-center justify-center;
}
</style>

803
packages/nc-gui/components/workspace/integrations/List.vue

@ -0,0 +1,803 @@
<script lang="ts" setup>
import type { IntegrationType, UserType, WorkspaceUserType } from 'nocodb-sdk'
import dayjs from 'dayjs'
type SortFields = 'title' | 'sub_type' | 'created_at' | 'created_by' | 'source_count'
const {
integrations,
isLoadingIntegrations,
deleteConfirmText,
integrationPaginationData,
successConfirmModal,
searchQuery,
loadIntegrations,
deleteIntegration,
editIntegration,
duplicateIntegration,
getIntegration,
} = useIntegrationStore()
const { $api, $e } = useNuxtApp()
const { collaborators } = storeToRefs(useWorkspace())
const isDeleteIntegrationModalOpen = ref(false)
const toBeDeletedIntegration = ref<
| (IntegrationType & {
sources?: {
id: string
alias: string
project_title: string
base_id: string
}[]
})
| null
>(null)
const isLoadingGetLinkedSources = ref(false)
const tableWrapper = ref<HTMLDivElement>()
const titleHeaderCellRef = ref<HTMLDivElement>()
const orderBy = ref<Partial<Record<SortFields, 'asc' | 'desc' | undefined>>>({})
const localCollaborators = ref<User[] | UserType[]>([])
const { width } = useElementBounding(titleHeaderCellRef)
const collaboratorsMap = computed<Map<string, (WorkspaceUserType & { id: string }) | User | UserType>>(() => {
const map = new Map()
;(isEeUI ? collaborators.value : localCollaborators.value)?.forEach((coll) => {
if (coll?.id) {
map.set(coll.id, coll)
}
})
return map
})
const filteredIntegrations = computed(() =>
(integrations.value || []).sort((a, b) => {
if (orderBy.value.title) {
if (a.title && b.title) {
return orderBy.value.title === 'asc' ? (a.title < b.title ? -1 : 1) : a.title > b.title ? -1 : 1
}
} else if (orderBy.value.sub_type) {
if (a.sub_type && b.sub_type) {
return orderBy.value.sub_type === 'asc' ? (a.sub_type < b.sub_type ? -1 : 1) : a.sub_type > b.sub_type ? -1 : 1
}
} else if (orderBy.value.created_at) {
if (a?.created_at && b?.created_at) {
return orderBy.value.created_at === 'asc'
? dayjs(a.created_at).local().isBefore(dayjs(b.created_at).local())
? -1
: 1
: dayjs(a.created_at).local().isAfter(dayjs(b.created_at).local())
? -1
: 1
}
} else if (orderBy.value.created_by) {
if (a.created_by && b.created_by && collaboratorsMap.value.get(a.created_by) && collaboratorsMap.value.get(b.created_by)) {
return orderBy.value.created_by === 'asc'
? collaboratorsMap.value.get(a.created_by)?.email < collaboratorsMap.value.get(b.created_by)?.email
? -1
: 1
: collaboratorsMap.value.get(a.created_by)?.email > collaboratorsMap.value.get(b.created_by)?.email
? -1
: 1
}
} else if (orderBy.value.source_count) {
if (a.source_count !== undefined && b.source_count !== undefined) {
return orderBy.value.source_count === 'asc' ? a.source_count - b.source_count : b.source_count - a.source_count
}
}
return 0
}),
)
async function loadConnections(
page = integrationPaginationData.value.page,
limit = integrationPaginationData.value.pageSize,
updateCurrentPage = true,
) {
try {
if (updateCurrentPage) {
integrationPaginationData.value.page = 1
}
await loadIntegrations(undefined, undefined, updateCurrentPage ? 1 : page, limit)
} catch {}
}
const handleChangePage = async (page: number) => {
integrationPaginationData.value.page = page
await loadConnections(undefined, undefined, false)
}
const { onLeft, onRight, onUp, onDown } = usePaginationShortcuts({
paginationDataRef: integrationPaginationData,
changePage: handleChangePage,
isViewDataLoading: isLoadingIntegrations,
})
const openDeleteIntegration = async (source: IntegrationType) => {
isLoadingGetLinkedSources.value = true
$e('c:integration:delete')
deleteConfirmText.value = null
isDeleteIntegrationModalOpen.value = true
toBeDeletedIntegration.value = source
const connectionDetails = await getIntegration(source, {
includeSources: true,
})
toBeDeletedIntegration.value.sources = connectionDetails?.sources || []
isLoadingGetLinkedSources.value = false
}
const onDeleteConfirm = async () => {
await deleteIntegration(toBeDeletedIntegration.value, true)
}
const loadOrgUsers = async () => {
try {
const response: any = await $api.orgUsers.list()
if (!response?.list) return
localCollaborators.value = response.list as UserType[]
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
const updateOrderBy = (field: SortFields) => {
if (!integrations.value?.length) return
// Only single field sort supported, other old field sort config will reset
if (orderBy.value?.[field] === 'asc') {
orderBy.value = {
[field]: 'desc',
}
} else if (orderBy.value?.[field] === 'desc') {
orderBy.value = {
[field]: undefined,
}
} else {
orderBy.value = {
[field]: 'asc',
}
}
}
const handleSearchConnection = useDebounceFn(() => {
loadConnections()
}, 250)
const renderAltOrOptlKey = () => {
return isMac() ? '⌥' : 'ALT'
}
useEventListener(tableWrapper, 'scroll', () => {
const stickyHeaderCell = tableWrapper.value?.querySelector('th.cell-title')
const nonStickyHeaderFirstCell = tableWrapper.value?.querySelector('th.cell-type')
if (!stickyHeaderCell?.getBoundingClientRect().right || !nonStickyHeaderFirstCell?.getBoundingClientRect().left) {
return
}
if (nonStickyHeaderFirstCell?.getBoundingClientRect().left < stickyHeaderCell?.getBoundingClientRect().right) {
tableWrapper.value?.classList.add('sticky-shadow')
} else {
tableWrapper.value?.classList.remove('sticky-shadow')
}
})
onMounted(async () => {
if (!isEeUI) {
await Promise.allSettled([!integrations.value.length && loadIntegrations(), loadOrgUsers()])
} else if (!integrations.value.length) {
await loadIntegrations()
}
})
// Keyboard shortcuts for pagination
onKeyStroke('ArrowLeft', onLeft)
onKeyStroke('ArrowRight', onRight)
onKeyStroke('ArrowUp', onUp)
onKeyStroke('ArrowDown', onDown)
</script>
<template>
<div class="h-full flex flex-col gap-6 nc-workspace-connections">
<div class="flex justify-between gap-12">
<div class="text-sm font-normal text-gray-600">
<div>
Connections simplify managing stored configurations for different integrations.
<a target="_blank" rel="noopener noreferrer"> Learn more </a>
</div>
</div>
<div class="flex items-center justify-end gap-3 mx-1">
<a-input
v-model:value="searchQuery"
type="text"
class="nc-search-integration-input !min-w-[300px] nc-input-sm flex-none"
:placeholder="`${$t('general.search')} ${$t('general.connections').toLowerCase()}`"
allow-clear
@input="handleSearchConnection"
>
<template #prefix>
<GeneralIcon icon="search" class="mr-2 h-4 w-4 text-gray-500" />
</template>
</a-input>
</div>
</div>
<div class="table-container relative flex-1">
<div
ref="tableWrapper"
class="nc-workspace-integration-table relative nc-scrollbar-thin !overflow-auto max-h-[calc(100%_-_40px)]"
:class="{
'h-full': filteredIntegrations?.length,
}"
>
<table class="!sticky top-0 z-5 w-full">
<thead>
<tr>
<th
class="cell-title !hover:bg-gray-100 select-none cursor-pointer"
:class="{
'cursor-not-allowed': !filteredIntegrations?.length,
'!text-gray-700': orderBy.title,
}"
@click="updateOrderBy('title')"
>
<div ref="titleHeaderCellRef" class="flex items-center gap-3">
<div>Name</div>
<GeneralIcon
v-if="orderBy.title"
icon="chevronDown"
class="flex-none"
:class="{
'transform rotate-180': orderBy?.title === 'asc',
}"
/>
<GeneralIcon v-else icon="chevronUpDown" class="flex-none" />
</div>
</th>
<th
class="cell-type !hover:bg-gray-100 select-none cursor-pointer"
:class="{
'cursor-not-allowed': !filteredIntegrations?.length,
'!text-gray-700': orderBy.sub_type,
}"
@click="updateOrderBy('sub_type')"
>
<div class="flex items-center gap-3">
<div>Type</div>
<GeneralIcon
v-if="orderBy.sub_type"
icon="chevronDown"
class="flex-none"
:class="{
'transform rotate-180': orderBy.sub_type === 'asc',
}"
/>
<GeneralIcon v-else icon="chevronUpDown" class="flex-none" />
</div>
</th>
<th
class="cell-created-date !hover:bg-gray-100 select-none cursor-pointer"
:class="{
'cursor-not-allowed': !filteredIntegrations?.length,
'!text-gray-700': orderBy.created_at,
}"
@click="updateOrderBy('created_at')"
>
<div class="flex items-center gap-3">
<div>Date added</div>
<GeneralIcon
v-if="orderBy.created_at"
icon="chevronDown"
class="flex-none"
:class="{
'transform rotate-180': orderBy.created_at === 'asc',
}"
/>
<GeneralIcon v-else icon="chevronUpDown" class="flex-none" />
</div>
</th>
<th
class="cell-added-by !hover:bg-gray-100 select-none cursor-pointer"
:class="{
'cursor-not-allowed': !filteredIntegrations?.length,
'!text-gray-700': orderBy.created_by,
}"
@click="updateOrderBy('created_by')"
>
<div class="flex items-center gap-3">
<div>Added by</div>
<GeneralIcon
v-if="orderBy.created_by"
icon="chevronDown"
class="flex-none"
:class="{
'transform rotate-180': orderBy.created_by === 'asc',
}"
/>
<GeneralIcon v-else icon="chevronUpDown" class="flex-none" />
</div>
</th>
<th
class="cell-usage !hover:bg-gray-100 select-none cursor-pointer"
:class="{
'cursor-not-allowed': !filteredIntegrations?.length,
}"
@click="updateOrderBy('source_count')"
>
<div class="flex items-center gap-3">
<div>Usage</div>
<GeneralIcon
v-if="orderBy?.source_count"
icon="chevronDown"
class="flex-none"
:class="{
'transform rotate-180': orderBy?.source_count === 'asc',
}"
/>
<GeneralIcon v-else icon="chevronUpDown" class="flex-none" />
</div>
</th>
<th class="cell-actions">
<div>Actions</div>
</th>
</tr>
</thead>
</table>
<template v-if="filteredIntegrations?.length">
<table class="h-full max-h-[calc(100%_-_55px)] w-full">
<tbody>
<tr v-for="integration of filteredIntegrations" :key="integration.id" @click="editIntegration(integration)">
<td class="cell-title">
<div
class="gap-3"
:style="{
maxWidth: `${width}px`,
}"
>
<NcTooltip placement="bottom" class="truncate" show-on-truncate-only>
<template #title> {{ integration.title }}</template>
{{ integration.title }}
</NcTooltip>
<span v-if="integration.is_private">
<NcBadge :border="false" class="text-primary !h-4.5 bg-brand-50 text-xs">{{
$t('general.private')
}}</NcBadge>
</span>
</div>
</td>
<td class="cell-type">
<div>
<NcBadge rounded="lg" class="flex items-center gap-2 px-2 py-1 !h-7 truncate">
<WorkspaceIntegrationsIcon
v-if="integration.sub_type"
:integration-type="integration.sub_type"
size="xs"
class="!p-0 !bg-transparent"
/>
<NcTooltip placement="bottom" show-on-truncate-only class="text-sm truncate">
<template #title> {{ clientTypesMap[integration?.sub_type]?.text || integration?.sub_type }}</template>
{{
integration.sub_type && clientTypesMap[integration.sub_type]
? clientTypesMap[integration.sub_type]?.text
: integration.sub_type
}}
</NcTooltip>
</NcBadge>
</div>
</td>
<td class="cell-created-date">
<div>
<NcTooltip placement="bottom" show-on-truncate-only>
<template #title> {{ dayjs(integration.created_at).local().format('DD MMM YYYY') }}</template>
{{ dayjs(integration.created_at).local().format('DD MMM YYYY') }}
</NcTooltip>
</div>
</td>
<td class="cell-added-by">
<div>
<div
v-if="integration.created_by && collaboratorsMap.get(integration.created_by)?.email"
class="w-full flex gap-3 items-center"
>
<GeneralUserIcon
:email="collaboratorsMap.get(integration.created_by)?.email"
size="base"
class="flex-none"
/>
<div class="flex-1 flex flex-col max-w-[calc(100%_-_44px)]">
<div class="w-full flex gap-3">
<NcTooltip
class="text-sm !leading-5 text-gray-800 capitalize font-semibold truncate"
show-on-truncate-only
placement="bottom"
>
<template #title>
{{
collaboratorsMap.get(integration.created_by)?.display_name ||
collaboratorsMap
.get(integration.created_by)
?.email?.slice(0, collaboratorsMap.get(integration.created_by)?.email.indexOf('@'))
}}
</template>
{{
collaboratorsMap.get(integration.created_by)?.display_name ||
collaboratorsMap
.get(integration.created_by)
?.email?.slice(0, collaboratorsMap.get(integration.created_by)?.email.indexOf('@'))
}}
</NcTooltip>
</div>
<NcTooltip class="text-xs !leading-4 text-gray-600 truncate" show-on-truncate-only placement="bottom">
<template #title>
{{ collaboratorsMap.get(integration.created_by)?.email }}
</template>
{{ collaboratorsMap.get(integration.created_by)?.email }}
</NcTooltip>
</div>
</div>
<template v-else>{{ integration.created_by }} </template>
</div>
</td>
<td class="cell-usage">
<div>{{ integration?.source_count ?? 0 }}</div>
</td>
<td class="cell-actions" @click.stop>
<div class="justify-end">
<NcDropdown placement="bottomRight">
<NcButton size="small" type="secondary">
<GeneralIcon icon="threeDotVertical" />
</NcButton>
<template #overlay>
<NcMenu>
<NcMenuItem @click="editIntegration(integration)">
<GeneralIcon class="text-gray-800" icon="edit" />
<span>{{ $t('general.edit') }}</span>
</NcMenuItem>
<NcMenuItem @click="duplicateIntegration(integration)">
<GeneralIcon class="text-gray-800" icon="duplicate" />
<span>{{ $t('general.duplicate') }}</span>
</NcMenuItem>
<NcDivider />
<NcMenuItem class="!text-red-500 !hover:bg-red-50" @click="openDeleteIntegration(integration)">
<GeneralIcon icon="delete" />
{{ $t('general.delete') }}
</NcMenuItem>
</NcMenu>
</template>
</NcDropdown>
</div>
</td>
</tr>
</tbody>
</table>
</template>
</div>
<div
v-show="isLoadingIntegrations"
class="flex items-center justify-center absolute left-0 top-0 w-full h-full z-10 pb-10 pointer-events-none"
>
<div class="flex flex-col justify-center items-center gap-2">
<GeneralLoader size="xlarge" />
<span class="text-center">{{ $t('general.loading') }}</span>
</div>
</div>
<div
v-if="!isLoadingIntegrations && (!integrations?.length || !filteredIntegrations.length)"
class="flex-none integration-table-empty flex items-center justify-center py-8 px-6 h-full max-h-[calc(100%_-_94px)]"
>
<div
v-if="integrations?.length && !filteredIntegrations.length"
class="px-2 py-6 text-gray-500 flex flex-col items-center gap-6 text-center"
>
<img
src="~assets/img/placeholder/no-search-result-found.png"
class="!w-[164px] flex-none"
alt="No search results found"
/>
{{ $t('title.noResultsMatchedYourSearch') }}
</div>
<div v-else class="flex-none text-center flex flex-col items-center gap-3">
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" class="!my-0" />
</div>
</div>
<div
v-if="integrationPaginationData.totalRows"
class="flex flex-row justify-center items-center bg-gray-50 min-h-10"
:class="{
'pointer-events-none': isLoadingIntegrations,
}"
>
<div class="flex justify-between items-center w-full px-6">
<div>&nbsp;</div>
<NcPagination
v-model:current="integrationPaginationData.page"
v-model:page-size="integrationPaginationData.pageSize"
:total="+integrationPaginationData.totalRows"
show-size-changer
:use-stored-page-size="false"
:prev-page-tooltip="`${renderAltOrOptlKey()}+←`"
:next-page-tooltip="`${renderAltOrOptlKey()}+→`"
:first-page-tooltip="`${renderAltOrOptlKey()}+↓`"
:last-page-tooltip="`${renderAltOrOptlKey()}+↑`"
@update:current="loadConnections(undefined, undefined, false)"
@update:page-size="loadConnections(integrationPaginationData.page, $event, false)"
/>
<div class="text-gray-500 text-xs">
{{ integrationPaginationData.totalRows }} {{ integrationPaginationData.totalRows === 1 ? 'record' : 'records' }}
</div>
</div>
</div>
</div>
<GeneralDeleteModal
v-model:visible="isDeleteIntegrationModalOpen"
:entity-name="$t('general.connection')"
:on-delete="onDeleteConfirm"
:delete-label="$t('general.delete')"
:show-default-delete-msg="!isLoadingGetLinkedSources && !toBeDeletedIntegration?.sources?.length"
>
<template #entity-preview>
<template v-if="isLoadingGetLinkedSources">
<div class="rounded-lg overflow-hidden">
<a-skeleton-input active class="h-9 !rounded-md !w-full"></a-skeleton-input>
</div>
<div class="rounded-lg overflow-hidden mt-2">
<a-skeleton-input active class="h-9 !rounded-md !w-full"></a-skeleton-input>
</div>
</template>
<div v-else-if="toBeDeletedIntegration" class="w-full flex flex-col text-gray-800">
<div class="flex flex-row items-center py-2 px-3.25 bg-gray-50 rounded-lg text-gray-700 mb-4">
<WorkspaceIntegrationsIcon
:integration-type="toBeDeletedIntegration.sub_type"
size="xs"
class="!p-0 !bg-transparent"
/>
<div
class="text-ellipsis overflow-hidden select-none w-full pl-3"
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }"
>
{{ toBeDeletedIntegration.title }}
</div>
</div>
<div v-if="toBeDeletedIntegration?.sources?.length" class="flex flex-col pb-2 text-small leading-[18px] text-gray-500">
<div class="mb-1">Following external data sources using this connection will also be removed</div>
<ul class="!list-disc ml-6 mb-0">
<li
v-for="(source, idx) of toBeDeletedIntegration.sources"
:key="idx"
class="marker:text-gray-500 !marker:(flex items-center !-mt-1)"
>
<div class="flex items-center gap-1">
<div class="flex items-center">
&nbsp;
<GeneralProjectIcon
type="database"
class="!grayscale min-w-4 flex-none -ml-1"
:style="{
filter: 'grayscale(100%) brightness(115%)',
}"
/>
</div>
<NcTooltip class="!truncate !max-w-[45%] flex-none" show-on-truncate-only>
<template #title>
{{ source.project_title }}
</template>
{{ source.project_title }}
</NcTooltip>
>
<GeneralBaseLogo
class="!grayscale min-w-4 flex-none"
:style="{
filter: 'grayscale(100%) brightness(115%)',
}"
/>
<NcTooltip class="truncate !max-w-[45%] capitalize" show-on-truncate-only>
<template #title>
{{ source.alias }}
</template>
{{ source.alias }}
</NcTooltip>
</div>
</li>
</ul>
<div class="mt-2">Do you want to proceed anyway?</div>
</div>
</div>
</template>
</GeneralDeleteModal>
<NcModal v-model:visible="successConfirmModal.isOpen" centered size="small" @keydown.esc="successConfirmModal.isOpen = false">
<div class="flex gap-4">
<div>
<GeneralIcon icon="circleCheckSolid" class="flex-none !text-green-700 mt-0.5 !h-6 !w-6" />
</div>
<div class="flex flex-col gap-3">
<div class="flex">
<h3 class="!m-0 text-base font-weight-700 flex-1">
{{ successConfirmModal.title }}
</h3>
<NcButton size="xsmall" type="text" @click="successConfirmModal.isOpen = false">
<GeneralIcon icon="close" class="text-gray-600" />
</NcButton>
</div>
<div class="text-sm text-gray-700">
{{ successConfirmModal.description }}
</div>
<!-- Todo: add link -->
<a target="_blank" rel="noopener noreferrer"> Learn more </a>
</div>
</div>
</NcModal>
</div>
</template>
<style lang="scss" scoped>
.source-card-link {
@apply !text-black !no-underline;
.nc-new-integration-type-title {
@apply text-sm font-weight-600 text-gray-600;
}
}
.source-card {
@apply flex items-center border-1 rounded-lg p-3 cursor-pointer hover:bg-gray-50;
width: 288px;
.name {
@apply ml-4 text-md font-semibold;
}
}
:deep(.ant-input-affix-wrapper.nc-search-integration-input) {
&:not(:has(.ant-input-clear-icon-hidden)):has(.ant-input-clear-icon) {
@apply border-[var(--ant-primary-5)];
}
}
.nc-new-integration-type-wrapper {
@apply flex flex-col gap-3;
}
.table-container {
@apply border-1 border-gray-200 rounded-lg overflow-hidden w-full;
.nc-workspace-integration-table {
&.sticky-shadow {
th,
td {
&.cell-title {
@apply border-r-1 border-gray-200;
}
}
}
&:not(.sticky-shadow) {
th,
td {
&.cell-title {
@apply border-r-1 border-transparent;
}
}
}
thead {
@apply w-full;
th {
@apply bg-gray-50 text-sm text-gray-500 font-weight-500;
&.cell-title {
@apply sticky left-0 z-4 bg-gray-50;
}
}
}
tbody {
@apply w-full;
tr {
@apply cursor-pointer;
td {
@apply text-sm text-gray-600;
&.cell-title {
@apply sticky left-0 z-4 bg-white !text-gray-800 font-semibold;
}
}
}
}
tr {
@apply h-[54px] flex border-b-1 border-gray-200 w-full;
&:hover td {
@apply !bg-gray-50;
}
&.selected td {
@apply !bg-gray-50;
}
th,
td {
@apply h-full;
& > div {
@apply px-6 h-full flex-1 flex items-center;
}
&.cell-title {
@apply flex-1 sticky left-0 z-5;
& > div {
@apply min-w-[250px];
}
}
&.cell-added-by {
@apply basis-[20%];
& > div {
@apply min-w-[250px];
}
}
&.cell-type {
@apply basis-[20%];
& > div {
@apply min-w-[178px];
}
}
&.cell-created-date {
@apply basis-[20%];
& > div {
@apply min-w-[158px];
}
}
&.cell-usage {
@apply w-[120px];
& > div {
@apply min-w-[118px];
}
}
&.cell-actions {
@apply w-[100px];
& > div {
@apply min-w-[98px];
}
}
}
}
}
}
.cell-header {
@apply text-xs font-semibold text-gray-500;
}
</style>

55
packages/nc-gui/components/workspace/integrations/Panel.vue

@ -0,0 +1,55 @@
<script lang="ts" setup>
import type { iconMap } from '#imports'
const props = withDefaults(
defineProps<{
title: string
icon?: keyof typeof iconMap
collapsible?: boolean
collapsed?: boolean
}>(),
{
collapsible: false,
collapsed: true,
},
)
const panelRef = ref<HTMLElement | null>(null)
const collapsed = ref(props.collapsible ? props.collapsed : false)
const toggleCollapse = () => {
if (!props.collapsible) return
collapsed.value = !collapsed.value
}
</script>
<template>
<div ref="panelRef" class="panel" :data-label="props.title" :data-icon="props.icon">
<div class="flex items-center gap-2" @click="toggleCollapse">
<template v-if="props.collapsible">
<GeneralIcon :icon="collapsed ? 'arrowRight' : 'arrowDown'" />
</template>
<GeneralIcon v-else-if="props.icon" :icon="props.icon" />
<div class="panel-label" :class="{ 'cursor-pointer': props.collapsible, 'cursor-default': !props.collapsible }">
{{ props.title }}
</div>
<slot name="header-info"></slot>
</div>
<div v-if="!collapsed" class="panel-body"><slot></slot></div>
</div>
</template>
<style lang="scss" scoped>
.panel {
@apply border-1 border-gray-200 px-6 py-4 rounded-lg mb-4;
.panel-label {
@apply text-md font-weight-bold flex-1;
}
.panel-body {
@apply mt-4;
}
}
</style>

38
packages/nc-gui/components/workspace/integrations/SupportedDocs.vue

@ -0,0 +1,38 @@
<script lang="ts" setup>
const supportedDocs = [
{
title: 'Configure a PostgreSQL Integration',
href: '',
},
{
title: 'Getting started with Integrations',
href: '',
},
{
title: 'Troubleshoot database connection',
href: '',
},
] as {
title: string
href: string
}[]
</script>
<template>
<div>
<div class="w-full flex flex-col gap-3">
<div class="text-sm text-gray-800 font-semibold">Supported Docs</div>
<div>
<div v-for="doc of supportedDocs" class="flex items-center gap-1">
<div class="h-7 w-7 flex items-center justify-center">
<GeneralIcon icon="bookOpen" class="flex-none w-4 h-4 text-gray-600"/>
</div>
<a :href="doc.href" target="_blank" rel="noopener noreferrer" class="!text-gray-700 text-sm !no-underline !hover:underline">
{{ doc.title }}
</a>
</div>
</div>
</div>
</div>
</template>

1258
packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue

File diff suppressed because it is too large Load Diff

15
packages/nc-gui/components/workspace/integrations/forms/MySql.vue

@ -0,0 +1,15 @@
<template>
<div class="panels">
<div class="panel">DUMMY</div>
</div>
</template>
<style lang="scss" scoped>
.panels {
@apply w-3/4 flex flex-col;
.panel {
@apply border-1 border-gray-200 p-6 rounded-lg;
}
}
</style>

157
packages/nc-gui/components/workspace/integrations/forms/PostgreSql.vue

@ -0,0 +1,157 @@
<script lang="ts" setup>
const { activeIntegration, categories, activeCategory } = useIntegrationStore()
const { copy } = useCopy()
const copyIp = async () => {
await copy('52.15.226.51')
message.success('Copied to clipboard')
}
const panelsRef = ref<HTMLElement | null>(null)
onMounted(() => {
if (!panelsRef.value) return
const panels = panelsRef.value.querySelectorAll('.panel')
panels.forEach((panel) => {
categories.value.push({
label: panel.getAttribute('data-label') as string,
icon: panel.getAttribute('data-icon'),
})
})
// focus first input
nextTick(() => {
const firstInput = panels[0].querySelector('input')
if (firstInput) firstInput.focus()
})
})
const onInputFocus = () => {
const target = document.activeElement
const panel = target?.closest('.panel')
if (panel) {
activeCategory.value = {
label: panel.getAttribute('data-label') as string,
icon: panel.getAttribute('data-icon'),
}
}
}
</script>
<template>
<div ref="panelsRef" class="panels">
<WorkspaceIntegrationsPanel title="Integration Details" icon="info">
<template #header-info>
<div
class="text-gray-500 !text-xs font-weight-normal flex items-center gap-2 cursor-pointer flex items-center"
@click="copyIp"
>
<GeneralIcon icon="info" class="text-primary" />
Whitelist our ip: 52.15.226.51 to allow database access
<GeneralIcon icon="duplicate" class="text-gray-800 w-5 h-5 p-1 border-1 rounded-md border-gray-200" />
</div>
</template>
<div>
<div class="flex flex-col w-1/2 pr-3">
<label class="!text-xs font-weight-normal pb-1">Title</label>
<a-input v-model:value="activeIntegration.payload.title" class="input-text" :maxlength="255" @focus="onInputFocus" />
</div>
</div>
</WorkspaceIntegrationsPanel>
<WorkspaceIntegrationsPanel title="Connection Details" icon="link">
<div class="input-group">
<div class="input-item">
<label class="!text-xs font-weight-normal pb-1">Host</label>
<a-input v-model:value="activeIntegration.payload.host" class="input-text" @focus="onInputFocus" />
</div>
<div class="input-item">
<label class="!text-xs font-weight-normal pb-1">Port</label>
<a-input v-model:value="activeIntegration.payload.port" class="input-text" @focus="onInputFocus" />
</div>
<div class="input-item">
<label class="!text-xs font-weight-normal pb-1">User</label>
<a-input v-model:value="activeIntegration.payload.user" class="input-text" autocomplete="off" @focus="onInputFocus" />
</div>
<div class="input-item">
<label class="!text-xs font-weight-normal pb-1">Password</label>
<a-input
v-model:value="activeIntegration.payload.password"
class="input-text"
type="password"
autocomplete="off"
@focus="onInputFocus"
/>
</div>
<div class="input-item">
<label class="!text-xs font-weight-normal pb-1">Schema</label>
<a-input v-model:value="activeIntegration.payload.schema" class="input-text" @focus="onInputFocus" />
</div>
<div class="input-item">
<label class="!text-xs font-weight-normal pb-1">Database</label>
<a-input v-model:value="activeIntegration.payload.database" class="input-text" @focus="onInputFocus" />
</div>
</div>
</WorkspaceIntegrationsPanel>
<WorkspaceIntegrationsPanel title="SSL & Advanced Parameters" icon="lock" :collapsible="true">
<div class="input-group">
<div class="input-item">
<label class="!text-xs font-weight-normal pb-1">SSL Mode</label>
<a-select v-model:value="activeIntegration.payload.sslMode" class="input-text" @focus="onInputFocus">
<a-select-option value="disable">Disable</a-select-option>
<a-select-option value="require">Require</a-select-option>
<a-select-option value="verify-ca">Verify CA</a-select-option>
<a-select-option value="verify-full">Verify Full</a-select-option>
</a-select>
</div>
<div class="input-item">
<label class="!text-xs font-weight-normal pb-1">SSL Root Certificate</label>
<a-input v-model:value="activeIntegration.payload.sslRootCert" class="input-text" @focus="onInputFocus" />
</div>
<div class="input-item">
<label class="!text-xs font-weight-normal pb-1">SSL Certificate</label>
<a-input v-model:value="activeIntegration.payload.sslCert" class="input-text" @focus="onInputFocus" />
</div>
<div class="input-item">
<label class="!text-xs font-weight-normal pb-1">SSL Key</label>
<a-input v-model:value="activeIntegration.payload.sslKey" class="input-text" @focus="onInputFocus" />
</div>
</div>
<div class="w-full border-t-1 mt-2 mb-4"></div>
<div class="input-group">
<div class="input-item">
<label class="!text-xs font-weight-normal pb-1">Extra Connection Parameters</label>
<a-input v-model:value="activeIntegration.payload.sslCert" class="input-text" @focus="onInputFocus" />
</div>
</div>
</WorkspaceIntegrationsPanel>
<WorkspaceIntegrationsPanel title="Connection JSON" icon="code">DUMMY</WorkspaceIntegrationsPanel>
</div>
</template>
<style lang="scss" scoped>
.panels {
@apply w-3/4 flex flex-col;
.panel {
@apply border-1 border-gray-200 px-4 py-4 rounded-lg mb-4;
.panel-label {
@apply text-md font-weight-bold;
}
.input-text {
@apply w-full rounded-md;
}
.input-group {
@apply flex flex-wrap w-full;
.input-item {
@apply flex flex-col w-1/2 pr-3 mb-3;
}
}
}
}
</style>

240
packages/nc-gui/components/workspace/integrations/newAvailableList.vue

@ -0,0 +1,240 @@
<script lang="ts" setup>
import NcModal from '~/components/nc/Modal.vue'
const props = withDefaults(
defineProps<{
isModal?: boolean
}>(),
{
isModal: false,
},
)
const { isModal } = props
const { pageMode, IntegrationsPageMode, integrationType, requestIntegration, addIntegration, saveIntegraitonRequest } =
useIntegrationStore()
const isAddNewIntegrationModalOpen = computed({
get: () => {
return pageMode.value === IntegrationsPageMode.LIST
},
set: (value: boolean) => {
if (value) {
pageMode.value = IntegrationsPageMode.LIST
} else {
pageMode.value = null
}
},
})
const handleAddIntegration = (type: typeof integrationType) => {
if (requestIntegration.value.isOpen) {
requestIntegration.value.isOpen = false
}
addIntegration(type)
}
</script>
<template>
<component
:is="isModal ? NcModal : 'div'"
v-model:visible="isAddNewIntegrationModalOpen"
centered
size="large"
:class="{
'h-full': !isModal,
}"
wrap-class-name="nc-modal-available-integrations-list"
@keydown.esc="isAddNewIntegrationModalOpen = false"
>
<div class="h-full flex flex-col">
<div v-if="isModal" class="p-4 w-full flex items-center justify-between gap-3 border-b-1 border-gray-200">
<NcButton type="text" size="small" @click="isAddNewIntegrationModalOpen = false">
<GeneralIcon icon="arrowLeft" />
</NcButton>
<GeneralIcon icon="gitCommit" class="flex-none h-5 w-5" />
<div class="flex-1 text-base font-weight-700">New Connection</div>
<div class="flex items-center gap-3">
<NcButton size="small" type="text" @click="isAddNewIntegrationModalOpen = false">
<GeneralIcon icon="close" class="text-gray-600" />
</NcButton>
</div>
</div>
<div
class="flex flex-col nc-workspace-settings-integrations-new-available-list"
:class="{
'h-[calc(80vh_-_66px)] p-6': isModal,
'h-full': !isModal,
}"
>
<div class="w-full flex justify-center">
<div
class="flex flex-col gap-6 w-full"
:class="{
'max-w-[1088px]': isModal,
}"
>
<div
class="text-sm font-normal text-gray-600"
:class="{
'max-w-[740px]': !isModal,
}"
>
<div>
Centralise your operations by aggregating information from various external platforms into NocoDB. Select from the
available integrations below to get started. <a target="_blank" rel="noopener noreferrer"> Learn more </a>
</div>
</div>
<div class="integration-type-wrapper">
<div class="integration-type-title">Databases</div>
<div class="integration-type-list">
<div class="source-card" @click="handleAddIntegration(integrationType.MySQL)">
<WorkspaceIntegrationsIcon :integration-type="integrationType.MySQL" size="md" />
<div class="name flex-1">MySQL</div>
<div class="action-btn">+</div>
</div>
<div class="source-card" @click="handleAddIntegration(integrationType.PostgreSQL)">
<WorkspaceIntegrationsIcon :integration-type="integrationType.PostgreSQL" size="md" />
<div class="name flex-1">PostgreSQL</div>
<div class="action-btn">+</div>
</div>
</div>
</div>
<!-- Todo:APIs -->
<!-- <div>
<div>APIs</div>
<div></div>
</div> -->
<div class="integration-type-wrapper">
<div>
<div class="integration-type-title">Others</div>
<div class="integration-type-subtitle"></div>
</div>
<div>
<div
class="source-card-request-integration"
:class="{
active: requestIntegration.isOpen,
}"
>
<div
v-if="!requestIntegration.isOpen"
class="source-card-item border-none"
@click="requestIntegration.isOpen = true"
>
<WorkspaceIntegrationsIcon integration-type="request" size="md" />
<div class="name">Request New Integration</div>
</div>
<div v-show="requestIntegration.isOpen" class="flex flex-col gap-4">
<div class="flex items-center justify-between gap-4">
<div class="text-base font-bold text-gray-800">Request Integration</div>
<NcButton size="xsmall" type="text" @click="requestIntegration.isOpen = false">
<GeneralIcon icon="close" class="text-gray-600" />
</NcButton>
</div>
<div class="flex flex-col gap-2">
<a-textarea
v-model:value="requestIntegration.msg"
class="!rounded-md !text-sm !min-h-[120px] max-h-[500px] nc-scrollbar-thin"
size="large"
hide-details
placeholder="Provide integration name and your use-case."
/>
</div>
<div class="flex items-center justify-end gap-3">
<NcButton size="small" type="secondary" @click="requestIntegration.isOpen = false">
{{ $t('general.cancel') }}
</NcButton>
<NcButton
:disabled="!requestIntegration.msg?.trim()"
:loading="requestIntegration.isLoading"
size="small"
@click="saveIntegraitonRequest(requestIntegration.msg)"
>
{{ $t('general.submit') }}
</NcButton>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</component>
</template>
<style lang="scss" scoped>
.source-card-request-integration {
@apply flex flex-col gap-4 border-1 rounded-xl p-3 w-[352px] overflow-hidden transition-all duration-300 max-w-[720px];
&.active {
@apply w-full;
}
&:not(.active) {
@apply cursor-pointer hover:bg-gray-50;
}
.source-card-item {
@apply flex items-center;
.name {
@apply ml-4 text-md font-semibold;
}
}
}
.source-card-link {
@apply !text-black !no-underline;
.nc-new-integration-type-title {
@apply text-sm font-weight-600 text-gray-600;
}
}
.source-card {
@apply flex items-center border-1 rounded-xl p-3 cursor-pointer hover:bg-gray-50;
width: 352px;
.name {
@apply ml-4 text-md font-semibold;
}
}
.nc-workspace-settings-integrations-new-available-list {
.integration-type-wrapper {
@apply flex flex-col gap-3;
.source-card:hover {
.action-btn {
@apply block;
}
}
.integration-type-title {
@apply text-sm text-gray-500 font-weight-700;
}
.integration-type-subtitle {
@apply text-sm text-gray-500 font-weight-700;
}
.integration-type-list {
@apply flex gap-4 flex-wrap;
}
.action-btn {
@apply hidden text-2xl text-gray-500;
}
}
}
</style>
<style lang="scss">
.nc-modal-available-integrations-list {
.nc-modal {
@apply !p-0;
height: min(calc(100vh - 100px), 1024px);
max-height: min(calc(100vh - 100px), 1024px) !important;
}
}
</style>

130
packages/nc-gui/components/workspace/integrations/view.vue

@ -0,0 +1,130 @@
<script lang="ts" setup>
import { useTitle } from '@vueuse/core'
const { isUIAllowed } = useRoles()
const workspaceStore = useWorkspace()
const { loadRoles } = useRoles()
const { activeWorkspace: _activeWorkspace } = storeToRefs(workspaceStore)
const { loadCollaborators } = workspaceStore
const { isFromIntegrationPage, integrationPaginationData, activeViewTab, loadIntegrations } = useProvideIntegrationViewStore()
const currentWorkspace = computedAsync(async () => {
await loadRoles(undefined, {}, _activeWorkspace.value?.id)
return _activeWorkspace.value
})
watch(
() => currentWorkspace.value?.title,
(title) => {
if (!title) return
const capitalizedTitle = title.charAt(0).toUpperCase() + title.slice(1)
useTitle(capitalizedTitle)
},
{
immediate: true,
},
)
onMounted(() => {
isFromIntegrationPage.value = true
until(() => currentWorkspace.value?.id)
.toMatch((v) => !!v)
.then(async () => {
await Promise.all([loadCollaborators({} as any, currentWorkspace.value!.id), loadIntegrations()])
})
})
onBeforeMount(() => {
isFromIntegrationPage.value = false
})
</script>
<template>
<div v-if="currentWorkspace" class="flex w-full max-w-[97.5rem] flex-col nc-workspace-integrations">
<div class="flex gap-2 items-center min-w-0 py-4 px-6">
<h1 class="text-base capitalize font-weight-bold tracking-[0.5px] mb-0 nc-workspace-title truncate min-w-10 capitalize">
<span class="text-gray-500"> {{ currentWorkspace?.title }} ></span> {{ $t('general.integrations') }}
</h1>
</div>
<NcTabs v-model:activeKey="activeViewTab">
<template #leftExtra>
<div class="w-6"></div>
</template>
<template v-if="isUIAllowed('workspaceIntegrations')">
<a-tab-pane key="integrations" class="w-full">
<template #tab>
<div class="flex flex-row items-center pb-1 gap-x-1.5" data-testid="nc-workspace-settings-tab-integrations">
<GeneralIcon icon="integration" />
{{ $t('general.integrations') }}
</div>
</template>
<div class="h-[calc(100vh-92px)] p-6">
<WorkspaceIntegrationsNewAvailableList />
</div>
</a-tab-pane>
</template>
<template v-if="isUIAllowed('workspaceIntegrations')">
<a-tab-pane key="connections" class="w-full">
<template #tab>
<div class="flex flex-row items-center pb-1 gap-x-1.5" data-testid="nc-workspace-settings-tab-integrations">
<GeneralIcon icon="gitCommit" />
{{ $t('general.connections') }}
<div
v-if="integrationPaginationData?.totalRows"
class="tab-info flex-none"
:class="{
'bg-primary-selected': activeViewTab === 'connections',
'bg-gray-50': activeViewTab !== 'connections',
}"
>
{{ integrationPaginationData.totalRows }}
</div>
</div>
</template>
<div class="h-[calc(100vh-92px)] p-6">
<WorkspaceIntegrationsList />
</div>
</a-tab-pane>
</template>
</NcTabs>
<WorkspaceIntegrationsEditOrAdd></WorkspaceIntegrationsEditOrAdd>
</div>
</template>
<style lang="scss" scoped>
.nc-workspace-avatar {
@apply min-w-6 h-6 rounded-[6px] flex items-center justify-center text-white font-weight-bold uppercase;
font-size: 0.7rem;
}
.tab {
@apply flex flex-row items-center gap-x-2;
}
:deep(.ant-tabs-nav) {
@apply !pl-0;
}
:deep(.ant-tabs-nav-list) {
@apply !gap-5;
}
:deep(.ant-tabs-tab) {
@apply !pt-0 !pb-2.5 !ml-0;
}
.ant-tabs-content {
@apply !h-full;
}
.ant-tabs-content-top {
@apply !h-full;
}
.tab-info {
@apply flex pl-1.25 px-1.5 py-0.75 rounded-md text-xs;
}
</style>

4
packages/nc-gui/composables/useApi/index.ts

@ -1,5 +1,5 @@
import type { AxiosError, AxiosResponse } from 'axios' import type { AxiosError, AxiosResponse } from 'axios'
import { Api } from 'nocodb-sdk' import { Api, type Api as BaseAPI } from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import type { CreateApiOptions, UseApiProps, UseApiReturn } from './types' import type { CreateApiOptions, UseApiProps, UseApiReturn } from './types'
import { addAxiosInterceptors } from './interceptors' import { addAxiosInterceptors } from './interceptors'
@ -58,7 +58,7 @@ export function useApi<Data = any, RequestConfig = any>({
const nuxtApp = useNuxtApp() const nuxtApp = useNuxtApp()
/** api instance - with interceptors for token refresh already bound */ /** api instance - with interceptors for token refresh already bound */
const api = useGlobalInstance && !!nuxtApp.$api ? nuxtApp.$api : createApiInstance(apiOptions) const api: BaseAPI<any> = useGlobalInstance && !!nuxtApp.$api ? nuxtApp.$api : createApiInstance(apiOptions)
/** set loading to true and increment local and global request counter */ /** set loading to true and increment local and global request counter */
// Long Polling causes the loading spinner to never stop // Long Polling causes the loading spinner to never stop

5
packages/nc-gui/composables/useGlobal/actions.ts

@ -171,6 +171,10 @@ export function useGlobalActions(state: State): Actions {
state.isAddNewRecordGridMode.value = isGridMode state.isAddNewRecordGridMode.value = isGridMode
} }
const updateSyncDataUpvotes = (upvotes: string[]) => {
state.syncDataUpvotes.value = upvotes
}
return { return {
signIn, signIn,
signOut, signOut,
@ -184,5 +188,6 @@ export function useGlobalActions(state: State): Actions {
setGridViewPageSize, setGridViewPageSize,
setLeftSidebarSize, setLeftSidebarSize,
setAddNewRecordGridMode, setAddNewRecordGridMode,
updateSyncDataUpvotes,
} }
} }

1
packages/nc-gui/composables/useGlobal/state.ts

@ -63,6 +63,7 @@ export function useGlobalState(storageKey = 'nocodb-gui-v2'): State {
current: INITIAL_LEFT_SIDEBAR_WIDTH, current: INITIAL_LEFT_SIDEBAR_WIDTH,
}, },
isAddNewRecordGridMode: true, isAddNewRecordGridMode: true,
syncDataUpvotes: [],
} }
/** saves a reactive state, any change to these values will write/delete to localStorage */ /** saves a reactive state, any change to these values will write/delete to localStorage */

2
packages/nc-gui/composables/useGlobal/types.ts

@ -58,6 +58,7 @@ export interface StoredState {
current: number current: number
} }
isAddNewRecordGridMode: boolean isAddNewRecordGridMode: boolean
syncDataUpvotes: string[]
} }
export type State = ToRefs<Omit<StoredState, 'token'>> & { export type State = ToRefs<Omit<StoredState, 'token'>> & {
@ -96,6 +97,7 @@ export interface Actions {
setGridViewPageSize: (pageSize: number) => void setGridViewPageSize: (pageSize: number) => void
setLeftSidebarSize: (params: { old?: number; current?: number }) => void setLeftSidebarSize: (params: { old?: number; current?: number }) => void
setAddNewRecordGridMode: (isGridMode: boolean) => void setAddNewRecordGridMode: (isGridMode: boolean) => void
updateSyncDataUpvotes: (upvotes: string[]) => void
} }
export type ReadonlyState = Readonly<Pick<State, 'token' | 'user'>> & Omit<State, 'token' | 'user'> export type ReadonlyState = Readonly<Pick<State, 'token' | 'user'>> & Omit<State, 'token' | 'user'>

355
packages/nc-gui/composables/useIntegrationsStore.ts

@ -0,0 +1,355 @@
import type { IntegrationType, PaginatedType } from 'nocodb-sdk'
import { IntegrationsType } from 'nocodb-sdk'
import { ClientType } from '../lib/enums'
import GeneralBaseLogo from '~/components/general/BaseLogo.vue'
import type { IntegrationStoreEvents as IntegrationStoreEventsTypes } from '#imports'
enum IntegrationsPageMode {
LIST,
ADD,
EDIT,
}
const integrationType: Record<'PostgreSQL' | 'MySQL', ClientType> = {
PostgreSQL: ClientType.PG,
MySQL: ClientType.MYSQL,
}
type IntegrationsSubType = (typeof integrationType)[keyof typeof integrationType]
function defaultValues(type: IntegrationsSubType) {
const genericValues = {
payload: {},
}
switch (type) {
case integrationType.PostgreSQL:
return {
...genericValues,
type: integrationType.PostgreSQL,
title: 'PostgreSQL',
logo: h(GeneralBaseLogo, {
'source-type': 'pg',
'class': 'logo',
}),
}
case integrationType.MySQL:
return {
...genericValues,
type: integrationType.MySQL,
title: 'MySQL',
logo: h(GeneralBaseLogo, {
'source-type': 'mysql2',
'class': 'logo',
}),
}
}
}
const [useProvideIntegrationViewStore, _useIntegrationStore] = useInjectionState(() => {
const router = useRouter()
const route = router.currentRoute
const { api } = useApi()
const pageMode = ref<IntegrationsPageMode | null>(null)
const activeIntegration = ref<IntegrationType | null>(null)
const workspaceStore = useWorkspace()
const { activeWorkspaceId } = storeToRefs(workspaceStore)
const integrations = ref<IntegrationType[]>([])
const searchQuery = ref('')
const integrationPaginationData = ref<PaginatedType>({ page: 1, pageSize: 25, totalRows: 0 })
const deleteConfirmText = ref<string | null>()
const isLoadingIntegrations = ref(false)
const eventBus = useEventBus<IntegrationStoreEventsTypes>(Symbol('integrationStore'))
const { $e } = useNuxtApp()
const { t } = useI18n()
const isFromIntegrationPage = ref(false)
const successConfirmModal = ref({
isOpen: false,
title: t('msg.success.connectionAdded'),
connectionTitle: '',
description: t('msg.success.connectionAddedDesc'),
})
const requestIntegration = ref({
isOpen: false,
msg: '',
isLoading: false,
})
const activeViewTab = computed({
get() {
return (route.value.query?.tab as string) ?? 'integrations'
},
set(tab: string) {
if (requestIntegration.value.isOpen) {
requestIntegration.value.isOpen = false
}
router.push({ query: { ...route.value.query, tab } })
},
})
const loadIntegrations = async (
databaseOnly = false,
baseId = undefined,
page: number = integrationPaginationData.value.page!,
limit: number = integrationPaginationData.value.pageSize!,
) => {
try {
if (!activeWorkspaceId.value) return
isLoadingIntegrations.value = true
if (!databaseOnly && limit * (page - 1) > integrationPaginationData.value.totalRows!) {
integrationPaginationData.value.page = 1
page = 1
}
const { list, pageInfo } = await api.integration.list(
databaseOnly
? {
type: IntegrationsType.Database,
includeDatabaseInfo: true,
baseId,
}
: {
offset: limit * (page - 1),
limit,
...(searchQuery.value.trim() ? { query: searchQuery.value } : {}),
},
)
integrations.value = list
if (!databaseOnly) {
integrationPaginationData.value.totalRows = pageInfo.totalRows ?? 0
}
} catch (e) {
await message.error(await extractSdkResponseErrorMsg(e))
integrations.value = []
integrationPaginationData.value.totalRows = 0
integrationPaginationData.value.page = 1
} finally {
isLoadingIntegrations.value = false
}
}
const addIntegration = (type: IntegrationsSubType) => {
activeIntegration.value = defaultValues(type)
pageMode.value = IntegrationsPageMode.ADD
$e('c:integration:add')
}
const deleteIntegration = async (integration: IntegrationType, force = false) => {
if (!integration?.id) return
try {
await api.integration.delete(integration.id, {
query: force ? { force: 'true' } : {},
})
$e('a:integration:delete')
await loadIntegrations()
// await message.success(`Connection ${integration.title} deleted successfully`)
return true
} catch (e) {
const error = await extractSdkResponseErrorMsgv2(e)
if (error.error === NcErrorType.INTEGRATION_NOT_FOUND) {
await message.error(error.message?.replace(integration.id, integration.title!))
window.location.reload()
return
}
await message.error(await extractSdkResponseErrorMsg(e))
}
deleteConfirmText.value = null
}
const updateIntegration = async (integration: IntegrationType) => {
if (!integration.id) return
try {
await api.integration.update(integration.id, integration)
$e('a:integration:update')
await loadIntegrations()
pageMode.value = null
activeIntegration.value = null
await message.success(`Connection "${integration.title}" updated successfully`)
} catch (e) {
await message.error(await extractSdkResponseErrorMsg(e))
}
}
const saveIntegration = async (
integration: IntegrationType,
mode: 'create' | 'duplicate' = 'create',
loadDatasourceInfo = false,
baseId: string | undefined = undefined,
) => {
try {
const response = await api.integration.create(integration)
if (mode === 'create') {
$e('a:integration:create')
} else {
$e('a:integration:duplicate')
}
if (response && response?.id) {
if (!loadDatasourceInfo) {
integrations.value.push(response)
}
}
await loadIntegrations(loadDatasourceInfo, baseId)
eventBus.emit(IntegrationStoreEvents.INTEGRATION_ADD, response)
pageMode.value = null
activeIntegration.value = null
if (response?.title && mode === 'create') {
if (isFromIntegrationPage.value) {
activeViewTab.value = 'connections'
successConfirmModal.value.connectionTitle = response.title ?? ''
successConfirmModal.value.isOpen = true
} else {
await message.success(`Connection "${response.title}" created successfully`)
}
}
} catch (e) {
await message.error(await extractSdkResponseErrorMsg(e))
}
}
const duplicateIntegration = async (integration: IntegrationType) => {
if (!integration?.id) return
try {
isLoadingIntegrations.value = true
saveIntegration(
{
title: integration.title,
config: {},
type: integration.type,
copy_from_id: integration.id,
},
'duplicate',
)
} catch (e) {
await message.error(await extractSdkResponseErrorMsg(e))
} finally {
isLoadingIntegrations.value = false
}
}
const getIntegration = async (
integration: IntegrationType,
options?: {
includeConfig?: boolean
includeSources?: boolean
},
) => {
if (!integration?.id) return
try {
const integrationWithConfig = await api.integration.read(integration.id, {
...(options || {}),
})
return integrationWithConfig
} catch (e) {
const error = await extractSdkResponseErrorMsgv2(e)
if (error.error === NcErrorType.INTEGRATION_NOT_FOUND) {
await message.error(error.message?.replace(integration.id!, integration.title!))
window.location.reload()
return
}
await message.error(await extractSdkResponseErrorMsg(e))
}
}
const editIntegration = async (integration: IntegrationType) => {
if (!integration?.id) return
try {
const integrationWithConfig = await getIntegration(integration, { includeConfig: true })
activeIntegration.value = integrationWithConfig
pageMode.value = IntegrationsPageMode.EDIT
$e('c:integration:edit')
} catch {}
}
const saveIntegraitonRequest = async (msg: string) => {
if (!msg?.trim()) return
requestIntegration.value.isLoading = true
try {
$e('a:integration:new-request', {
value: requestIntegration.value.msg,
})
requestIntegration.value.isLoading = false
requestIntegration.value.isOpen = false
requestIntegration.value.msg = ''
await message.success('Your request has been successfully submitted')
} catch (e) {
requestIntegration.value.isLoading = false
await message.error(await extractSdkResponseErrorMsg(e))
}
}
return {
IntegrationsPageMode,
integrationType,
pageMode,
activeIntegration,
integrations,
isLoadingIntegrations,
deleteConfirmText,
eventBus,
requestIntegration,
integrationPaginationData,
activeViewTab,
isFromIntegrationPage,
successConfirmModal,
searchQuery,
addIntegration,
loadIntegrations,
deleteIntegration,
updateIntegration,
saveIntegration,
editIntegration,
duplicateIntegration,
saveIntegraitonRequest,
getIntegration,
}
}, 'integrations-store')
export { useProvideIntegrationViewStore }
export function useIntegrationStore() {
const integrationStore = _useIntegrationStore()
if (integrationStore == null) return useProvideIntegrationViewStore()
return integrationStore
}

71
packages/nc-gui/lang/en.json

@ -271,7 +271,13 @@
"colour": "Colour", "colour": "Colour",
"use": "Use", "use": "Use",
"stack": "Stack", "stack": "Stack",
"ipAddress": "IP Address" "ipAddress": "IP Address",
"integration": "Integration",
"integrations": "Integrations",
"connection": "Connection",
"connections": "Connections",
"private": "Private",
"request" : "Request"
}, },
"objects": { "objects": {
"files": "files", "files": "files",
@ -335,7 +341,33 @@
"tall": "Tall", "tall": "Tall",
"extra": "Extra" "extra": "Extra"
}, },
"externalDb": "External Database" "externalDb": "External Database",
"syncData":{
"appleNumbers": "Apple numbers",
"asana": "Asana",
"box": "Box",
"github": "Github",
"gitlab": "Gitlab",
"googleCalendar": "Google Calendar",
"googleDrive": "Google Drive",
"googleSheets": "Google Sheets",
"hubspot": "Hubspot",
"jira": "Jira",
"mailchimp": "Mailchimp",
"microsoftAccess": "Microsoft Access",
"microsoftExcel": "Microsoft Excel",
"microsoftOutlook": "Microsoft Outlook",
"miro": "Miro",
"salesforce": "Salesforce",
"snowflake": "Snowflake",
"stripe": "Stripe",
"surveyMonkey": "SurveyMonkey",
"tableau": "Tableau",
"trello": "Trello",
"typeform": "Typeform",
"workday": "Workday",
"zendesk": "Zendesk"
}
}, },
"datatype": { "datatype": {
"ID": "ID", "ID": "ID",
@ -405,7 +437,7 @@
"parameterName": "Parameter Name", "parameterName": "Parameter Name",
"currencyLocale": "Currency Locale", "currencyLocale": "Currency Locale",
"currencyCode": "Currency Code", "currencyCode": "Currency Code",
"searchMembers": "Search Members", "searchMembers": "Search members",
"noMembersFound": "No members found", "noMembersFound": "No members found",
"dateJoined": "Date Joined", "dateJoined": "Date Joined",
"tokenName": "Token name", "tokenName": "Token name",
@ -519,7 +551,10 @@
"noOptionsFound": "No options found", "noOptionsFound": "No options found",
"surveyFormSubmitConfirmMsg": "Are you sure you want to submit this form?", "surveyFormSubmitConfirmMsg": "Are you sure you want to submit this form?",
"noResultsMatchedYourSearch": "Your search did not yield any matching results", "noResultsMatchedYourSearch": "Your search did not yield any matching results",
"looksLikeThisStackIsEmpty": "Looks like this stack does not have any records" "looksLikeThisStackIsEmpty": "Looks like this stack does not have any records",
"fromScratch": "From scratch",
"fromFileAndExternalSources": "From files & external sources",
"directlyInRealTime": "Directly in real time"
}, },
"labels": { "labels": {
"changeDisplayValueField": "Change display value field", "changeDisplayValueField": "Change display value field",
@ -544,11 +579,12 @@
"dropHere": "Drop here", "dropHere": "Drop here",
"addMore": "Add more", "addMore": "Add more",
"clearAllFiles": "Clear all files", "clearAllFiles": "Clear all files",
"integration": "Integration",
"notRecommended": "Not recommended", "notRecommended": "Not recommended",
"allowMetaWrite": "Allow Schema Change", "allowMetaWrite": "Allow Schema Change",
"allowDataWrite": "Allow Data Write/Edit", "allowDataWrite": "Allow Data Write/Edit",
"selectView": "Select a View", "selectView": "Select a View",
"connectionDetails": "Connection Details", "connectionDetails": "Source Connection Details",
"metaSync": "Meta Sync", "metaSync": "Meta Sync",
"mention": "Mention", "mention": "Mention",
"today": "Today", "today": "Today",
@ -674,7 +710,7 @@
"allTables": "All Tables", "allTables": "All Tables",
"members": "Members", "members": "Members",
"dataSources": "Data Sources", "dataSources": "Data Sources",
"connectDataSource": "Connect a Data Source", "connectDataSource": "Connect External Data",
"searchProjects": "Search Bases", "searchProjects": "Search Bases",
"createdBy": "Created By", "createdBy": "Created By",
"viewingAttachmentsOf": "Viewing Attachments of", "viewingAttachmentsOf": "Viewing Attachments of",
@ -715,8 +751,8 @@
"dbType": "Database Type", "dbType": "Database Type",
"servername": "servername / hostAddr", "servername": "servername / hostAddr",
"sqliteFile": "SQLite File", "sqliteFile": "SQLite File",
"hostAddress": "Host Address", "hostAddress": "Host address",
"port": "Port Number", "port": "Port number",
"username": "Username", "username": "Username",
"password": "Password", "password": "Password",
"schemaName": "Schema name", "schemaName": "Schema name",
@ -872,7 +908,9 @@
"signUpForFree": "Sign up for free", "signUpForFree": "Sign up for free",
"coverImageField": "Cover image", "coverImageField": "Cover image",
"fitImage": "Fit image", "fitImage": "Fit image",
"coverImageArea": "Cover image" "coverImageArea": "Cover image",
"syncData": "Sync data",
"syncDataModalSubtitle" : "Register the services you are interested in to get notified when they become available"
}, },
"activity": { "activity": {
"hideWeekends": "Hide weekends", "hideWeekends": "Hide weekends",
@ -1129,11 +1167,14 @@
"group": "Group" "group": "Group"
}, },
"tooltip": { "tooltip": {
"privateConnection": "Enable to make this connection private and hidden from other creators in this workspace.",
"optionalDatabaseName": "Optional. Uses default database \"{database}\" if left blank",
"optionalSchemaName": "Optional. Uses default schema \"{schema}\" if left blank.",
"schemaChangeDisabled": "Schema editing is disabled for this data source.", "schemaChangeDisabled": "Schema editing is disabled for this data source.",
"typeNotAllowed": "This datatype is not allowed.", "typeNotAllowed": "This datatype is not allowed.",
"dataWriteOptionDisabled": "Data editing can disable only when ‘Schema edit’ is disabled and becomes enabled otherwise", "dataWriteOptionDisabled": "Data editing can disable only when ‘Schema edit’ is disabled and becomes enabled otherwise",
"allowMetaWrite": "This option allows modification of database schema, including adding, altering, or deleting tables and columns. Use with caution, as changes may impact the structural integrity of your database.", "allowMetaWrite": "This option allows modification of database schema, including adding, altering, or deleting tables and columns. Use with caution, as changes may impact the structural integrity of your database.",
"allowDataWrite": "This option allows create, update, or delete of records within database tables. Ideal for administrative users need to change data directly.", "allowDataWrite": "This option allows creating, updating, or deleting records within database tables. Ideal for administrative users needing to change data directly.",
"reachedSourceLimit": "Limited to 10 data sources per base", "reachedSourceLimit": "Limited to 10 data sources per base",
"saveChanges": "Save changes", "saveChanges": "Save changes",
"xcDB": "Create a new base", "xcDB": "Create a new base",
@ -1341,7 +1382,7 @@
"calendarNoFields": "Calendar view requires a date or date time field to be setup. Try setting up a calendar view after adding a date/ date time field!", "calendarNoFields": "Calendar view requires a date or date time field to be setup. Try setting up a calendar view after adding a date/ date time field!",
"kanbanNoFields": "Kanban view requires a single select field to be setup. Try setting up a kanban view after adding a single select field!", "kanbanNoFields": "Kanban view requires a single select field to be setup. Try setting up a kanban view after adding a single select field!",
"mapNoFields": "Map view requires a geo data field to be setup. Try setting up a map view after adding a geo data field!", "mapNoFields": "Map view requires a geo data field to be setup. Try setting up a map view after adding a geo data field!",
"dbValid": "Please make sure database you are trying to connect is valid! This operation can cause schema loss!!", "dbValid": "Ensure database validity to prevent schema loss",
"barcode": { "barcode": {
"renderError": "Barcode error - please check compatibility between input and barcode type" "renderError": "Barcode error - please check compatibility between input and barcode type"
}, },
@ -1616,8 +1657,8 @@
"duplicateSystemColumnName": "Name already used for system field", "duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of field name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of field name exceeds the max {value} characters",
"projectNameExceeds50Characters": "Base name exceeds 50 characters", "projectNameExceeds50Characters": "{title} name exceeds 50 characters",
"projectNameCannotStartWithSpace": "Base name cannot start with space", "projectNameCannotStartWithSpace": "{title} name cannot start with space",
"requiredField": "Required field", "requiredField": "Required field",
"ipNotAllowed": "IP not allowed", "ipNotAllowed": "IP not allowed",
"targetFileIsNotAnAcceptedFileType": "Target file is not an accepted file type", "targetFileIsNotAnAcceptedFileType": "Target file is not an accepted file type",
@ -1689,7 +1730,9 @@
"columnCreated": "Field created", "columnCreated": "Field created",
"passwordChanged": "Password changed successfully. Please login again.", "passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully", "settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully" "roleUpdated": "Role updated successfully",
"connectionAdded": "Connection Successfully Created",
"connectionAddedDesc": "All Base owners & creators in this Workspace can now use this connection to easily add a new Data Source to their Base."
} }
} }
} }

1
packages/nc-gui/lib/acl.ts

@ -40,6 +40,7 @@ const rolePermissions = {
baseReorder: true, baseReorder: true,
orgAdminPanel: true, orgAdminPanel: true,
workspaceAuditList: true, workspaceAuditList: true,
workspaceIntegrations: true,
}, },
}, },
[OrgUserRoles.VIEWER]: { [OrgUserRoles.VIEWER]: {

41
packages/nc-gui/lib/enums.ts

@ -1,12 +1,4 @@
export enum ClientType { export { ClientType } from 'nocodb-sdk'
MYSQL = 'mysql2',
MSSQL = 'mssql',
PG = 'pg',
SQLITE = 'sqlite3',
VITESS = 'vitess',
SNOWFLAKE = 'snowflake',
DATABRICKS = 'databricks',
}
export enum Language { export enum Language {
ar = 'العربية', ar = 'العربية',
@ -181,3 +173,34 @@ export enum AuditLogsDateRange {
export enum ExtensionsEvents { export enum ExtensionsEvents {
DUPLICATE = 'duplicate', DUPLICATE = 'duplicate',
} }
export enum IntegrationStoreEvents {
INTEGRATION_ADD = 'integration-add',
}
export enum SyncDataType {
// APPLE_NUMBERS = 'apple-numbers',
ASANA = 'asana',
BOX = 'box',
GITHUB = 'github',
GITLAB = 'gitlab',
GOOGLE_CALENDAR = 'google-calendar',
GOOGLE_DRIVE = 'google-drive',
GOOGLE_SHEETS = 'google-sheets',
HUBSPOT = 'hubspot',
JIRA = 'jira',
MAILCHIMP = 'mailchimp',
MICROSOFT_ACCESS = 'microsoft-access',
MICROSOFT_EXCEL = 'microsoft-excel',
MICROSOFT_OUTLOOK = 'microsoft-outlook',
MIRO = 'miro',
SALESFORCE = 'salesforce',
SNOWFLAKE = 'snowflake',
STRIPE = 'stripe',
SURVEYMONKEY = 'surveymonkey',
TABLEAU = 'tableau',
TRELLO = 'trello',
TYPEFORM = 'typeform',
WORKDAY = 'workday',
ZENDESK = 'zendesk',
}

1
packages/nc-gui/pages/account/index.vue

@ -69,7 +69,6 @@ const logout = async () => {
<div class="select-none">{{ $t('labels.profile') }}</div> <div class="select-none">{{ $t('labels.profile') }}</div>
</div> </div>
</NcMenuItem> </NcMenuItem>
<NcMenuItem <NcMenuItem
key="tokens" key="tokens"
class="item" class="item"

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

@ -39,7 +39,10 @@ const isSharedView = computed(() => {
const routeName = (route.value.name as string) || '' const routeName = (route.value.name as string) || ''
// check route is not base page by route name // check route is not base page by route name
return !routeName.startsWith('index-typeOrId-baseId-') && !['index', 'index-typeOrId'].includes(routeName) return (
!routeName.startsWith('index-typeOrId-baseId-') &&
!['index', 'index-typeOrId', 'index-typeOrId-integrations'].includes(routeName)
)
}) })
const isSharedFormView = computed(() => { const isSharedFormView = computed(() => {

3
packages/nc-gui/pages/index/[typeOrId]/integrations.vue

@ -0,0 +1,3 @@
<template>
<WorkspaceIntegrationsView />
</template>

15
packages/nc-gui/store/workspace.ts

@ -41,6 +41,8 @@ export const useWorkspace = defineStore('workspaceStore', () => {
const isWorkspaceSettingsPageOpened = computed(() => route.value.name === 'index-typeOrId-settings') const isWorkspaceSettingsPageOpened = computed(() => route.value.name === 'index-typeOrId-settings')
const isIntegrationsPageOpened = computed(() => route.value.name === 'index-typeOrId-integrations')
const isWorkspaceLoading = ref(true) const isWorkspaceLoading = ref(true)
const isCollaboratorsLoading = ref(true) const isCollaboratorsLoading = ref(true)
const isInvitingCollaborators = ref(false) const isInvitingCollaborators = ref(false)
@ -223,6 +225,17 @@ export const useWorkspace = defineStore('workspaceStore', () => {
} }
} }
// Todo: write logic to navigate to integrations
const navigateToIntegrations = async (_?: string, cmdOrCtrl?: boolean) => {
if (cmdOrCtrl) {
await navigateTo('/nc/integrations', {
open: navigateToBlankTargetOpenOption,
})
} else {
await navigateTo('/nc/integrations')
}
}
const auditLogsQuery = ref<Partial<AuditLogsQuery>>(defaultAuditLogsQuery) const auditLogsQuery = ref<Partial<AuditLogsQuery>>(defaultAuditLogsQuery)
const audits = ref<null | Array<AuditType>>(null) const audits = ref<null | Array<AuditType>>(null)
@ -312,6 +325,8 @@ export const useWorkspace = defineStore('workspaceStore', () => {
auditPaginationData, auditPaginationData,
loadAudits, loadAudits,
isIntegrationsPageOpened,
navigateToIntegrations,
} }
}) })

25
packages/nc-gui/utils/baseCreateUtils.ts

@ -1,4 +1,4 @@
import { SSLUsage } from 'nocodb-sdk' import { SSLUsage, type BoolType } from 'nocodb-sdk'
import { ClientType } from '~/lib/enums' import { ClientType } from '~/lib/enums'
// todo: move to noco-sdk // todo: move to noco-sdk
@ -19,6 +19,10 @@ interface ProjectCreateForm {
} }
sslUse?: SSLUsage sslUse?: SSLUsage
extraParameters: { key: string; value: string }[] extraParameters: { key: string; value: string }[]
is_private?: BoolType
is_schema_readonly?: BoolType
is_data_readonly?: BoolType
fk_integration_id?: string
} }
interface DefaultConnection { interface DefaultConnection {
@ -58,19 +62,7 @@ interface DatabricksConnection {
const defaultHost = 'localhost' const defaultHost = 'localhost'
const testDataBaseNames = { export { getTestDatabaseName } from 'nocodb-sdk'
[ClientType.MYSQL]: null,
mysql: null,
[ClientType.PG]: 'postgres',
oracledb: 'xe',
[ClientType.MSSQL]: undefined,
[ClientType.SQLITE]: 'a.sqlite',
}
export const getTestDatabaseName = (db: { client: ClientType; connection?: { database?: string } }) => {
if (db.client === ClientType.PG || db.client === ClientType.SNOWFLAKE) return db.connection?.database
return testDataBaseNames[db.client as keyof typeof testDataBaseNames]
}
export const clientTypes = [ export const clientTypes = [
{ {
@ -99,6 +91,11 @@ export const clientTypes = [
}, },
] ]
export const clientTypesMap = clientTypes.reduce((acc, curr) => {
acc[curr.value] = curr
return acc
}, {} as Record<string, (typeof clientTypes)[0]>)
const homeDir = '' const homeDir = ''
type ConnectionClientType = type ConnectionClientType =

67
packages/nc-gui/utils/iconUtils.ts

@ -208,6 +208,41 @@ import NcRefresh from '~icons/nc-icons/refresh'
import NcPlay from '~icons/nc-icons/play' import NcPlay from '~icons/nc-icons/play'
import GoogleDocs from '~icons/nc-icons/google-docs' import GoogleDocs from '~icons/nc-icons/google-docs'
import NcGlobe from '~icons/nc-icons/globe' import NcGlobe from '~icons/nc-icons/globe'
import NcIntegration from '~icons/nc-icons/integration'
import NcGitCommit from '~icons/nc-icons/git-commit'
import NcCircle from '~icons/nc-icons/circle'
import NcServer1 from '~icons/nc-icons/server1'
import NcThumbsUpOutline from '~icons/nc-icons/thumbs-up-outline'
// Sync data
import NcAppleSolid from '~icons/nc-icons/apple_solid'
import NcAsana from '~icons/nc-icons/asana'
import NcBox from '~icons/nc-icons/box'
import NcGithubSolid from '~icons/nc-icons/github_solid'
import NcGitlab from '~icons/nc-icons/gitlab'
import NcGoogleCalendar from '~icons/nc-icons/google_calendar'
import NcGoogleSheet from '~icons/nc-icons/google_sheet'
import NcGoogleDrive from '~icons/nc-icons/google-drive'
import NcHubspot from '~icons/nc-icons/hubspot'
import NcJira from '~icons/nc-icons/jira'
import NcMailchimp from '~icons/nc-icons/mailchimp'
import NcMicrosoftAccess from '~icons/nc-icons/microsoft_access'
import NcMicrosoftExcel from '~icons/nc-icons/microsoft_excel'
import NcMicrosoftOutlook from '~icons/nc-icons/microsoft_outlook'
import NcMiro from '~icons/nc-icons/miro'
import NcSalesforce from '~icons/nc-icons/salesforce'
import NcSnowflake from '~icons/nc-icons/snowflake'
import NcStripe from '~icons/nc-icons/stripe'
import NcSurveyMonkey from '~icons/nc-icons/survey_monkey'
import NcTableau from '~icons/nc-icons/tableau'
import NcTrello from '~icons/nc-icons/trello'
import NcTypeform from '~icons/nc-icons/typeform'
import NcWorkday from '~icons/nc-icons/workday'
import NcZendesk from '~icons/nc-icons/zendesk'
import NcBookOpen from '~icons/nc-icons/book-open'
import NcCircleCheckSolid from '~icons/nc-icons/check-circle-solid'
import NcAlertTriangleSolid from '~icons/nc-icons/alert-triangle-solid'
// keep it for reference // keep it for reference
// todo: remove it after all icons are migrated // todo: remove it after all icons are migrated
@ -646,6 +681,38 @@ export const iconMap = {
googleDocs: GoogleDocs, googleDocs: GoogleDocs,
pdfFile: MdiPdf, pdfFile: MdiPdf,
globe: NcGlobe, globe: NcGlobe,
integration: NcIntegration,
gitCommit: NcGitCommit,
circle: NcCircle,
server1: NcServer1,
thumbsUpOutline: NcThumbsUpOutline,
appleSolid: NcAppleSolid,
asana: NcAsana,
box: NcBox,
githubSolid: NcGithubSolid,
gitlab: NcGitlab,
googleCalendar: NcGoogleCalendar,
googleSheet: NcGoogleSheet,
googleDrive: NcGoogleDrive,
hubspot: NcHubspot,
jira: NcJira,
mailchimp: NcMailchimp,
microsoftAccess: NcMicrosoftAccess,
microsoftExcel: NcMicrosoftExcel,
microsoftOutlook: NcMicrosoftOutlook,
miro: NcMiro,
salesforce: NcSalesforce,
snowflake: NcSnowflake,
stripe: NcStripe,
surveyMonkey: NcSurveyMonkey,
tableau: NcTableau,
trello: NcTrello,
typeform: NcTypeform,
workday: NcWorkday,
zendesk: NcZendesk,
bookOpen: NcBookOpen,
circleCheckSolid: NcCircleCheckSolid,
alertTriangleSolid: NcAlertTriangleSolid
} }
export const getMdiIcon = (type: string): any => { export const getMdiIcon = (type: string): any => {

38
packages/nc-gui/utils/syncDataUtils.ts

@ -0,0 +1,38 @@
import type { FunctionalComponent, SVGAttributes } from 'nuxt/dist/app/compat/capi'
import { SyncDataType } from '~/lib/enums'
export const syncDataTypes = [
// { title: 'objects.syncData.appleNumbers', value: SyncDataType.APPLE_NUMBERS, icon: iconMap.appleSolid },
{ title: 'objects.syncData.asana', value: SyncDataType.ASANA, icon: iconMap.asana },
{ title: 'objects.syncData.box', value: SyncDataType.BOX, icon: iconMap.box },
{ title: 'objects.syncData.github', value: SyncDataType.GITHUB, icon: iconMap.githubSolid },
{ title: 'objects.syncData.gitlab', value: SyncDataType.GITLAB, icon: iconMap.gitlab },
{ title: 'objects.syncData.googleCalendar', value: SyncDataType.GOOGLE_CALENDAR, icon: iconMap.googleCalendar },
{ title: 'objects.syncData.googleDrive', value: SyncDataType.GOOGLE_DRIVE, icon: iconMap.googleDrive },
{ title: 'objects.syncData.googleSheets', value: SyncDataType.GOOGLE_SHEETS, icon: iconMap.googleSheet },
{ title: 'objects.syncData.hubspot', value: SyncDataType.HUBSPOT, icon: iconMap.hubspot },
{ title: 'objects.syncData.jira', value: SyncDataType.JIRA, icon: iconMap.jira },
{ title: 'objects.syncData.mailchimp', value: SyncDataType.MAILCHIMP, icon: iconMap.mailchimp },
{ title: 'objects.syncData.microsoftAccess', value: SyncDataType.MICROSOFT_ACCESS, icon: iconMap.microsoftAccess },
{ title: 'objects.syncData.microsoftExcel', value: SyncDataType.MICROSOFT_EXCEL, icon: iconMap.microsoftExcel },
{ title: 'objects.syncData.microsoftOutlook', value: SyncDataType.MICROSOFT_OUTLOOK, icon: iconMap.microsoftOutlook },
{ title: 'objects.syncData.miro', value: SyncDataType.MIRO, icon: iconMap.miro },
{ title: 'objects.syncData.salesforce', value: SyncDataType.SALESFORCE, icon: iconMap.salesforce },
{ title: 'objects.syncData.snowflake', value: SyncDataType.SNOWFLAKE, icon: iconMap.snowflake },
{ title: 'objects.syncData.stripe', value: SyncDataType.STRIPE, icon: iconMap.stripe },
{ title: 'objects.syncData.surveyMonkey', value: SyncDataType.SURVEYMONKEY, icon: iconMap.surveyMonkey },
{ title: 'objects.syncData.tableau', value: SyncDataType.TABLEAU, icon: iconMap.tableau },
{ title: 'objects.syncData.trello', value: SyncDataType.TRELLO, icon: iconMap.trello },
{ title: 'objects.syncData.typeform', value: SyncDataType.TYPEFORM, icon: iconMap.typeform },
{ title: 'objects.syncData.workday', value: SyncDataType.WORKDAY, icon: iconMap.workday },
{ title: 'objects.syncData.zendesk', value: SyncDataType.ZENDESK, icon: iconMap.zendesk },
] as {
title: string
icon: FunctionalComponent<SVGAttributes, {}, any, {}>
value: SyncDataType
}[]
export const syncDataTypesMap = syncDataTypes.reduce((acc, curr) => {
acc[curr.value] = curr
return acc
}, {})

26
packages/nc-gui/utils/validation.ts

@ -75,22 +75,36 @@ export const layoutTitleValidator = {
}, },
} }
export const baseTitleValidator = { export const baseTitleValidator = (title: 'project' | 'connection' = 'project') => {
return {
validator: (rule: any, value: any) => { validator: (rule: any, value: any) => {
const { t } = getI18n().global const { t } = getI18n().global
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (value?.length > 50) { if (value?.length > 50) {
reject(new Error(t('msg.error.projectNameExceeds50Characters'))) reject(
new Error(
t('msg.error.projectNameExceeds50Characters', {
title: title === 'project' ? t('objects.project') : t('general.connection'),
}),
),
)
} }
if (value[0] === ' ') { if (value[0] === ' ') {
reject(new Error(t('msg.error.projectNameCannotStartWithSpace'))) reject(
new Error(
t('msg.error.projectNameCannotStartWithSpace', {
title: title === 'project' ? t('objects.project') : t('general.connection'),
}),
),
)
} }
resolve(true) resolve(true)
}) })
}, },
}
} }
export const fieldRequiredValidator = () => { export const fieldRequiredValidator = () => {
@ -192,11 +206,7 @@ export const extraParameterValidator = {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const { t } = getI18n().global const { t } = getI18n().global
for (const param of value) { for (const param of value) {
if (param.key === '') { if (!value.every((el) => el.key === '') && value.filter((el: any) => el.key === param.key).length !== 1) {
// return reject(new Error('Parameter key cannot be empty'))
return reject(new Error(t('msg.error.parameterKeyCannotBeEmpty')))
}
if (value.filter((el: any) => el.key === param.key).length !== 1) {
// return reject(new Error('Duplicate parameter keys are not allowed')) // return reject(new Error('Duplicate parameter keys are not allowed'))
return reject(new Error(t('msg.error.duplicateParameterKeysAreNotAllowed'))) return reject(new Error(t('msg.error.duplicateParameterKeysAreNotAllowed')))
} }

13
packages/nocodb-sdk/src/lib/enums.ts

@ -151,6 +151,9 @@ export enum AppEvents {
COMMENT_CREATE = 'comment.create', COMMENT_CREATE = 'comment.create',
COMMENT_DELETE = 'comment.delete', COMMENT_DELETE = 'comment.delete',
COMMENT_UPDATE = 'comment.update', COMMENT_UPDATE = 'comment.update',
INTEGRATION_DELETE = 'integration.delete',
INTEGRATION_CREATE = 'integration.create',
INTEGRATION_UPDATE = 'integration.update',
} }
export enum ClickhouseTables { export enum ClickhouseTables {
@ -332,6 +335,16 @@ export enum SourceRestriction {
DATA_READONLY = 'is_data_readonly', DATA_READONLY = 'is_data_readonly',
} }
export enum ClientType {
MYSQL = 'mysql2',
MSSQL = 'mssql',
PG = 'pg',
SQLITE = 'sqlite3',
VITESS = 'vitess',
SNOWFLAKE = 'snowflake',
DATABRICKS = 'databricks',
}
export enum SSLUsage { export enum SSLUsage {
No = 'No', No = 'No',
Allowed = 'Allowed', Allowed = 'Allowed',

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

@ -192,6 +192,8 @@ export enum NcErrorType {
INVALID_PK_VALUE = 'INVALID_PK_VALUE', INVALID_PK_VALUE = 'INVALID_PK_VALUE',
COLUMN_ASSOCIATED_WITH_LINK = 'COLUMN_ASSOCIATED_WITH_LINK', COLUMN_ASSOCIATED_WITH_LINK = 'COLUMN_ASSOCIATED_WITH_LINK',
TABLE_ASSOCIATED_WITH_LINK = 'TABLE_ASSOCIATED_WITH_LINK', TABLE_ASSOCIATED_WITH_LINK = 'TABLE_ASSOCIATED_WITH_LINK',
INTEGRATION_NOT_FOUND= 'INTEGRATION_NOT_FOUND',
INTEGRATION_LINKED_WITH_BASES= 'INTEGRATION_LINKED_WITH_BASES',
} }
type Roles = OrgUserRoles | ProjectRoles | WorkspaceUserRoles; type Roles = OrgUserRoles | ProjectRoles | WorkspaceUserRoles;

19
packages/nocodb-sdk/src/lib/helperFunctions.ts

@ -1,5 +1,6 @@
import UITypes, { isNumericCol } from './UITypes'; import UITypes, { isNumericCol } from './UITypes';
import { RolesObj, RolesType } from './globals'; import { RolesObj, RolesType } from './globals';
import { ClientType } from './enums';
// import {RelationTypes} from "./globals"; // import {RelationTypes} from "./globals";
@ -203,3 +204,21 @@ export {
populateUniqueFileName, populateUniqueFileName,
roundUpToPrecision, roundUpToPrecision,
}; };
const testDataBaseNames = {
[ClientType.MYSQL]: null,
mysql: null,
[ClientType.PG]: 'postgres',
oracledb: 'xe',
[ClientType.MSSQL]: undefined,
[ClientType.SQLITE]: 'a.sqlite',
};
export const getTestDatabaseName = (db: {
client: ClientType;
connection?: { database?: string };
}) => {
if (db.client === ClientType.PG || db.client === ClientType.SNOWFLAKE)
return db.connection?.database;
return testDataBaseNames[db.client as keyof typeof testDataBaseNames];
};

1
packages/nocodb/src/controllers/bases.controller.ts

@ -73,6 +73,7 @@ export class BasesController {
) { ) {
const base = await this.projectsService.getProjectWithInfo(context, { const base = await this.projectsService.getProjectWithInfo(context, {
baseId: baseId, baseId: baseId,
includeConfig: false,
}); });
this.projectsService.sanitizeProject(base); this.projectsService.sanitizeProject(base);

139
packages/nocodb/src/controllers/integrations.controller.ts

@ -0,0 +1,139 @@
import {
Body,
Controller,
Delete,
Get,
Param,
Patch,
Post,
Query,
Req,
UseGuards,
} from '@nestjs/common';
import { IntegrationReqType, IntegrationsType } from 'nocodb-sdk';
import { GlobalGuard } from '~/guards/global/global.guard';
import { Acl } from '~/middlewares/extract-ids/extract-ids.middleware';
import { IntegrationsService } from '~/services/integrations.service';
import { MetaApiLimiterGuard } from '~/guards/meta-api-limiter.guard';
import { TenantContext } from '~/decorators/tenant-context.decorator';
import { NcContext, NcRequest } from '~/interface/config';
@Controller()
@UseGuards(MetaApiLimiterGuard, GlobalGuard)
export class IntegrationsController {
constructor(private readonly integrationsService: IntegrationsService) {}
@Get(['/api/v2/meta/integrations/:integrationId'])
@Acl('integrationGet', {
scope: 'org',
})
async integrationGet(
@TenantContext() context: NcContext,
@Param('integrationId') integrationId: string,
@Query('includeConfig') includeConfig: string,
@Query('includeSources') includeSources: string,
@Req() req: NcRequest,
) {
const integration = await this.integrationsService.integrationGetWithConfig(
context,
{
integrationId,
includeSources: includeSources === 'true',
},
);
// hide config if not the owner or if not requested
if (
includeConfig !== 'true' ||
(integration.is_private && req.user.id !== integration.created_by)
)
integration.config = undefined;
return integration;
}
@Post(['/api/v2/meta/integrations'])
@Acl('integrationCreate', {
scope: 'org',
})
async integrationCreate(
@TenantContext() context: NcContext,
@Body() integration: IntegrationReqType,
@Req() req: NcRequest,
) {
return await this.integrationsService.integrationCreate(context, {
integration,
req,
});
}
@Delete(['/api/v2/meta/integrations/:integrationId'])
@Acl('integrationDelete', {
scope: 'org',
})
async integrationDelete(
@TenantContext() context: NcContext,
@Param('integrationId') integrationId: string,
@Req() req: NcRequest,
@Query('force') force: string,
) {
return await this.integrationsService.integrationDelete(context, {
req,
integrationId,
force: force === 'true',
});
}
@Patch(['/api/v2/meta/integrations/:integrationId'])
@Acl('integrationUpdate', {
scope: 'org',
})
async integrationUpdate(
@TenantContext() context: NcContext,
@Param('integrationId') integrationId: string,
@Body() body: IntegrationReqType,
@Req() req: NcRequest,
) {
const integration = await this.integrationsService.integrationUpdate(
context,
{
integration: body,
integrationId,
req,
},
);
return integration;
}
@Get(['/api/v2/meta/integrations'])
@Acl('integrationList', {
scope: 'org',
extendedScope: 'base',
})
async integrationList(
@Req() req: NcRequest,
@Query('type') type: IntegrationsType,
@Query('includeDatabaseInfo') includeDatabaseInfo?: string,
@Query('limit') limit?: string,
@Query('offset') offset?: string,
@Query('query') query?: string,
) {
const integrations = await this.integrationsService.integrationList({
req,
includeDatabaseInfo: includeDatabaseInfo === 'true',
type,
// if limit/offset is not provided, then return all integrations
limit: limit && (+limit || 25),
offset: offset && (+offset || 0),
query
});
if (!includeDatabaseInfo) {
for (const integration of integrations.list) {
integration.config = undefined;
}
}
return integrations;
}
}

4
packages/nocodb/src/controllers/sources.controller.ts

@ -35,7 +35,7 @@ export class SourcesController {
}); });
if (source.isMeta()) { if (source.isMeta()) {
delete source.config; source.config = undefined;
} }
return source; return source;
@ -78,7 +78,7 @@ export class SourcesController {
for (const source of sources) { for (const source of sources) {
if (source.isMeta()) { if (source.isMeta()) {
delete source.config; source.config = undefined;
} }
} }

74
packages/nocodb/src/controllers/utils.controller.ts

@ -9,7 +9,12 @@ import {
Req, Req,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { validateAndExtractSSLProp } from 'nocodb-sdk'; import { ProjectRoles, validateAndExtractSSLProp } from 'nocodb-sdk';
import {
getTestDatabaseName,
IntegrationsType,
OrgUserRoles,
} from 'nocodb-sdk';
import { GlobalGuard } from '~/guards/global/global.guard'; import { GlobalGuard } from '~/guards/global/global.guard';
import { UtilsService } from '~/services/utils.service'; import { UtilsService } from '~/services/utils.service';
import { Acl } from '~/middlewares/extract-ids/extract-ids.middleware'; import { Acl } from '~/middlewares/extract-ids/extract-ids.middleware';
@ -17,6 +22,11 @@ import { MetaApiLimiterGuard } from '~/guards/meta-api-limiter.guard';
import { PublicApiLimiterGuard } from '~/guards/public-api-limiter.guard'; import { PublicApiLimiterGuard } from '~/guards/public-api-limiter.guard';
import { TelemetryService } from '~/services/telemetry.service'; import { TelemetryService } from '~/services/telemetry.service';
import { NcRequest } from '~/interface/config'; import { NcRequest } from '~/interface/config';
import { Integration } from '~/models';
import { MetaTable, RootScopes } from '~/utils/globals';
import { NcError } from '~/helpers/catchError';
import { deepMerge } from '~/utils';
import Noco from '~/Noco';
@Controller() @Controller()
export class UtilsController { export class UtilsController {
@ -50,19 +60,69 @@ export class UtilsController {
scope: 'org', scope: 'org',
}) })
@HttpCode(200) @HttpCode(200)
async testConnection(@Body() body: any, @Req() _req: NcRequest) { async testConnection(@Body() body: any, @Req() req: NcRequest) {
body.pool = { body.pool = {
min: 0, min: 0,
max: 1, max: 1,
}; };
body.connection.ssl = validateAndExtractSSLProp( let config = { ...body };
body.connection,
body.sslUse, if (body.fk_integration_id) {
body.client, const integration = await Integration.get(
{
workspace_id: RootScopes.BYPASS,
},
body.fk_integration_id,
);
if (!integration || integration.type !== IntegrationsType.Database) {
NcError.integrationNotFound(body.fk_integration_id);
}
if (integration.is_private && integration.created_by !== req.user.id) {
NcError.forbidden('You do not have access to this integration');
}
if (!req.user.roles[OrgUserRoles.CREATOR]) {
// check if user have owner/creator role in any of the base in the workspace
const baseWithPermission = await Noco.ncMeta
.knex(MetaTable.PROJECT_USERS)
.innerJoin(
MetaTable.PROJECT,
`${MetaTable.PROJECT}.id`,
`${MetaTable.PROJECT_USERS}.base_id`,
)
.where(`${MetaTable.PROJECT_USERS}.fk_user_id`, req.user.id)
.where((qb) => {
qb.where(
`${MetaTable.PROJECT_USERS}.roles`,
ProjectRoles.OWNER,
).orWhere(`${MetaTable.PROJECT_USERS}.roles`, ProjectRoles.CREATOR);
})
.first();
if (!baseWithPermission)
NcError.forbidden('You do not have access to this integration');
}
config = await integration.getConfig();
deepMerge(config, body);
if (config?.connection?.database) {
config.connection.database = getTestDatabaseName(config);
}
}
if (config.connection?.ssl) {
config.connection.ssl = validateAndExtractSSLProp(
config.connection,
config.sslUse,
config.client,
); );
}
return await this.utilsService.testConnection({ body }); return await this.utilsService.testConnection({ body: config });
} }
@UseGuards(PublicApiLimiterGuard) @UseGuards(PublicApiLimiterGuard)

42
packages/nocodb/src/helpers/catchError.ts

@ -1,5 +1,6 @@
import { NcErrorType } from 'nocodb-sdk'; import { NcErrorType } from 'nocodb-sdk';
import { Logger } from '@nestjs/common'; import { Logger } from '@nestjs/common';
import type { BaseType, SourceType } from 'nocodb-sdk';
import type { ErrorObject } from 'ajv'; import type { ErrorObject } from 'ajv';
import { defaultLimitConfig } from '~/helpers/extractLimitAndOffset'; import { defaultLimitConfig } from '~/helpers/extractLimitAndOffset';
@ -479,6 +480,14 @@ const errorHelpers: {
message: (id: string) => `Source '${id}' not found`, message: (id: string) => `Source '${id}' not found`,
code: 404, code: 404,
}, },
[NcErrorType.INTEGRATION_NOT_FOUND]: {
message: (id: string) => `Connection '${id}' not found`,
code: 404,
},
[NcErrorType.INTEGRATION_LINKED_WITH_BASES]: {
message: (bases) => `Connection linked with following bases '${bases}'`,
code: 404,
},
[NcErrorType.TABLE_NOT_FOUND]: { [NcErrorType.TABLE_NOT_FOUND]: {
message: (id: string) => `Table '${id}' not found`, message: (id: string) => `Table '${id}' not found`,
code: 404, code: 404,
@ -833,4 +842,37 @@ export class NcError {
static sourceMetaReadOnly(name: string) { static sourceMetaReadOnly(name: string) {
NcError.forbidden(`Source '${name}' schema is read-only`); NcError.forbidden(`Source '${name}' schema is read-only`);
} }
static integrationNotFound(id: string, args?: NcErrorArgs) {
throw new NcBaseErrorv2(NcErrorType.INTEGRATION_NOT_FOUND, {
params: id,
...(args || {}),
});
}
static integrationLinkedWithMultiple(
bases: BaseType[],
sources: SourceType[],
args?: NcErrorArgs,
) {
throw new NcBaseErrorv2(NcErrorType.INTEGRATION_LINKED_WITH_BASES, {
params: bases.map((s) => s.title).join(', '),
details: {
bases: bases.map((b) => {
return {
id: b.id,
title: b.title,
};
}),
sources: sources.map((s) => {
return {
id: s.id,
base_id: s.base_id,
title: s.alias,
};
}),
},
...(args || {}),
});
}
} }

7
packages/nocodb/src/meta/meta.service.ts

@ -59,7 +59,7 @@ export class MetaService {
base_id: string, base_id: string,
target: string, target: string,
) { ) {
if (workspace_id === base_id) { if (workspace_id === base_id || base_id === RootScopes.WORKSPACE) {
return; return;
} }
@ -126,14 +126,13 @@ export class MetaService {
}); });
} }
} else { } else {
if (!base_id) { if (!base_id && base_id !== RootScopes.WORKSPACE) {
NcError.metaError({ NcError.metaError({
message: 'Base ID is required', message: 'Base ID is required',
sql: '', sql: '',
}); });
} }
if (base_id !== RootScopes.WORKSPACE) insertObj.base_id = base_id;
insertObj.base_id = base_id;
} }
await this.knexConnection(target).insert({ await this.knexConnection(target).insert({

4
packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts

@ -42,6 +42,7 @@ import * as nc_052_field_aggregation from '~/meta/migrations/v2/nc_052_field_agg
import * as nc_053_jobs from '~/meta/migrations/v2/nc_053_jobs'; import * as nc_053_jobs from '~/meta/migrations/v2/nc_053_jobs';
import * as nc_054_id_length from '~/meta/migrations/v2/nc_054_id_length'; import * as nc_054_id_length from '~/meta/migrations/v2/nc_054_id_length';
import * as nc_055_junction_pk from '~/meta/migrations/v2/nc_055_junction_pk'; import * as nc_055_junction_pk from '~/meta/migrations/v2/nc_055_junction_pk';
import * as nc_056_integration from '~/meta/migrations/v2/nc_056_integration';
// Create a custom migration source class // Create a custom migration source class
export default class XcMigrationSourcev2 { export default class XcMigrationSourcev2 {
@ -95,6 +96,7 @@ export default class XcMigrationSourcev2 {
'nc_053_jobs', 'nc_053_jobs',
'nc_054_id_length', 'nc_054_id_length',
'nc_055_junction_pk', 'nc_055_junction_pk',
'nc_056_integration',
]); ]);
} }
@ -192,6 +194,8 @@ export default class XcMigrationSourcev2 {
return nc_054_id_length; return nc_054_id_length;
case 'nc_055_junction_pk': case 'nc_055_junction_pk':
return nc_055_junction_pk; return nc_055_junction_pk;
case 'nc_056_integration':
return nc_056_integration;
} }
} }
} }

44
packages/nocodb/src/meta/migrations/v2/nc_042_integrations.ts

@ -0,0 +1,44 @@
import type { Knex } from 'knex';
import { MetaTable } from '~/utils/globals';
const up = async (knex: Knex) => {
await knex.schema.createTable(MetaTable.INTEGRATIONS, (table) => {
table.string('id', 20).primary();
table.string('fk_workspace_id', 20);
table.string('fk_user_id', 20);
table.string('title');
table.text('config');
table.string('type');
table.string('sub_type');
table.float('order');
table.boolean('deleted').defaultTo(false);
table.timestamps(true, true);
});
await knex.schema.alterTable(MetaTable.BASES, (table) => {
table.string('fk_integration_id', 20);
});
/*
Move MetaTable.BASES records to MetaTable.INTEGRATIONS
*/
};
const down = async (knex: Knex) => {
await knex.schema.dropTable(MetaTable.INTEGRATIONS);
await knex.schema.alterTable(MetaTable.BASES, (table) => {
table.dropColumn('fk_integration_id');
});
};
export { up, down };

105
packages/nocodb/src/meta/migrations/v2/nc_056_integration.ts

@ -0,0 +1,105 @@
import { IntegrationsType, ProjectRoles } from 'nocodb-sdk';
import { customAlphabet } from 'nanoid';
import type { Knex } from 'knex';
import { MetaTable } from '~/utils/globals';
const log = (message: string) => {
console.log(`nc_055_integration: ${message}`);
};
let hrTime = process.hrtime();
const logExecutionTime = (message: string) => {
const [seconds, nanoseconds] = process.hrtime(hrTime);
const elapsedSeconds = seconds + nanoseconds / 1e9;
log(`${message} in ${elapsedSeconds}s`);
};
const up = async (knex: Knex) => {
const nanoidv2 = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 14);
if (!(await knex.schema.hasTable(MetaTable.INTEGRATIONS))) {
await knex.schema.createTable(MetaTable.INTEGRATIONS, (table) => {
table.string('id', 20).primary();
table.string('title', 128);
table.text('config');
table.text('meta');
table.string('type', 20).index();
table.string('sub_type', 20);
table.boolean('is_private').defaultTo(false);
table.boolean('deleted').defaultTo(false);
table.string('created_by', 20).index();
table.float('order');
table.timestamps(true, true);
});
}
if (!(await knex.schema.hasColumn(MetaTable.BASES, 'fk_integration_id'))) {
await knex.schema.alterTable(MetaTable.BASES, (table) => {
table.string('fk_integration_id', 20).index();
});
}
hrTime = process.hrtime();
// get all external sources, add them to integrations table and map back to bases
const sources = await knex(MetaTable.BASES)
.select(`${MetaTable.BASES}.*`)
.select(`${MetaTable.PROJECT_USERS}.fk_user_id as created_by`)
.innerJoin(
MetaTable.PROJECT,
`${MetaTable.BASES}.base_id`,
`${MetaTable.PROJECT}.id`,
)
.where((qb) =>
qb
.where(`${MetaTable.BASES}.is_meta`, false)
.orWhereNull(`${MetaTable.BASES}.is_meta`),
)
.leftJoin(MetaTable.PROJECT_USERS, (qb) => {
qb.on(
`${MetaTable.PROJECT}.id`,
`${MetaTable.PROJECT_USERS}.base_id`,
).andOn(
`${MetaTable.PROJECT_USERS}.roles`,
knex.raw('?', [ProjectRoles.OWNER]),
);
});
logExecutionTime('Fetched external sources');
hrTime = process.hrtime();
for (const source of sources) {
const integrationId = `int${nanoidv2()}`;
const integration = {
title: source.alias,
id: integrationId,
config: source.config,
type: IntegrationsType.Database,
sub_type: source.type,
created_by: source.created_by,
created_at: source.created_at,
updated_at: source.updated_at,
};
await knex(MetaTable.INTEGRATIONS).insert(integration);
await knex(MetaTable.BASES).where('id', source.id).update({
fk_integration_id: integrationId,
});
}
logExecutionTime('Migrated external sources');
};
const down = async (knex: Knex) => {
await knex.schema.dropTable(MetaTable.INTEGRATIONS);
await knex.schema.alterTable(MetaTable.BASES, (table) => {
table.dropColumn('fk_integration_id');
});
};
export { up, down };

45
packages/nocodb/src/middlewares/extract-ids/extract-ids.middleware.ts

@ -354,6 +354,20 @@ export class ExtractIdsMiddleware implements NestMiddleware, CanActivate {
req.ncBaseId = req.query.base_id; req.ncBaseId = req.query.base_id;
} }
// if integration list endpoint is called with baseId, then extract baseId if it's valid
if (
req.route.path === '/api/v2/meta/integrations' &&
req.method === 'GET' &&
req.query.baseId
) {
// check if baseId is valid and under the workspace
const base = await Base.get(context, req.query.baseId);
if (!base) {
NcError.baseNotFound(req.query.baseId);
}
req.ncBaseId = base.id;
}
req.context = { req.context = {
workspace_id: null, workspace_id: null,
base_id: req.ncBaseId, base_id: req.ncBaseId,
@ -402,6 +416,10 @@ export class AclMiddleware implements NestInterceptor {
); );
const scope = this.reflector.get<string>('scope', context.getHandler()); const scope = this.reflector.get<string>('scope', context.getHandler());
const extendedScope = this.reflector.get<string>(
'extendedScope',
context.getHandler(),
);
const req = context.switchToHttp().getRequest(); const req = context.switchToHttp().getRequest();
@ -426,6 +444,10 @@ export class AclMiddleware implements NestInterceptor {
const roles: Record<string, boolean> = extractRolesObj(userScopeRole); const roles: Record<string, boolean> = extractRolesObj(userScopeRole);
// extendedScope is used to allow access based on extended scope in which permission is prefixed with scope name and separated by underscore
const extendedScopeRoles =
extendedScope && getUserRoleForScope(req.user, extendedScope);
if (req?.user?.is_api_token && blockApiTokenAccess) { if (req?.user?.is_api_token && blockApiTokenAccess) {
NcError.apiTokenNotAllowed(); NcError.apiTokenNotAllowed();
} }
@ -448,7 +470,7 @@ export class AclMiddleware implements NestInterceptor {
const isAllowed = const isAllowed =
roles && roles &&
Object.entries(roles).some(([name, hasRole]) => { (Object.entries(roles).some(([name, hasRole]) => {
return ( return (
hasRole && hasRole &&
rolePermissions[name] && rolePermissions[name] &&
@ -458,7 +480,22 @@ export class AclMiddleware implements NestInterceptor {
(rolePermissions[name].include && (rolePermissions[name].include &&
rolePermissions[name].include[permissionName])) rolePermissions[name].include[permissionName]))
); );
}); }) ||
// extendedScope is used to allow access based on extended scope in which permission is prefixed with scope name and separated by underscore
(extendedScopeRoles &&
Object.entries(extendedScopeRoles).some(([name, hasRole]) => {
return (
hasRole &&
rolePermissions[name] &&
(rolePermissions[name] === '*' ||
(rolePermissions[name].exclude &&
!rolePermissions[name].exclude[
scope + '_' + permissionName
]) ||
(rolePermissions[name].include &&
rolePermissions[name].include[scope + '_' + permissionName]))
);
})));
if (!isAllowed) { if (!isAllowed) {
NcError.forbidden( NcError.forbidden(
`${permissionName} - ${getRolesLabels( `${permissionName} - ${getRolesLabels(
@ -525,15 +562,19 @@ export const Acl =
scope = 'base', scope = 'base',
allowedRoles, allowedRoles,
blockApiTokenAccess, blockApiTokenAccess,
extendedScope,
}: { }: {
scope?: string; scope?: string;
allowedRoles?: (OrgUserRoles | string)[]; allowedRoles?: (OrgUserRoles | string)[];
blockApiTokenAccess?: boolean; blockApiTokenAccess?: boolean;
extendedScope?: string;
} = {}, } = {},
) => ) =>
(target: any, key?: string, descriptor?: PropertyDescriptor) => { (target: any, key?: string, descriptor?: PropertyDescriptor) => {
SetMetadata('permission', permissionName)(target, key, descriptor); SetMetadata('permission', permissionName)(target, key, descriptor);
SetMetadata('scope', scope)(target, key, descriptor); SetMetadata('scope', scope)(target, key, descriptor);
// extendedScope is used to allow access based on extended scope in which permission is prefixed with scope name and separated by underscore
SetMetadata('extendedScope', extendedScope)(target, key, descriptor);
SetMetadata('allowedRoles', allowedRoles)(target, key, descriptor); SetMetadata('allowedRoles', allowedRoles)(target, key, descriptor);
SetMetadata('blockApiTokenAccess', blockApiTokenAccess)( SetMetadata('blockApiTokenAccess', blockApiTokenAccess)(
target, target,

35
packages/nocodb/src/models/Base.ts

@ -94,14 +94,16 @@ export default class Base implements BaseType {
} }
await NocoCache.del(CacheScope.INSTANCE_META); await NocoCache.del(CacheScope.INSTANCE_META);
return this.getWithInfo(context, baseId, ncMeta).then(async (base) => { return this.getWithInfo(context, baseId, true, ncMeta).then(
async (base) => {
await NocoCache.appendToList( await NocoCache.appendToList(
CacheScope.PROJECT, CacheScope.PROJECT,
[], [],
`${CacheScope.PROJECT}:${baseId}`, `${CacheScope.PROJECT}:${baseId}`,
); );
return base; return base;
}); },
);
} }
static async list( static async list(
@ -153,7 +155,7 @@ export default class Base implements BaseType {
) )
.map((p) => { .map((p) => {
const base = this.castType(p); const base = this.castType(p);
promises.push(base.getSources(ncMeta)); promises.push(base.getSources(false, ncMeta));
return base; return base;
}); });
@ -196,19 +198,30 @@ export default class Base implements BaseType {
return this.castType(baseData); return this.castType(baseData);
} }
async getSources(ncMeta = Noco.ncMeta): Promise<Source[]> { async getSources(
return (this.sources = await Source.list( includeConfig = true,
ncMeta = Noco.ncMeta,
): Promise<Source[]> {
const sources = await Source.list(
{ workspace_id: this.fk_workspace_id, base_id: this.id }, { workspace_id: this.fk_workspace_id, base_id: this.id },
{ baseId: this.id }, { baseId: this.id },
ncMeta, ncMeta,
)); );
this.sources = sources;
if (!includeConfig) {
sources.forEach((s) => {
s.config = undefined;
s.integration_config = undefined;
});
}
return sources;
} }
// todo: hide credentials
// @ts-ignore // @ts-ignore
static async getWithInfo( static async getWithInfo(
context: NcContext, context: NcContext,
baseId: string, baseId: string,
includeConfig = true,
ncMeta = Noco.ncMeta, ncMeta = Noco.ncMeta,
): Promise<Base> { ): Promise<Base> {
let baseData = let baseData =
@ -246,7 +259,7 @@ export default class Base implements BaseType {
if (baseData) { if (baseData) {
const base = this.castType(baseData); const base = this.castType(baseData);
await base.getSources(ncMeta); await base.getSources(includeConfig, ncMeta);
return base; return base;
} }
@ -459,7 +472,7 @@ export default class Base implements BaseType {
) { ) {
const base = await this.getByTitle(context, title, ncMeta); const base = await this.getByTitle(context, title, ncMeta);
if (base) { if (base) {
await base.getSources(ncMeta); await base.getSources(false, ncMeta);
} }
return base; return base;
@ -562,7 +575,7 @@ export default class Base implements BaseType {
if (base) { if (base) {
// parse meta // parse meta
base.meta = parseMetaProp(base); base.meta = parseMetaProp(base);
await base.getSources(ncMeta); await base.getSources(false, ncMeta);
} }
return base; return base;
@ -575,7 +588,7 @@ export default class Base implements BaseType {
) { ) {
const base = await this.get(context, baseId, ncMeta); const base = await this.get(context, baseId, ncMeta);
if (base) { if (base) {
const sources = await base.getSources(ncMeta); const sources = await base.getSources(false, ncMeta);
for (const source of sources) { for (const source of sources) {
await NcConnectionMgrv2.deleteAwait(source); await NcConnectionMgrv2.deleteAwait(source);
} }

2
packages/nocodb/src/models/BaseUser.ts

@ -452,7 +452,7 @@ export default class BaseUser {
.map((p) => { .map((p) => {
const base = Base.castType(p); const base = Base.castType(p);
base.meta = parseMetaProp(base); base.meta = parseMetaProp(base);
promises.push(base.getSources(ncMeta)); promises.push(base.getSources(false, ncMeta));
return base; return base;
}); });

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

Loading…
Cancel
Save