Compare commits

..

5 Commits

Author SHA1 Message Date
github-actions[bot]
61a8c6930f chore: update versions (#2962)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@2.0.7

# @nhost/dashboard

## 1.30.0

### Minor Changes

- 50441a8: feat: add ui for project autoscaler settings and run services
autoscaler settings

## 1.29.0

### Minor Changes

-   55d8bb5: feat: integrate turnstile for signup verification
-   2a2e54c: fix: update docs url in run services form tooltip
- 18f942f: fix: display long error messages in error toast without
overflow

### Patch Changes

-   @nhost/react-apollo@13.0.0
-   @nhost/nextjs@2.1.22

## 1.28.2

### Patch Changes

- 52a38fe: chore: update dependencies to address security
vulnerabilities
-   Updated dependencies [52a38fe]
    -   @nhost/nextjs@2.1.21

## 1.28.1

### Patch Changes

-   9735fa2: chore: remove broken link

## 1.28.0

### Minor Changes

- 526183a: feat: allow filtering users in "make request as" in graphql
section
-   be3b85b: feat: add conceal errors toggle on auth settings page

### Patch Changes

- 35a2f12: fix: prevent run service details from opening when attempting
to delete
    -   @nhost/react-apollo@12.0.6
    -   @nhost/nextjs@2.1.20

## 1.27.0

### Minor Changes

-   a7cd02c: fix: resolve rate limit query

## 1.26.0

### Minor Changes

-   3773ad7: chore: update pricing information
- b63250d: fix: not allow run service creation form resubmission while
creating a run service
-   a44a1d4: feat: add rate limits settings page

### Patch Changes

-   @nhost/react-apollo@12.0.5
-   @nhost/nextjs@2.1.19

## 1.25.0

### Minor Changes

- d1ceede: feat: add setting to migrate postgres major and/or minor
versions
- e5d3d1a: fix: allow manually typing column for custom check in
database row permissions

### Patch Changes

-   @nhost/react-apollo@12.0.4
-   @nhost/nextjs@2.1.18

## 1.24.1

### Patch Changes

- 49f2e55: fix: use service subdomain in service form and service
details dialog
- 598b988: fix: use current project subdomain in ServiceDetailsDialog
component

## 1.24.0

### Minor Changes

-   abb24af: chore: add redirect to support page when project is locked
- 18a6455: feat: show contact us info and locked reason when project is
locked

### Patch Changes

-   e31eefa: fix: include ingresses field when updating run services

## 1.23.0

### Minor Changes

-   33284d3: fix: don't show double scrollbar in configuration editor

### Patch Changes

-   @nhost/react-apollo@12.0.3
-   @nhost/nextjs@2.1.17

## 1.22.0

### Minor Changes

-   998c037: fix: align drop-down list in select component
- 807b8c0: fix: show city name in region selection for project creation

## 1.21.0

### Minor Changes

- a2efeed: fix: improve project health error handling, add unknown state
and polling interval for health state

## 1.20.0

### Minor Changes

- 8ea4210: fix: error toasts can be closed individually, instead of
dismissing all toasts at once
- 58919ba: chore: add blink animation when project health service is
updating

## 1.19.0

### Minor Changes

- b519862: fix: get configuration in configuration editor using local
development environment

## 1.18.0

### Minor Changes

- 502abad: feat: add services health checks indicators to the overview
page
-   b3ff6ad: chore: update title text on service status modal
- dbadf59: feat: add project configuration TOML editor to the settings
page

## 1.17.0

### Minor Changes

- 77fba27: fix: postgres version validation when activating ai in ai
settings page
-   ac6d1b6: feat: use name instead of awsName

## 1.16.3

### Patch Changes

- 87a37cf: fix: remove unnecessary isPlatform check from verify button
disable logic on custom domains
    -   @nhost/react-apollo@12.0.2
    -   @nhost/nextjs@2.1.16

## 1.16.2

### Patch Changes

- a9413af: fix: update `GetAllWorkspacesAndProjects` query polling to
use exponential backoff
    -   @nhost/react-apollo@12.0.1
    -   @nhost/nextjs@2.1.15

## 1.16.1

### Patch Changes

-   @nhost/react-apollo@12.0.0
-   @nhost/nextjs@2.1.14

## 1.16.0

### Minor Changes

- c6d5c5c: feat: add toggle switch to enable/disable public access in
the database settings

## 1.15.2

### Patch Changes

-   @nhost/react-apollo@11.0.4
-   @nhost/nextjs@2.1.13

## 1.15.1

### Patch Changes

-   @nhost/react-apollo@11.0.3
-   @nhost/nextjs@2.1.12

## 1.15.0

### Minor Changes

-   a7bde37: feat: send metadata in the edit form

### Patch Changes

- 1bc615b: feat: improve error message handling in `ErrorToast`
component
    -   @nhost/react-apollo@11.0.2
    -   @nhost/nextjs@2.1.11

## 1.14.0

### Minor Changes

-   a448d7d: feat: allow configuring postmark and delete SMTP settings

## 1.13.3

### Patch Changes

-   5924bc3: fix: include password in `GetSmtpSettings` query
- c5ad634: fix: resolved an issue where one-click install links were
broken on Safari
- 7278991: fix: update graphql auto-embeddings configuration to use
String type for model field

## 1.13.2

### Patch Changes

-   026f84f: fix: use configuration server URL from environment variable

## 1.13.1

### Patch Changes

-   7e9a2ce: fix: resolve issue where run services form fails to open

## 1.13.0

### Minor Changes

-   dd5d262: feat: add model field to the auto-embeddings form
- 09962be: feat: enable settings and run services when running the
dashboard locally
- 9cdecb6: feat: enable users to update their email address from the
account settings page

## 1.12.2

### Patch Changes

-   c195c51: fix: send email upon signin for unverified users

## 1.12.1

### Patch Changes

- 93ebdf8: fix: use service urls when initilizaing NhostClient running
local dashboard
    -   @nhost/react-apollo@11.0.1
    -   @nhost/nextjs@2.1.10

## 1.12.0

### Minor Changes

- f242e4b: feat: add connect with github to the user's account settings
-   768ca17: chore: update dependencies
- d62bd0f: fix: "Track this" option within the SQL editor now correctly
updates the metadata
- 91c2bb6: feat: refactor sign-in and sign-up pages to enforce email
verification

### Patch Changes

-   943831f: fix: resolve an error toast issue when unpausing a project
-   Updated dependencies [768ca17]
    -   @nhost/react-apollo@11.0.0
    -   @nhost/nextjs@2.1.9

## 1.11.2

### Patch Changes

-   @nhost/react-apollo@10.0.2
-   @nhost/nextjs@2.1.8

## 1.11.1

### Patch Changes

-   981404f: fix: set default value for healthCheck field validation

## 1.11.0

### Minor Changes

- 7789469: chore: upgrade dependency `@graphql-codegen/cli` to `5.0.2`
to address vulnerability
- 6c11b75: feat: add update user displayName section in account settings

### Patch Changes

-   @nhost/react-apollo@10.0.1
-   @nhost/nextjs@2.1.7

## 1.10.0

### Minor Changes

-   49a80c2: chore: update dependencies
-   150c04a: feat: add healthcheck config to run services

### Patch Changes

- e03f141: fix: allow insert, update and delete on tables in `auth` and
`storage` schemas
- 28676f4: feat: add min postgres version check to enable the ai service
-   Updated dependencies [49a80c2]
    -   @nhost/react-apollo@10.0.0
    -   @nhost/nextjs@2.1.6

## 1.9.0

### Minor Changes

-   d86e5c9: feat: add support for filtering the logs using a RegExp

## 1.8.3

### Patch Changes

-   @nhost/react-apollo@9.0.3
-   @nhost/nextjs@2.1.5

## 1.8.2

### Patch Changes

- 6df4f02: fix: use custom error toast and show correct message when
sending an invite

## 1.8.1

### Patch Changes

-   @nhost/react-apollo@9.0.2
-   @nhost/nextjs@2.1.4

## 1.8.0

### Minor Changes

- 713d53c: feat: add catch-all route for workspace/project - useful for
documentation

### Patch Changes

-   3db2999: fix: refresh table list after running SQL using the editor
- 3c4dd55: fix: handle `Error` objects properly in the `ErrorToast`
component
- 92b434e: fix: resolve an issue where the checkbox in the data-grid
header did not select all rows
    -   @nhost/react-apollo@9.0.1
    -   @nhost/nextjs@2.1.3

## 1.7.0

### Minor Changes

-   0d8d0eb: Update docs and dashboard references

## 1.6.9

### Patch Changes

-   @nhost/react-apollo@9.0.0
-   @nhost/nextjs@2.1.2

## 1.6.8

### Patch Changes

-   @nhost/react-apollo@8.0.1
-   @nhost/nextjs@2.1.1

## 1.6.7

### Patch Changes

-   5ef5189: fix: update `@apollo/client` to `3.9.4` to fix a cache bug

## 1.6.6

### Patch Changes

-   3ba485e: fix: added discord.com to connect-src
-   e5bab6a: chore: update dependencies
-   Updated dependencies [b19ffed]
-   Updated dependencies [e5bab6a]
    -   @nhost/nextjs@2.1.0
    -   @nhost/react-apollo@8.0.0

## 1.6.5

### Patch Changes

- ba73bb4: fix: update ErrorToast component to show the internal graphql
error
- d5337ff: fix: utilize accumulator in the creation of validation schema
within data grid utils

## 1.6.4

### Patch Changes

-   7c2a1c2: feat: show error and debug info in the error toast

## 1.6.3

### Patch Changes

-   6b8aad5: fix: add bare nhost.run to CSP

## 1.6.2

### Patch Changes

-   b18edc0: feat: added CSP and X-Frame-Options

## 1.6.1

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit
- 3b8473b: chore: update turbo to `1.11.3` and pnpm to `8.10.5` in
Dockerfile
-   Updated dependencies [8d91f71]
    -   @nhost/react-apollo@7.0.2
    -   @nhost/nextjs@2.0.2

## 1.6.0

### Minor Changes

-   3ff1c2b53: fix: show upgrade option for pro projects

## 1.5.0

### Minor Changes

-   c2ef17c0a: feat: add support for new Team plan

## 1.4.0

### Minor Changes

-   7883bbcbd: feat: don't show deprecated plans
- 44be6dc0a: feat: set redirectTo during sign-in to support preview
environments

### Patch Changes

- 3c3594898: fix: allow access to graphite when configured running in
local dashboard
-   32c246b7a: chore: update docs icon

## 1.3.2

### Patch Changes

-   174b4165b: chore: use env variables when running graphql codegen
-   7c977e714: chore: change `Allowed Roles` to `Default Allowed Roles`
-   46f028b9f: fix: remove hardcoded ai version setting

## 1.3.1

### Patch Changes

- af33c21d1: chore: remove backendUrl deprecation notice and remove all
references to `providersUpdated`

## 1.3.0

### Minor Changes

-   04784d880: Fix graphite's default version

## 1.2.0

### Minor Changes

-   5733162ed: feat: add settings and ui for graphite

## 1.1.0

### Minor Changes

-   e2b79b5ec: chore: remove sharp from deps

## 1.0.1

### Patch Changes

-   @nhost/react-apollo@7.0.1
-   @nhost/nextjs@2.0.1

## 1.0.0

### Major Changes

- bc9eff6e4: chore: remove support for using backendUrl when
instantiating the Nhost client

### Patch Changes

-   Updated dependencies [bc9eff6e4]
    -   @nhost/nextjs@2.0.0
    -   @nhost/react-apollo@7.0.0

## 0.21.1

### Patch Changes

-   97ced73a3: fix(dashboard): prevent dashboard from resolving secrets

## 0.21.0

### Minor Changes

- ed1a8d458: Update alert message on increasing PostgreSQL's volume
capacity
-   2e2248fd4: feat(dashboard): add SQL editor

## 0.20.28

### Patch Changes

-   7c2c31082: feat: add support for users to delete their account
    -   @nhost/react-apollo@6.0.1
    -   @nhost/nextjs@1.13.40

## 0.20.27

### Patch Changes

- fa79b7709: chore(dashboard): tweaks and fixes to the service form and
dialog
-   8df84d782: fix(dashboard): allow resetting custom domains
    -   @nhost/react-apollo@6.0.0
    -   @nhost/nextjs@1.13.39

## 0.20.26

### Patch Changes

- 331ba0376: feat(dashboard): add postgres storage capacity modifier in
the settings
-   b7f801874: feat(dashboard): add new settings page for custom domains

## 0.20.25

### Patch Changes

-   @nhost/react-apollo@5.0.38

## 0.20.24

### Patch Changes

-   e10389ecf: fix(dashboard): disable run tab when developing locally
    -   @nhost/react-apollo@5.0.37

## 0.20.23

### Patch Changes

-   c01568a7d: chore(dashboard): show alert to update oauth providers

## 0.20.22

### Patch Changes

-   c3efb7ec8: feat(dashboard): query latest announcement from platform

## 0.20.21

### Patch Changes

-   3e46d3873: chore: update link to node18 announcement

## 0.20.20

### Patch Changes

-   @nhost/react-apollo@5.0.36
-   @nhost/nextjs@1.13.38

## 0.20.19

### Patch Changes

-   75c4c8ae3: feat(dashboard): make env value input multiline

## 0.20.18

### Patch Changes

- 425d485f8: fix(dashboard): make sure dedicated resources pricing
follows total resources

## 0.20.17

### Patch Changes

-   ae324f67f: fix(dashboard): remove unused graphql fields

## 0.20.16

### Patch Changes

-   df5b4302c: chore(dashboard): remove run feature flag
- bf4a1f6c2: feat(dashboard): fetch auth, postgres, hasura and storage
versions from dashboard
- 34fc08ca7: fix(dashboard/run): show correct private registry in
service details
-   885d10620: chore(dashboard): change feedback to contact us

## 0.20.15

### Patch Changes

- ed16c8b5d: feat(run): add a confirmation dialog when deleting a run
service
- 216990888: fix(run): center loading indicator when selecting a project

## 0.20.14

### Patch Changes

-   9fbea9787: feat: add node18 announcement

## 0.20.13

### Patch Changes

- e84acf469: fix(run): handle subdomain undefined error when creating a
new service

## 0.20.12

### Patch Changes

-   b7c799d62: feat(run): add dialog to copy registry and URLs

## 0.20.11

### Patch Changes

-   8903e6abd: fix(dashboard): show correct egress limit in usage stats

## 0.20.10

### Patch Changes

- 666a75a23: feat(dashboard): add functions execution time and egress
volume to usage stats

## 0.20.9

### Patch Changes

-   5e1e80aa8: fix(dashboard): show correct locales in user details
    -   @nhost/react-apollo@5.0.35
    -   @nhost/nextjs@1.13.37

## 0.20.8

### Patch Changes

-   @nhost/react-apollo@5.0.34
-   @nhost/nextjs@1.13.36

## 0.20.7

### Patch Changes

-   4a7ede11e: fix: distinguish files that were not uploaded
- 202b64723: feat(nhost-run): add support for one-click-install run
services
- 074a0fa11: feat(dashboard): add settings toggle to enable/disable
antivirus
    -   @nhost/react-apollo@5.0.33
    -   @nhost/nextjs@1.13.35

## 0.20.6

### Patch Changes

-   b20761e97: feat(services): add pricing info and confirmation dialog
-   90df6d81d: fix(services): handle null values when editing a service
-   aa8508467: fix: query service logs correctly
    feat: enable multiline support for environment value input

## 0.20.5

### Patch Changes

-   8d7f84b8d: fix: make announcement adapt to theme

## 0.20.4

### Patch Changes

-   3b75bfce2: fix: make announcement close properly
- f49819075: fix: show correct values when dedicated resources are
disabled

## 0.20.3

### Patch Changes

-   e643bd362: fix(services): fix errors when config is null
-   bcdab66bf: feat: add annoucement for nhost run
-   f967a2e59: added note about storage not being able to be downsized
-   311c7756d: chore(services): consistent naming for compute

## 0.20.2

### Patch Changes

-   9073182d5: chore(dashboard): bump `turbo` to 1.10.11
-   ece717d6e: feat(logs): show services in the logs page
- 82b335311: feat(metrics): change grafana link to point to the
dashboards
- b135ef695: fix(services): set command as optional and set min replicas
to 0

## 0.20.1

### Patch Changes

-   3d5c34f4c: fix(auth): fix users pagination limit

## 0.20.0

### Minor Changes

-   c99d117d1: feat(services): add support for custom services

## 0.19.2

### Patch Changes

-   face99ccd: chore(deps): bump turbo version
-   cfe527307: style: tweak pull config warning in dark mode
- a9d7da8af: chore(deps): update dependency @types/pluralize to ^0.0.30
-   9aa4371ef: chore: add hasura-auth version 0.21.2
- d14e112bf: chore(deps): update dependency prettier-plugin-tailwindcss
to ^0.4.0
-   d3e8bb94a: chore(deps): update dependency vite-plugin-dts to v3

## 0.19.1

### Patch Changes

-   @nhost/react-apollo@5.0.32
-   @nhost/nextjs@1.13.34

## 0.19.0

### Minor Changes

- 9c61c69a7: chore(dashboard):add postgres 14.6-20230705-1 to the
version selector

### Patch Changes

-   47bda15ff: feat(settings): add warning to pull config

## 0.18.0

### Minor Changes

- ee0b9b8ed: chore(dashboard):add hasura v2.28.2 and v2.29.0 to the
version selector

## 0.17.20

### Patch Changes

-   @nhost/react-apollo@5.0.31
-   @nhost/nextjs@1.13.33

## 0.17.19

### Patch Changes

-   f866120a6: fix(users): use the password length from the config

## 0.17.18

### Patch Changes

-   @nhost/react-apollo@5.0.30
-   @nhost/nextjs@1.13.32

## 0.17.17

### Patch Changes

-   ea7b102c0: fix(pat): highlight expired tokens

## 0.17.16

### Patch Changes

- b3b64a3b7: chore(deps): bump `@types/react` to `v18.2.14` and
`@types/react-dom` to `v18.2.6`
-   32b221f94: chore(deps): bump `graphiql` to `v3`
-   3a56c12df: chore(deps): bump `turbo` to `v1.10.6`
-   Updated dependencies [b3b64a3b7]
    -   @nhost/react-apollo@5.0.29
    -   @nhost/nextjs@1.13.31

## 0.17.15

### Patch Changes

-   f41fdc12a: chore(deps): bump `turbo` to `1.10.5`
-   6199c1c55: fix(projects): don't redirect to 404 page
-   Updated dependencies [07a45fde0]
    -   @nhost/react-apollo@5.0.28
    -   @nhost/nextjs@1.13.30

## 0.17.14

### Patch Changes

- 80b22724d: chore(deps): bump `@types/react` to `v18.2.13`,
`@types/react-dom` to `v18.2.6` and `@storybook/testing-library` to
`v0.2.0`

## 0.17.13

### Patch Changes

-   cc02902cb: chore(docs): update environment variable documentation

## 0.17.12

### Patch Changes

-   660d339e1: fix(storybook): don't break storybook
-   660d339e1: fix(tests): prevent warnings during tests
    -   @nhost/react-apollo@5.0.27
    -   @nhost/nextjs@1.13.29

## 0.17.11

### Patch Changes

- bd4d0c270: chore(dashboard):add postgres 14.6-20230613-1 to the
version selector

## 0.17.10

### Patch Changes

-   c8c2a10b2: fix(database): don't break the password reset flow
- e70b45498: chore(deps): bump `@types/react` to `v18.2.12` and
`@types/react-dom` to `v18.2.5`

## 0.17.9

### Patch Changes

- 842055099: chore(deps): bump `turbo` to `v1.10.3` and `pnpm` to
`v8.6.2`
- fd12aa0a8: chore(projects): remove the postgres password input from
the project creation screen
-   022b76e78: chore(deps): bump `@types/react` to `v18.2.11`
-   3555ab2b7: chore(deps): bump `vitest` monorepo to `v0.32.0`
-   c43e54922: feat(backups): add download button to backups

## 0.17.8

### Patch Changes

-   d0457fe5c: feat(settings): improve the dashboard and config parity
    -   @nhost/react-apollo@5.0.26
    -   @nhost/nextjs@1.13.28

## 0.17.7

### Patch Changes

-   4f0368b95: fix(account): don't break account settings page

## 0.17.6

### Patch Changes

- 64a8f41d0: chore(resources): lower the maximum allowed resources per
service

## 0.17.5

### Patch Changes

-   @nhost/react-apollo@5.0.25
-   @nhost/nextjs@1.13.27

## 0.17.4

### Patch Changes

- 9b1d0f7a5: fix(deployments): use correct timestamp for deployment
details
-   6d2963ffa: chore(deps): bump `@types/react` to `v18.2.8`
- 8871267b9: chore(deps): downgrade `pnpm` to `v8.5.1` because of no
Turborepo support

## 0.17.3

### Patch Changes

-   01eeef9de: chore(misc): under the hood improvements
- 21e13db05: chore(deps): bump `@types/react` to `v18.2.7` and `turbo`
to `v1.10.1`
- f16433ae6: chore(secrets): allow empty secrets and environment
variables
-   aa3c62989: chore(cli): bump Nhost CLI version to v1.0
    -   @nhost/react-apollo@5.0.24
    -   @nhost/nextjs@1.13.26

## 0.17.2

### Patch Changes

-   88a4983f: chore(misc): under the hood improvements

## 0.17.1

### Patch Changes

-   9b0d4dde: feat(secrets): enable secrets

## 0.17.0

### Minor Changes

-   15d84a19: Add postgres 14.6-20230525

## 0.16.14

### Patch Changes

-   4c626174: chore: updated import paths, improved directory structure
-   cc047b71: chore(deps): bump `@fontsource` monorepo to `v5.0.0`
-   99edd012: feat(account): add support for personal access tokens

## 0.16.13

### Patch Changes

-   78c7109c: feat(settings): allow selecting service versions

## 0.16.12

### Patch Changes

- 399009d6: fix(gql): don't enter an infinite loop when fetching remote
app data
- 329e5a91: fix(deployments): use the same sorting of deployments
everywhere
- 6d559d6e: chore(settings): add under the hood improvements to the
settings page
- 12eb236c: chore(deps): bump `prettier-plugin-tailwindcss` to `v0.3.0`
-   f9b81a2a: chore(deps): bump `turbo` to `v1.9.8`
-   1345741b: fix(projects): don't redirect to 404 on project creation
-   Updated dependencies [7fea29a8]
    -   @nhost/react-apollo@5.0.23
    -   @nhost/nextjs@1.13.25

## 0.16.11

### Patch Changes

- 1230b722: fix(projects): don't redirect to 404 on when the project is
renamed
    -   @nhost/react-apollo@5.0.22
    -   @nhost/nextjs@1.13.24

## 0.16.10

### Patch Changes

-   Updated dependencies [da03bf39]
    -   @nhost/react-apollo@5.0.21
    -   @nhost/nextjs@1.13.23

## 0.16.9

### Patch Changes

- 349aac36: fix(settings): use region domain when constructing the
postgres connection string

## 0.16.8

### Patch Changes

- 20fb69fa: chore(projects): change the way how API URLs are constructed

## 0.16.7

### Patch Changes

- 49f9b837: chore(docker): bump `pnpm` to `v8.4.0` and `turbo` to
`v1.9.3`
- 3f478a4e: chore(deps): bump `vitest` to `v0.31.0`, `@types/react` to
`v18.2.6` and `@types/react-dom` to `v18.2.4`

## 0.16.6

### Patch Changes

- d926f156: fix(projects): redirect to 404 when an invalid project is
opened
- 49b99728: fix(projects): disable features for non-owner members of
workspaces

## 0.16.5

### Patch Changes

-   12e2855f: chore(deps): bump `jsdom` to v22
-   e4972b83: feat(metrics): add Grafana page

## 0.16.4

### Patch Changes

- 3f396a9e: fix(projects): unpause after upgrading a paused project to
pro
- 3f396a9e: fix(projects): don't redirect to 404 page after project
creation

## 0.16.3

### Patch Changes

-   Updated dependencies [90c60311]
    -   @nhost/react-apollo@5.0.20
    -   @nhost/nextjs@1.13.22

## 0.16.2

### Patch Changes

-   0f34f0c6: fix(projects): disallow downgrading to free plan
- 8da291ad: chore(deps): bump `@types/react` to v18.2.0 and
`@types/react-dom` to v18.2.1

## 0.16.1

### Patch Changes

- adc828a5: fix(gql): don't enter an infinite loop when fetching remote
app data

## 0.16.0

### Minor Changes

-   2fb1145f: feat(compute): add support for replicas

### Patch Changes

- d8ceccec: chore(env): remove deprecated `NHOST_BACKEND_URL`
environment variable

## 0.15.2

### Patch Changes

-   84b84ab7: fix(projects): filter projects by workspace

## 0.15.1

### Patch Changes

-   2faf7907: chore(deps): bump `graphql-request` to v6
-   f1b5a944: chore(deps): bump `@vitejs/plugin-react` to v4
-   7f1785ac: chore(deps): bump `@types/react` to v18.0.37
    -   @nhost/react-apollo@5.0.19

## 0.15.0

### Minor Changes

-   85889ee8: feat(dashboard): add Compute management to the settings

## 0.14.8

### Patch Changes

-   668c8771: chore(dialogs): unify dialog management of payment dialogs

## 0.14.7

### Patch Changes

-   d4ccc656: chore: cleanup unused code
    -   @nhost/react-apollo@5.0.18
    -   @nhost/nextjs@1.13.21

## 0.14.6

### Patch Changes

-   b299cfc9: chore(deps): bump `vitest` to v0.30.0
-   411cb65b: chore(projects): refactor workspace and project hooks
- 43b1b144: chore(deps): bump `@types/react` to v18.0.34 and
`@types/react-dom` to v18.0.11
-   Updated dependencies [43b1b144]
    -   @nhost/react-apollo@5.0.17
    -   @nhost/nextjs@1.13.20

## 0.14.5

### Patch Changes

-   ba0d57ee: fix(i18n): revert i18n library
-   3328ed05: feat(projects): improve overview when there is an error

## 0.14.4

### Patch Changes

-   5e0920ba: chore(deps): bump `next-seo` to v6
-   706c9dc3: chore(deps): bump `@types/react` to 18.0.33
-   99f8f6b3: feat(metrics): show metrics on the overview

## 0.14.3

### Patch Changes

-   @nhost/react-apollo@5.0.16

## 0.14.2

### Patch Changes

-   3cb67300: fix(logs): don't break UI when clearing time picker
-   7453bf3b: feat(projects): show project creator info
-   c166dad0: chore(tests): improve auth page tests
-   6a290bb2: chore(deps): bump `@types/react` to 18.0.32

## 0.14.1

### Patch Changes

-   @nhost/react-apollo@5.0.15
-   @nhost/nextjs@1.13.19

## 0.14.0

### Minor Changes

-   6e1f03ea: feat(dashboard): add support for the Azure AD provider

### Patch Changes

-   1bd2c373: chore(deps): bump `turbo` to 1.8.6
-   d329b621: chore(deps): bump `@types/react` to 18.0.30
-   cb248f0d: fix(tests): avoid name collision in database tests
-   867c8076: chore(deps): bump `@types/react` to 18.0.29

## 0.13.10

### Patch Changes

- e93b06ab: fix(dashboard): remove left margin from workspace list on
mobile
-   1c4806bf: chore(deps): bump `sharp` to 0.32.0
    -   @nhost/react-apollo@5.0.14
    -   @nhost/nextjs@1.13.18

## 0.13.9

### Patch Changes

-   912ed76c: chore(dashboard): bump `@apollo/client` to 3.7.10
-   Updated dependencies [912ed76c]
    -   @nhost/react-apollo@5.0.13

## 0.13.8

### Patch Changes

-   7c127372: chore(dashboard): bump `react-error-boundary` to v4

## 0.13.7

### Patch Changes

- 9130ab12: chore(dashboard): bump `yup` to v1 and `@hookform/resolvers`
to v3

## 0.13.6

### Patch Changes

- 253dd235: using new mutation to create projects + refactor Create
Project page.

## 0.13.5

### Patch Changes

-   @nhost/react-apollo@5.0.12
-   @nhost/nextjs@1.13.17

## 0.13.4

### Patch Changes

-   b48bc034: fix(dashboard): disable new users
-   798e591b: fix(dashboard): show correct date in data grid

## 0.13.3

### Patch Changes

-   bfb4c1a6: chore(dashboard): remove `useAxios` property
-   d8d8394b: Dashboard: allow to override hasura admin secret in docker
-   Updated dependencies [ce1ee40d]
    -   @nhost/nextjs@1.13.16
    -   @nhost/react-apollo@5.0.11

## 0.13.2

### Patch Changes

-   beed2eba: Fix docker entrypoint for dashboard
- 2c8559a3: fix(dashboard): refresh project list after deleting a
project
-   4329d048: chore(dashboard): bump `graphiql` dependencies

## 0.13.1

### Patch Changes

-   cbb1fc5b: chore(dashboard): cleanup GraphQL operations

## 0.13.0

### Minor Changes

-   088584e7: feat(dashboard): add support for custom local subdomains

### Patch Changes

-   2ac90dfd: fix(dashboard): improve mobile responsive layout
-   Updated dependencies [f375eacc]
    -   @nhost/nextjs@1.13.15
    -   @nhost/react-apollo@5.0.10

## 0.12.4

### Patch Changes

-   @nhost/react-apollo@5.0.9
-   @nhost/nextjs@1.13.14

## 0.12.3

### Patch Changes

-   2b1338f7: chore(dashboard): bump `turbo` to 1.8.3
- 5223ee93: fix(dashboard): show correct deployment status on the main
page
-   850a049c: chore(deps): update docker/build-push-action action to v4
-   Updated dependencies [850a049c]
    -   @nhost/nextjs@1.13.13
    -   @nhost/react-apollo@5.0.8

## 0.12.2

### Patch Changes

-   4bf40995: chore(deps): bump `typescript` to `4.9.5`
-   8bb097c9: chore(deps): bump `vitest`
- 35d52aab: chore(deps): replace `cross-fetch` with `isomorphic-unfetch`
-   Updated dependencies [4bf40995]
-   Updated dependencies [8bb097c9]
-   Updated dependencies [35d52aab]
    -   @nhost/react-apollo@5.0.7
    -   @nhost/nextjs@1.13.12

## 0.12.1

### Patch Changes

-   c96d7ccd: fix(dashboard): fix docker builds

## 0.12.0

### Minor Changes

-   d1671210: feat(dashboard): use mimir to manage project configuration

### Patch Changes

-   f65e4de9: chore(deps): bump @graphql-codegen monorepo to v3

## 0.11.20

### Patch Changes

-   4b4f0d01: chore(dashboard): improve dialog management

## 0.11.19

### Patch Changes

-   @nhost/react-apollo@5.0.6
-   @nhost/nextjs@1.13.11

## 0.11.18

### Patch Changes

-   01318860: fix(nhost-js): use correct URL for functions requests
-   Updated dependencies [01318860]
    -   @nhost/react-apollo@5.0.5
    -   @nhost/nextjs@1.13.10

## 0.11.17

### Patch Changes

-   f673adea: fix(dashboard): set correct Content-Type for user creation
-   445d8ef4: chore(deps): bump `@nhost/react-apollo` to 5.0.4
-   445d8ef4: chore(deps): bump `@nhost/nextjs` to 1.13.9
- 0368663d: fix(dashboard): allow permission editing for auth and
storage schemas
-   Updated dependencies [445d8ef4]
-   Updated dependencies [445d8ef4]
    -   @nhost/react-apollo@5.0.4
    -   @nhost/nextjs@1.13.9

## 0.11.16

### Patch Changes

-   b755e908: fix(dashboard): use correct date for last seen
-   2d9145f9: chore(deps): revert GraphQL client
- 1ddf704c: fix(dashboard): don't show false positive message for failed
user creation
    -   @nhost/react-apollo@5.0.3
    -   @nhost/nextjs@1.13.8

## 0.11.15

### Patch Changes

-   @nhost/react-apollo@5.0.2
-   @nhost/nextjs@1.13.7

## 0.11.14

### Patch Changes

- 2cc18dcb: fix(dashboard): prevent permission editor dropdown from
being always open

## 0.11.13

### Patch Changes

- 3343a363: chore(dashboard): bump `@testing-library/react` to v14 and
`@testing-library/dom` to v9
    -   @nhost/react-apollo@5.0.1
    -   @nhost/nextjs@1.13.6

## 0.11.12

### Patch Changes

- 87eda76e: chore(dashboard): bump `@types/react` to v18.0.28 and
`@types/react-dom` to v18.0.11
-   6f0ac570: feat(dashboard): show dashboard version in account menu

## 0.11.11

### Patch Changes

-   bf1e4071: chore(dashboard): bump `react-is` version to `18.2.0`
-   Updated dependencies [bf1e4071]
-   Updated dependencies [5013213b]
    -   @nhost/nextjs@1.13.5
    -   @nhost/react-apollo@4.13.5

## 0.11.10

### Patch Changes

- a37a430b: fix(dashboard): don't break UI when deployments are
unavailable
    -   @nhost/react-apollo@4.13.4
    -   @nhost/nextjs@1.13.4

## 0.11.9

### Patch Changes

-   7b970e68: fix(dashboard): fix header link color

## 0.11.8

### Patch Changes

- f33242f2: feat(dashboard): add new sign up, sign in and reset password
pages

## 0.11.7

### Patch Changes

-   e9c8909c: fix(dashboard): use correct theme color in dark mode

## 0.11.6

### Patch Changes

-   902f486b: fix(dashboard): re-enable Hasura on logs page

## 0.11.5

### Patch Changes

-   1f9720fa: fix(dashboard): apply select permissions properly

## 0.11.4

### Patch Changes

-   deb14b51: fix(dashboard): don't break billing form

## 0.11.3

### Patch Changes

-   @nhost/react-apollo@4.13.3
-   @nhost/nextjs@1.13.3

## 0.11.2

### Patch Changes

-   f143e51d: chore(dashboard): pin Turborepo to 1.6.3

## 0.11.1

### Patch Changes

-   c2b5a41a: chore(dashboard): select system colors by default

## 0.11.0

### Minor Changes

-   1ebaf429: feat(dashboard): introduce Dark Mode 🌚

### Patch Changes

- 63b445c4: fixed duplicated logs bug and made to date count during live
mode

## 0.10.1

### Patch Changes

-   e146d32e: chore(deps): update dependency @types/react to v18.0.27
-   59347fcd: correct allowed role name
-   5b65cac9: updated authentication documentation
-   963f9b5e: feat(dashboard): include project info in feedback

## 0.10.0

### Minor Changes

-   ed4c7801: chore(dashboard): remove Functions section

## 0.9.10

### Patch Changes

-   4e2f8ccd: fix(dashboard): don't break Auth page in local mode

## 0.9.9

### Patch Changes

-   31abbe5f: fix(dashboard): enable toggle when settings are filled in

## 0.9.8

### Patch Changes

- 5bdd31ad: chore(dashboard): list fewer images per page on the Storage
page
- 5121851c: fix(dashboard): don't throw validation error for valid
permission rules

## 0.9.7

### Patch Changes

-   c126b20d: fix(dashboard): correct redeployment button

## 0.9.6

### Patch Changes

-   36c3519c: feat(dashboard): retrigger deployments

## 0.9.5

### Patch Changes

- 200e9f77: chore(deps): update dependency @types/react-dom to v18.0.10
-   Updated dependencies [200e9f77]
    -   @nhost/nextjs@1.13.2
    -   @nhost/react-apollo@4.13.2

## 0.9.4

### Patch Changes

- dbd3ded5: fix(dashboard): workspaces creation, new form, correct
redirects.

## 0.9.3

### Patch Changes

-   85f0f943: fix(dashboard): don't break the table creation process

## 0.9.2

### Patch Changes

-   Updated dependencies [d42c27ae]
-   Updated dependencies [927be4a2]
    -   @nhost/nextjs@1.13.1
    -   @nhost/react-apollo@4.13.1

## 0.9.1

### Patch Changes

-   d0f80811: fix(dashboard): don't show error when signing out the user

## 0.9.0

### Minor Changes

- d92891b2: feat(dashboard): add Permission Editor to the Database
section

### Patch Changes

-   3d379128: fix(dashboard): create new user
    -   @nhost/react-apollo@4.13.0
    -   @nhost/nextjs@1.13.0

## 0.8.1

### Patch Changes

-   7cadd944: fix(dashboard): display Twitter provider settings

## 0.8.0

### Minor Changes

-   9a1aa7bb: add functions to the log dashboard
-   f29abe62: feat(dashboard): Users Management v2

### Patch Changes

-   7766624b: feat(dashboard): add JWT secret editor modal
    -   @nhost/react-apollo@4.12.1
    -   @nhost/nextjs@1.12.1

## 0.7.13

### Patch Changes

-   dd0738d5: fix(dashboard): provisioning status polling

## 0.7.12

### Patch Changes

-   b21222b3: chore(deps): update dependency @types/node to v16
-   9e0486a3: fix(dashboard): close modals when navigating
-   Updated dependencies [b21222b3]
-   Updated dependencies [65687bee]
-   Updated dependencies [54df0df4]
    -   @nhost/nextjs@1.12.0
    -   @nhost/react-apollo@4.12.0

## 0.7.11

### Patch Changes

-   d6527122: fix(dashboard): use correct service URLs

## 0.7.10

### Patch Changes

-   Updated dependencies [57db5b83]
    -   @nhost/nextjs@1.11.0
    -   @nhost/nhost-js@1.7.0
    -   @nhost/react@0.17.0
    -   @nhost/react-apollo@4.11.0

## 0.7.9

### Patch Changes

- a6d31dc2: fix(dashboard): don't break the UI when project is not
loaded yet

## 0.7.8

### Patch Changes

- 7f251111: Use `NhostProvider` instead of `NhostReactProvider` and
`NhostNextProvider`

    `NhostReactProvider` and `NhostNextProvider` are now deprecated

-   f4d70f88: fix(dashboard): do not break when region is nullish

- 4a9471cc: Windows Live Provider displayed link updated to match
backend url

- 594488e4: fix(dashboard): do not show error when submitting Apple
provider settings

-   Updated dependencies [7f251111]
    -   @nhost/nextjs@1.10.0
    -   @nhost/react@0.16.0
    -   @nhost/react-apollo@4.10.0

## 0.7.7

### Patch Changes

-   80b604ad: fix(dashboard): use correct Hasura slug

## 0.7.6

### Patch Changes

-   2d2beb53: fix(dashboard): prevent error on GraphQL page
-   ac8efcbd: chore(dashboard): deprecate old DNS name

## 0.7.5

### Patch Changes

-   132a4f4b: chore(dashboard): remove unused dependencies
- 132a4f4b: chore(deps): synchronize @types/react-dom and @types/react
versions
-   db57572f: fix(dashboard): correct section paddings when no env vars
-   Updated dependencies [132a4f4b]
    -   @nhost/react@0.15.2
    -   @nhost/react-apollo@4.9.2
    -   @nhost/nextjs@1.9.3

## 0.7.4

### Patch Changes

-   34d85e54: chore(deps): update dependency critters to ^0.0.16
- 9b93cf95: chore(deps): update dependency @netlify/functions to ^0.11.0
-   e0439030: chore(deps): update dependency @types/react-dom to v18.0.9
-   Updated dependencies [82124329]
    -   @nhost/nextjs@1.9.2

## 0.7.3

### Patch Changes

-   a1193da4: fix(dashboard): remove character limit from env var inputs

## 0.7.2

### Patch Changes

-   44f13f62: chore(dashboard): cleanup unused files

## 0.7.1

### Patch Changes

- e01cb2ed: chore(dashboard): change settings sidebar menu item density

## 0.7.0

### Minor Changes

- db342f45: chore(dashboard): refactor Roles and Permissions settings
sections
-   8b9fa0b1: feat(dashboard): add Environment Variables page

### Patch Changes

-   Updated dependencies [66b4f3d0]
-   Updated dependencies [2e6923dc]
-   Updated dependencies [ef117c28]
-   Updated dependencies [aebb8225]
    -   @nhost/core@0.9.4
    -   @nhost/nhost-js@1.6.2
    -   @nhost/nextjs@1.9.1
    -   @nhost/react@0.15.1
    -   @nhost/react-apollo@4.9.1

## 0.6.0

### Minor Changes

-   eef9c914: feat(dashboard): add Roles and Permissions page

## 0.5.0

### Minor Changes

-   a48dd5bf: feat(dashboard): make backend port configurable

## 0.4.3

### Patch Changes

-   5de965d9: fix(dashboard): alphabetic ordering of providers
-   b9087a4a: fix(dashboard): console -> dashboard terminology
-   ca012d79: docs(workos): WorkOS Docs

## 0.4.2

### Patch Changes

-   89bd37bc: fix(dashboard): correct redirect URL input opacity
-   Updated dependencies [4601d84e]
-   Updated dependencies [843087cb]
    -   @nhost/react@0.15.0
    -   @nhost/nextjs@1.9.0
    -   @nhost/react-apollo@4.9.0

## 0.4.1

### Patch Changes

-   766cb612: fix(dashboard): correct redirect URL for oauth providers
-   Updated dependencies [53bdc294]
-   Updated dependencies [f2aaff05]
    -   @nhost/nextjs@1.8.3
    -   @nhost/core@0.9.3
    -   @nhost/react@0.14.3
    -   @nhost/nhost-js@1.6.1
    -   @nhost/react-apollo@4.8.3

## 0.4.0

### Minor Changes

-   9211743d: feat(dashboard): migrate Settings page features

## 0.3.0

### Minor Changes

-   73da6a67: fix(dashboard): avoid using BACKEND_URL locally

## 0.2.0

### Minor Changes

-   db118f97: feat(dashboard): generate Docker image

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-29 19:35:53 +01:00
Hassan Ben Jobrane
8f169885f7 fix: storage grid preview and layout (#2961) 2024-10-29 19:32:35 +01:00
github-actions[bot]
6104e72204 chore: update versions (#2958)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@2.0.6

# @nhost/dashboard

## 1.30.0

### Minor Changes

- 50441a8: feat: add ui for project autoscaler settings and run services
autoscaler settings

## 1.29.0

### Minor Changes

-   55d8bb5: feat: integrate turnstile for signup verification
-   2a2e54c: fix: update docs url in run services form tooltip
- 18f942f: fix: display long error messages in error toast without
overflow

### Patch Changes

-   @nhost/react-apollo@13.0.0
-   @nhost/nextjs@2.1.22

## 1.28.2

### Patch Changes

- 52a38fe: chore: update dependencies to address security
vulnerabilities
-   Updated dependencies [52a38fe]
    -   @nhost/nextjs@2.1.21

## 1.28.1

### Patch Changes

-   9735fa2: chore: remove broken link

## 1.28.0

### Minor Changes

- 526183a: feat: allow filtering users in "make request as" in graphql
section
-   be3b85b: feat: add conceal errors toggle on auth settings page

### Patch Changes

- 35a2f12: fix: prevent run service details from opening when attempting
to delete
    -   @nhost/react-apollo@12.0.6
    -   @nhost/nextjs@2.1.20

## 1.27.0

### Minor Changes

-   a7cd02c: fix: resolve rate limit query

## 1.26.0

### Minor Changes

-   3773ad7: chore: update pricing information
- b63250d: fix: not allow run service creation form resubmission while
creating a run service
-   a44a1d4: feat: add rate limits settings page

### Patch Changes

-   @nhost/react-apollo@12.0.5
-   @nhost/nextjs@2.1.19

## 1.25.0

### Minor Changes

- d1ceede: feat: add setting to migrate postgres major and/or minor
versions
- e5d3d1a: fix: allow manually typing column for custom check in
database row permissions

### Patch Changes

-   @nhost/react-apollo@12.0.4
-   @nhost/nextjs@2.1.18

## 1.24.1

### Patch Changes

- 49f2e55: fix: use service subdomain in service form and service
details dialog
- 598b988: fix: use current project subdomain in ServiceDetailsDialog
component

## 1.24.0

### Minor Changes

-   abb24af: chore: add redirect to support page when project is locked
- 18a6455: feat: show contact us info and locked reason when project is
locked

### Patch Changes

-   e31eefa: fix: include ingresses field when updating run services

## 1.23.0

### Minor Changes

-   33284d3: fix: don't show double scrollbar in configuration editor

### Patch Changes

-   @nhost/react-apollo@12.0.3
-   @nhost/nextjs@2.1.17

## 1.22.0

### Minor Changes

-   998c037: fix: align drop-down list in select component
- 807b8c0: fix: show city name in region selection for project creation

## 1.21.0

### Minor Changes

- a2efeed: fix: improve project health error handling, add unknown state
and polling interval for health state

## 1.20.0

### Minor Changes

- 8ea4210: fix: error toasts can be closed individually, instead of
dismissing all toasts at once
- 58919ba: chore: add blink animation when project health service is
updating

## 1.19.0

### Minor Changes

- b519862: fix: get configuration in configuration editor using local
development environment

## 1.18.0

### Minor Changes

- 502abad: feat: add services health checks indicators to the overview
page
-   b3ff6ad: chore: update title text on service status modal
- dbadf59: feat: add project configuration TOML editor to the settings
page

## 1.17.0

### Minor Changes

- 77fba27: fix: postgres version validation when activating ai in ai
settings page
-   ac6d1b6: feat: use name instead of awsName

## 1.16.3

### Patch Changes

- 87a37cf: fix: remove unnecessary isPlatform check from verify button
disable logic on custom domains
    -   @nhost/react-apollo@12.0.2
    -   @nhost/nextjs@2.1.16

## 1.16.2

### Patch Changes

- a9413af: fix: update `GetAllWorkspacesAndProjects` query polling to
use exponential backoff
    -   @nhost/react-apollo@12.0.1
    -   @nhost/nextjs@2.1.15

## 1.16.1

### Patch Changes

-   @nhost/react-apollo@12.0.0
-   @nhost/nextjs@2.1.14

## 1.16.0

### Minor Changes

- c6d5c5c: feat: add toggle switch to enable/disable public access in
the database settings

## 1.15.2

### Patch Changes

-   @nhost/react-apollo@11.0.4
-   @nhost/nextjs@2.1.13

## 1.15.1

### Patch Changes

-   @nhost/react-apollo@11.0.3
-   @nhost/nextjs@2.1.12

## 1.15.0

### Minor Changes

-   a7bde37: feat: send metadata in the edit form

### Patch Changes

- 1bc615b: feat: improve error message handling in `ErrorToast`
component
    -   @nhost/react-apollo@11.0.2
    -   @nhost/nextjs@2.1.11

## 1.14.0

### Minor Changes

-   a448d7d: feat: allow configuring postmark and delete SMTP settings

## 1.13.3

### Patch Changes

-   5924bc3: fix: include password in `GetSmtpSettings` query
- c5ad634: fix: resolved an issue where one-click install links were
broken on Safari
- 7278991: fix: update graphql auto-embeddings configuration to use
String type for model field

## 1.13.2

### Patch Changes

-   026f84f: fix: use configuration server URL from environment variable

## 1.13.1

### Patch Changes

-   7e9a2ce: fix: resolve issue where run services form fails to open

## 1.13.0

### Minor Changes

-   dd5d262: feat: add model field to the auto-embeddings form
- 09962be: feat: enable settings and run services when running the
dashboard locally
- 9cdecb6: feat: enable users to update their email address from the
account settings page

## 1.12.2

### Patch Changes

-   c195c51: fix: send email upon signin for unverified users

## 1.12.1

### Patch Changes

- 93ebdf8: fix: use service urls when initilizaing NhostClient running
local dashboard
    -   @nhost/react-apollo@11.0.1
    -   @nhost/nextjs@2.1.10

## 1.12.0

### Minor Changes

- f242e4b: feat: add connect with github to the user's account settings
-   768ca17: chore: update dependencies
- d62bd0f: fix: "Track this" option within the SQL editor now correctly
updates the metadata
- 91c2bb6: feat: refactor sign-in and sign-up pages to enforce email
verification

### Patch Changes

-   943831f: fix: resolve an error toast issue when unpausing a project
-   Updated dependencies [768ca17]
    -   @nhost/react-apollo@11.0.0
    -   @nhost/nextjs@2.1.9

## 1.11.2

### Patch Changes

-   @nhost/react-apollo@10.0.2
-   @nhost/nextjs@2.1.8

## 1.11.1

### Patch Changes

-   981404f: fix: set default value for healthCheck field validation

## 1.11.0

### Minor Changes

- 7789469: chore: upgrade dependency `@graphql-codegen/cli` to `5.0.2`
to address vulnerability
- 6c11b75: feat: add update user displayName section in account settings

### Patch Changes

-   @nhost/react-apollo@10.0.1
-   @nhost/nextjs@2.1.7

## 1.10.0

### Minor Changes

-   49a80c2: chore: update dependencies
-   150c04a: feat: add healthcheck config to run services

### Patch Changes

- e03f141: fix: allow insert, update and delete on tables in `auth` and
`storage` schemas
- 28676f4: feat: add min postgres version check to enable the ai service
-   Updated dependencies [49a80c2]
    -   @nhost/react-apollo@10.0.0
    -   @nhost/nextjs@2.1.6

## 1.9.0

### Minor Changes

-   d86e5c9: feat: add support for filtering the logs using a RegExp

## 1.8.3

### Patch Changes

-   @nhost/react-apollo@9.0.3
-   @nhost/nextjs@2.1.5

## 1.8.2

### Patch Changes

- 6df4f02: fix: use custom error toast and show correct message when
sending an invite

## 1.8.1

### Patch Changes

-   @nhost/react-apollo@9.0.2
-   @nhost/nextjs@2.1.4

## 1.8.0

### Minor Changes

- 713d53c: feat: add catch-all route for workspace/project - useful for
documentation

### Patch Changes

-   3db2999: fix: refresh table list after running SQL using the editor
- 3c4dd55: fix: handle `Error` objects properly in the `ErrorToast`
component
- 92b434e: fix: resolve an issue where the checkbox in the data-grid
header did not select all rows
    -   @nhost/react-apollo@9.0.1
    -   @nhost/nextjs@2.1.3

## 1.7.0

### Minor Changes

-   0d8d0eb: Update docs and dashboard references

## 1.6.9

### Patch Changes

-   @nhost/react-apollo@9.0.0
-   @nhost/nextjs@2.1.2

## 1.6.8

### Patch Changes

-   @nhost/react-apollo@8.0.1
-   @nhost/nextjs@2.1.1

## 1.6.7

### Patch Changes

-   5ef5189: fix: update `@apollo/client` to `3.9.4` to fix a cache bug

## 1.6.6

### Patch Changes

-   3ba485e: fix: added discord.com to connect-src
-   e5bab6a: chore: update dependencies
-   Updated dependencies [b19ffed]
-   Updated dependencies [e5bab6a]
    -   @nhost/nextjs@2.1.0
    -   @nhost/react-apollo@8.0.0

## 1.6.5

### Patch Changes

- ba73bb4: fix: update ErrorToast component to show the internal graphql
error
- d5337ff: fix: utilize accumulator in the creation of validation schema
within data grid utils

## 1.6.4

### Patch Changes

-   7c2a1c2: feat: show error and debug info in the error toast

## 1.6.3

### Patch Changes

-   6b8aad5: fix: add bare nhost.run to CSP

## 1.6.2

### Patch Changes

-   b18edc0: feat: added CSP and X-Frame-Options

## 1.6.1

### Patch Changes

-   8d91f71: chore: update deps and enable pnpm audit
- 3b8473b: chore: update turbo to `1.11.3` and pnpm to `8.10.5` in
Dockerfile
-   Updated dependencies [8d91f71]
    -   @nhost/react-apollo@7.0.2
    -   @nhost/nextjs@2.0.2

## 1.6.0

### Minor Changes

-   3ff1c2b53: fix: show upgrade option for pro projects

## 1.5.0

### Minor Changes

-   c2ef17c0a: feat: add support for new Team plan

## 1.4.0

### Minor Changes

-   7883bbcbd: feat: don't show deprecated plans
- 44be6dc0a: feat: set redirectTo during sign-in to support preview
environments

### Patch Changes

- 3c3594898: fix: allow access to graphite when configured running in
local dashboard
-   32c246b7a: chore: update docs icon

## 1.3.2

### Patch Changes

-   174b4165b: chore: use env variables when running graphql codegen
-   7c977e714: chore: change `Allowed Roles` to `Default Allowed Roles`
-   46f028b9f: fix: remove hardcoded ai version setting

## 1.3.1

### Patch Changes

- af33c21d1: chore: remove backendUrl deprecation notice and remove all
references to `providersUpdated`

## 1.3.0

### Minor Changes

-   04784d880: Fix graphite's default version

## 1.2.0

### Minor Changes

-   5733162ed: feat: add settings and ui for graphite

## 1.1.0

### Minor Changes

-   e2b79b5ec: chore: remove sharp from deps

## 1.0.1

### Patch Changes

-   @nhost/react-apollo@7.0.1
-   @nhost/nextjs@2.0.1

## 1.0.0

### Major Changes

- bc9eff6e4: chore: remove support for using backendUrl when
instantiating the Nhost client

### Patch Changes

-   Updated dependencies [bc9eff6e4]
    -   @nhost/nextjs@2.0.0
    -   @nhost/react-apollo@7.0.0

## 0.21.1

### Patch Changes

-   97ced73a3: fix(dashboard): prevent dashboard from resolving secrets

## 0.21.0

### Minor Changes

- ed1a8d458: Update alert message on increasing PostgreSQL's volume
capacity
-   2e2248fd4: feat(dashboard): add SQL editor

## 0.20.28

### Patch Changes

-   7c2c31082: feat: add support for users to delete their account
    -   @nhost/react-apollo@6.0.1
    -   @nhost/nextjs@1.13.40

## 0.20.27

### Patch Changes

- fa79b7709: chore(dashboard): tweaks and fixes to the service form and
dialog
-   8df84d782: fix(dashboard): allow resetting custom domains
    -   @nhost/react-apollo@6.0.0
    -   @nhost/nextjs@1.13.39

## 0.20.26

### Patch Changes

- 331ba0376: feat(dashboard): add postgres storage capacity modifier in
the settings
-   b7f801874: feat(dashboard): add new settings page for custom domains

## 0.20.25

### Patch Changes

-   @nhost/react-apollo@5.0.38

## 0.20.24

### Patch Changes

-   e10389ecf: fix(dashboard): disable run tab when developing locally
    -   @nhost/react-apollo@5.0.37

## 0.20.23

### Patch Changes

-   c01568a7d: chore(dashboard): show alert to update oauth providers

## 0.20.22

### Patch Changes

-   c3efb7ec8: feat(dashboard): query latest announcement from platform

## 0.20.21

### Patch Changes

-   3e46d3873: chore: update link to node18 announcement

## 0.20.20

### Patch Changes

-   @nhost/react-apollo@5.0.36
-   @nhost/nextjs@1.13.38

## 0.20.19

### Patch Changes

-   75c4c8ae3: feat(dashboard): make env value input multiline

## 0.20.18

### Patch Changes

- 425d485f8: fix(dashboard): make sure dedicated resources pricing
follows total resources

## 0.20.17

### Patch Changes

-   ae324f67f: fix(dashboard): remove unused graphql fields

## 0.20.16

### Patch Changes

-   df5b4302c: chore(dashboard): remove run feature flag
- bf4a1f6c2: feat(dashboard): fetch auth, postgres, hasura and storage
versions from dashboard
- 34fc08ca7: fix(dashboard/run): show correct private registry in
service details
-   885d10620: chore(dashboard): change feedback to contact us

## 0.20.15

### Patch Changes

- ed16c8b5d: feat(run): add a confirmation dialog when deleting a run
service
- 216990888: fix(run): center loading indicator when selecting a project

## 0.20.14

### Patch Changes

-   9fbea9787: feat: add node18 announcement

## 0.20.13

### Patch Changes

- e84acf469: fix(run): handle subdomain undefined error when creating a
new service

## 0.20.12

### Patch Changes

-   b7c799d62: feat(run): add dialog to copy registry and URLs

## 0.20.11

### Patch Changes

-   8903e6abd: fix(dashboard): show correct egress limit in usage stats

## 0.20.10

### Patch Changes

- 666a75a23: feat(dashboard): add functions execution time and egress
volume to usage stats

## 0.20.9

### Patch Changes

-   5e1e80aa8: fix(dashboard): show correct locales in user details
    -   @nhost/react-apollo@5.0.35
    -   @nhost/nextjs@1.13.37

## 0.20.8

### Patch Changes

-   @nhost/react-apollo@5.0.34
-   @nhost/nextjs@1.13.36

## 0.20.7

### Patch Changes

-   4a7ede11e: fix: distinguish files that were not uploaded
- 202b64723: feat(nhost-run): add support for one-click-install run
services
- 074a0fa11: feat(dashboard): add settings toggle to enable/disable
antivirus
    -   @nhost/react-apollo@5.0.33
    -   @nhost/nextjs@1.13.35

## 0.20.6

### Patch Changes

-   b20761e97: feat(services): add pricing info and confirmation dialog
-   90df6d81d: fix(services): handle null values when editing a service
-   aa8508467: fix: query service logs correctly
    feat: enable multiline support for environment value input

## 0.20.5

### Patch Changes

-   8d7f84b8d: fix: make announcement adapt to theme

## 0.20.4

### Patch Changes

-   3b75bfce2: fix: make announcement close properly
- f49819075: fix: show correct values when dedicated resources are
disabled

## 0.20.3

### Patch Changes

-   e643bd362: fix(services): fix errors when config is null
-   bcdab66bf: feat: add annoucement for nhost run
-   f967a2e59: added note about storage not being able to be downsized
-   311c7756d: chore(services): consistent naming for compute

## 0.20.2

### Patch Changes

-   9073182d5: chore(dashboard): bump `turbo` to 1.10.11
-   ece717d6e: feat(logs): show services in the logs page
- 82b335311: feat(metrics): change grafana link to point to the
dashboards
- b135ef695: fix(services): set command as optional and set min replicas
to 0

## 0.20.1

### Patch Changes

-   3d5c34f4c: fix(auth): fix users pagination limit

## 0.20.0

### Minor Changes

-   c99d117d1: feat(services): add support for custom services

## 0.19.2

### Patch Changes

-   face99ccd: chore(deps): bump turbo version
-   cfe527307: style: tweak pull config warning in dark mode
- a9d7da8af: chore(deps): update dependency @types/pluralize to ^0.0.30
-   9aa4371ef: chore: add hasura-auth version 0.21.2
- d14e112bf: chore(deps): update dependency prettier-plugin-tailwindcss
to ^0.4.0
-   d3e8bb94a: chore(deps): update dependency vite-plugin-dts to v3

## 0.19.1

### Patch Changes

-   @nhost/react-apollo@5.0.32
-   @nhost/nextjs@1.13.34

## 0.19.0

### Minor Changes

- 9c61c69a7: chore(dashboard):add postgres 14.6-20230705-1 to the
version selector

### Patch Changes

-   47bda15ff: feat(settings): add warning to pull config

## 0.18.0

### Minor Changes

- ee0b9b8ed: chore(dashboard):add hasura v2.28.2 and v2.29.0 to the
version selector

## 0.17.20

### Patch Changes

-   @nhost/react-apollo@5.0.31
-   @nhost/nextjs@1.13.33

## 0.17.19

### Patch Changes

-   f866120a6: fix(users): use the password length from the config

## 0.17.18

### Patch Changes

-   @nhost/react-apollo@5.0.30
-   @nhost/nextjs@1.13.32

## 0.17.17

### Patch Changes

-   ea7b102c0: fix(pat): highlight expired tokens

## 0.17.16

### Patch Changes

- b3b64a3b7: chore(deps): bump `@types/react` to `v18.2.14` and
`@types/react-dom` to `v18.2.6`
-   32b221f94: chore(deps): bump `graphiql` to `v3`
-   3a56c12df: chore(deps): bump `turbo` to `v1.10.6`
-   Updated dependencies [b3b64a3b7]
    -   @nhost/react-apollo@5.0.29
    -   @nhost/nextjs@1.13.31

## 0.17.15

### Patch Changes

-   f41fdc12a: chore(deps): bump `turbo` to `1.10.5`
-   6199c1c55: fix(projects): don't redirect to 404 page
-   Updated dependencies [07a45fde0]
    -   @nhost/react-apollo@5.0.28
    -   @nhost/nextjs@1.13.30

## 0.17.14

### Patch Changes

- 80b22724d: chore(deps): bump `@types/react` to `v18.2.13`,
`@types/react-dom` to `v18.2.6` and `@storybook/testing-library` to
`v0.2.0`

## 0.17.13

### Patch Changes

-   cc02902cb: chore(docs): update environment variable documentation

## 0.17.12

### Patch Changes

-   660d339e1: fix(storybook): don't break storybook
-   660d339e1: fix(tests): prevent warnings during tests
    -   @nhost/react-apollo@5.0.27
    -   @nhost/nextjs@1.13.29

## 0.17.11

### Patch Changes

- bd4d0c270: chore(dashboard):add postgres 14.6-20230613-1 to the
version selector

## 0.17.10

### Patch Changes

-   c8c2a10b2: fix(database): don't break the password reset flow
- e70b45498: chore(deps): bump `@types/react` to `v18.2.12` and
`@types/react-dom` to `v18.2.5`

## 0.17.9

### Patch Changes

- 842055099: chore(deps): bump `turbo` to `v1.10.3` and `pnpm` to
`v8.6.2`
- fd12aa0a8: chore(projects): remove the postgres password input from
the project creation screen
-   022b76e78: chore(deps): bump `@types/react` to `v18.2.11`
-   3555ab2b7: chore(deps): bump `vitest` monorepo to `v0.32.0`
-   c43e54922: feat(backups): add download button to backups

## 0.17.8

### Patch Changes

-   d0457fe5c: feat(settings): improve the dashboard and config parity
    -   @nhost/react-apollo@5.0.26
    -   @nhost/nextjs@1.13.28

## 0.17.7

### Patch Changes

-   4f0368b95: fix(account): don't break account settings page

## 0.17.6

### Patch Changes

- 64a8f41d0: chore(resources): lower the maximum allowed resources per
service

## 0.17.5

### Patch Changes

-   @nhost/react-apollo@5.0.25
-   @nhost/nextjs@1.13.27

## 0.17.4

### Patch Changes

- 9b1d0f7a5: fix(deployments): use correct timestamp for deployment
details
-   6d2963ffa: chore(deps): bump `@types/react` to `v18.2.8`
- 8871267b9: chore(deps): downgrade `pnpm` to `v8.5.1` because of no
Turborepo support

## 0.17.3

### Patch Changes

-   01eeef9de: chore(misc): under the hood improvements
- 21e13db05: chore(deps): bump `@types/react` to `v18.2.7` and `turbo`
to `v1.10.1`
- f16433ae6: chore(secrets): allow empty secrets and environment
variables
-   aa3c62989: chore(cli): bump Nhost CLI version to v1.0
    -   @nhost/react-apollo@5.0.24
    -   @nhost/nextjs@1.13.26

## 0.17.2

### Patch Changes

-   88a4983f: chore(misc): under the hood improvements

## 0.17.1

### Patch Changes

-   9b0d4dde: feat(secrets): enable secrets

## 0.17.0

### Minor Changes

-   15d84a19: Add postgres 14.6-20230525

## 0.16.14

### Patch Changes

-   4c626174: chore: updated import paths, improved directory structure
-   cc047b71: chore(deps): bump `@fontsource` monorepo to `v5.0.0`
-   99edd012: feat(account): add support for personal access tokens

## 0.16.13

### Patch Changes

-   78c7109c: feat(settings): allow selecting service versions

## 0.16.12

### Patch Changes

- 399009d6: fix(gql): don't enter an infinite loop when fetching remote
app data
- 329e5a91: fix(deployments): use the same sorting of deployments
everywhere
- 6d559d6e: chore(settings): add under the hood improvements to the
settings page
- 12eb236c: chore(deps): bump `prettier-plugin-tailwindcss` to `v0.3.0`
-   f9b81a2a: chore(deps): bump `turbo` to `v1.9.8`
-   1345741b: fix(projects): don't redirect to 404 on project creation
-   Updated dependencies [7fea29a8]
    -   @nhost/react-apollo@5.0.23
    -   @nhost/nextjs@1.13.25

## 0.16.11

### Patch Changes

- 1230b722: fix(projects): don't redirect to 404 on when the project is
renamed
    -   @nhost/react-apollo@5.0.22
    -   @nhost/nextjs@1.13.24

## 0.16.10

### Patch Changes

-   Updated dependencies [da03bf39]
    -   @nhost/react-apollo@5.0.21
    -   @nhost/nextjs@1.13.23

## 0.16.9

### Patch Changes

- 349aac36: fix(settings): use region domain when constructing the
postgres connection string

## 0.16.8

### Patch Changes

- 20fb69fa: chore(projects): change the way how API URLs are constructed

## 0.16.7

### Patch Changes

- 49f9b837: chore(docker): bump `pnpm` to `v8.4.0` and `turbo` to
`v1.9.3`
- 3f478a4e: chore(deps): bump `vitest` to `v0.31.0`, `@types/react` to
`v18.2.6` and `@types/react-dom` to `v18.2.4`

## 0.16.6

### Patch Changes

- d926f156: fix(projects): redirect to 404 when an invalid project is
opened
- 49b99728: fix(projects): disable features for non-owner members of
workspaces

## 0.16.5

### Patch Changes

-   12e2855f: chore(deps): bump `jsdom` to v22
-   e4972b83: feat(metrics): add Grafana page

## 0.16.4

### Patch Changes

- 3f396a9e: fix(projects): unpause after upgrading a paused project to
pro
- 3f396a9e: fix(projects): don't redirect to 404 page after project
creation

## 0.16.3

### Patch Changes

-   Updated dependencies [90c60311]
    -   @nhost/react-apollo@5.0.20
    -   @nhost/nextjs@1.13.22

## 0.16.2

### Patch Changes

-   0f34f0c6: fix(projects): disallow downgrading to free plan
- 8da291ad: chore(deps): bump `@types/react` to v18.2.0 and
`@types/react-dom` to v18.2.1

## 0.16.1

### Patch Changes

- adc828a5: fix(gql): don't enter an infinite loop when fetching remote
app data

## 0.16.0

### Minor Changes

-   2fb1145f: feat(compute): add support for replicas

### Patch Changes

- d8ceccec: chore(env): remove deprecated `NHOST_BACKEND_URL`
environment variable

## 0.15.2

### Patch Changes

-   84b84ab7: fix(projects): filter projects by workspace

## 0.15.1

### Patch Changes

-   2faf7907: chore(deps): bump `graphql-request` to v6
-   f1b5a944: chore(deps): bump `@vitejs/plugin-react` to v4
-   7f1785ac: chore(deps): bump `@types/react` to v18.0.37
    -   @nhost/react-apollo@5.0.19

## 0.15.0

### Minor Changes

-   85889ee8: feat(dashboard): add Compute management to the settings

## 0.14.8

### Patch Changes

-   668c8771: chore(dialogs): unify dialog management of payment dialogs

## 0.14.7

### Patch Changes

-   d4ccc656: chore: cleanup unused code
    -   @nhost/react-apollo@5.0.18
    -   @nhost/nextjs@1.13.21

## 0.14.6

### Patch Changes

-   b299cfc9: chore(deps): bump `vitest` to v0.30.0
-   411cb65b: chore(projects): refactor workspace and project hooks
- 43b1b144: chore(deps): bump `@types/react` to v18.0.34 and
`@types/react-dom` to v18.0.11
-   Updated dependencies [43b1b144]
    -   @nhost/react-apollo@5.0.17
    -   @nhost/nextjs@1.13.20

## 0.14.5

### Patch Changes

-   ba0d57ee: fix(i18n): revert i18n library
-   3328ed05: feat(projects): improve overview when there is an error

## 0.14.4

### Patch Changes

-   5e0920ba: chore(deps): bump `next-seo` to v6
-   706c9dc3: chore(deps): bump `@types/react` to 18.0.33
-   99f8f6b3: feat(metrics): show metrics on the overview

## 0.14.3

### Patch Changes

-   @nhost/react-apollo@5.0.16

## 0.14.2

### Patch Changes

-   3cb67300: fix(logs): don't break UI when clearing time picker
-   7453bf3b: feat(projects): show project creator info
-   c166dad0: chore(tests): improve auth page tests
-   6a290bb2: chore(deps): bump `@types/react` to 18.0.32

## 0.14.1

### Patch Changes

-   @nhost/react-apollo@5.0.15
-   @nhost/nextjs@1.13.19

## 0.14.0

### Minor Changes

-   6e1f03ea: feat(dashboard): add support for the Azure AD provider

### Patch Changes

-   1bd2c373: chore(deps): bump `turbo` to 1.8.6
-   d329b621: chore(deps): bump `@types/react` to 18.0.30
-   cb248f0d: fix(tests): avoid name collision in database tests
-   867c8076: chore(deps): bump `@types/react` to 18.0.29

## 0.13.10

### Patch Changes

- e93b06ab: fix(dashboard): remove left margin from workspace list on
mobile
-   1c4806bf: chore(deps): bump `sharp` to 0.32.0
    -   @nhost/react-apollo@5.0.14
    -   @nhost/nextjs@1.13.18

## 0.13.9

### Patch Changes

-   912ed76c: chore(dashboard): bump `@apollo/client` to 3.7.10
-   Updated dependencies [912ed76c]
    -   @nhost/react-apollo@5.0.13

## 0.13.8

### Patch Changes

-   7c127372: chore(dashboard): bump `react-error-boundary` to v4

## 0.13.7

### Patch Changes

- 9130ab12: chore(dashboard): bump `yup` to v1 and `@hookform/resolvers`
to v3

## 0.13.6

### Patch Changes

- 253dd235: using new mutation to create projects + refactor Create
Project page.

## 0.13.5

### Patch Changes

-   @nhost/react-apollo@5.0.12
-   @nhost/nextjs@1.13.17

## 0.13.4

### Patch Changes

-   b48bc034: fix(dashboard): disable new users
-   798e591b: fix(dashboard): show correct date in data grid

## 0.13.3

### Patch Changes

-   bfb4c1a6: chore(dashboard): remove `useAxios` property
-   d8d8394b: Dashboard: allow to override hasura admin secret in docker
-   Updated dependencies [ce1ee40d]
    -   @nhost/nextjs@1.13.16
    -   @nhost/react-apollo@5.0.11

## 0.13.2

### Patch Changes

-   beed2eba: Fix docker entrypoint for dashboard
- 2c8559a3: fix(dashboard): refresh project list after deleting a
project
-   4329d048: chore(dashboard): bump `graphiql` dependencies

## 0.13.1

### Patch Changes

-   cbb1fc5b: chore(dashboard): cleanup GraphQL operations

## 0.13.0

### Minor Changes

-   088584e7: feat(dashboard): add support for custom local subdomains

### Patch Changes

-   2ac90dfd: fix(dashboard): improve mobile responsive layout
-   Updated dependencies [f375eacc]
    -   @nhost/nextjs@1.13.15
    -   @nhost/react-apollo@5.0.10

## 0.12.4

### Patch Changes

-   @nhost/react-apollo@5.0.9
-   @nhost/nextjs@1.13.14

## 0.12.3

### Patch Changes

-   2b1338f7: chore(dashboard): bump `turbo` to 1.8.3
- 5223ee93: fix(dashboard): show correct deployment status on the main
page
-   850a049c: chore(deps): update docker/build-push-action action to v4
-   Updated dependencies [850a049c]
    -   @nhost/nextjs@1.13.13
    -   @nhost/react-apollo@5.0.8

## 0.12.2

### Patch Changes

-   4bf40995: chore(deps): bump `typescript` to `4.9.5`
-   8bb097c9: chore(deps): bump `vitest`
- 35d52aab: chore(deps): replace `cross-fetch` with `isomorphic-unfetch`
-   Updated dependencies [4bf40995]
-   Updated dependencies [8bb097c9]
-   Updated dependencies [35d52aab]
    -   @nhost/react-apollo@5.0.7
    -   @nhost/nextjs@1.13.12

## 0.12.1

### Patch Changes

-   c96d7ccd: fix(dashboard): fix docker builds

## 0.12.0

### Minor Changes

-   d1671210: feat(dashboard): use mimir to manage project configuration

### Patch Changes

-   f65e4de9: chore(deps): bump @graphql-codegen monorepo to v3

## 0.11.20

### Patch Changes

-   4b4f0d01: chore(dashboard): improve dialog management

## 0.11.19

### Patch Changes

-   @nhost/react-apollo@5.0.6
-   @nhost/nextjs@1.13.11

## 0.11.18

### Patch Changes

-   01318860: fix(nhost-js): use correct URL for functions requests
-   Updated dependencies [01318860]
    -   @nhost/react-apollo@5.0.5
    -   @nhost/nextjs@1.13.10

## 0.11.17

### Patch Changes

-   f673adea: fix(dashboard): set correct Content-Type for user creation
-   445d8ef4: chore(deps): bump `@nhost/react-apollo` to 5.0.4
-   445d8ef4: chore(deps): bump `@nhost/nextjs` to 1.13.9
- 0368663d: fix(dashboard): allow permission editing for auth and
storage schemas
-   Updated dependencies [445d8ef4]
-   Updated dependencies [445d8ef4]
    -   @nhost/react-apollo@5.0.4
    -   @nhost/nextjs@1.13.9

## 0.11.16

### Patch Changes

-   b755e908: fix(dashboard): use correct date for last seen
-   2d9145f9: chore(deps): revert GraphQL client
- 1ddf704c: fix(dashboard): don't show false positive message for failed
user creation
    -   @nhost/react-apollo@5.0.3
    -   @nhost/nextjs@1.13.8

## 0.11.15

### Patch Changes

-   @nhost/react-apollo@5.0.2
-   @nhost/nextjs@1.13.7

## 0.11.14

### Patch Changes

- 2cc18dcb: fix(dashboard): prevent permission editor dropdown from
being always open

## 0.11.13

### Patch Changes

- 3343a363: chore(dashboard): bump `@testing-library/react` to v14 and
`@testing-library/dom` to v9
    -   @nhost/react-apollo@5.0.1
    -   @nhost/nextjs@1.13.6

## 0.11.12

### Patch Changes

- 87eda76e: chore(dashboard): bump `@types/react` to v18.0.28 and
`@types/react-dom` to v18.0.11
-   6f0ac570: feat(dashboard): show dashboard version in account menu

## 0.11.11

### Patch Changes

-   bf1e4071: chore(dashboard): bump `react-is` version to `18.2.0`
-   Updated dependencies [bf1e4071]
-   Updated dependencies [5013213b]
    -   @nhost/nextjs@1.13.5
    -   @nhost/react-apollo@4.13.5

## 0.11.10

### Patch Changes

- a37a430b: fix(dashboard): don't break UI when deployments are
unavailable
    -   @nhost/react-apollo@4.13.4
    -   @nhost/nextjs@1.13.4

## 0.11.9

### Patch Changes

-   7b970e68: fix(dashboard): fix header link color

## 0.11.8

### Patch Changes

- f33242f2: feat(dashboard): add new sign up, sign in and reset password
pages

## 0.11.7

### Patch Changes

-   e9c8909c: fix(dashboard): use correct theme color in dark mode

## 0.11.6

### Patch Changes

-   902f486b: fix(dashboard): re-enable Hasura on logs page

## 0.11.5

### Patch Changes

-   1f9720fa: fix(dashboard): apply select permissions properly

## 0.11.4

### Patch Changes

-   deb14b51: fix(dashboard): don't break billing form

## 0.11.3

### Patch Changes

-   @nhost/react-apollo@4.13.3
-   @nhost/nextjs@1.13.3

## 0.11.2

### Patch Changes

-   f143e51d: chore(dashboard): pin Turborepo to 1.6.3

## 0.11.1

### Patch Changes

-   c2b5a41a: chore(dashboard): select system colors by default

## 0.11.0

### Minor Changes

-   1ebaf429: feat(dashboard): introduce Dark Mode 🌚

### Patch Changes

- 63b445c4: fixed duplicated logs bug and made to date count during live
mode

## 0.10.1

### Patch Changes

-   e146d32e: chore(deps): update dependency @types/react to v18.0.27
-   59347fcd: correct allowed role name
-   5b65cac9: updated authentication documentation
-   963f9b5e: feat(dashboard): include project info in feedback

## 0.10.0

### Minor Changes

-   ed4c7801: chore(dashboard): remove Functions section

## 0.9.10

### Patch Changes

-   4e2f8ccd: fix(dashboard): don't break Auth page in local mode

## 0.9.9

### Patch Changes

-   31abbe5f: fix(dashboard): enable toggle when settings are filled in

## 0.9.8

### Patch Changes

- 5bdd31ad: chore(dashboard): list fewer images per page on the Storage
page
- 5121851c: fix(dashboard): don't throw validation error for valid
permission rules

## 0.9.7

### Patch Changes

-   c126b20d: fix(dashboard): correct redeployment button

## 0.9.6

### Patch Changes

-   36c3519c: feat(dashboard): retrigger deployments

## 0.9.5

### Patch Changes

- 200e9f77: chore(deps): update dependency @types/react-dom to v18.0.10
-   Updated dependencies [200e9f77]
    -   @nhost/nextjs@1.13.2
    -   @nhost/react-apollo@4.13.2

## 0.9.4

### Patch Changes

- dbd3ded5: fix(dashboard): workspaces creation, new form, correct
redirects.

## 0.9.3

### Patch Changes

-   85f0f943: fix(dashboard): don't break the table creation process

## 0.9.2

### Patch Changes

-   Updated dependencies [d42c27ae]
-   Updated dependencies [927be4a2]
    -   @nhost/nextjs@1.13.1
    -   @nhost/react-apollo@4.13.1

## 0.9.1

### Patch Changes

-   d0f80811: fix(dashboard): don't show error when signing out the user

## 0.9.0

### Minor Changes

- d92891b2: feat(dashboard): add Permission Editor to the Database
section

### Patch Changes

-   3d379128: fix(dashboard): create new user
    -   @nhost/react-apollo@4.13.0
    -   @nhost/nextjs@1.13.0

## 0.8.1

### Patch Changes

-   7cadd944: fix(dashboard): display Twitter provider settings

## 0.8.0

### Minor Changes

-   9a1aa7bb: add functions to the log dashboard
-   f29abe62: feat(dashboard): Users Management v2

### Patch Changes

-   7766624b: feat(dashboard): add JWT secret editor modal
    -   @nhost/react-apollo@4.12.1
    -   @nhost/nextjs@1.12.1

## 0.7.13

### Patch Changes

-   dd0738d5: fix(dashboard): provisioning status polling

## 0.7.12

### Patch Changes

-   b21222b3: chore(deps): update dependency @types/node to v16
-   9e0486a3: fix(dashboard): close modals when navigating
-   Updated dependencies [b21222b3]
-   Updated dependencies [65687bee]
-   Updated dependencies [54df0df4]
    -   @nhost/nextjs@1.12.0
    -   @nhost/react-apollo@4.12.0

## 0.7.11

### Patch Changes

-   d6527122: fix(dashboard): use correct service URLs

## 0.7.10

### Patch Changes

-   Updated dependencies [57db5b83]
    -   @nhost/nextjs@1.11.0
    -   @nhost/nhost-js@1.7.0
    -   @nhost/react@0.17.0
    -   @nhost/react-apollo@4.11.0

## 0.7.9

### Patch Changes

- a6d31dc2: fix(dashboard): don't break the UI when project is not
loaded yet

## 0.7.8

### Patch Changes

- 7f251111: Use `NhostProvider` instead of `NhostReactProvider` and
`NhostNextProvider`

    `NhostReactProvider` and `NhostNextProvider` are now deprecated

-   f4d70f88: fix(dashboard): do not break when region is nullish

- 4a9471cc: Windows Live Provider displayed link updated to match
backend url

- 594488e4: fix(dashboard): do not show error when submitting Apple
provider settings

-   Updated dependencies [7f251111]
    -   @nhost/nextjs@1.10.0
    -   @nhost/react@0.16.0
    -   @nhost/react-apollo@4.10.0

## 0.7.7

### Patch Changes

-   80b604ad: fix(dashboard): use correct Hasura slug

## 0.7.6

### Patch Changes

-   2d2beb53: fix(dashboard): prevent error on GraphQL page
-   ac8efcbd: chore(dashboard): deprecate old DNS name

## 0.7.5

### Patch Changes

-   132a4f4b: chore(dashboard): remove unused dependencies
- 132a4f4b: chore(deps): synchronize @types/react-dom and @types/react
versions
-   db57572f: fix(dashboard): correct section paddings when no env vars
-   Updated dependencies [132a4f4b]
    -   @nhost/react@0.15.2
    -   @nhost/react-apollo@4.9.2
    -   @nhost/nextjs@1.9.3

## 0.7.4

### Patch Changes

-   34d85e54: chore(deps): update dependency critters to ^0.0.16
- 9b93cf95: chore(deps): update dependency @netlify/functions to ^0.11.0
-   e0439030: chore(deps): update dependency @types/react-dom to v18.0.9
-   Updated dependencies [82124329]
    -   @nhost/nextjs@1.9.2

## 0.7.3

### Patch Changes

-   a1193da4: fix(dashboard): remove character limit from env var inputs

## 0.7.2

### Patch Changes

-   44f13f62: chore(dashboard): cleanup unused files

## 0.7.1

### Patch Changes

- e01cb2ed: chore(dashboard): change settings sidebar menu item density

## 0.7.0

### Minor Changes

- db342f45: chore(dashboard): refactor Roles and Permissions settings
sections
-   8b9fa0b1: feat(dashboard): add Environment Variables page

### Patch Changes

-   Updated dependencies [66b4f3d0]
-   Updated dependencies [2e6923dc]
-   Updated dependencies [ef117c28]
-   Updated dependencies [aebb8225]
    -   @nhost/core@0.9.4
    -   @nhost/nhost-js@1.6.2
    -   @nhost/nextjs@1.9.1
    -   @nhost/react@0.15.1
    -   @nhost/react-apollo@4.9.1

## 0.6.0

### Minor Changes

-   eef9c914: feat(dashboard): add Roles and Permissions page

## 0.5.0

### Minor Changes

-   a48dd5bf: feat(dashboard): make backend port configurable

## 0.4.3

### Patch Changes

-   5de965d9: fix(dashboard): alphabetic ordering of providers
-   b9087a4a: fix(dashboard): console -> dashboard terminology
-   ca012d79: docs(workos): WorkOS Docs

## 0.4.2

### Patch Changes

-   89bd37bc: fix(dashboard): correct redirect URL input opacity
-   Updated dependencies [4601d84e]
-   Updated dependencies [843087cb]
    -   @nhost/react@0.15.0
    -   @nhost/nextjs@1.9.0
    -   @nhost/react-apollo@4.9.0

## 0.4.1

### Patch Changes

-   766cb612: fix(dashboard): correct redirect URL for oauth providers
-   Updated dependencies [53bdc294]
-   Updated dependencies [f2aaff05]
    -   @nhost/nextjs@1.8.3
    -   @nhost/core@0.9.3
    -   @nhost/react@0.14.3
    -   @nhost/nhost-js@1.6.1
    -   @nhost/react-apollo@4.8.3

## 0.4.0

### Minor Changes

-   9211743d: feat(dashboard): migrate Settings page features

## 0.3.0

### Minor Changes

-   73da6a67: fix(dashboard): avoid using BACKEND_URL locally

## 0.2.0

### Minor Changes

-   db118f97: feat(dashboard): generate Docker image

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-29 15:11:31 +01:00
Hassan Ben Jobrane
65ca5deb4c chore: add run autoscaler and GRPC port config under organization projects (#2959) 2024-10-29 14:58:00 +01:00
Hassan Ben Jobrane
e42832a012 fix: include organization projects in run one click install project selector form (#2957) 2024-10-29 14:03:42 +01:00
44 changed files with 3008 additions and 126 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/dashboard",
"version": "2.0.5",
"version": "2.0.7",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",

View File

@@ -18,6 +18,7 @@ import PinnedMainNav from '@/components/layout/MainNav/PinnedMainNav';
import { OrgStatus } from '@/features/orgs/components/OrgStatus';
import { useIsHealthy } from '@/features/orgs/projects/common/hooks/useIsHealthy';
import { useNotFoundRedirect } from '@/features/projects/common/hooks/useNotFoundRedirect';
import { cn } from '@/lib/utils';
import Image from 'next/image';
import { useRouter } from 'next/router';
import {
@@ -90,7 +91,7 @@ export default function AuthenticatedLayout({
<Container
rootClassName="h-full"
className="grid justify-center max-w-md grid-flow-row gap-2 my-12 text-center"
className="my-12 grid max-w-md grid-flow-row justify-center gap-2 text-center"
>
<div className="mx-auto">
<Image
@@ -127,18 +128,23 @@ export default function AuthenticatedLayout({
}
return (
<BaseLayout className="flex flex-col h-full" {...props}>
<BaseLayout className="flex h-full flex-col" {...props}>
<Header className="flex py-1" />
<div
className="relative flex flex-row h-full overflow-x-hidden"
className="relative flex h-full flex-row overflow-hidden"
ref={setMainNavContainer}
>
{mainNavPinned && isMdOrLarger && <PinnedMainNav />}
<div className="relative flex flex-row w-full h-full bg-accent">
<div
className={cn(
'relative flex h-full w-full flex-row bg-accent',
mainNavPinned && isMdOrLarger ? 'overflow-x-auto' : '',
)}
>
{(!mainNavPinned || !isMdOrLarger) && (
<div className="flex justify-center w-6 h-full">
<div className="flex h-full w-6 justify-center">
<MainNav container={mainNavContainer} />
</div>
)}
@@ -148,7 +154,7 @@ export default function AuthenticatedLayout({
className: 'flex flex-col items-center',
}}
>
<div className="flex flex-col w-full h-full">
<div className="flex h-full w-full flex-col overflow-auto">
<OrgStatus />
{children}
</div>

View File

@@ -53,7 +53,7 @@ export default function PinnedMainNav() {
}
return (
<div className="h-full w-full border-r p-0 sm:max-w-[310px]">
<div className="flex h-full w-full flex-shrink-0 flex-col border-r p-0 sm:max-w-[310px]">
<div className="flex justify-end w-full h-12 p-1 border-b bg-background">
<Button
variant="ghost"

View File

@@ -17,7 +17,7 @@ export default function SettingsLayout({ children }: SettingsLayoutProps) {
return (
<Box
sx={{ backgroundColor: 'background.default' }}
className="flex flex-col flex-auto w-full overflow-x-hidden overflow-y-auto"
className="flex flex-col flex-auto w-full h-full overflow-x-hidden overflow-y-auto"
>
<Box
sx={{ backgroundColor: 'background.default' }}

View File

@@ -75,6 +75,7 @@ export default function ServiceForm({
memory: 128,
},
replicas: 1,
autoscaler: null,
},
reValidateMode: 'onSubmit',
resolver: yupResolver(validationSchema),
@@ -98,7 +99,7 @@ export default function ServiceForm({
return uuidv4();
}, [serviceID]);
const privateRegistryImage = `registry.${project.region.name}.${project.region.domain}/${newServiceID}`;
const privateRegistryImage = `registry.${project?.region.name}.${project?.region.domain}/${newServiceID}`;
let initialImageType: 'public' | 'private' | 'nhost' = 'public';
@@ -166,6 +167,11 @@ export default function ServiceForm({
capacity: item.capacity,
})),
replicas: sanitizedValues.replicas,
autoscaler: sanitizedValues.autoscaler
? {
maxReplicas: sanitizedValues.autoscaler?.maxReplicas,
}
: null,
},
environment: sanitizedValues.environment.map((item) => ({
name: item.name,
@@ -329,7 +335,7 @@ export default function ServiceForm({
<Tooltip title="Name of the service, must be unique per project.">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -353,7 +359,7 @@ export default function ServiceForm({
<Tooltip title="Command to run when to start the service. This is optional as the image may already have a baked-in command.">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -408,7 +414,7 @@ export default function ServiceForm({
{createServiceFormError && (
<Alert
severity="error"
className="grid grid-flow-col items-center justify-between px-4 py-3"
className="grid items-center justify-between grid-flow-col px-4 py-3"
>
<span className="text-left">
<strong>Error:</strong> {createServiceFormError.message}

View File

@@ -26,6 +26,12 @@ export const validationSchema = Yup.object({
memory: Yup.number().min(MIN_SERVICES_MEM).max(MAX_SERVICES_MEM).required(),
}),
replicas: Yup.number().min(0).max(MAX_SERVICE_REPLICAS).required(),
autoscaler: Yup.object()
.shape({
maxReplicas: Yup.number().min(0).max(MAX_SERVICE_REPLICAS),
})
.nullable()
.default(undefined),
ports: Yup.array().of(
Yup.object().shape({
port: Yup.number().required(),

View File

@@ -9,7 +9,7 @@ import { Option } from '@/components/ui/v2/Option';
import { Select } from '@/components/ui/v2/Select';
import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { InfoCard } from '@/features/projects/overview/components/InfoCard';
import { PortTypes } from '@/features/services/components/ServiceForm/components/PortsFormSection/PortsFormSectionTypes';
import { type ServiceFormValues } from '@/features/services/components/ServiceForm/ServiceFormTypes';
@@ -19,7 +19,7 @@ import { useFieldArray, useFormContext, useWatch } from 'react-hook-form';
export default function PortsFormSection() {
const form = useFormContext<ServiceFormValues>();
const { currentProject } = useCurrentWorkspaceAndProject();
const { project } = useProject();
const {
register,
@@ -38,12 +38,13 @@ export default function PortsFormSection() {
const showURL = (index: number) =>
formValues.subdomain &&
formValues.ports[index]?.type === PortTypes.HTTP &&
(formValues.ports[index]?.type === PortTypes.HTTP ||
formValues.ports[index]?.type === PortTypes.GRPC) &&
formValues.ports[index]?.publish;
return (
<Box className="p-4 space-y-4 rounded border-1">
<Box className="flex flex-row items-center justify-between ">
<Box className="flex flex-row items-center justify-between">
<Box className="flex flex-row items-center space-x-2">
<Text variant="h4" className="font-semibold">
Ports
@@ -106,7 +107,7 @@ export default function PortsFormSection() {
},
}}
>
{['http', 'tcp', 'udp']?.map((portType) => (
{['http', 'tcp', 'udp', 'grpc']?.map((portType) => (
<Option key={portType} value={portType}>
{portType}
</Option>
@@ -137,8 +138,8 @@ export default function PortsFormSection() {
title="URL"
value={getRunServicePortURL(
formValues?.subdomain,
currentProject?.region.name,
currentProject?.region.domain,
project?.region.name,
project?.region.domain,
formValues.ports[index],
)}
/>

View File

@@ -1,16 +1,32 @@
import { Box } from '@/components/ui/v2/Box';
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
import { Slider } from '@/components/ui/v2/Slider';
import { InfoOutlinedIcon } from '@/components/ui/v2/icons/InfoOutlinedIcon';
import { Input } from '@/components/ui/v2/Input';
import { Switch } from '@/components/ui/v2/Switch';
import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip';
import { MAX_SERVICE_REPLICAS } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import type { ServiceFormValues } from '@/features/services/components/ServiceForm/ServiceFormTypes';
import { useState } from 'react';
import { useFormContext, useWatch } from 'react-hook-form';
export default function ReplicasFormSection() {
const { setValue } = useFormContext<ServiceFormValues>();
const {
register,
setValue,
trigger: triggerValidation,
} = useFormContext<ServiceFormValues>();
const { replicas, autoscaler } = useWatch<ServiceFormValues>();
const [autoscalerEnabled, setAutoscalerEnabled] = useState(!!autoscaler);
const { replicas } = useWatch<ServiceFormValues>();
const toggleAutoscalerEnabled = async (enabled: boolean) => {
setAutoscalerEnabled(enabled);
if (!enabled) {
setValue('autoscaler', null);
} else {
setValue('autoscaler.maxReplicas', 10);
}
};
const handleReplicasChange = (value: string) => {
const updatedReplicas = parseInt(value, 10);
@@ -20,42 +36,95 @@ export default function ReplicasFormSection() {
// TODO Trigger revalidate storage
};
const handleMaxReplicasChange = (value: string) => {
const updatedReplicas = parseInt(value, 10);
setValue('autoscaler.maxReplicas', updatedReplicas, { shouldDirty: true });
triggerValidation('autoscaler.maxReplicas');
};
return (
<Box className="space-y-4 rounded border-1 p-4">
<Box className="p-4 space-y-4 rounded border-1">
<Box className="flex flex-row items-center space-x-2">
<Text variant="h4" className="font-semibold">
Replicas ({replicas})
</Text>
<Tooltip
title={
<span>
Number of replicas for the service. Multiple replicas can process
requests/work in parallel. You can set replicas to 0 to pause the
service. Refer to{' '}
<Text className="text-white">
Learn more about{' '}
<a
target="_blank"
rel="noopener noreferrer"
href="https://docs.nhost.io/run/resources"
href="https://docs.nhost.io/platform/service-replicas"
className="underline"
>
resources
</a>{' '}
for more information.
</span>
Service Replicas
</a>
</Text>
}
>
<InfoIcon aria-label="Info" className="h-4 w-4" color="primary" />
<InfoIcon aria-label="Info" className="w-4 h-4" color="primary" />
</Tooltip>
</Box>
<Slider
value={replicas}
onChange={(_event, value) => handleReplicasChange(value.toString())}
min={0}
max={MAX_SERVICE_REPLICAS}
step={1}
aria-label="Replicas"
marks
/>
<Box className="flex flex-col justify-between gap-4 lg:flex-row">
<Box className="flex flex-col gap-4 lg:flex-row lg:gap-8">
<Box className="flex flex-row items-center gap-2">
<Text className="w-28 lg:w-auto">Replicas</Text>
<Input
{...register('replicas')}
onChange={(event) => handleReplicasChange(event.target.value)}
type="number"
id="replicas"
placeholder="Replicas"
className="max-w-28"
hideEmptyHelperText
fullWidth
onWheel={(e) => (e.target as HTMLInputElement).blur()}
autoComplete="off"
slotProps={{
inputRoot: {
min: 0,
},
}}
/>
</Box>
<Box className="flex flex-row items-center gap-2">
<Text className="w-28 text-nowrap lg:w-auto">Max Replicas</Text>
<Input
value={autoscaler?.maxReplicas}
onChange={(event) => handleMaxReplicasChange(event.target.value)}
type="number"
id="maxReplicas"
placeholder="10"
disabled={!autoscalerEnabled}
className="max-w-28"
hideEmptyHelperText
fullWidth
onWheel={(e) => (e.target as HTMLInputElement).blur()}
autoComplete="off"
slotProps={{
inputRoot: {
min: 0,
},
}}
/>
</Box>
</Box>
<Box className="flex flex-row items-center gap-3">
<Switch
checked={autoscalerEnabled}
onChange={(e) => toggleAutoscalerEnabled(e.target.checked)}
className="self-center"
/>
<Text>Autoscaler</Text>
<Tooltip title="Enable autoscaler to automatically provision extra run service replicas when needed.">
<InfoOutlinedIcon className="w-4 h-4 text-black" />
</Tooltip>
</Box>
</Box>
</Box>
);
}

View File

@@ -50,7 +50,7 @@ export default function ServicesList({
openDrawer({
title: (
<Box className="flex flex-row items-center space-x-2">
<CubeIcon className="h-5 w-5" />
<CubeIcon className="w-5 h-5" />
<Text>Edit {service.config?.name ?? 'unset'}</Text>
</Box>
),
@@ -75,6 +75,7 @@ export default function ServicesList({
memory: 128,
},
replicas: service.config?.resources?.replicas,
autoscaler: service?.config?.resources?.autoscaler,
storage: service.config?.resources?.storage,
}}
onSubmit={() => onCreateOrUpdate()}
@@ -109,13 +110,13 @@ export default function ServicesList({
onClick={() => viewService(service)}
>
<Box
className="flex w-full flex-row justify-between"
className="flex flex-row justify-between w-full"
sx={{
backgroundColor: 'transparent',
}}
>
<div className="flex flex-1 flex-row items-center space-x-4">
<CubeIcon className="h-5 w-5" />
<div className="flex flex-row items-center flex-1 space-x-4">
<CubeIcon className="w-5 h-5" />
<div className="flex flex-col">
<Text variant="h4" className="font-semibold">
{service.config?.name ?? 'unset'}
@@ -131,7 +132,7 @@ export default function ServicesList({
</div>
</div>
<div className="hidden flex-row items-center space-x-2 md:flex">
<div className="flex-row items-center hidden space-x-2 md:flex">
<Text variant="subtitle1" className="font-mono text-xs">
{service.id ?? service.serviceID}
</Text>
@@ -144,7 +145,7 @@ export default function ServicesList({
}}
aria-label="Service Id"
>
<CopyIcon className="h-4 w-4" />
<CopyIcon className="w-4 h-4" />
</IconButton>
</div>
</Box>
@@ -174,7 +175,7 @@ export default function ServicesList({
onClick={() => viewService(service)}
className="z-50 grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
>
<UserIcon className="h-4 w-4" />
<UserIcon className="w-4 h-4" />
<Text className="font-medium">View Service</Text>
</Dropdown.Item>
<Divider component="li" />
@@ -187,7 +188,7 @@ export default function ServicesList({
}}
disabled={!isPlatform}
>
<TrashIcon className="h-4 w-4" />
<TrashIcon className="w-4 h-4" />
<Text className="font-medium" color="error">
Delete Service
</Text>

View File

@@ -0,0 +1,67 @@
import { render, screen } from '@/tests/testUtils';
import type { Column } from 'react-table';
import { expect, test } from 'vitest';
import DataGrid from './DataGrid';
interface MockDataDetails {
id: number;
name: string;
}
const mockColumns: Column<MockDataDetails>[] = [
{ id: 'id', Header: 'ID', accessor: 'id' },
{ id: 'name', Header: 'Name', accessor: 'name' },
];
const mockData: MockDataDetails[] = [
{ id: 1, name: 'foo' },
{ id: 2, name: 'bar' },
];
test('should render an empty state if columns are not available', () => {
render(<DataGrid columns={[]} data={[]} />);
expect(screen.getByText(/columns not found/i)).toBeInTheDocument();
});
test('should render columns and empty state message if data is unavailable', () => {
render(<DataGrid columns={mockColumns} data={[]} />);
expect(screen.getByRole('table')).toBeInTheDocument();
expect(screen.getByRole('columnheader', { name: /id/i })).toBeInTheDocument();
expect(
screen.getByRole('columnheader', { name: /name/i }),
).toBeInTheDocument();
expect(screen.getByText(/no data is available/i)).toBeInTheDocument();
});
test('should render custom empty state message if data is unavailable', () => {
const customEmptyStateMessage = 'custom empty state message';
render(
<DataGrid
columns={mockColumns}
data={[]}
emptyStateMessage={customEmptyStateMessage}
/>,
);
expect(screen.getByText(customEmptyStateMessage)).toBeInTheDocument();
});
test('should display a loading indicator', async () => {
render(<DataGrid columns={mockColumns} data={[]} loading />);
// Activity indicator is not immediately displayed, so we need to wait
expect(await screen.findByRole('progressbar')).toBeInTheDocument();
});
test('should render data if provided', () => {
render(<DataGrid columns={mockColumns} data={mockData} />);
expect(screen.getAllByRole('row')).toHaveLength(2);
expect(screen.getByRole('cell', { name: /1/i })).toBeInTheDocument();
expect(screen.getByRole('cell', { name: /foo/i })).toBeInTheDocument();
});

View File

@@ -0,0 +1,185 @@
import type { UseDataGridOptions } from '@/components/dataGrid/DataGrid/useDataGrid';
import { DataGridBody } from '@/components/dataGrid/DataGridBody';
import { DataGridConfigProvider } from '@/components/dataGrid/DataGridConfigProvider';
import { DataGridFrame } from '@/components/dataGrid/DataGridFrame';
import type { DataGridHeaderProps } from '@/components/dataGrid/DataGridHeader';
import { DataGridHeader } from '@/components/dataGrid/DataGridHeader';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Box } from '@/components/ui/v2/Box';
import { DataBrowserEmptyState } from '@/features/database/dataGrid/components/DataBrowserEmptyState';
import type { DataBrowserGridColumn } from '@/features/database/dataGrid/types/dataBrowser';
import type { ForwardedRef } from 'react';
import { forwardRef, useEffect, useRef } from 'react';
import mergeRefs from 'react-merge-refs';
import type { Column, Row, SortingRule, TableOptions } from 'react-table';
import { twMerge } from 'tailwind-merge';
import useDataGrid from './useDataGrid';
export interface DataGridProps<TColumnData extends object>
extends Omit<UseDataGridOptions<TColumnData>, 'tableRef'> {
/**
* Available columns.
*/
columns: Column<TColumnData>[];
/**
* Data to be displayed in the table.
*/
data: any[];
/**
* Text to be displayed when no data is available in the data grid.
*
* @default null
*/
emptyStateMessage?: string;
/**
* Additional configuration options for the `react-table` hook.
*/
options?: Omit<TableOptions<TColumnData>, 'columns' | 'data'>;
/**
* Additional data grid controls. This component will be part of the Data Grid
* context, so it can use Data Grid configuration.
*/
controls?:
| React.ReactNode
| ((selectedFlatRows: Row<TColumnData>[]) => React.ReactNode);
/**
* Function to be called when columns are sorted in the table.
*/
onSort?: (args: SortingRule<TColumnData>[]) => void;
/**
* Function to be called when the user wants to insert a new row.
*/
onInsertRow?: VoidFunction;
/**
* Function to be called when the user wants to insert a new column.
*/
onInsertColumn?: VoidFunction;
/**
* Function to be called when the user wants to remove a column.
*/
onRemoveColumn?: (column: DataBrowserGridColumn<TColumnData>) => void;
/**
* Function to be called when the user wants to edit a column.
*/
onEditColumn?: (column: DataBrowserGridColumn<TColumnData>) => void;
/**
* Determines whether or not data is loading.
*/
loading?: boolean;
/**
* Class name to be applied to the data grid.
*/
className?: string;
/**
* Sort configuration.
*/
sortBy?: SortingRule<TColumnData>[];
/**
* Props to be passed to the `DataGridHeader` component.
*/
headerProps?: DataGridHeaderProps<TColumnData>;
}
function DataGrid<TColumnData extends object>(
{
columns,
data,
allowSelection,
allowSort,
allowResize,
emptyStateMessage,
options = {},
headerProps,
controls,
sortBy,
onSort,
onInsertRow,
onInsertColumn,
onEditColumn,
onRemoveColumn,
loading,
className,
}: DataGridProps<TColumnData>,
ref: ForwardedRef<HTMLDivElement>,
) {
const tableRef = useRef<HTMLDivElement>();
const { toggleAllRowsSelected, setSortBy, ...dataGridProps } =
useDataGrid<TColumnData>({
columns: columns || [],
data: data || [],
allowSelection,
allowSort,
allowResize,
...options,
});
useEffect(() => {
if (!sortBy && setSortBy) {
setSortBy([]);
}
}, [setSortBy, sortBy]);
useEffect(() => {
if (onSort && allowSort) {
onSort(dataGridProps.state.sortBy);
if (toggleAllRowsSelected) {
toggleAllRowsSelected(false);
}
}
}, [allowSort, dataGridProps.state.sortBy, onSort, toggleAllRowsSelected]);
return (
<DataGridConfigProvider
toggleAllRowsSelected={toggleAllRowsSelected}
setSortBy={setSortBy}
tableRef={tableRef}
{...dataGridProps}
>
<>
{controls}
{columns.length === 0 && !loading && (
<DataBrowserEmptyState
title="Columns not found"
description="Please create a column before adding data to the table."
/>
)}
{columns.length > 0 && (
<Box
ref={mergeRefs([ref, tableRef])}
sx={{ backgroundColor: 'background.default' }}
className={twMerge(
'overflow-x-auto',
!loading && 'h-full',
className,
)}
>
<DataGridFrame>
<DataGridHeader
onInsertColumn={onInsertColumn}
onEditColumn={onEditColumn}
onRemoveColumn={onRemoveColumn}
{...headerProps}
/>
<DataGridBody
emptyStateMessage={emptyStateMessage}
loading={loading}
onInsertRow={onInsertRow}
allowInsertColumn={Boolean(onRemoveColumn)}
/>
</DataGridFrame>
</Box>
)}
{loading && <ActivityIndicator delay={1000} className="my-4" />}
</>
</DataGridConfigProvider>
);
}
export default forwardRef(DataGrid) as <TColumnData extends object>(
props: DataGridProps<TColumnData> & { ref?: ForwardedRef<HTMLDivElement> },
) => ReturnType<typeof DataGrid>;

View File

@@ -0,0 +1,4 @@
export * from './DataGrid';
export { default as DataGrid } from './DataGrid';
export * from './useDataGrid';
export { default as useDataGrid } from './useDataGrid';

View File

@@ -0,0 +1,110 @@
import { Checkbox } from '@/components/ui/v2/Checkbox';
import type { MutableRefObject } from 'react';
import { useMemo } from 'react';
import type { PluginHook, TableInstance, TableOptions } from 'react-table';
import {
useBlockLayout,
useResizeColumns,
useRowSelect,
useSortBy,
useTable,
} from 'react-table';
export interface UseDataGridBaseOptions {
/**
* Determines whether data grid columns are selectable.
*
* @default false
*/
allowSelection?: boolean;
/**
* Determines whether data grid columns are sortable.
*
* @default false
*/
allowSort?: boolean;
/**
* Determine whether data grid columns are resizable.
*
* @default false
*/
allowResize?: boolean;
/**
* Reference to the data grid root element.
*/
tableRef?: MutableRefObject<HTMLDivElement>;
}
export type UseDataGridOptions<T extends object = {}> = TableOptions<T> &
UseDataGridBaseOptions;
export type UseDataGridReturn<T extends object = {}> = TableInstance<T> &
UseDataGridBaseOptions;
export default function useDataGrid<T extends object>(
{ allowSelection, allowSort, allowResize, ...options }: UseDataGridOptions<T>,
...plugins: PluginHook<T>[]
): UseDataGridReturn<T> {
const defaultColumn = useMemo(
() => ({
width: 32,
minWidth: 32,
Cell: ({ value }: { value: any }) => (
<span className="truncate">
{typeof value === 'object' ? JSON.stringify(value) : value}
</span>
),
}),
[],
);
const pluginHooks = [
useBlockLayout,
useResizeColumns,
useSortBy,
useRowSelect,
];
const tableData = useTable<T>(
{
defaultColumn,
...options,
},
...pluginHooks,
...plugins,
(hooks) =>
allowSelection
? hooks.visibleColumns.push((columns) => [
{
id: 'selection',
Header: ({ rows, getToggleAllRowsSelectedProps }: any) => (
<Checkbox
disabled={rows.length === 0}
{...getToggleAllRowsSelectedProps({ style: null })}
style={{
...getToggleAllRowsSelectedProps().style,
cursor: rows.length === 0 ? 'default' : 'pointer',
}}
/>
),
Cell: ({ row }: any) => {
const originalValue = row.original as any;
return (
<Checkbox
{...row.getToggleRowSelectedProps()}
// disable selection if row is just a upload preview
checked={originalValue.uploading ? false : row.isSelected}
disabled={originalValue.uploading}
/>
);
},
disableSortBy: true,
disableResizing: true,
},
...columns,
])
: hooks.visibleColumns,
);
return { ...tableData, allowSort, allowResize, allowSelection };
}

View File

@@ -0,0 +1,315 @@
import type { DataGridProps } from '@/components/dataGrid/DataGrid';
import { DataGridCell } from '@/components/dataGrid/DataGridCell';
import { useDataGridConfig } from '@/components/dataGrid/DataGridConfigProvider';
import type { BoxProps } from '@/components/ui/v2/Box';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import type { DataBrowserGridColumn } from '@/features/database/dataGrid/types/dataBrowser';
import type { DetailedHTMLProps, HTMLProps, KeyboardEvent } from 'react';
import { Fragment, useMemo, useRef } from 'react';
import type { Row } from 'react-table';
import { twMerge } from 'tailwind-merge';
export interface DataGridBodyProps<T extends object>
extends Omit<
DetailedHTMLProps<HTMLProps<HTMLDivElement>, HTMLDivElement>,
'children'
>,
Pick<DataGridProps<T>, 'onInsertRow' | 'emptyStateMessage' | 'loading'> {
/**
* Determines whether column insertion is allowed.
*/
allowInsertColumn?: boolean;
}
interface InsertPlaceholderTableRowProps extends BoxProps {
/**
* Function to be called when the user wants to insert a new row.
*/
onInsertRow: VoidFunction;
}
function InsertPlaceholderTableRow({
onInsertRow,
...props
}: InsertPlaceholderTableRowProps) {
return (
<Box className="h-12 border-b-1 border-r-1" {...props}>
<Button
onClick={onInsertRow}
variant="borderless"
color="secondary"
className="h-full w-full justify-start rounded-none px-2 py-3 text-xs font-normal hover:shadow-none focus:shadow-none focus:outline-none"
startIcon={
<PlusIcon className="h-4 w-4" sx={{ color: 'text.secondary' }} />
}
>
Insert New Row
</Button>
</Box>
);
}
// TODO: Get rid of Data Browser related code from here. This component should
// be generic and not depend on Data Browser related data types and logic.
export default function DataGridBody<T extends object>({
emptyStateMessage = 'No data is available',
loading,
onInsertRow,
allowInsertColumn,
...props
}: DataGridBodyProps<T>) {
const { getTableBodyProps, totalColumnsWidth, rows, prepareRow, columns } =
useDataGridConfig<T>();
const SELECTION_CELL_WIDTH = 32;
const ADD_COLUMN_CELL_WIDTH = 100;
const bodyRef = useRef<HTMLDivElement>();
const primaryAndUniqueKeys = useMemo(
() =>
columns
.filter(
(column: DataBrowserGridColumn<T>) =>
column.isPrimary || column.isUnique,
)
.map((column) => column.id),
[columns],
);
function handleKeyDown(event: KeyboardEvent<HTMLDivElement>, row: Row<T>) {
const { id: rowId } = row;
const cellId = document.activeElement.id;
const currentRow = bodyRef.current.children.namedItem(rowId);
if (event.key === 'ArrowUp') {
event.preventDefault();
if (!currentRow.previousElementSibling) {
return;
}
const cellInPreviousRow =
currentRow.previousElementSibling.children.namedItem(cellId);
if (cellInPreviousRow instanceof HTMLElement) {
cellInPreviousRow.scrollIntoView({
block: 'nearest',
});
cellInPreviousRow.focus();
}
}
if (event.key === 'ArrowDown') {
event.preventDefault();
if (!currentRow.nextElementSibling) {
return;
}
const cellInNextRow =
currentRow.nextElementSibling.children.namedItem(cellId);
if (cellInNextRow instanceof HTMLElement) {
cellInNextRow.scrollIntoView({ block: 'nearest' });
cellInNextRow.focus();
}
}
if (event.key === 'ArrowLeft' || (event.shiftKey && event.key === 'Tab')) {
let previousFocusableCellInRow: HTMLElement;
let previousFocusableCellInRowFound = false;
currentRow.childNodes.forEach((node) => {
if (node === currentRow.children.namedItem(cellId)) {
previousFocusableCellInRowFound = true;
}
if (
node instanceof HTMLElement &&
node.tabIndex > -1 &&
!previousFocusableCellInRowFound
) {
previousFocusableCellInRow = node;
}
});
if (previousFocusableCellInRow) {
event.preventDefault();
previousFocusableCellInRow.scrollIntoView({
block: 'nearest',
inline: 'center',
});
previousFocusableCellInRow.focus();
}
}
if (
event.key === 'ArrowRight' ||
(!event.shiftKey && event.key === 'Tab')
) {
let nextFocusableCellInRow: HTMLElement;
let nextFocusableCellInRowFound = false;
currentRow.childNodes.forEach((node) => {
if (
node instanceof HTMLElement &&
node.tabIndex > -1 &&
parseInt(node.id, 10) > parseInt(cellId, 10) &&
!nextFocusableCellInRowFound
) {
nextFocusableCellInRowFound = true;
nextFocusableCellInRow = node;
}
});
if (nextFocusableCellInRow) {
event.preventDefault();
nextFocusableCellInRow.scrollIntoView({
block: 'nearest',
inline: 'center',
});
nextFocusableCellInRow.focus();
}
}
}
const getBackgroundCellColor = (
row: Row<T>,
column: DataBrowserGridColumn<T>,
) => {
// Grey out files not uploaded
if (!row.values.isUploaded) {
return 'grey.200';
}
if (column.isDisabled) {
return 'grey.100';
}
return 'background.paper';
};
return (
<div {...getTableBodyProps()} ref={bodyRef} {...props}>
{rows.length === 0 && !loading && (
<div className="flex flex-nowrap pr-5">
{onInsertRow ? (
<InsertPlaceholderTableRow
style={{
width: allowInsertColumn
? totalColumnsWidth + ADD_COLUMN_CELL_WIDTH
: totalColumnsWidth - SELECTION_CELL_WIDTH,
}}
onInsertRow={onInsertRow}
/>
) : (
<Box
className="inline-flex h-12 items-center border-b-1 border-r-1 px-2 py-1.5 text-xs"
sx={{ color: 'text.secondary' }}
style={{
width: allowInsertColumn
? totalColumnsWidth + ADD_COLUMN_CELL_WIDTH
: totalColumnsWidth,
}}
>
{emptyStateMessage}
</Box>
)}
</div>
)}
{rows.map((row, index) => {
let rowKey = index.toString();
if (primaryAndUniqueKeys && primaryAndUniqueKeys.length > 0) {
rowKey = primaryAndUniqueKeys
.map((key) => row.values[key])
.filter(Boolean)
.join('-');
} else {
rowKey = `${index}-${Object.keys(row.values)
.map((key) => String(row.values[key]))
.join('-')}`;
}
prepareRow(row);
const rowProps = row.getRowProps({
style: {
width: allowInsertColumn
? totalColumnsWidth + ADD_COLUMN_CELL_WIDTH
: totalColumnsWidth,
},
});
return (
<Fragment key={rowKey.toString()}>
<div
{...rowProps}
id={row.id}
className="flex scroll-mt-10"
role="row"
onKeyDown={(event) => handleKeyDown(event, row)}
tabIndex={-1}
>
{row.cells.map((cell, cellIndex) => {
const column = cell.column as DataBrowserGridColumn<T>;
const isCellDisabled =
cell.value !== 0 &&
!cell.value &&
column.type !== 'boolean' &&
column.id !== 'selection' &&
column.isDisabled;
return (
<DataGridCell
{...cell.getCellProps({
style: {
display: 'inline-flex',
alignItems: 'center',
},
})}
cell={cell}
sx={{
backgroundColor: getBackgroundCellColor(row, column),
color: isCellDisabled ? 'text.secondary' : 'text.primary',
}}
className={twMerge(
'h-12 font-display text-xs motion-safe:transition-colors',
'border-b-1 border-r-1',
'scroll-ml-8 scroll-mt-[57px]',
column.id === 'selection' &&
'sticky left-0 z-20 justify-center px-0',
)}
isEditable={!column.isDisabled && column.isEditable}
id={cellIndex.toString()}
key={column.id}
>
{cell.render('Cell')}
</DataGridCell>
);
})}
{allowInsertColumn && (
<Box className="h-12 w-25 border-b-1 border-r-1" />
)}
</div>
{onInsertRow && index === rows.length - 1 && (
<InsertPlaceholderTableRow
{...rowProps}
key=""
onInsertRow={onInsertRow}
/>
)}
</Fragment>
);
})}
</div>
);
}

View File

@@ -0,0 +1,2 @@
export * from './DataGridBody';
export { default as DataGridBody } from './DataGridBody';

View File

@@ -0,0 +1,121 @@
import type { CommonDataGridCellProps } from '@/components/dataGrid/DataGridCell';
import { useDataGridCell } from '@/components/dataGrid/DataGridCell';
import { ReadOnlyToggle } from '@/components/presentational/ReadOnlyToggle';
import { Dropdown } from '@/components/ui/v2/Dropdown';
import type { KeyboardEvent as ReactKeyboardEvent, MouseEvent } from 'react';
import { twMerge } from 'tailwind-merge';
export type DataGridBooleanCellProps<TData extends object> =
CommonDataGridCellProps<TData, boolean | null>;
export default function DataGridBooleanCell<TData extends object>({
onSave,
optimisticValue,
temporaryValue,
onTemporaryValueChange,
cell: {
column: { isNullable },
},
}: DataGridBooleanCellProps<TData>) {
const {
inputRef,
isEditing,
focusCell,
editCell,
cancelEditCell,
isSelected,
} = useDataGridCell<HTMLInputElement>();
async function handleMenuClick(
event: MouseEvent<HTMLLIElement> | ReactKeyboardEvent<HTMLLIElement>,
value: boolean | null,
) {
event.stopPropagation();
await onSave(value);
cancelEditCell();
}
async function handleMenuKeyDown(event: ReactKeyboardEvent<HTMLDivElement>) {
if (
event.key === 'ArrowLeft' ||
event.key === 'ArrowRight' ||
event.key === 'ArrowUp' ||
event.key === 'ArrowDown'
) {
event.stopPropagation();
}
// We need to restore the temporary value, because editing was cancelled
if (event.key === 'Escape' && onTemporaryValueChange) {
event.stopPropagation();
onTemporaryValueChange(optimisticValue);
cancelEditCell();
}
if (event.key === 'Tab' && onSave) {
await onSave(temporaryValue);
cancelEditCell();
}
}
function handleTemporaryValueChange(value: boolean | null) {
if (onTemporaryValueChange) {
onTemporaryValueChange(value);
}
}
return isSelected ? (
<Dropdown.Root id="boolean-data-editor" className="h-full w-full">
<Dropdown.Trigger
id="boolean-trigger"
className={twMerge(
'h-full w-full border-none p-0 outline-none',
isEditing && 'p-1.5',
)}
ref={inputRef}
onClick={editCell}
autoFocus={false}
sx={{ '&:hover': { backgroundColor: 'transparent !important' } }}
>
<ReadOnlyToggle checked={optimisticValue} />
</Dropdown.Trigger>
<Dropdown.Content
menu
disablePortal
onKeyDown={handleMenuKeyDown}
PaperProps={{ className: 'w-[200px]' }}
TransitionProps={{ onExited: focusCell }}
>
<Dropdown.Item
selected={optimisticValue === true}
onKeyUp={() => handleTemporaryValueChange(true)}
onClick={(event) => handleMenuClick(event, true)}
>
<ReadOnlyToggle checked />
</Dropdown.Item>
<Dropdown.Item
selected={optimisticValue === false}
onKeyUp={() => handleTemporaryValueChange(false)}
onClick={(event) => handleMenuClick(event, false)}
>
<ReadOnlyToggle checked={false} />
</Dropdown.Item>
{isNullable && (
<Dropdown.Item
selected={optimisticValue === null}
onKeyUp={() => handleTemporaryValueChange(null)}
onClick={(event) => handleMenuClick(event, null)}
>
<ReadOnlyToggle checked={null} />
</Dropdown.Item>
)}
</Dropdown.Content>
</Dropdown.Root>
) : (
<ReadOnlyToggle checked={optimisticValue} />
);
}

View File

@@ -0,0 +1,2 @@
export * from './DataGridBooleanCell';
export { default as DataGridBooleanCell } from './DataGridBooleanCell';

View File

@@ -0,0 +1,381 @@
import { useDialog } from '@/components/common/DialogProvider';
import type { BoxProps } from '@/components/ui/v2/Box';
import { Box } from '@/components/ui/v2/Box';
import { Tooltip, useTooltip } from '@/components/ui/v2/Tooltip';
import type {
ColumnType,
DataBrowserGridCell,
DataBrowserGridCellProps,
} from '@/features/database/dataGrid/types/dataBrowser';
import { triggerToast } from '@/utils/toast';
import type {
FocusEvent,
JSXElementConstructor,
KeyboardEvent,
MouseEvent,
ReactElement,
ReactNode,
ReactPortal,
} from 'react';
import {
Children,
cloneElement,
isValidElement,
useEffect,
useState,
} from 'react';
import { twMerge } from 'tailwind-merge';
import DataGridCellProvider from './DataGridCellProvider';
import useDataGridCell from './useDataGridCell';
export interface CommonDataGridCellProps<TData extends object, TValue = any>
extends DataBrowserGridCellProps<TData, TValue> {
/**
* Function that is called when the cell is saved.
*/
onSave?: (value: TValue, options?: { reset: boolean }) => Promise<void>;
/**
* Optimistic value for the cell.
*/
optimisticValue?: TValue;
/**
* Function to be called when the optimistic value should be changed.
*/
onOptimisticValueChange?: (value: TValue) => void;
/**
* Temporary value for the cell. This is used for storing the current input
* value, that should be later saved as an optimistic value before saving the
* data.
*/
temporaryValue?: TValue;
/**
* Function to be called when the temporary value should be changed.
*/
onTemporaryValueChange?: (value: TValue) => void;
}
export interface DataGridCellProps<TData extends object, TValue = unknown>
extends BoxProps {
/**
* Current cell's props.
*/
cell: DataBrowserGridCell<TData, TValue>;
/**
* Determines whether the cell is editable.
*/
isEditable?: boolean;
/**
* Determines the column's type.
*/
columnType?: ColumnType;
}
function DataGridCellContent<TData extends object = {}, TValue = unknown>({
isEditable,
children,
className,
cell: {
value: originalValue,
column: { onCellEdit, id, isNullable, isPrimary, type },
row,
},
...props
}: DataGridCellProps<TData, TValue>) {
const { openAlertDialog } = useDialog();
const {
title: tooltipTitle,
open: tooltipOpen,
openTooltip,
closeTooltip,
resetTooltipTitle,
} = useTooltip();
const [optimisticValue, setOptimisticValue] = useState<TValue>(originalValue);
const [temporaryValue, setTemporaryValue] = useState<TValue>(originalValue);
useEffect(() => {
setOptimisticValue(originalValue);
setTemporaryValue(originalValue);
}, [originalValue]);
const {
cellRef,
inputRef,
focusCell,
focusInput,
blurInput,
clickInput,
isEditing,
isSelected,
selectCell,
deselectCell,
cancelEditCell,
editCell,
focusPrevCell,
focusNextCell,
} = useDataGridCell();
function activateInput() {
if (isPrimary) {
openTooltip("Primary keys can't be edited.");
return;
}
editCell();
if (type === 'boolean') {
clickInput();
} else {
focusInput();
}
}
async function handleClick(event: MouseEvent<HTMLDivElement>) {
if (!isEditable || isEditing || isPrimary) {
return;
}
if (event.detail === 2 && type !== 'boolean') {
editCell();
await focusInput();
}
}
function handleFocus() {
if (!isEditable) {
return;
}
selectCell();
}
async function handleSave(
value: TValue,
options: { reset: boolean } = { reset: false },
) {
if (!onCellEdit) {
return;
}
const normalizedValue =
value !== null && typeof value === 'object'
? JSON.stringify(value)
: String(value);
const normalizedOptimisticValue =
optimisticValue !== null && typeof optimisticValue === 'object'
? JSON.stringify(optimisticValue)
: String(optimisticValue);
// We are making sure that optimistic value is not equal to the current
// value. If it is, we are not going to save the value.
if (
normalizedValue.replace(/\n/gi, '\\n') ===
normalizedOptimisticValue.replace(/\n/gi, '\\n') &&
!options.reset
) {
return;
}
// In case of an error, we need to reset optimistic value
const latestOptimisticValue = optimisticValue;
setOptimisticValue(value);
try {
const data = await onCellEdit({
row,
columnsToUpdate: {
[id]: {
value: !options.reset ? value : undefined,
reset: options.reset,
},
},
});
// Syncing optimistic value with server-side value
setTemporaryValue(data.original[id.toString()]);
setOptimisticValue(data.original[id.toString()]);
} catch (error) {
triggerToast(`Error: ${error.message || 'Unknown error occurred.'}`);
// Resetting values
setTemporaryValue(latestOptimisticValue);
setOptimisticValue(latestOptimisticValue);
}
}
async function handleBlur(event: FocusEvent<HTMLDivElement>) {
// We are deselecting cell only if focus target is not a descendant of it.
if (!isEditable || event.currentTarget.contains(event.relatedTarget)) {
return;
}
await handleSave(temporaryValue);
closeTooltip();
deselectCell();
}
function resetCell() {
if (isPrimary) {
openTooltip('Primary keys are non-nullable.');
return;
}
if (!isNullable) {
openTooltip(
<span>
<strong>{id}</strong>
is non-nullable.
</span>,
);
return;
}
openAlertDialog({
title: 'Set value to null',
payload: (
<p>
Are you sure you want to set this cell to <strong>null</strong>?
</p>
),
props: {
primaryButtonText: 'Set to null',
primaryButtonColor: 'error',
onPrimaryAction: async () => {
await handleSave(null, { reset: true });
focusCell();
},
},
});
}
async function handleKeyDown(event: KeyboardEvent<HTMLDivElement>) {
if (!isEditable) {
return;
}
if (event.key === 'Escape') {
closeTooltip();
}
// Resetting temporary value and focusing cell on Escape when input field is
// focused
if (event.key === 'Escape' && event.target === inputRef.current) {
setTemporaryValue(optimisticValue);
await focusCell();
cancelEditCell();
}
// Activating input field on Enter
if (event.key === 'Enter' && event.target === cellRef.current) {
activateInput();
}
// Focusing next cell on Tab
if (event.key === 'Tab' && !event.shiftKey) {
event.stopPropagation();
const nextCellAvailable = focusNextCell();
if (!nextCellAvailable) {
event.preventDefault();
event.stopPropagation();
await blurInput();
await focusCell();
}
}
// Focusing previous cell on Shift-Tab
if (event.key === 'Tab' && event.shiftKey) {
event.stopPropagation();
const prevCellAvailable = focusPrevCell();
if (!prevCellAvailable) {
event.preventDefault();
event.stopPropagation();
await blurInput();
await focusCell();
}
}
// Initiating cell reset when cell is focused
if (event.key === 'Backspace' && event.target === cellRef.current) {
resetCell();
}
}
const content = (
<Box
ref={cellRef}
className={twMerge(
'relative grid h-full w-full cursor-default grid-flow-col items-center gap-1',
isEditable &&
'focus-within:outline-none focus-within:ring-0 focus:ring-0',
isSelected && 'shadow-outline',
isEditing ? 'p-0.5 shadow-outline-dark' : 'px-2 py-1.5',
className,
)}
onFocus={handleFocus}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
tabIndex={isEditable ? 0 : undefined}
onClick={handleClick}
role="textbox"
sx={{ backgroundColor: 'transparent' }}
{...props}
>
{Children.map(
children,
(
child:
| ReactNode
| ReactPortal
| ReactElement<unknown, string | JSXElementConstructor<any>>,
) => {
if (!isValidElement(child)) {
return null;
}
return cloneElement(child, {
...child.props,
onSave: handleSave,
optimisticValue,
onOptimisticValueChange: setOptimisticValue,
temporaryValue,
onTemporaryValueChange: setTemporaryValue,
});
},
)}
</Box>
);
if (isEditable) {
return (
<Tooltip
disableHoverListener
disableFocusListener
open={tooltipOpen}
title={tooltipTitle || ''}
TransitionProps={{ onExited: resetTooltipTitle }}
>
{content}
</Tooltip>
);
}
return content;
}
export default function DataGridCell<TData extends object, TValue = unknown>(
props: DataGridCellProps<TData, TValue>,
) {
return (
<DataGridCellProvider>
<DataGridCellContent {...props} />
</DataGridCellProvider>
);
}

View File

@@ -0,0 +1,238 @@
import type { MutableRefObject, PropsWithChildren } from 'react';
import { createContext, useCallback, useMemo, useReducer, useRef } from 'react';
export interface DataGridCellContextProps<T extends HTMLElement> {
/**
* This `ref` should be attached to the cell element.
*/
cellRef: MutableRefObject<HTMLDivElement>;
/**
* This `ref` should be attached to the input element inside the data grid cell.
*/
inputRef: MutableRefObject<T>;
/**
* Determines whether or not the cell is currently being edited.
*/
isEditing: boolean;
/**
* Determines whether or not the cell is currently selected.
*/
isSelected: boolean;
/**
* Function to be called to start editing.
*/
editCell: VoidFunction;
/**
* Function to be called to cancel editing.
*/
cancelEditCell: VoidFunction;
/**
* Function to be called to select the cell, but not start editing.
*/
selectCell: VoidFunction;
/**
* Function to be called to deselect cell and cancel editing.
*/
deselectCell: VoidFunction;
/**
* Function to be called to focus cell.
*/
focusCell: () => Promise<void>;
/**
* Function to be called to blur cell.
*/
blurCell: () => Promise<void>;
/**
* Function to be called to programatically focus the input in the cell.
*/
focusInput: () => Promise<void>;
/**
* Function to be called to programatically blur the input in the cell.
*/
blurInput: () => Promise<void>;
/**
* Function to be called to programmatically click the input in the cell.
*/
clickInput: () => Promise<void>;
/**
* Function to be called to navigate to next cell if available.
*
* @returns `true` if there is a next cell to focus, `false` otherwise.
*/
focusNextCell: () => boolean;
/**
* Function to be called to navigate to previous cell if available.
*
* @returns `true` if there is a previous cell to focus, `false` otherwise.
*/
focusPrevCell: () => boolean;
}
export const DataGridCellContext =
createContext<DataGridCellContextProps<any>>(null);
interface EditAndSelectState {
isEditing: boolean;
isSelected: boolean;
}
type EditAndSelectAction =
| { type: 'EDIT' }
| { type: 'CANCEL_EDIT' }
| { type: 'SELECT' }
| { type: 'DESELECT' };
function editAndSelectCellReducer(
state: EditAndSelectState,
action: EditAndSelectAction,
): EditAndSelectState {
switch (action.type) {
case 'EDIT':
return { ...state, isEditing: true, isSelected: true };
case 'CANCEL_EDIT':
return { ...state, isEditing: false };
case 'SELECT':
return { ...state, isSelected: true };
case 'DESELECT':
return { ...state, isEditing: false, isSelected: false };
default:
return { ...state };
}
}
export default function DataGridCellProvider<TInput extends HTMLElement>({
children,
}: PropsWithChildren<unknown>) {
const cellRef = useRef<HTMLDivElement>();
const inputRef = useRef<TInput>();
const [{ isEditing, isSelected }, dispatch] = useReducer(
editAndSelectCellReducer,
{
isEditing: false,
isSelected: false,
},
);
function focusCell() {
return new Promise<void>((resolve) => {
requestAnimationFrame(() => {
cellRef.current?.focus();
resolve();
});
});
}
function deselectCell() {
dispatch({ type: 'DESELECT' });
}
const focusPrevCell = useCallback(() => {
const prevCellAvailable =
cellRef.current.previousElementSibling instanceof HTMLElement &&
cellRef.current.previousElementSibling.tabIndex > -1;
requestAnimationFrame(() => {
if (prevCellAvailable) {
(cellRef.current.previousElementSibling as HTMLElement).focus();
deselectCell();
}
});
return prevCellAvailable;
}, []);
const focusNextCell = useCallback(() => {
const nextCellAvailable =
cellRef.current.nextElementSibling instanceof HTMLElement &&
cellRef.current.nextElementSibling.tabIndex > -1;
requestAnimationFrame(() => {
if (nextCellAvailable) {
(cellRef.current.nextElementSibling as HTMLElement).focus();
deselectCell();
}
});
return nextCellAvailable;
}, []);
function blurCell() {
return new Promise<void>((resolve) => {
requestAnimationFrame(() => {
cellRef.current?.blur();
resolve();
});
});
}
function focusInput() {
return new Promise<void>((resolve) => {
requestAnimationFrame(() => {
inputRef.current?.focus();
resolve();
});
});
}
function blurInput() {
return new Promise<void>((resolve) => {
requestAnimationFrame(() => {
inputRef.current?.blur();
resolve();
});
});
}
function clickInput() {
return new Promise<void>((resolve) => {
requestAnimationFrame(() => {
inputRef.current?.click();
resolve();
});
});
}
function editCell() {
dispatch({ type: 'EDIT' });
}
function cancelEditCell() {
dispatch({ type: 'CANCEL_EDIT' });
}
function selectCell() {
dispatch({ type: 'SELECT' });
}
const value = useMemo(
() => ({
focusCell,
blurCell,
focusInput,
blurInput,
clickInput,
isEditing,
isSelected,
editCell,
cancelEditCell,
selectCell,
deselectCell,
cellRef,
inputRef,
focusPrevCell,
focusNextCell,
}),
[focusNextCell, focusPrevCell, isEditing, isSelected],
);
return (
<DataGridCellContext.Provider value={value}>
{children}
</DataGridCellContext.Provider>
);
}

View File

@@ -0,0 +1,5 @@
export * from './DataGridCell';
export { default as DataGridCell } from './DataGridCell';
export * from './DataGridCellProvider';
export { default as DataGridCellProvider } from './DataGridCellProvider';
export { default as useDataGridCell } from './useDataGridCell';

View File

@@ -0,0 +1,10 @@
import { useContext } from 'react';
import type { DataGridCellContextProps } from './DataGridCellProvider';
import { DataGridCellContext } from './DataGridCellProvider';
export default function useDataGridCell<TInput extends HTMLElement>() {
const context =
useContext<DataGridCellContextProps<TInput>>(DataGridCellContext);
return context;
}

View File

@@ -0,0 +1,6 @@
import type { UseDataGridReturn } from '@/components/dataGrid/DataGrid';
import { createContext } from 'react';
const DataGridConfigContext = createContext<Partial<UseDataGridReturn>>(null);
export default DataGridConfigContext;

View File

@@ -0,0 +1,16 @@
import type { UseDataGridReturn } from '@/components/dataGrid/DataGrid';
import type { PropsWithChildren } from 'react';
import DataGridConfigContext from './DataGridConfigContext';
export default function DataGridConfigProvider<T extends object = {}>({
children,
...value
}: PropsWithChildren<UseDataGridReturn<T>>) {
return (
<DataGridConfigContext.Provider
value={value as unknown as UseDataGridReturn<{}>}
>
{children}
</DataGridConfigContext.Provider>
);
}

View File

@@ -0,0 +1,3 @@
export { default as DataGridConfigContext } from './DataGridConfigContext';
export { default as DataGridConfigProvider } from './DataGridConfigProvider';
export { default as useDataGridConfig } from './useDataGridConfig';

View File

@@ -0,0 +1,15 @@
import type { UseDataGridReturn } from '@/components/dataGrid/DataGrid';
import { useContext } from 'react';
import DataGridConfigContext from './DataGridConfigContext';
export default function useDataGridConfig<T extends object = {}>() {
const context = useContext(DataGridConfigContext);
if (!context) {
throw new Error(
`useDataGridConfig must be used within a DataGridConfigContext`,
);
}
return context as unknown as UseDataGridReturn<T>;
}

View File

@@ -0,0 +1,166 @@
import type { CommonDataGridCellProps } from '@/components/dataGrid/DataGridCell';
import { useDataGridCell } from '@/components/dataGrid/DataGridCell';
import { Input, inputClasses } from '@/components/ui/v2/Input';
import type { TextProps } from '@/components/ui/v2/Text';
import { Text } from '@/components/ui/v2/Text';
import { getDateComponents } from '@/utils/getDateComponents';
import type { ChangeEvent, KeyboardEvent } from 'react';
import { twMerge } from 'tailwind-merge';
export interface DataGridDateCellProps<TData extends object>
extends CommonDataGridCellProps<TData, string> {
/**
* Props to be passed to date display.
*/
dateProps?: TextProps;
/**
* Props to be passed to time display.
*/
timeProps?: TextProps;
}
export default function DataGridDateCell<TData extends object>({
onSave,
optimisticValue,
temporaryValue,
onTemporaryValueChange,
cell: {
column: { specificType },
},
dateProps,
timeProps,
className,
}: DataGridDateCellProps<TData>) {
const { className: dateClassName, ...restDateProps } = dateProps || {};
const { className: timeClassName, ...restTimeProps } = timeProps || {};
// Note: No date (year-month-day) is saved for time / timetz columns, so we
// need to add it manually.
const date =
optimisticValue && specificType !== 'interval'
? new Date(
specificType === 'time' || specificType === 'timetz'
? `1970-01-01 ${optimisticValue}`
: optimisticValue,
)
: undefined;
const { year, month, day, hour, minute, second } = getDateComponents(date, {
adjustTimezone: ['date', 'timetz', 'timestamptz'].includes(specificType),
});
const { inputRef, focusCell, isEditing, cancelEditCell } =
useDataGridCell<HTMLInputElement>();
async function handleSave() {
if (onSave) {
await onSave(temporaryValue || '');
}
}
async function handleKeyDown(event: KeyboardEvent<HTMLInputElement>) {
if (
event.key === 'ArrowLeft' ||
event.key === 'ArrowRight' ||
event.key === 'ArrowUp' ||
event.key === 'ArrowDown' ||
event.key === 'Backspace'
) {
event.stopPropagation();
}
if (event.key === 'Tab') {
await handleSave();
}
if (event.key === 'Enter') {
await handleSave();
await focusCell();
cancelEditCell();
}
}
function handleChange(event: ChangeEvent<HTMLInputElement>) {
if (event.target instanceof HTMLInputElement && onTemporaryValueChange) {
onTemporaryValueChange(event.target.value);
}
}
if (isEditing) {
return (
<Input
ref={inputRef}
value={
temporaryValue !== null && typeof temporaryValue !== 'undefined'
? temporaryValue
: ''
}
onKeyDown={handleKeyDown}
onChange={handleChange}
fullWidth
className="absolute top-0 z-10 -mx-0.5 h-full place-content-stretch"
sx={{
[`&.${inputClasses.focused}`]: {
boxShadow: `inset 0 0 0 1.5px rgba(0, 82, 205, 1)`,
borderColor: 'transparent !important',
borderRadius: 0,
backgroundColor: (theme) =>
theme.palette.mode === 'dark'
? `${theme.palette.secondary[100]} !important`
: `${theme.palette.common.white} !important`,
},
[`& .${inputClasses.input}`]: {
backgroundColor: 'transparent',
},
}}
slotProps={{
inputWrapper: { className: 'h-full' },
input: { className: 'h-full' },
inputRoot: {
className:
'resize-none outline-none focus:outline-none !text-xs focus:ring-0',
},
}}
/>
);
}
if (!optimisticValue) {
return (
<Text className="truncate text-xs" color="secondary">
null
</Text>
);
}
if (specificType === 'interval') {
return <Text className="truncate text-xs">{optimisticValue}</Text>;
}
return (
<div className={twMerge('grid grid-flow-row', className)}>
{specificType !== 'time' && specificType !== 'timetz' && (
<Text
className={twMerge('truncate text-xs', dateClassName)}
{...restDateProps}
>
{[year, month, day].filter(Boolean).join('-')}
</Text>
)}
{specificType !== 'date' && (
<Text
className={twMerge('truncate text-xs', timeClassName)}
color={
specificType === 'time' || specificType === 'timetz'
? 'primary'
: 'secondary'
}
{...restTimeProps}
>
{[hour, minute, second].filter(Boolean).join(':')}
</Text>
)}
</div>
);
}

View File

@@ -0,0 +1,2 @@
export * from './DataGridDateCell';
export { default as DataGridDateCell } from './DataGridDateCell';

View File

@@ -0,0 +1,29 @@
import { useDataGridConfig } from '@/components/dataGrid/DataGridConfigProvider';
import clsx from 'clsx';
import type { DetailedHTMLProps, HTMLProps } from 'react';
export type DataGridFrameProps = DetailedHTMLProps<
HTMLProps<HTMLDivElement>,
HTMLDivElement
>;
export default function DataGridFrame<T extends object>({
style,
children,
className,
...props
}: DataGridFrameProps) {
const { getTableProps } = useDataGridConfig<T>();
const { style: reactTableStyle, ...restTableProps } = getTableProps();
return (
<div
{...restTableProps}
{...props}
className={clsx('min-w-min', className)}
style={{ ...reactTableStyle, minWidth: undefined, ...style }}
>
{children}
</div>
);
}

View File

@@ -0,0 +1,2 @@
export * from './DataGridFrame';
export { default as DataGridFrame } from './DataGridFrame';

View File

@@ -0,0 +1,233 @@
import type { DataGridProps } from '@/components/dataGrid/DataGrid';
import { useDataGridConfig } from '@/components/dataGrid/DataGridConfigProvider';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { Divider } from '@/components/ui/v2/Divider';
import { Dropdown } from '@/components/ui/v2/Dropdown';
import { ArrowDownIcon } from '@/components/ui/v2/icons/ArrowDownIcon';
import { ArrowUpIcon } from '@/components/ui/v2/icons/ArrowUpIcon';
import { PencilIcon } from '@/components/ui/v2/icons/PencilIcon';
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import { TrashIcon } from '@/components/ui/v2/icons/TrashIcon';
import type { DataBrowserGridColumn } from '@/features/database/dataGrid/types/dataBrowser';
import type { DetailedHTMLProps, HTMLProps } from 'react';
import { twMerge } from 'tailwind-merge';
export interface HeaderActionProps
extends DetailedHTMLProps<HTMLProps<HTMLElement>, HTMLElement> {}
export interface DataGridHeaderProps<T extends object>
extends Omit<
DetailedHTMLProps<HTMLProps<HTMLDivElement>, HTMLDivElement>,
'children'
>,
Pick<
DataGridProps<T>,
'onRemoveColumn' | 'onEditColumn' | 'onInsertColumn'
> {
/**
* Props to be passed to component slots.
*/
componentsProps?: {
/**
* Props to be passed to the `Edit Column` header action item.
*/
editActionProps?: HeaderActionProps;
/**
* Props to be passed to the `Delete Column` header action item.
*/
deleteActionProps?: HeaderActionProps;
/**
* Props to be passed to the `Delete Column` header action item.
*/
insertActionProps?: HeaderActionProps;
};
}
// TODO: Get rid of Data Browser related code from here. This component should
// be generic and not depend on Data Browser related data types and logic.
export default function DataGridHeader<T extends object>({
className,
onRemoveColumn,
onEditColumn,
onInsertColumn,
componentsProps,
...props
}: DataGridHeaderProps<T>) {
const { flatHeaders, allowSort, allowResize } = useDataGridConfig<T>();
return (
<div
className={twMerge(
'sticky top-0 z-30 inline-flex w-full items-center pr-5',
className,
)}
{...props}
>
{flatHeaders.map((column: DataBrowserGridColumn<T>) => {
const headerProps = column.getHeaderProps({
style: { display: 'inline-grid' },
});
return (
<Dropdown.Root
sx={{
backgroundColor: (theme) =>
column.isDisabled
? theme.palette.background.default
: theme.palette.background.paper,
color: 'text.primary',
borderColor: 'grey.300',
}}
className={twMerge(
'group relative inline-flex self-stretch overflow-hidden font-display text-xs font-bold focus:outline-none focus-visible:outline-none',
'border-b-1 border-r-1',
column.id === 'selection' && 'sticky left-0 max-w-2',
)}
style={{
...headerProps.style,
maxWidth:
column.id === 'selection' ? 32 : headerProps.style?.maxWidth,
width:
column.id === 'selection' ? '100%' : headerProps.style?.width,
zIndex:
column.id === 'selection' ? 10 : headerProps.style?.zIndex,
position: null,
}}
key={column.id}
>
{column.id === 'selection' ? (
<span
{...headerProps}
className="relative grid w-full grid-flow-col items-center justify-between p-2"
>
{column.render('Header')}
</span>
) : (
<Dropdown.Trigger
className={twMerge(
'focus:outline-none motion-safe:transition-colors',
)}
disabled={
column.isDisabled || (column.disableSortBy && !onRemoveColumn)
}
hideChevron
>
<span
{...headerProps}
className="relative grid w-full grid-flow-col items-center justify-between p-2"
>
{column.render('Header')}
{allowSort && (
<Box component="span" sx={{ color: 'text.primary' }}>
{column.isSorted && !column.isSortedDesc && (
<ArrowUpIcon className="h-3 w-3" />
)}
{column.isSorted && column.isSortedDesc && (
<ArrowDownIcon className="h-3 w-3" />
)}
</Box>
)}
</span>
{allowResize && !column.disableResizing && (
<span
{...column.getResizerProps({
onClick: (event: Event) => event.stopPropagation(),
})}
className="absolute -right-0.5 bottom-0 top-0 z-10 h-full w-1.5 group-hover:bg-slate-900 group-hover:bg-opacity-20 group-active:bg-slate-900 group-active:bg-opacity-20 motion-safe:transition-colors"
/>
)}
</Dropdown.Trigger>
)}
<Dropdown.Content
menu
PaperProps={{ className: 'w-52 mt-1' }}
className="p-0"
>
{onEditColumn && (
<Dropdown.Item
onClick={() => onEditColumn(column)}
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
disabled={componentsProps?.editActionProps?.disabled}
>
<PencilIcon
className="h-4 w-4"
sx={{ color: 'text.secondary' }}
/>
<span>Edit Column</span>
</Dropdown.Item>
)}
{onEditColumn && <Divider component="li" sx={{ margin: 0 }} />}
{!column.disableSortBy && (
<Dropdown.Item
onClick={() => column.toggleSortBy(false)}
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
>
<ArrowUpIcon
className="h-4 w-4"
sx={{ color: 'text.secondary' }}
/>
<span>Sort Ascending</span>
</Dropdown.Item>
)}
{!column.disableSortBy && (
<Dropdown.Item
onClick={() => column.toggleSortBy(true)}
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
>
<ArrowDownIcon
className="h-4 w-4"
sx={{ color: 'text.secondary' }}
/>
<span>Sort Descending</span>
</Dropdown.Item>
)}
{onRemoveColumn && !column.isPrimary && (
<Divider component="li" className="my-1" />
)}
{onRemoveColumn && !column.isPrimary && (
<Dropdown.Item
onClick={() => onRemoveColumn(column)}
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
disabled={componentsProps?.deleteActionProps?.disabled}
sx={{ color: 'error.main' }}
>
<TrashIcon className="h-4 w-4" sx={{ color: 'error.main' }} />
<span>Delete Column</span>
</Dropdown.Item>
)}
</Dropdown.Content>
</Dropdown.Root>
);
})}
{onInsertColumn && (
<Box className="group relative inline-flex w-25 self-stretch overflow-hidden border-b-1 border-r-1 font-display text-xs font-bold focus:outline-none focus-visible:outline-none">
<Button
onClick={onInsertColumn}
variant="borderless"
color="secondary"
className="h-full w-full rounded-none text-xs hover:shadow-none focus:shadow-none focus:outline-none"
aria-label="Insert New Column"
disabled={componentsProps?.insertActionProps?.disabled}
>
<PlusIcon className="h-4 w-4" sx={{ color: 'text.disabled' }} />
</Button>
</Box>
)}
</div>
);
}

View File

@@ -0,0 +1,2 @@
export * from './DataGridHeader';
export { default as DataGridHeader } from './DataGridHeader';

View File

@@ -0,0 +1,110 @@
import type { CommonDataGridCellProps } from '@/components/dataGrid/DataGridCell';
import { useDataGridCell } from '@/components/dataGrid/DataGridCell';
import { Input, inputClasses } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text';
import type { ChangeEvent, KeyboardEvent } from 'react';
export type DataGridNumericCellProps<TData extends object> =
CommonDataGridCellProps<TData, number>;
export default function DataGridNumericCell<TData extends object>({
onSave,
optimisticValue,
temporaryValue,
onTemporaryValueChange,
}: DataGridNumericCellProps<TData>) {
const { inputRef, focusCell, isEditing, cancelEditCell } =
useDataGridCell<HTMLInputElement>();
async function handleSave() {
if (onSave) {
if (typeof temporaryValue === 'number') {
await onSave(temporaryValue);
} else {
await onSave(null);
}
}
}
async function handleKeyDown(event: KeyboardEvent<HTMLInputElement>) {
if (
event.key === 'ArrowLeft' ||
event.key === 'ArrowRight' ||
event.key === 'ArrowUp' ||
event.key === 'ArrowDown' ||
event.key === 'Backspace'
) {
event.stopPropagation();
}
if (event.key === 'Tab') {
await handleSave();
}
if (event.key === 'Enter') {
await handleSave();
await focusCell();
cancelEditCell();
}
}
function handleChange(event: ChangeEvent<HTMLInputElement>) {
if (onTemporaryValueChange) {
if (event.target.value) {
onTemporaryValueChange(parseInt(event.target.value, 10));
} else {
onTemporaryValueChange(null);
}
}
}
if (isEditing) {
return (
<Input
type="number"
ref={inputRef}
value={
temporaryValue !== null && typeof temporaryValue !== 'undefined'
? temporaryValue
: ''
}
onKeyDown={handleKeyDown}
onChange={handleChange}
fullWidth
className="absolute top-0 z-10 -mx-0.5 h-full place-content-stretch"
sx={{
[`&.${inputClasses.focused}`]: {
boxShadow: `inset 0 0 0 1.5px rgba(0, 82, 205, 1)`,
borderColor: 'transparent !important',
borderRadius: 0,
backgroundColor: (theme) =>
theme.palette.mode === 'dark'
? `${theme.palette.secondary[100]} !important`
: `${theme.palette.common.white} !important`,
},
[`& .${inputClasses.input}`]: {
backgroundColor: 'transparent',
},
}}
slotProps={{
inputWrapper: { className: 'h-full' },
input: { className: 'h-full' },
inputRoot: {
className:
'resize-none outline-none focus:outline-none !text-xs focus:ring-0',
},
}}
/>
);
}
if (optimisticValue === null || typeof optimisticValue === 'undefined') {
return (
<Text className="truncate !text-xs" color="disabled">
null
</Text>
);
}
return <Text className="truncate !text-xs">{optimisticValue}</Text>;
}

View File

@@ -0,0 +1,2 @@
export * from './DataGridNumericCell';
export { default as DataGridNumericCell } from './DataGridNumericCell';

View File

@@ -0,0 +1,91 @@
import type { BoxProps } from '@/components/ui/v2/Box';
import { Box } from '@/components/ui/v2/Box';
import type { IconButtonProps } from '@/components/ui/v2/IconButton';
import { IconButton } from '@/components/ui/v2/IconButton';
import { ChevronLeftIcon } from '@/components/ui/v2/icons/ChevronLeftIcon';
import { ChevronRightIcon } from '@/components/ui/v2/icons/ChevronRightIcon';
import { Text } from '@/components/ui/v2/Text';
import clsx from 'clsx';
export interface DataGridPaginationProps extends BoxProps {
/**
* Number of pages.
*/
totalPages: number;
/**
* Current page.
*/
currentPage: number;
/**
* Function to be called when navigating to the previous page.
*/
onOpenPrevPage: VoidFunction;
/**
* Function to be called when navigating to the next page.
*/
onOpenNextPage: VoidFunction;
/**
* Props to be passed to the next button component.
*/
nextButtonProps?: IconButtonProps;
/**
* Props to be passed to the previous button component.
*/
prevButtonProps?: IconButtonProps;
}
export default function DataGridPagination({
className,
totalPages,
currentPage,
onOpenPrevPage,
onOpenNextPage,
nextButtonProps,
prevButtonProps,
...props
}: DataGridPaginationProps) {
return (
<Box
className={clsx(
'grid grid-flow-col items-center justify-around rounded-md border-1',
className,
)}
{...props}
>
<IconButton
variant="borderless"
color="secondary"
disabled={currentPage === 1}
onClick={onOpenPrevPage}
aria-label="Previous page"
{...prevButtonProps}
>
<ChevronLeftIcon className="h-4 w-4" />
</IconButton>
<span
className={clsx(
'mx-1 inline-block font-display font-medium',
currentPage > 99 ? 'text-xs' : 'text-sm+',
)}
>
{currentPage}
<Text component="span" className="mx-1 inline-block" color="disabled">
/
</Text>
{totalPages}
</span>
<IconButton
variant="borderless"
color="secondary"
disabled={currentPage === totalPages}
onClick={onOpenNextPage}
aria-label="Next page"
{...nextButtonProps}
>
<ChevronRightIcon className="h-4 w-4" />
</IconButton>
</Box>
);
}

View File

@@ -0,0 +1,2 @@
export * from './DataGridPagination';
export { default as DataGridPagination } from './DataGridPagination';

View File

@@ -0,0 +1,410 @@
import { Modal } from '@/components/ui/v1/Modal';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Box } from '@/components/ui/v2/Box';
import { IconButton } from '@/components/ui/v2/IconButton';
import { AudioPreviewIcon } from '@/components/ui/v2/icons/AudioPreviewIcon';
import { FilePreviewIcon } from '@/components/ui/v2/icons/FilePreviewIcon';
import { PDFPreviewIcon } from '@/components/ui/v2/icons/PDFPreviewIcon';
import { VideoPreviewIcon } from '@/components/ui/v2/icons/VideoPreviewIcon';
import { XIcon } from '@/components/ui/v2/icons/XIcon';
import { useAppClient } from '@/features/orgs/projects/hooks/useAppClient';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import clsx from 'clsx';
import type { ReactNode } from 'react';
import { useEffect, useReducer, useState } from 'react';
import type { CellProps } from 'react-table';
export type PreviewProps = {
fetchBlob: (
init?: RequestInit,
size?: { width?: number; height?: number },
) => Promise<Blob | null>;
mimeType?: string;
alt?: string;
blob?: Blob;
id?: string;
};
export type DataGridPreviewCellProps<TData extends object> = CellProps<
TData,
PreviewProps
> & {
/**
* Preview to use when the file is not an image or blob can't be fetched
* properly.
*
* @default null
*/
fallbackPreview?: ReactNode;
};
function useBlob({
fetchBlob,
blob,
mimeType,
}: Pick<PreviewProps, 'fetchBlob' | 'blob' | 'mimeType'>) {
const [objectUrl, setObjectUrl] = useState<string>();
const [error, setError] = useState<Error>();
const [loading, setLoading] = useState<boolean>(false);
// This side-effect fetches the blob of the file from the server and sets the
// relevant `objectUrl` state. Abort controller is reponsible for cancelling
// the fetch if the component is unmounted.
useEffect(() => {
const abortController = new AbortController();
async function generateOptimizedObjectUrl() {
// todo: it could be more declarative if this function was called with the
// actual preview URL here, not pre-generated in useFiles
const fetchedBlob = await fetchBlob(
{ signal: abortController.signal },
mimeType !== 'image/svg+xml' && { width: 80, height: 40 },
);
if (fetchedBlob) {
return URL.createObjectURL(fetchedBlob);
}
return undefined;
}
async function generateObjectUrl() {
setLoading(false);
setError(undefined);
if (objectUrl || (mimeType && !mimeType?.startsWith('image'))) {
return;
}
if (blob) {
setObjectUrl(URL.createObjectURL(blob));
return;
}
try {
setLoading(true);
const generatedObjectUrl = await generateOptimizedObjectUrl();
if (!abortController.signal.aborted) {
setObjectUrl(generatedObjectUrl);
}
} catch (generateError) {
if (!abortController.signal.aborted) {
setError(generateError);
}
}
if (!abortController.signal.aborted) {
setLoading(false);
}
}
generateObjectUrl();
return () => abortController.abort();
}, [blob, fetchBlob, objectUrl, mimeType]);
return { objectUrl, error, loading };
}
const previewableImages = [
'image/jpeg',
'image/png',
'image/svg+xml',
'image/webp',
];
const previewableVideos = [
'video/mp4',
'video/x-m4v',
'video/3gpp',
'video/3gpp2',
];
const previewableFileTypes = [
...previewableImages,
...previewableVideos,
'audio/',
'application/json',
];
function previewReducer(
state: { loading: boolean; error?: Error; data?: string },
action:
| { type: 'PREVIEW_LOADING' }
| { type: 'CLEAR_PREVIEW' }
| { type: 'PREVIEW_FETCHED'; payload: string }
| { type: 'PREVIEW_ERROR'; payload: Error },
): { loading: boolean; error?: Error; data?: string } {
switch (action.type) {
case 'PREVIEW_LOADING':
return { ...state, loading: true, error: undefined, data: undefined };
case 'PREVIEW_FETCHED':
return {
...state,
loading: false,
error: undefined,
data: action.payload,
};
case 'PREVIEW_ERROR':
return {
...state,
loading: false,
error: action.payload,
data: undefined,
};
case 'CLEAR_PREVIEW':
return { ...state, loading: false, error: undefined, data: undefined };
default:
return { ...state };
}
}
export default function DataGridPreviewCell<TData extends object>({
value: { fetchBlob, id, mimeType, alt, blob },
fallbackPreview = null,
}: DataGridPreviewCellProps<TData>) {
const { project } = useProject({ target: 'user-project' });
const appClient = useAppClient();
const { objectUrl, loading, error } = useBlob({ fetchBlob, blob, mimeType });
const [showModal, setShowModal] = useState(false);
const [
{ loading: previewLoading, error: previewError, data: previewUrl },
dispatch,
] = useReducer(previewReducer, {
loading: false,
error: undefined,
data: undefined,
});
const isPreviewable = previewableFileTypes.some(
(type) => mimeType?.startsWith(type) || mimeType === type,
);
const isVideo = mimeType?.startsWith('video');
const isAudio = mimeType?.startsWith('audio');
const isImage = mimeType?.startsWith('image');
const isJson = mimeType === 'application/json';
async function handleOpenPreview() {
if (!mimeType) {
dispatch({
type: 'PREVIEW_ERROR',
payload: new Error('mimeType is not defined.'),
});
return;
}
if (isPreviewable) {
setShowModal(true);
dispatch({ type: 'PREVIEW_LOADING' });
}
const { presignedUrl } = await appClient.storage
.setAdminSecret(project?.config?.hasura.adminSecret)
.getPresignedUrl({ fileId: id });
if (!presignedUrl) {
dispatch({
type: 'PREVIEW_ERROR',
payload: new Error('Presigned URL could not be fetched.'),
});
return;
}
if (!isPreviewable) {
window.open(presignedUrl.url, '_blank', 'noopener noreferrer');
return;
}
dispatch({ type: 'PREVIEW_FETCHED', payload: presignedUrl.url });
}
if (loading) {
return <ActivityIndicator delay={500} className="mx-auto" />;
}
if (error) {
return (
<Box
className="grid w-full grid-flow-col items-center justify-center gap-1 text-center"
sx={{ color: 'error.main' }}
>
<FilePreviewIcon error /> Error
</Box>
);
}
return (
<>
<Modal
wrapperClassName="items-center"
showModal={showModal}
close={() => setShowModal(false)}
afterLeave={() => dispatch({ type: 'CLEAR_PREVIEW' })}
className={clsx(
previewableImages.includes(mimeType) || isVideo || isAudio
? 'mx-12 flex h-screen items-center justify-center'
: 'mt-4 inline-block h-near-screen w-full px-12',
)}
>
<Box
className={clsx(
!isJson && 'bg-checker-pattern',
'relative mx-auto flex overflow-hidden rounded-md',
)}
sx={{
backgroundColor: isJson && 'background.default',
color: 'text.primary',
}}
>
{!previewLoading && (
<IconButton
aria-label="Close"
variant="borderless"
color="secondary"
className="absolute right-2 top-2 z-50 p-2"
sx={{
[`&:hover, &:active, &:focus`]: {
backgroundColor: (theme) => {
if (isAudio || isVideo || isJson) {
return 'common.black';
}
return theme.palette.mode === 'dark'
? 'grey.800'
: 'grey.200';
},
},
}}
onClick={() => setShowModal(false)}
>
<XIcon
className="h-5 w-5"
sx={{
color: (theme) => {
if (isAudio || isVideo || isJson) {
return 'common.white';
}
return theme.palette.mode === 'dark'
? 'grey.100'
: 'grey.700';
},
}}
/>
</IconButton>
)}
{previewLoading && !previewUrl && (
<ActivityIndicator
delay={500}
className="mx-auto"
label="Loading preview..."
/>
)}
{previewError && (
<Box
className="px-6 py-3.5 pr-12 text-start font-medium"
sx={{ color: 'error.main' }}
>
<p>Error: Preview can&apos;t be loaded.</p>
<p>{previewError.message}</p>
</Box>
)}
{previewUrl && isImage && (
<picture className="h-auto max-h-near-screen min-h-38 min-w-38">
<source srcSet={previewUrl} type={mimeType} />
<img
src={previewUrl}
alt={alt}
className="h-full w-full object-scale-down"
/>
</picture>
)}
{previewUrl && isVideo && (
<video
autoPlay
controls
className="h-auto max-h-near-screen w-full bg-black"
>
<track kind="captions" />
<source src={previewUrl} type={mimeType} />
Your browser does not support the video tag.
</video>
)}
{previewUrl && isAudio && (
<audio autoPlay controls className="h-28 bg-black">
<track kind="captions" />
<source src={previewUrl} type={mimeType} />
Your browser does not support the audio tag.
</audio>
)}
{!previewLoading &&
previewUrl &&
!previewableImages.includes(mimeType) &&
!isVideo &&
!isAudio && (
<iframe
src={previewUrl}
className="h-near-screen w-full"
title="File preview"
/>
)}
</Box>
</Modal>
<div className="flex h-full w-full justify-center">
{previewableImages.includes(mimeType) && objectUrl && (
<button
type="button"
aria-label={alt}
onClick={handleOpenPreview}
className="mx-auto h-full"
>
<picture className="h-full w-20">
<source srcSet={objectUrl} type={mimeType} />
<img
src={objectUrl}
alt={alt}
className="h-full w-full object-scale-down"
/>
</picture>
</button>
)}
{(!previewableImages.includes(mimeType) || !objectUrl) && (
<button
type="button"
onClick={handleOpenPreview}
aria-label={alt}
className="grid h-full w-full items-center justify-center self-center"
>
{isVideo && <VideoPreviewIcon className="h-5 w-5" />}
{isAudio && <AudioPreviewIcon className="h-5 w-5" />}
{mimeType === 'application/pdf' && (
<PDFPreviewIcon className="h-5 w-5" />
)}
{!isVideo &&
!isAudio &&
mimeType !== 'application/pdf' &&
fallbackPreview}
</button>
)}
</div>
</>
);
}

View File

@@ -0,0 +1,2 @@
export * from './DataGridPreviewCell';
export { default as DataGridPreviewCell } from './DataGridPreviewCell';

View File

@@ -0,0 +1,243 @@
import type { CommonDataGridCellProps } from '@/components/dataGrid/DataGridCell';
import { useDataGridCell } from '@/components/dataGrid/DataGridCell';
import { Button } from '@/components/ui/v2/Button';
import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
import { Input, inputClasses } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text';
import { copy } from '@/utils/copy';
import type { ChangeEvent, KeyboardEvent, Ref } from 'react';
import { useEffect } from 'react';
export type DataGridTextCellProps<TData extends object> =
CommonDataGridCellProps<TData, string>;
export default function DataGridTextCell<TData extends object>({
onSave,
optimisticValue,
temporaryValue,
onTemporaryValueChange,
cell: {
column: { isCopiable, specificType },
},
}: DataGridTextCellProps<TData>) {
const isMultiline =
specificType === 'text' ||
specificType === 'bpchar' ||
specificType === 'varchar' ||
specificType === 'json' ||
specificType === 'jsonb';
const normalizedOptimisticValue =
optimisticValue !== null && typeof optimisticValue === 'object'
? optimisticValue
: (String(optimisticValue) || '').replace(/(\\n)+/gi, ' ');
const normalizedTemporaryValue =
temporaryValue !== null && typeof temporaryValue === 'object'
? JSON.stringify(temporaryValue)
: temporaryValue;
const { inputRef, focusCell, isEditing, cancelEditCell } = useDataGridCell<
HTMLInputElement | HTMLTextAreaElement
>();
useEffect(() => {
if (isEditing && isMultiline) {
const textArea = inputRef.current as HTMLTextAreaElement;
textArea.setSelectionRange(textArea.value.length, textArea.value.length);
}
}, [inputRef, isEditing, isMultiline]);
async function handleSave() {
if (onSave) {
await onSave((normalizedTemporaryValue || '').replace(/\n/gi, `\\n`));
}
}
async function handleInputKeyDown(event: KeyboardEvent<HTMLInputElement>) {
if (
event.key === 'ArrowLeft' ||
event.key === 'ArrowRight' ||
event.key === 'ArrowUp' ||
event.key === 'ArrowDown' ||
event.key === 'Backspace'
) {
event.stopPropagation();
}
if (event.key === 'Tab') {
await handleSave();
}
if (event.key === 'Enter') {
await handleSave();
await focusCell();
cancelEditCell();
}
}
async function handleTextAreaKeyDown(
event: KeyboardEvent<HTMLTextAreaElement>,
) {
if (
event.key === 'ArrowLeft' ||
event.key === 'ArrowRight' ||
event.key === 'ArrowUp' ||
event.key === 'ArrowDown' ||
event.key === 'Backspace'
) {
event.stopPropagation();
}
// Saving content Enter / CTRL + Enter / CMD + Enter (macOS) - but not on
// Shift + Enter
if (
(!event.shiftKey && event.key === 'Enter') ||
(event.ctrlKey && event.key === 'Enter') ||
(event.metaKey && event.key === 'Enter')
) {
event.preventDefault();
event.stopPropagation();
await handleSave();
await focusCell();
cancelEditCell();
}
if (event.key === 'Tab') {
await handleSave();
}
}
function handleChange(
event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) {
if (onTemporaryValueChange) {
onTemporaryValueChange(event.target.value);
}
}
if (isEditing && isMultiline) {
return (
<Input
multiline
ref={inputRef as Ref<HTMLInputElement>}
value={(normalizedTemporaryValue || '').replace(/\\n/gi, `\n`)}
onChange={handleChange}
onKeyDown={handleTextAreaKeyDown}
fullWidth
className="absolute top-0 z-10 -mx-0.5 h-full min-h-38"
rows={5}
sx={{
[`&.${inputClasses.focused}`]: {
boxShadow: `inset 0 0 0 1.5px rgba(0, 82, 205, 1)`,
borderColor: 'transparent !important',
borderRadius: 0,
backgroundColor: (theme) =>
theme.palette.mode === 'dark'
? `${theme.palette.secondary[100]} !important`
: `${theme.palette.common.white} !important`,
},
[`& .${inputClasses.input}`]: {
backgroundColor: 'transparent',
},
}}
slotProps={{
inputRoot: {
className:
'resize-none outline-none focus:outline-none !text-xs focus:ring-0',
},
}}
/>
);
}
if (isEditing) {
return (
<Input
ref={inputRef as Ref<HTMLInputElement>}
value={(normalizedTemporaryValue || '').replace(/\\n/gi, `\n`)}
onChange={handleChange}
onKeyDown={handleInputKeyDown}
fullWidth
className="absolute top-0 z-10 -mx-0.5 h-full place-content-stretch"
sx={{
[`&.${inputClasses.focused}`]: {
boxShadow: `inset 0 0 0 1.5px rgba(0, 82, 205, 1)`,
borderColor: 'transparent !important',
borderRadius: 0,
backgroundColor: (theme) =>
theme.palette.mode === 'dark'
? `${theme.palette.secondary[100]} !important`
: `${theme.palette.common.white} !important`,
},
[`& .${inputClasses.input}`]: {
backgroundColor: 'transparent',
},
}}
slotProps={{
inputWrapper: { className: 'h-full' },
input: { className: 'h-full' },
inputRoot: {
className:
'resize-none outline-none focus:outline-none !text-xs focus:ring-0',
},
}}
/>
);
}
if (!optimisticValue) {
return (
<Text className="truncate !text-xs" color="secondary">
{optimisticValue === '' ? 'empty' : 'null'}
</Text>
);
}
if (isCopiable) {
return (
<div className="grid grid-flow-col items-center justify-start gap-1">
<Button
variant="borderless"
color="secondary"
onClick={(event) => {
event.stopPropagation();
const copiableValue =
typeof optimisticValue === 'object'
? JSON.stringify(optimisticValue)
: String(optimisticValue).replace(/\\n/gi, '\n');
copy(copiableValue, 'Value');
}}
className="-ml-px min-w-0 p-0"
aria-label="Copy value"
sx={{
color: (theme) =>
theme.palette.mode === 'dark'
? 'text.secondary'
: 'text.disabled',
}}
>
<CopyIcon className="h-4 w-4" />
</Button>
<Text className="truncate text-xs">
{typeof normalizedOptimisticValue === 'object'
? JSON.stringify(normalizedOptimisticValue)
: normalizedOptimisticValue}
</Text>
</div>
);
}
return (
<Text className="truncate text-xs">
{typeof normalizedOptimisticValue === 'object'
? JSON.stringify(normalizedOptimisticValue)
: normalizedOptimisticValue}
</Text>
);
}

View File

@@ -0,0 +1,2 @@
export * from './DataGridTextCell';
export { default as DataGridTextCell } from './DataGridTextCell';

View File

@@ -1,10 +1,11 @@
import type { DataGridProps } from '@/components/dataGrid/DataGrid';
import { DataGrid } from '@/components/dataGrid/DataGrid';
import { DataGridBooleanCell } from '@/components/dataGrid/DataGridBooleanCell';
import { DataGridDateCell } from '@/components/dataGrid/DataGridDateCell';
import type { PreviewProps } from '@/components/dataGrid/DataGridPreviewCell';
import { DataGridPreviewCell } from '@/components/dataGrid/DataGridPreviewCell';
import { DataGridTextCell } from '@/components/dataGrid/DataGridTextCell';
import type { DataGridProps } from '@/features/orgs/projects/storage/dataGrid/components/DataGrid';
import { DataGrid } from '@/features/orgs/projects/storage/dataGrid/components/DataGrid';
import { DataGridBooleanCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridBooleanCell';
import { DataGridDateCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridDateCell';
import type { PreviewProps } from '@/features/orgs/projects/storage/dataGrid/components/DataGridPreviewCell';
import { DataGridPreviewCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridPreviewCell';
import { DataGridTextCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridTextCell';
import { FilePreviewIcon } from '@/components/ui/v2/icons/FilePreviewIcon';
import { useAppClient } from '@/features/orgs/projects/hooks/useAppClient';
import { useProject } from '@/features/orgs/projects/hooks/useProject';

View File

@@ -28,6 +28,7 @@ export default function useNotFoundRedirect() {
router.pathname === '/' ||
router.pathname === '/account' ||
router.pathname === '/support/ticket' ||
router.pathname === '/run-one-click-install' ||
orgSlug ||
(orgSlug && appSubdomain) ||
// If we are on a valid workspace and project, we don't want to redirect to 404

View File

@@ -47,7 +47,7 @@ export default function SettingsAuthenticationPage() {
return (
<Container
className="grid max-w-5xl grid-flow-row gap-y-6 bg-transparent"
className="grid max-w-5xl grid-flow-row bg-transparent gap-y-6"
rootClassName="bg-transparent"
>
<AuthServiceVersionSettings />

View File

@@ -30,7 +30,7 @@ export default function StoragePage() {
: project.config?.hasura.adminSecret,
}}
>
<div className="h-full pb-25 xs+:pb-[53px]">
<div className="h-full max-w-full pb-25 xs+:pb-[56.5px]">
<RetryableErrorBoundary>
<FilesDataGrid />
</RetryableErrorBoundary>

View File

@@ -1,6 +1,5 @@
import { useDialog } from '@/components/common/DialogProvider';
import { AuthenticatedLayout } from '@/components/layout/AuthenticatedLayout';
import { Container } from '@/components/layout/Container';
import { RetryableErrorBoundary } from '@/components/presentational/RetryableErrorBoundary';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Box } from '@/components/ui/v2/Box';
@@ -9,44 +8,58 @@ import { Input } from '@/components/ui/v2/Input';
import { List } from '@/components/ui/v2/List';
import { ListItem } from '@/components/ui/v2/ListItem';
import { Text } from '@/components/ui/v2/Text';
import { Badge } from '@/components/ui/v3/badge';
import { useOrgs } from '@/features/orgs/projects/hooks/useOrgs';
import { useWorkspaces } from '@/features/orgs/projects/hooks/useWorkspaces';
import { InfoCard } from '@/features/projects/overview/components/InfoCard';
import {
useGetAllWorkspacesAndProjectsQuery,
type GetAllWorkspacesAndProjectsQuery,
} from '@/utils/__generated__/graphql';
import { cn } from '@/lib/utils';
import { Divider } from '@mui/material';
import { useUserData } from '@nhost/nextjs';
import debounce from 'lodash.debounce';
import Image from 'next/image';
import { useRouter } from 'next/router';
import type { ChangeEvent, ReactElement } from 'react';
import { Fragment, useCallback, useEffect, useMemo, useState } from 'react';
type Workspace = Omit<
GetAllWorkspacesAndProjectsQuery['workspaces'][0],
'__typename'
>;
interface ProjectSelectorOption {
type: 'workspace-project' | 'org-project';
projectName: string;
projectPathDescriptor: string;
route: string;
isFree: boolean;
plan: string;
}
export default function SelectWorkspaceAndProject() {
const user = useUserData();
const router = useRouter();
const { openAlertDialog } = useDialog();
const { orgs, loading: loadingOrgs } = useOrgs();
const { workspaces, loading: loadingWorkspaces } = useWorkspaces();
const { data, loading } = useGetAllWorkspacesAndProjectsQuery({
skip: !user,
});
const workspaceProjects: ProjectSelectorOption[] = workspaces.flatMap(
(workspace) =>
workspace.projects.map((project) => ({
type: 'workspace-project',
projectName: project.name,
projectPathDescriptor: `${workspace.name}/${project.name}`,
route: `${workspace.slug}/${project.slug}/services`,
isFree: project.legacyPlan.isFree,
plan: project.legacyPlan.name,
})),
);
const workspaces: Workspace[] = data?.workspaces || [];
const projects = workspaces.flatMap((workspace) =>
workspace.projects.map((project) => ({
workspaceName: workspace.name,
const orgProjects: ProjectSelectorOption[] = orgs.flatMap((org) =>
org.apps.map((project) => ({
type: 'org-project',
projectName: project.name,
value: `${workspace.slug}/${project.slug}`,
isFree: project.legacyPlan.isFree,
projectPathDescriptor: `${org.name}/${project.name}`,
route: `/orgs/${org.slug}/projects/${project.subdomain}/run`,
isFree: org.plan.isFree,
plan: org.plan.name,
})),
);
const projects = [...orgProjects, ...workspaceProjects];
const [filter, setFilter] = useState('');
const handleFilterChange = useMemo(
@@ -89,17 +102,12 @@ export default function SelectWorkspaceAndProject() {
}
}, [checkConfigFromQuery, router.query]);
const goToServices = async (project: {
workspaceName: string;
projectName: string;
value: string;
isFree: boolean;
}) => {
const goToServices = async (project: ProjectSelectorOption) => {
if (!project) {
openAlertDialog({
title: 'Please select a workspace and a project',
title: 'Please select a project',
payload:
'You must select a workspace and a project before proceeding to create the run service',
'You must select a project before proceeding to create the run service',
props: {
primaryButtonText: 'Ok',
hideSecondaryAction: true,
@@ -111,8 +119,8 @@ export default function SelectWorkspaceAndProject() {
if (project.isFree) {
openAlertDialog({
title: 'The project must have a pro plan',
payload: 'Creating run services is only availabel for pro projects',
title: 'Cannot proceed',
payload: 'Creating run services is only available on a Pro plan',
props: {
primaryButtonText: 'Ok',
hideSecondaryAction: true,
@@ -122,11 +130,7 @@ export default function SelectWorkspaceAndProject() {
return;
}
await router.push({
pathname: `/${project.value}/services`,
// Keep the same query params that got us here
query: router.query,
});
await router.push({ pathname: project.route, query: router.query });
};
const projectsToDisplay = filter
@@ -135,32 +139,27 @@ export default function SelectWorkspaceAndProject() {
)
: projects;
if (loading) {
if (loadingWorkspaces || loadingOrgs) {
return (
<div className="flex w-full justify-center">
<ActivityIndicator
delay={500}
label="Loading workspaces and projects..."
/>
<div className="flex justify-center w-full">
<ActivityIndicator delay={500} label="Loading projects..." />
</div>
);
}
return (
<Container>
<div className="mx-auto grid max-w-[760px] grid-flow-row gap-4 py-6 sm:py-14">
<Text variant="h2" component="h1" className="">
New Run Service
</Text>
<div className="flex flex-col items-start w-full h-full px-5 py-4 mx-auto bg-background">
<div className="mx-auto flex h-full w-full max-w-[760px] flex-col gap-4 py-6 sm:py-14">
<h1 className="text-2xl font-medium">New Run Service</h1>
<InfoCard
title="Please select the workspace and the project where you want to create the service"
title="Please select the project where you want to create the service"
disableCopy
value=""
/>
<div>
<div className="mb-2 flex w-full">
<div className="flex w-full mb-2">
<Input
placeholder="Search..."
onChange={handleFilterChange}
@@ -170,15 +169,15 @@ export default function SelectWorkspaceAndProject() {
</div>
<RetryableErrorBoundary>
{projectsToDisplay.length === 0 ? (
<Box className="h-import py-2">
<Box className="py-2 h-import">
<Text variant="subtitle2">No results found.</Text>
</Box>
) : (
<List className="h-import overflow-y-auto">
<List className="flex flex-col gap-2 overflow-y-auto h-import">
{projectsToDisplay.map((project, index) => (
<Fragment key={project.value}>
<Fragment key={project.projectPathDescriptor}>
<ListItem.Root
className="grid grid-flow-col justify-start gap-2 py-2.5"
className="flex flex-row items-center justify-center gap-4"
secondaryAction={
<Button
variant="borderless"
@@ -189,19 +188,35 @@ export default function SelectWorkspaceAndProject() {
</Button>
}
>
<ListItem.Avatar>
<span className="inline-block h-6 w-6 overflow-hidden rounded-md">
<Image
src="/logos/new.svg"
alt="Nhost Logo"
width={24}
height={24}
/>
</span>
<ListItem.Avatar className="flex items-center justify-center h-full">
<Image
src="/logos/new.svg"
alt="Nhost Logo"
className="w-10 h-10 rounded-md"
width={38}
height={38}
/>
</ListItem.Avatar>
<ListItem.Text
primary={project.projectName}
secondary={`${project.workspaceName} / ${project.projectName}`}
primary={
<div className="flex items-center">
<span>{project.projectName}</span>
<Badge
variant={project.isFree ? 'outline' : 'default'}
className={cn(
'hover:none ml-2 h-5 px-[6px] text-[10px]',
project.isFree && 'bg-muted',
project.type === 'workspace-project' &&
'bg-orange-200 text-foreground hover:bg-orange-200 dark:bg-orange-500',
)}
>
{project.type === 'workspace-project'
? 'Legacy'
: project.plan}
</Badge>
</div>
}
secondary={project.projectPathDescriptor}
/>
</ListItem.Root>
@@ -213,7 +228,7 @@ export default function SelectWorkspaceAndProject() {
</RetryableErrorBoundary>
</div>
</div>
</Container>
</div>
);
}