Compare commits

...

81 Commits

Author SHA1 Message Date
github-actions[bot]
5e91221d5a chore: update versions (#2672)
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@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

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-18 15:49:01 +01:00
Hassan Ben Jobrane
7278991a59 fix: dashboard: update graphql auto-embeddings configuration to use String type for model field (#2674) 2024-04-18 15:22:32 +01:00
Hassan Ben Jobrane
5924bc3248 fix: dashboard: include password in GetSmtpSettings query (#2673) 2024-04-18 15:14:02 +01:00
Hassan Ben Jobrane
c5ad634799 fix: dashboard: check for undefined router query on Safari when accessing base64config (#2671) 2024-04-18 10:04:18 +01:00
github-actions[bot]
426b93a19f chore: update versions (#2670)
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@1.13.2

### Patch Changes

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

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-17 14:15:04 +01:00
Hassan Ben Jobrane
026f84f466 fix: dashboard: use config server url from env variable (#2669)
related to https://github.com/nhost/cli/issues/862
2024-04-17 14:02:11 +01:00
github-actions[bot]
384fac00b1 chore: update versions (#2664)
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@1.13.1

### Patch Changes

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

## @nhost/docs@2.10.1

### Patch Changes

-   9525fd7: fix: update AI docs for 0.5.0
-   076fd4a: fix: update permissions page to indicate we use jsonpath

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-16 10:53:36 +01:00
Hassan Ben Jobrane
7e9a2ce136 fix: dashboard: fix isPlatform check when opening run services form (#2666) 2024-04-16 10:38:28 +01:00
David Barroso
076fd4a7c0 fix (docs): update permissions page to indicate we use jsonpath (#2663) 2024-04-15 14:50:21 +02:00
David Barroso
9525fd74b3 fix (docs): update AI docs for 0.5.0 (#2662) 2024-04-15 14:33:59 +02:00
github-actions[bot]
8a2bc98214 chore: update versions (#2648)
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@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

## @nhost/docs@2.10.0

### Minor Changes

-   87ae23b: feat: added "advanced graphql" documentation

### Patch Changes

-   b2be364: feat: added postmark native integration

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-15 13:12:24 +01:00
Hassan Ben Jobrane
dd5d262062 feat: dashboard: add model field to auto-embeddings form (#2661)
fixes https://github.com/nhost/nhost/issues/2660
2024-04-15 12:51:57 +01:00
Hassan Ben Jobrane
09962bef37 feat: dashboard: enable local settings (#2647)
fixes https://github.com/nhost/projects/issues/66
2024-04-15 12:49:20 +01:00
Hassan Ben Jobrane
9cdecb6b23 feat: dashboard: add email field to account settings (#2612)
fixes https://github.com/nhost/nhost/issues/2561
2024-04-11 10:47:03 +01:00
David Barroso
e7eb90318e fix: observability: amend export of graphql dashboard (#2655) 2024-04-11 11:44:56 +02:00
Hassan Ben Jobrane
f67f22d321 chore: update @google-cloud/translate to 8.2.0 (#2654) 2024-04-11 10:00:13 +01:00
David Barroso
87ae23ba05 feat (docs/observability): added docs and observability dashboard for "advanced graphql" (#2653)
Co-authored-by: Nuno Pato <nunopato@gmail.com>
2024-04-11 10:22:54 +02:00
David Barroso
b2be3642aa feat (docs): added postmark native integration (#2636) 2024-04-09 09:36:48 +02:00
github-actions[bot]
1230081ce6 chore: update versions (#2640)
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@1.12.2

### Patch Changes

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

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-05 12:48:55 +01:00
Hassan Ben Jobrane
c195c517de fix: send email upon signin for unverified users (#2639) 2024-04-05 11:48:14 +01:00
github-actions[bot]
6f419be2c1 chore: update versions (#2634)
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/apollo@6.2.1

### Patch Changes

-   @nhost/nhost-js@3.0.11

## @nhost/react-apollo@11.0.1

### Patch Changes

-   @nhost/apollo@6.2.1
-   @nhost/react@3.4.1

## @nhost/react-urql@8.0.1

### Patch Changes

-   @nhost/react@3.4.1

## @nhost/hasura-auth-js@2.4.1

### Patch Changes

-   bcd889b: fix: change expiresAt format to RFC3339 in createPATPromise

## @nhost/nextjs@2.1.10

### Patch Changes

-   @nhost/react@3.4.1

## @nhost/nhost-js@3.0.11

### Patch Changes

-   Updated dependencies [bcd889b]
    -   @nhost/hasura-auth-js@2.4.1

## @nhost/react@3.4.1

### Patch Changes

-   @nhost/nhost-js@3.0.11

## @nhost/vue@2.5.1

### Patch Changes

-   @nhost/nhost-js@3.0.11

## @nhost/docs@2.9.0

### Minor Changes

-   3c31657: chore: update docs with provider connect

### Patch Changes

-   992939c: feat: added social connect docs

## @nhost/dashboard@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

## @nhost-examples/cli@0.3.1

### Patch Changes

-   @nhost/nhost-js@3.0.11

## @nhost-examples/codegen-react-apollo@0.4.1

### Patch Changes

-   @nhost/react@3.4.1
-   @nhost/react-apollo@11.0.1

## @nhost-examples/codegen-react-query@0.4.1

### Patch Changes

-   @nhost/react@3.4.1

## @nhost-examples/codegen-react-urql@0.3.1

### Patch Changes

-   @nhost/react@3.4.1
-   @nhost/react-urql@8.0.1

## @nhost-examples/multi-tenant-one-to-many@2.2.1

### Patch Changes

-   @nhost/nhost-js@3.0.11

## @nhost-examples/nextjs@0.3.1

### Patch Changes

-   @nhost/react@3.4.1
-   @nhost/react-apollo@11.0.1
-   @nhost/nextjs@2.1.10

## @nhost-examples/node-storage@0.2.1

### Patch Changes

-   @nhost/nhost-js@3.0.11

## @nhost-examples/nextjs-server-components@0.4.1

### Patch Changes

-   @nhost/nhost-js@3.0.11

## @nhost-examples/react-apollo@0.8.1

### Patch Changes

-   @nhost/react@3.4.1
-   @nhost/react-apollo@11.0.1

## @nhost-examples/react-gqty@1.2.1

### Patch Changes

-   @nhost/react@3.4.1

## @nhost-examples/vue-apollo@0.6.1

### Patch Changes

-   @nhost/nhost-js@3.0.11
-   @nhost/apollo@6.2.1
-   @nhost/vue@2.5.1

## @nhost-examples/vue-quickstart@0.2.1

### Patch Changes

-   @nhost/apollo@6.2.1
-   @nhost/vue@2.5.1

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-04 10:49:02 +01:00
Hassan Ben Jobrane
93ebdf844f fix: dashboard: use service urls when running locally with the cli (#2622) 2024-04-04 10:28:38 +01:00
Hassan Ben Jobrane
bcd889b53a fix: hasura-auth-js: use RFC3339 format for expiresAt when creating a PAT (#2637) 2024-04-03 21:40:02 +01:00
David Barroso
992939cdcd feat (docs): added social connect docs (#2633)
Co-authored-by: Nuno Pato <nunopato@gmail.com>
2024-04-03 16:37:07 +02:00
Nuno Pato
3c31657c50 chore: update docs with provider connect (#2632)
Co-authored-by: David Barroso <dbarrosop@dravetech.com>
2024-04-03 14:27:49 +00:00
github-actions[bot]
a654d536e0 chore: update versions (#2618)
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/apollo@6.2.0

### Minor Changes

-   768ca17: chore: update dependencies

### Patch Changes

-   @nhost/nhost-js@3.0.10

## @nhost/google-translation@0.2.0

### Minor Changes

-   768ca17: chore: update dependencies

## @nhost/react-apollo@11.0.0

### Minor Changes

-   768ca17: chore: update dependencies

### Patch Changes

-   Updated dependencies [768ca17]
    -   @nhost/apollo@6.2.0
    -   @nhost/react@3.4.0

## @nhost/react-urql@8.0.0

### Minor Changes

-   768ca17: chore: update dependencies

### Patch Changes

-   Updated dependencies [768ca17]
    -   @nhost/react@3.4.0

## @nhost/stripe-graphql-js@1.2.0

### Minor Changes

-   768ca17: chore: update dependencies

## @nhost/react@3.4.0

### Minor Changes

-   768ca17: chore: update dependencies

### Patch Changes

-   @nhost/nhost-js@3.0.10

## @nhost/vue@2.5.0

### Minor Changes

-   768ca17: chore: update dependencies

### Patch Changes

-   @nhost/nhost-js@3.0.10

## @nhost/nextjs@2.1.9

### Patch Changes

-   Updated dependencies [768ca17]
    -   @nhost/react@3.4.0

## @nhost/dashboard@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

## @nhost/docs@2.8.0

### Minor Changes

-   768ca17: chore: update dependencies

### Patch Changes

-   9f2bf9e: chore: added hasura's authHook settings

## @nhost-examples/cli@0.3.0

### Minor Changes

-   768ca17: chore: update dependencies

### Patch Changes

-   @nhost/nhost-js@3.0.10

## @nhost-examples/codegen-react-apollo@0.4.0

### Minor Changes

-   768ca17: chore: update dependencies

### Patch Changes

-   Updated dependencies [768ca17]
    -   @nhost/react-apollo@11.0.0
    -   @nhost/react@3.4.0

## @nhost-examples/codegen-react-query@0.4.0

### Minor Changes

-   768ca17: chore: update dependencies

### Patch Changes

-   Updated dependencies [768ca17]
    -   @nhost/react@3.4.0

## @nhost-examples/codegen-react-urql@0.3.0

### Minor Changes

-   768ca17: chore: update dependencies

### Patch Changes

-   Updated dependencies [768ca17]
    -   @nhost/react-urql@8.0.0
    -   @nhost/react@3.4.0

## @nhost-examples/docker-compose@0.4.0

### Minor Changes

-   768ca17: chore: update dependencies

## @nhost-examples/multi-tenant-one-to-many@2.2.0

### Minor Changes

-   768ca17: chore: update dependencies

### Patch Changes

-   @nhost/nhost-js@3.0.10

## @nhost-examples/nextjs@0.3.0

### Minor Changes

-   768ca17: chore: update dependencies

### Patch Changes

-   Updated dependencies [768ca17]
    -   @nhost/react-apollo@11.0.0
    -   @nhost/react@3.4.0
    -   @nhost/nextjs@2.1.9

## @nhost-examples/node-storage@0.2.0

### Minor Changes

-   768ca17: chore: update dependencies

### Patch Changes

-   @nhost/nhost-js@3.0.10

## @nhost-examples/nextjs-server-components@0.4.0

### Minor Changes

-   768ca17: chore: update dependencies

### Patch Changes

-   @nhost/nhost-js@3.0.10

## @nhost-examples/sveltekit@0.4.0

### Minor Changes

-   768ca17: chore: update dependencies

## @nhost-examples/react-apollo@0.8.0

### Minor Changes

-   768ca17: chore: update dependencies

### Patch Changes

-   863b37d: chore: bump hasura-auth
-   Updated dependencies [768ca17]
    -   @nhost/react-apollo@11.0.0
    -   @nhost/react@3.4.0

## @nhost-examples/react-gqty@1.2.0

### Minor Changes

-   768ca17: chore: update dependencies

### Patch Changes

-   Updated dependencies [768ca17]
    -   @nhost/react@3.4.0

## @nhost-examples/serverless-functions@0.2.0

### Minor Changes

-   768ca17: chore: update dependencies

### Patch Changes

-   Updated dependencies [768ca17]
    -   @nhost/stripe-graphql-js@1.2.0

## @nhost-examples/vue-apollo@0.6.0

### Minor Changes

-   768ca17: chore: update dependencies

### Patch Changes

-   Updated dependencies [768ca17]
    -   @nhost/apollo@6.2.0
    -   @nhost/vue@2.5.0
    -   @nhost/nhost-js@3.0.10

## @nhost-examples/vue-quickstart@0.2.0

### Minor Changes

-   768ca17: chore: update dependencies

### Patch Changes

-   Updated dependencies [768ca17]
    -   @nhost/apollo@6.2.0
    -   @nhost/vue@2.5.0

## @nhost/docgen@0.3.0

### Minor Changes

-   768ca17: chore: update dependencies

## @nhost/sync-versions@0.2.0

### Minor Changes

-   768ca17: chore: update dependencies

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-04-03 13:29:25 +01:00
Hassan Ben Jobrane
91c2bb6f53 feat: dashboard: restrict signup/signin to verified emails only (#2610)
resolves https://github.com/nhost/nhost/issues/2585
2024-04-03 12:52:47 +01:00
David Barroso
9f2bf9ec2b chore (docs): added hasura's authHook settings (#2630) 2024-04-03 09:01:28 +02:00
Nuno Pato
d62bd0fc9a fix: dashboard: SQL editor "track this" (#2626)
fixes https://github.com/nhost/nhost/issues/2625

---------

Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-04-02 16:50:26 +00:00
github-actions[bot]
768ca17494 [Scheduled] Update dependencies (#2627)
Dependencies updated

Note - If you see this PR and the checks haven't run, close and reopen
the PR. See
https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#triggering-further-workflow-runs

---------

Co-authored-by: dbarrosop <dbarrosop@users.noreply.github.com>
Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-04-02 16:44:24 +01:00
Hassan Ben Jobrane
943831fe2e fix: await unpauseApplication promise (#2621)
fixes https://github.com/nhost/nhost/issues/2620
2024-03-26 09:29:10 +01:00
Hassan Ben Jobrane
f242e4b92f feat: connect with github (#2616)
fixes https://github.com/nhost/nhost/issues/2581
2024-03-25 13:22:15 +01:00
David Barroso
863b37d313 chore (examples/apollo): bump hasura-auth (#2617) 2024-03-23 12:07:22 +01:00
github-actions[bot]
c8a8d4fca3 chore: update versions (#2609)
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/hasura-auth-js@2.4.0

### Minor Changes

- 311374e: fix: ensure that the user remains signed in even after being
redirected with an error following an attempt to connect with a social
provider

### Patch Changes

-   1623e9b: chore: update `@simplewebauthn/browser` to `9.0.1`

## @nhost/vue@2.4.0

### Minor Changes

-   311374e: feat: add `connect` param to `useProviderLink` hook

### Patch Changes

-   @nhost/nhost-js@3.0.10

## @nhost/apollo@6.1.2

### Patch Changes

-   @nhost/nhost-js@3.0.10

## @nhost/react-apollo@10.0.2

### Patch Changes

-   Updated dependencies [311374e]
    -   @nhost/react@3.3.2
    -   @nhost/apollo@6.1.2

## @nhost/react-urql@7.0.2

### Patch Changes

-   Updated dependencies [311374e]
    -   @nhost/react@3.3.2

## @nhost/nextjs@2.1.8

### Patch Changes

-   Updated dependencies [311374e]
    -   @nhost/react@3.3.2

## @nhost/nhost-js@3.0.10

### Patch Changes

-   Updated dependencies [1623e9b]
-   Updated dependencies [311374e]
    -   @nhost/hasura-auth-js@2.4.0

## @nhost/react@3.3.2

### Patch Changes

-   311374e: feat: add `connect` param to `useProviderLink` hook
    -   @nhost/nhost-js@3.0.10

## @nhost-examples/docker-compose@0.3.0

### Minor Changes

- e40a452: chore: clarification on greyed-out options in the dashboard
when self-hosting

## @nhost-examples/react-apollo@0.7.0

### Minor Changes

- 311374e: feat: add example of how to connect a social auth provider to
an existing account

### Patch Changes

-   Updated dependencies [311374e]
    -   @nhost/react@3.3.2
    -   @nhost/react-apollo@10.0.2

## @nhost-examples/vue-apollo@0.5.0

### Minor Changes

- 311374e: feat: add example of how to connect a social auth provider to
an existing account

### Patch Changes

-   Updated dependencies [311374e]
    -   @nhost/vue@2.4.0
    -   @nhost/nhost-js@3.0.10
    -   @nhost/apollo@6.1.2

## @nhost/dashboard@1.11.2

### Patch Changes

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

## @nhost/docs@2.7.2

### Patch Changes

-   5c47e8e: feat: added hasura's stringifyNumericTypes setting

## @nhost-examples/cli@0.2.2

### Patch Changes

-   @nhost/nhost-js@3.0.10

## @nhost-examples/codegen-react-apollo@0.3.1

### Patch Changes

-   Updated dependencies [311374e]
    -   @nhost/react@3.3.2
    -   @nhost/react-apollo@10.0.2

## @nhost-examples/codegen-react-query@0.3.1

### Patch Changes

-   Updated dependencies [311374e]
    -   @nhost/react@3.3.2

## @nhost-examples/codegen-react-urql@0.2.1

### Patch Changes

-   Updated dependencies [311374e]
    -   @nhost/react@3.3.2
    -   @nhost/react-urql@7.0.2

## @nhost-examples/multi-tenant-one-to-many@2.1.2

### Patch Changes

-   @nhost/nhost-js@3.0.10

## @nhost-examples/nextjs@0.2.2

### Patch Changes

-   Updated dependencies [311374e]
    -   @nhost/react@3.3.2
    -   @nhost/react-apollo@10.0.2
    -   @nhost/nextjs@2.1.8

## @nhost-examples/node-storage@0.1.2

### Patch Changes

-   @nhost/nhost-js@3.0.10

## @nhost-examples/nextjs-server-components@0.3.2

### Patch Changes

-   @nhost/nhost-js@3.0.10

## @nhost-examples/react-gqty@1.1.2

### Patch Changes

-   Updated dependencies [311374e]
    -   @nhost/react@3.3.2

## @nhost-examples/vue-quickstart@0.1.2

### Patch Changes

-   Updated dependencies [311374e]
    -   @nhost/vue@2.4.0
    -   @nhost/apollo@6.1.2

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-03-21 17:54:02 +01:00
Hassan Ben Jobrane
311374e3fb feat: react-apollo: add example of how to connect github to an existing account (#2615)
fixes https://github.com/nhost/nhost/issues/2582
2024-03-21 17:38:54 +01:00
Seth Deegan
e40a4529b4 chore (examples/docker-compose): clarification on greyed-out options in the dashboard when self-hosting (#2564) 2024-03-18 11:03:10 +01:00
Hassan Ben Jobrane
1623e9bd20 chore: hasura-auth-js: upgrade @simplewebauthn/browser to 9.0.1 (#2611)
fixes https://github.com/nhost/nhost/issues/2597
2024-03-13 17:25:29 +01:00
David Barroso
5c47e8e675 feat (docs): added hasura's stringifyNumericTypes setting (#2608) 2024-03-12 11:29:53 +01:00
github-actions[bot]
9f9f1c64f4 chore: update versions (#2605)
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@1.11.1

### Patch Changes

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

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-03-11 11:02:17 +01:00
Hassan Ben Jobrane
981404f0b9 fix(dashboard): set default value for healthCheck field validation (#2604) 2024-03-11 10:45:33 +01:00
github-actions[bot]
4ad27e9d72 chore: update versions (#2599)
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/apollo@6.1.1

### Patch Changes

-   @nhost/nhost-js@3.0.9

## @nhost/react-apollo@10.0.1

### Patch Changes

-   @nhost/apollo@6.1.1
-   @nhost/react@3.3.1

## @nhost/react-urql@7.0.1

### Patch Changes

-   @nhost/react@3.3.1

## @nhost/stripe-graphql-js@1.1.1

### Patch Changes

-   7789469: chore: address linter errors and remove unnecessary imports

## @nhost/graphql-js@0.1.9

### Patch Changes

- 7789469: fix: resolve process is undefined error when running with
vitejs

## @nhost/nextjs@2.1.7

### Patch Changes

-   @nhost/react@3.3.1

## @nhost/nhost-js@3.0.9

### Patch Changes

-   Updated dependencies [7789469]
    -   @nhost/graphql-js@0.1.9

## @nhost/react@3.3.1

### Patch Changes

-   @nhost/nhost-js@3.0.9

## @nhost/vue@2.3.1

### Patch Changes

-   @nhost/nhost-js@3.0.9

## @nhost/dashboard@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

## @nhost-examples/codegen-react-apollo@0.3.0

### Minor Changes

- 7789469: chore: upgrade dependency `@graphql-codegen/cli` to `5.0.2`
to address vulnerability

### Patch Changes

-   @nhost/react@3.3.1
-   @nhost/react-apollo@10.0.1

## @nhost-examples/codegen-react-query@0.3.0

### Minor Changes

- 7789469: chore: upgrade dependency `@graphql-codegen/cli` to `5.0.2`
to address vulnerability

### Patch Changes

-   @nhost/react@3.3.1

## @nhost-examples/codegen-react-urql@0.2.0

### Minor Changes

- 7789469: chore: upgrade dependency `@graphql-codegen/cli` to `5.0.2`
to address vulnerability

### Patch Changes

-   @nhost/react@3.3.1
-   @nhost/react-urql@7.0.1

## @nhost-examples/react-apollo@0.6.0

### Minor Changes

- 7789469: chore: upgrade dependency `@graphql-codegen/cli` to `5.0.2`
to address vulnerability

### Patch Changes

-   @nhost/react@3.3.1
-   @nhost/react-apollo@10.0.1

## @nhost-examples/cli@0.2.1

### Patch Changes

-   @nhost/nhost-js@3.0.9

## @nhost-examples/multi-tenant-one-to-many@2.1.1

### Patch Changes

-   @nhost/nhost-js@3.0.9

## @nhost-examples/nextjs@0.2.1

### Patch Changes

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

## @nhost-examples/node-storage@0.1.1

### Patch Changes

-   @nhost/nhost-js@3.0.9

## @nhost-examples/nextjs-server-components@0.3.1

### Patch Changes

-   @nhost/nhost-js@3.0.9

## @nhost-examples/react-gqty@1.1.1

### Patch Changes

-   @nhost/react@3.3.1

## @nhost-examples/serverless-functions@0.1.1

### Patch Changes

-   Updated dependencies [7789469]
    -   @nhost/stripe-graphql-js@1.1.1

## @nhost-examples/vue-apollo@0.4.1

### Patch Changes

-   7789469: chore: address linter errors and remove unnecessary imports
    -   @nhost/nhost-js@3.0.9
    -   @nhost/apollo@6.1.1
    -   @nhost/vue@2.3.1

## @nhost-examples/vue-quickstart@0.1.1

### Patch Changes

-   @nhost/apollo@6.1.1
-   @nhost/vue@2.3.1

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-03-08 15:51:16 +01:00
Hassan Ben Jobrane
778946998a fix: graphql-js: resolve process is undefined error when running with vitejs (#2601)
fixes https://github.com/nhost/nhost/issues/2600
2024-03-08 15:23:13 +01:00
Hassan Ben Jobrane
6c11b75807 feat: dashboard: add update user displayName section in account settings (#2598)
fixes https://github.com/nhost/nhost/issues/1489
2024-03-07 17:23:35 +01:00
github-actions[bot]
2dc031d16c chore: update versions (#2592)
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-examples/react-apollo@0.5.0

### Minor Changes

- 08a7dd9: feat: add example workaround to the reset password ticket
expired issue

### Patch Changes

- f0a994a: fix: update allowedUrls and redirectTo to point to the
profile page

## @nhost-examples/vue-apollo@0.4.0

### Minor Changes

- 08a7dd9: feat: add example workaround to the reset password ticket
expired issue

### Patch Changes

- f0a994a: fix: update allowedUrls and redirectTo to point to the
profile page

## @nhost/docs@2.7.1

### Patch Changes

-   6cb2b63: feat: added nhost run env documentation
-   40bd3e4: fix: fixed wrong links in documentation

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-03-06 16:56:26 +01:00
Nicolas Bourdin
40bd3e4572 doc(link): fix wrong links in documentation (#2596)
Co-authored-by: Nicolas Bourdin <nicolas@epeak.co>
Co-authored-by: David Barroso <dbarrosop@dravetech.com>
2024-03-06 14:50:32 +01:00
David Barroso
6cb2b6331a feat (docs): added nhost run env documentation (#2594) 2024-03-06 14:13:02 +01:00
Hassan Ben Jobrane
08a7dd9894 feat(examples): add reset password ticket expired workarounds in the examples (#2590)
fixes https://github.com/nhost/nhost/issues/2314
2024-03-05 15:48:46 +01:00
Hassan Ben Jobrane
f0a994a26e fix(examples): update allowedUrls and redirectTo to point to /profile (#2591) 2024-03-05 14:17:01 +01:00
Hassan Ben Jobrane
4fbd6bd4fa chore: fix release with missing changeset (#2588)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-03-04 16:51:57 +01:00
github-actions[bot]
67fc77486c chore: update versions (#2578)
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/apollo@6.1.0

### Minor Changes

-   49a80c2: chore: update dependencies

### Patch Changes

-   @nhost/nhost-js@3.0.8

## @nhost/google-translation@0.1.0

### Minor Changes

-   49a80c2: chore: update dependencies

## @nhost/react-apollo@10.0.0

### Minor Changes

-   49a80c2: chore: update dependencies

### Patch Changes

-   Updated dependencies [49a80c2]
    -   @nhost/apollo@6.1.0
    -   @nhost/react@3.3.0

## @nhost/react-urql@7.0.0

### Minor Changes

-   49a80c2: chore: update dependencies

### Patch Changes

-   Updated dependencies [49a80c2]
    -   @nhost/react@3.3.0

## @nhost/stripe-graphql-js@1.1.0

### Minor Changes

-   49a80c2: chore: update dependencies

## @nhost/react@3.3.0

### Minor Changes

-   49a80c2: chore: update dependencies

### Patch Changes

-   @nhost/nhost-js@3.0.8

## @nhost/vue@2.3.0

### Minor Changes

-   49a80c2: chore: update dependencies

### Patch Changes

-   @nhost/nhost-js@3.0.8

## @nhost/nextjs@2.1.6

### Patch Changes

-   Updated dependencies [49a80c2]
    -   @nhost/react@3.3.0

## @nhost/dashboard@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

## @nhost/docs@2.7.0

### Minor Changes

-   49a80c2: chore: update dependencies

## @nhost-examples/cli@0.2.0

### Minor Changes

-   49a80c2: chore: update dependencies

### Patch Changes

-   @nhost/nhost-js@3.0.8

## @nhost-examples/codegen-react-apollo@0.2.0

### Minor Changes

-   49a80c2: chore: update dependencies

### Patch Changes

-   Updated dependencies [49a80c2]
    -   @nhost/react-apollo@10.0.0
    -   @nhost/react@3.3.0

## @nhost-examples/codegen-react-query@0.2.0

### Minor Changes

-   49a80c2: chore: update dependencies

### Patch Changes

-   Updated dependencies [49a80c2]
    -   @nhost/react@3.3.0

## @nhost-examples/codegen-react-urql@0.1.0

### Minor Changes

-   49a80c2: chore: update dependencies

### Patch Changes

-   Updated dependencies [49a80c2]
    -   @nhost/react-urql@7.0.0
    -   @nhost/react@3.3.0

## @nhost-examples/docker-compose@0.2.0

### Minor Changes

-   49a80c2: chore: update dependencies

## @nhost-examples/multi-tenant-one-to-many@2.1.0

### Minor Changes

-   49a80c2: chore: update dependencies

### Patch Changes

-   @nhost/nhost-js@3.0.8

## @nhost-examples/nextjs@0.2.0

### Minor Changes

-   49a80c2: chore: update dependencies

### Patch Changes

-   Updated dependencies [49a80c2]
    -   @nhost/react-apollo@10.0.0
    -   @nhost/react@3.3.0
    -   @nhost/nextjs@2.1.6

## @nhost-examples/node-storage@0.1.0

### Minor Changes

-   49a80c2: chore: update dependencies

### Patch Changes

-   @nhost/nhost-js@3.0.8

## @nhost-examples/nextjs-server-components@0.3.0

### Minor Changes

-   49a80c2: chore: update dependencies

### Patch Changes

-   @nhost/nhost-js@3.0.8

## @nhost-examples/sveltekit@0.3.0

### Minor Changes

-   49a80c2: chore: update dependencies

## @nhost-examples/react-apollo@0.4.0

### Minor Changes

-   49a80c2: chore: update dependencies

### Patch Changes

- 4f3fb34: fix: set redirectTo when doing sign in with github and
include vercel previews in allowed redirect URLs
-   Updated dependencies [49a80c2]
    -   @nhost/react-apollo@10.0.0
    -   @nhost/react@3.3.0

## @nhost-examples/react-gqty@1.1.0

### Minor Changes

-   49a80c2: chore: update dependencies

### Patch Changes

-   Updated dependencies [49a80c2]
    -   @nhost/react@3.3.0

## @nhost-examples/serverless-functions@0.1.0

### Minor Changes

-   49a80c2: chore: update dependencies

### Patch Changes

-   Updated dependencies [49a80c2]
    -   @nhost/stripe-graphql-js@1.1.0

## @nhost-examples/vue-apollo@0.3.0

### Minor Changes

-   49a80c2: chore: update dependencies

### Patch Changes

- 4f3fb34: fix: set redirectTo when doing sign in with github and
include vercel previews in allowed redirect URLs
-   Updated dependencies [49a80c2]
    -   @nhost/apollo@6.1.0
    -   @nhost/vue@2.3.0
    -   @nhost/nhost-js@3.0.8

## @nhost-examples/vue-quickstart@0.1.0

### Minor Changes

-   49a80c2: chore: update dependencies

### Patch Changes

-   Updated dependencies [49a80c2]
    -   @nhost/apollo@6.1.0
    -   @nhost/vue@2.3.0

## @nhost/docgen@0.2.0

### Minor Changes

-   49a80c2: chore: update dependencies

## @nhost/sync-versions@0.1.0

### Minor Changes

-   49a80c2: chore: update dependencies

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-03-04 16:16:06 +01:00
Hassan Ben Jobrane
4f3fb3446e fix: set redirectTo in oauth examples (#2586) 2024-03-04 15:33:24 +01:00
Hassan Ben Jobrane
49a80c22be chore: add changeset (#2584)
adds missing changeset for this PR
https://github.com/nhost/nhost/pull/2574
2024-03-04 13:52:38 +01:00
Hassan Ben Jobrane
28676f4cdc feat: dashboard: add min postgres version check to enable the ai service (#2576)
fixes https://github.com/nhost/nhost/issues/2439
2024-03-02 22:33:49 +01:00
Hassan Ben Jobrane
e03f14133c fix: dashboard: refactor database datagrid to allow insert/update/delete for tables auth and storage (#2577)
fixes https://github.com/nhost/nhost/issues/2476
2024-03-02 22:04:23 +01:00
Hassan Ben Jobrane
150c04a4f4 feat: dashboard: add healthcheck config to run services (#2575)
fixes https://github.com/nhost/nhost/issues/2410
2024-03-02 19:28:13 +01:00
github-actions[bot]
bccd67b1b1 [Scheduled] Update dependencies (#2574)
Dependencies updated

Note - If you see this PR and the checks haven't run, close and reopen
the PR. See
https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#triggering-further-workflow-runs

---------

Co-authored-by: David Barroso <dbarrosop@dravetech.com>
Co-authored-by: dbarrosop <dbarrosop@users.noreply.github.com>
Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-03-02 18:40:03 +01:00
David Barroso
b14fd2f14c chore: fix role to assume in gen update dependencies (#2573) 2024-03-01 12:21:38 +01:00
github-actions[bot]
68b3d23cd9 chore: update versions (#2572)
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@1.9.0

### Minor Changes

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

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-02-29 17:54:02 +01:00
Hassan Ben Jobrane
d86e5c9c16 feat(dashboard): query services list from be and filter the logs using a regex (#2552)
fixes: https://github.com/nhost/nhost/issues/2391
2024-02-29 17:38:38 +01:00
github-actions[bot]
b2cc1411d7 chore: update versions (#2571)
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/apollo@6.0.8

### Patch Changes

-   @nhost/nhost-js@3.0.8

## @nhost/react-apollo@9.0.3

### Patch Changes

-   @nhost/apollo@6.0.8
-   @nhost/react@3.2.3

## @nhost/react-urql@6.0.3

### Patch Changes

-   @nhost/react@3.2.3

## @nhost/graphql-js@0.1.8

### Patch Changes

- 407feea: fix: replace `jwt-decode` with `jose` to decode access tokens
in a non browser environment

## @nhost/nextjs@2.1.5

### Patch Changes

-   @nhost/react@3.2.3

## @nhost/nhost-js@3.0.8

### Patch Changes

-   Updated dependencies [407feea]
    -   @nhost/graphql-js@0.1.8

## @nhost/react@3.2.3

### Patch Changes

-   @nhost/nhost-js@3.0.8

## @nhost/vue@2.2.3

### Patch Changes

-   @nhost/nhost-js@3.0.8

## @nhost/dashboard@1.8.3

### Patch Changes

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

## @nhost-examples/cli@0.1.9

### Patch Changes

-   @nhost/nhost-js@3.0.8

## @nhost-examples/codegen-react-apollo@0.1.17

### Patch Changes

-   @nhost/react@3.2.3
-   @nhost/react-apollo@9.0.3

## @nhost-examples/codegen-react-query@0.1.18

### Patch Changes

-   @nhost/react@3.2.3

## @nhost-examples/codegen-react-urql@0.0.14

### Patch Changes

-   @nhost/react@3.2.3
-   @nhost/react-urql@6.0.3

## @nhost-examples/multi-tenant-one-to-many@2.0.7

### Patch Changes

-   @nhost/nhost-js@3.0.8

## @nhost-examples/nextjs@0.1.19

### Patch Changes

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

## @nhost-examples/node-storage@0.0.11

### Patch Changes

-   @nhost/nhost-js@3.0.8

## @nhost-examples/nextjs-server-components@0.2.5

### Patch Changes

-   @nhost/nhost-js@3.0.8

## @nhost-examples/react-apollo@0.3.3

### Patch Changes

-   @nhost/react@3.2.3
-   @nhost/react-apollo@9.0.3

## @nhost-examples/react-gqty@1.0.7

### Patch Changes

-   @nhost/react@3.2.3

## @nhost-examples/vue-apollo@0.2.4

### Patch Changes

-   @nhost/nhost-js@3.0.8
-   @nhost/apollo@6.0.8
-   @nhost/vue@2.2.3

## @nhost-examples/vue-quickstart@0.0.16

### Patch Changes

-   @nhost/apollo@6.0.8
-   @nhost/vue@2.2.3

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-02-29 15:43:48 +01:00
Hassan Ben Jobrane
407feeac37 fix: sdk: graphql-js: replace jwt-decode with jose to decode access tokens in both node and the browser (#2570) 2024-02-29 14:51:33 +01:00
github-actions[bot]
7b25c37c26 chore: update versions (#2569)
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@1.8.2

### Patch Changes

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

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-02-29 13:10:09 +01:00
Hassan Ben Jobrane
6df4f02e95 fix(dashboard): show correct message when sending invite fails (#2567) 2024-02-29 12:52:25 +01:00
github-actions[bot]
aaae98f019 chore: update versions (#2559)
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/docs@2.6.0

### Minor Changes

-   dc23dc0: fix: docs run references

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-02-25 23:02:36 -01:00
Nuno Pato
dc23dc0f49 fix: docs run references (#2558) 2024-02-25 22:47:38 -01:00
github-actions[bot]
82728da100 chore: update versions (#2556)
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/apollo@6.0.7

### Patch Changes

-   @nhost/nhost-js@3.0.7

## @nhost/react-apollo@9.0.2

### Patch Changes

-   @nhost/apollo@6.0.7
-   @nhost/react@3.2.2

## @nhost/react-urql@6.0.2

### Patch Changes

-   @nhost/react@3.2.2

## @nhost/graphql-js@0.1.7

### Patch Changes

- 2d68fee: fix: resolve an issue where unauthenticated graphql requests
are not sent

## @nhost/nextjs@2.1.4

### Patch Changes

-   @nhost/react@3.2.2

## @nhost/nhost-js@3.0.7

### Patch Changes

-   Updated dependencies [2d68fee]
    -   @nhost/graphql-js@0.1.7

## @nhost/react@3.2.2

### Patch Changes

-   @nhost/nhost-js@3.0.7

## @nhost/vue@2.2.2

### Patch Changes

-   @nhost/nhost-js@3.0.7

## @nhost/dashboard@1.8.1

### Patch Changes

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

## @nhost-examples/cli@0.1.8

### Patch Changes

-   @nhost/nhost-js@3.0.7

## @nhost-examples/codegen-react-apollo@0.1.16

### Patch Changes

-   @nhost/react@3.2.2
-   @nhost/react-apollo@9.0.2

## @nhost-examples/codegen-react-query@0.1.17

### Patch Changes

-   @nhost/react@3.2.2

## @nhost-examples/codegen-react-urql@0.0.13

### Patch Changes

-   @nhost/react@3.2.2
-   @nhost/react-urql@6.0.2

## @nhost-examples/multi-tenant-one-to-many@2.0.6

### Patch Changes

-   @nhost/nhost-js@3.0.7

## @nhost-examples/nextjs@0.1.18

### Patch Changes

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

## @nhost-examples/node-storage@0.0.10

### Patch Changes

-   @nhost/nhost-js@3.0.7

## @nhost-examples/nextjs-server-components@0.2.4

### Patch Changes

-   @nhost/nhost-js@3.0.7

## @nhost-examples/react-apollo@0.3.2

### Patch Changes

-   @nhost/react@3.2.2
-   @nhost/react-apollo@9.0.2

## @nhost-examples/react-gqty@1.0.6

### Patch Changes

-   @nhost/react@3.2.2

## @nhost-examples/vue-apollo@0.2.3

### Patch Changes

-   @nhost/nhost-js@3.0.7
-   @nhost/apollo@6.0.7
-   @nhost/vue@2.2.2

## @nhost-examples/vue-quickstart@0.0.15

### Patch Changes

-   @nhost/apollo@6.0.7
-   @nhost/vue@2.2.2

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-02-23 21:14:11 +01:00
Hassan Ben Jobrane
2d68fee54c fix(graphql-js): allow graphql requests with no access token (#2555) 2024-02-23 21:09:45 +01:00
github-actions[bot]
35010353c7 chore: update versions (#2547)
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/apollo@6.0.6

### Patch Changes

-   e0ab6d9: fix: add extra logic to check and wait for a valid JWT
    -   @nhost/nhost-js@3.0.6

## @nhost/react-apollo@9.0.1

### Patch Changes

-   Updated dependencies [e0ab6d9]
    -   @nhost/apollo@6.0.6
    -   @nhost/react@3.2.1

## @nhost/react-urql@6.0.1

### Patch Changes

-   @nhost/react@3.2.1

## @nhost/graphql-js@0.1.6

### Patch Changes

-   e0ab6d9: fix: add extra logic to check and wait for a valid JWT

## @nhost/hasura-auth-js@2.3.1

### Patch Changes

- 7baee8a: fix(hasura-auth-js): replace `jwt-decode` with `jose` for
decoding access tokens that works on both the browser and Node.js
-   e0ab6d9: fix: add extra logic to check and wait for a valid JWT

## @nhost/nextjs@2.1.3

### Patch Changes

-   @nhost/react@3.2.1

## @nhost/nhost-js@3.0.6

### Patch Changes

-   Updated dependencies [7baee8a]
-   Updated dependencies [e0ab6d9]
    -   @nhost/hasura-auth-js@2.3.1
    -   @nhost/graphql-js@0.1.6

## @nhost/react@3.2.1

### Patch Changes

-   @nhost/nhost-js@3.0.6

## @nhost/vue@2.2.1

### Patch Changes

-   @nhost/nhost-js@3.0.6

## @nhost/dashboard@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

## @nhost-examples/cli@0.1.7

### Patch Changes

-   @nhost/nhost-js@3.0.6

## @nhost-examples/codegen-react-apollo@0.1.15

### Patch Changes

-   @nhost/react-apollo@9.0.1
-   @nhost/react@3.2.1

## @nhost-examples/codegen-react-query@0.1.16

### Patch Changes

-   @nhost/react@3.2.1

## @nhost-examples/codegen-react-urql@0.0.12

### Patch Changes

-   @nhost/react@3.2.1
-   @nhost/react-urql@6.0.1

## @nhost-examples/docker-compose@0.1.1

### Patch Changes

-   aff059e: fix: timers

## @nhost-examples/multi-tenant-one-to-many@2.0.5

### Patch Changes

-   @nhost/nhost-js@3.0.6

## @nhost-examples/nextjs@0.1.17

### Patch Changes

-   @nhost/react-apollo@9.0.1
-   @nhost/react@3.2.1
-   @nhost/nextjs@2.1.3

## @nhost-examples/node-storage@0.0.9

### Patch Changes

-   @nhost/nhost-js@3.0.6

## @nhost-examples/nextjs-server-components@0.2.3

### Patch Changes

-   @nhost/nhost-js@3.0.6

## @nhost-examples/react-apollo@0.3.1

### Patch Changes

-   @nhost/react-apollo@9.0.1
-   @nhost/react@3.2.1

## @nhost-examples/react-gqty@1.0.5

### Patch Changes

-   @nhost/react@3.2.1

## @nhost-examples/vue-apollo@0.2.2

### Patch Changes

-   Updated dependencies [e0ab6d9]
    -   @nhost/apollo@6.0.6
    -   @nhost/nhost-js@3.0.6
    -   @nhost/vue@2.2.1

## @nhost-examples/vue-quickstart@0.0.14

### Patch Changes

-   Updated dependencies [e0ab6d9]
    -   @nhost/apollo@6.0.6
    -   @nhost/vue@2.2.1

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-02-23 10:07:53 +01:00
David Barroso
aff059ec71 fix (examples/docker-compose): timers (#2553) 2024-02-23 10:04:20 +01:00
Nuno Pato
713d53cfc0 feat: dashboard: add catch-all route (#2545) 2024-02-22 17:59:24 -01:00
Hassan Ben Jobrane
e0ab6d9a37 fix: JWT expired bug (#2533)
fixes: https://github.com/nhost/nhost/issues/2348

---------

Co-authored-by: David Barroso <dbarrosop@dravetech.com>
2024-02-22 18:44:43 +01:00
Hassan Ben Jobrane
7baee8a9cc fix(hasura-auth-js): use jose instead of jwt-decode to decode the accessToken and get the Hasura claims (#2550)
fixes https://github.com/nhost/nhost/issues/2513
2024-02-21 11:17:03 +01:00
Hassan Ben Jobrane
3db2999f60 fix(dashboard): refresh table list after running a SQL stmt using the editor (#2549)
fixes https://github.com/nhost/projects/issues/52
2024-02-20 13:40:46 +01:00
Hassan Ben Jobrane
3c4dd55045 fix(dashboard): handle Error alongside ApolloError properly in the ErrorToast component (#2548)
fixes https://github.com/nhost/nhost/issues/2525
2024-02-20 12:16:26 +01:00
Hassan Ben Jobrane
92b434e840 fix: refactor DataGridHeader component to allow for selecting all rows (#2546)
fixes https://github.com/nhost/nhost/issues/2526
2024-02-19 17:30:59 +01:00
github-actions[bot]
13d359602f chore: update versions (#2540)
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@1.7.0

### Minor Changes

-   0d8d0eb: Update docs and dashboard references

## @nhost/docs@2.5.0

### Minor Changes

-   0d8d0eb: Update docs and dashboard references

### Patch Changes

-   41617b9: feat: added elevated permissions docs
- 7db095f: chore: added a note about disk performance and CDN
information

## @nhost-examples/docker-compose@0.1.0

### Minor Changes

-   ed9df85: updated docker-compose.yaml and .env-example

## @nhost-examples/vue-apollo@0.2.1

### Patch Changes

-   c5c904b: fix: update signin methods settings

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-02-16 20:44:28 -01:00
Nuno Pato
0d8d0eb10f chore: update docs and fix dashboard references (#2543) 2024-02-16 18:21:30 -01:00
Seth Deegan
ed9df85778 Update docker-compose and .env-example (#2397)
The docker-compose example is severely outdated and the past
configuration used a docker image for the dashboard that did not allow
you to configure the URLs to the various API endpoints if you
self-hosted your own dashboard publicly.

The newest dashboard image allows you to do this so the docker-compose
has been updated to use this image and the env variables have been
updated accordingly.

Other variables have been updated in the docker-compose to support
self-hosting a public instance.

A commented traefik configuration in the docker-compose for the
dashboard service also allows the user to configure basic auth to
protect a publicly-facing dashboard.

---------

Co-authored-by: David Barroso <dbarrosop@dravetech.com>
Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-02-16 08:52:58 +01:00
David Barroso
41617b970a feat (docs): added elevated permissions docs (#2519)
Co-authored-by: Nuno Pato <nunopato@gmail.com>
2024-02-15 16:00:44 +01:00
Hassan Ben Jobrane
c5c904b716 fix(vue-apollo): update signin methods settings (#2541) 2024-02-15 13:48:41 +01:00
David Barroso
7db095fe92 chore: docs: added a note about disk performance and CDN information (#2539)
Fixes #2504
2024-02-15 11:47:33 +01:00
385 changed files with 14054 additions and 7681 deletions

View File

@@ -22,7 +22,7 @@ jobs:
- name: Configure aws - name: Configure aws
uses: aws-actions/configure-aws-credentials@v4 uses: aws-actions/configure-aws-credentials@v4
with: with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_PRODUCTION_CORE_ACCOUNT_ID }}:role/github-actions-nhost-be role-to-assume: arn:aws:iam::${{ secrets.AWS_PRODUCTION_CORE_ACCOUNT_ID }}:role/github-actions-nhost-${{ github.event.repository.name }}
aws-region: eu-central-1 aws-region: eu-central-1
- uses: nixbuild/nix-quick-install-action@v26 - uses: nixbuild/nix-quick-install-action@v26

3
config/.husky/pre-commit Executable file → Normal file
View File

@@ -1,4 +1,7 @@
#!/bin/sh #!/bin/sh
#
[ -n "$CI" ] && exit 0
. "$(dirname "$0")/_/husky.sh" . "$(dirname "$0")/_/husky.sh"
pnpm dlx lint-staged --config config/.lintstagedrc.js pnpm dlx lint-staged --config config/.lintstagedrc.js

View File

@@ -1,5 +1,149 @@
# @nhost/dashboard # @nhost/dashboard
## 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 ## 1.6.9
### Patch Changes ### Patch Changes

View File

@@ -28,6 +28,7 @@ ENV NEXT_PUBLIC_NHOST_STORAGE_URL __NEXT_PUBLIC_NHOST_STORAGE_URL__
ENV NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL __NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL__ ENV NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL __NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL__
ENV NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL __NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__ ENV NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL __NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__
ENV NEXT_PUBLIC_NHOST_HASURA_API_URL __NEXT_PUBLIC_NHOST_HASURA_API_URL__ ENV NEXT_PUBLIC_NHOST_HASURA_API_URL __NEXT_PUBLIC_NHOST_HASURA_API_URL__
ENV NEXT_PUBLIC_NHOST_CONFIGSERVER_URL __NEXT_PUBLIC_NHOST_CONFIGSERVER_URL__
RUN yarn global add pnpm@8.10.5 RUN yarn global add pnpm@8.10.5
COPY .gitignore .gitignore COPY .gitignore .gitignore

View File

@@ -21,5 +21,6 @@ find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_STORAGE_URL__~${NEXT_
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL__~${NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL}~g" {} + find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL__~${NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL}~g" {} +
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__~${NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL}~g" {} + find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__~${NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL}~g" {} +
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_API_URL__~${NEXT_PUBLIC_NHOST_HASURA_API_URL}~g" {} + find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_API_URL__~${NEXT_PUBLIC_NHOST_HASURA_API_URL}~g" {} +
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_CONFIGSERVER_URL__~${NEXT_PUBLIC_NHOST_CONFIGSERVER_URL}~g" {} +
exec "$@" exec "$@"

View File

@@ -16,8 +16,6 @@ const cspHeader = `
form-action 'self'; form-action 'self';
frame-ancestors 'none'; frame-ancestors 'none';
frame-src 'self' js.stripe.com; frame-src 'self' js.stripe.com;
block-all-mixed-content;
upgrade-insecure-requests;
`; `;
module.exports = withBundleAnalyzer({ module.exports = withBundleAnalyzer({
@@ -42,10 +40,6 @@ module.exports = withBundleAnalyzer({
key: 'X-Frame-Options', key: 'X-Frame-Options',
value: 'SAMEORIGIN', value: 'SAMEORIGIN',
}, },
{
key: 'Content-Security-Policy',
value: cspHeader.replace(/\n/g, ''),
},
], ],
}, },
]; ];

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/dashboard", "name": "@nhost/dashboard",
"version": "1.6.9", "version": "1.13.3",
"private": true, "private": true,
"scripts": { "scripts": {
"preinstall": "npx only-allow pnpm", "preinstall": "npx only-allow pnpm",
@@ -19,58 +19,58 @@
"e2e": "pnpm install-browsers && pnpm playwright test" "e2e": "pnpm install-browsers && pnpm playwright test"
}, },
"dependencies": { "dependencies": {
"@apollo/client": "^3.9.4", "@apollo/client": "^3.9.9",
"@codemirror/lang-sql": "^6.5.5", "@codemirror/lang-sql": "^6.6.2",
"@emotion/cache": "^11.11.0", "@emotion/cache": "^11.11.0",
"@emotion/react": "^11.11.3", "@emotion/react": "^11.11.4",
"@emotion/server": "^11.11.0", "@emotion/server": "^11.11.0",
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.5",
"@fontsource/inter": "^5.0.16", "@fontsource/inter": "^5.0.17",
"@fontsource/roboto-mono": "^5.0.16", "@fontsource/roboto-mono": "^5.0.17",
"@graphiql/react": "^0.20.2", "@graphiql/react": "^0.20.3",
"@graphiql/toolkit": "^0.9.1", "@graphiql/toolkit": "^0.9.1",
"@headlessui/react": "^1.7.18", "@headlessui/react": "^1.7.18",
"@heroicons/react": "^1.0.6", "@heroicons/react": "^1.0.6",
"@hookform/resolvers": "^3.3.4", "@hookform/resolvers": "^3.3.4",
"@mui/base": "5.0.0-beta.31", "@mui/base": "5.0.0-beta.31",
"@mui/material": "^5.15.7", "@mui/material": "^5.15.14",
"@mui/system": "^5.15.7", "@mui/system": "^5.15.14",
"@mui/x-date-pickers": "^5.0.20", "@mui/x-date-pickers": "^5.0.20",
"@nhost/nextjs": "workspace:*", "@nhost/nextjs": "workspace:*",
"@nhost/react-apollo": "workspace:*", "@nhost/react-apollo": "workspace:*",
"@segment/snippet": "^4.16.2", "@segment/snippet": "^4.16.2",
"@stripe/react-stripe-js": "^2.4.0", "@stripe/react-stripe-js": "^2.6.2",
"@stripe/stripe-js": "^1.54.2", "@stripe/stripe-js": "^1.54.2",
"@tailwindcss/forms": "^0.5.7", "@tailwindcss/forms": "^0.5.7",
"@tanstack/react-query": "^4.36.1", "@tanstack/react-query": "^4.36.1",
"@tanstack/react-table": "^8.11.7", "@tanstack/react-table": "^8.15.3",
"@tanstack/react-virtual": "^3.0.2", "@tanstack/react-virtual": "^3.2.0",
"@uiw/codemirror-theme-github": "^4.21.21", "@uiw/codemirror-theme-github": "^4.21.25",
"@uiw/react-codemirror": "^4.21.21", "@uiw/react-codemirror": "^4.21.25",
"analytics-node": "^6.2.0", "analytics-node": "^6.2.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"date-fns": "^2.30.0", "date-fns": "^2.30.0",
"framer-motion": "^10.18.0", "framer-motion": "^10.18.0",
"generate-password": "^1.7.1", "generate-password": "^1.7.1",
"graphiql": "^3.1.0", "graphiql": "^3.1.1",
"graphql": "16.8.1", "graphql": "16.8.1",
"graphql-request": "^6.1.0", "graphql-request": "^6.1.0",
"graphql-tag": "^2.12.6", "graphql-tag": "^2.12.6",
"graphql-ws": "^5.14.3", "graphql-ws": "^5.16.0",
"just-kebab-case": "^4.2.0", "just-kebab-case": "^4.2.0",
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",
"next": "^14.1.0", "next": "^14.1.4",
"next-seo": "^6.4.0", "next-seo": "^6.5.0",
"node-pg-format": "^1.3.5", "node-pg-format": "^1.3.5",
"pluralize": "^8.0.0", "pluralize": "^8.0.0",
"react": "18.2.0", "react": "18.2.0",
"react-children-utilities": "^2.10.0", "react-children-utilities": "^2.10.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-error-boundary": "^4.0.12", "react-error-boundary": "^4.0.13",
"react-hook-form": "^7.50.0", "react-hook-form": "^7.51.2",
"react-hot-toast": "^2.4.1", "react-hot-toast": "^2.4.1",
"react-intersection-observer": "^9.5.4", "react-intersection-observer": "^9.8.1",
"react-is": "18.2.0", "react-is": "18.2.0",
"react-loading-skeleton": "^2.2.0", "react-loading-skeleton": "^2.2.0",
"react-markdown": "^9.0.1", "react-markdown": "^9.0.1",
@@ -87,13 +87,13 @@
"tailwind-merge": "^1.14.0", "tailwind-merge": "^1.14.0",
"utility-types": "^3.11.0", "utility-types": "^3.11.0",
"validator": "^13.11.0", "validator": "^13.11.0",
"yup": "^1.3.3", "yup": "^1.4.0",
"yup-password": "^0.2.2" "yup-password": "^0.2.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.23.9", "@babel/core": "^7.24.3",
"@faker-js/faker": "^7.6.0", "@faker-js/faker": "^7.6.0",
"@graphql-codegen/cli": "^3.3.1", "@graphql-codegen/cli": "^5.0.2",
"@graphql-codegen/typescript": "^3.0.4", "@graphql-codegen/typescript": "^3.0.4",
"@graphql-codegen/typescript-operations": "^3.0.4", "@graphql-codegen/typescript-operations": "^3.0.4",
"@graphql-codegen/typescript-react-apollo": "^3.3.7", "@graphql-codegen/typescript-react-apollo": "^3.3.7",
@@ -106,50 +106,50 @@
"@storybook/addon-postcss": "^2.0.0", "@storybook/addon-postcss": "^2.0.0",
"@storybook/builder-webpack5": "^6.5.16", "@storybook/builder-webpack5": "^6.5.16",
"@storybook/manager-webpack5": "^6.5.16", "@storybook/manager-webpack5": "^6.5.16",
"@storybook/react": "^7.6.15", "@storybook/react": "^7.6.17",
"@storybook/testing-library": "^0.2.2", "@storybook/testing-library": "^0.2.2",
"@tailwindcss/typography": "^0.5.10", "@tailwindcss/typography": "^0.5.12",
"@testing-library/dom": "^9.3.4", "@testing-library/dom": "^9.3.4",
"@testing-library/jest-dom": "^5.17.0", "@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^14.2.0", "@testing-library/react": "^14.2.2",
"@testing-library/user-event": "^14.5.2", "@testing-library/user-event": "^14.5.2",
"@types/ace": "^0.0.48", "@types/ace": "^0.0.48",
"@types/bcryptjs": "^2.4.6", "@types/bcryptjs": "^2.4.6",
"@types/jest": "^29.5.11", "@types/jest": "^29.5.12",
"@types/lodash.debounce": "^4.0.9", "@types/lodash.debounce": "^4.0.9",
"@types/node": "^16.18.78", "@types/node": "^16.18.93",
"@types/pluralize": "^0.0.30", "@types/pluralize": "^0.0.30",
"@types/react": "^18.2.50", "@types/react": "^18.2.73",
"@types/react-dom": "^18.2.18", "@types/react-dom": "^18.2.23",
"@types/react-table": "^7.7.19", "@types/react-table": "^7.7.20",
"@types/shell-quote": "^1.7.5", "@types/shell-quote": "^1.7.5",
"@types/testing-library__jest-dom": "^5.14.9", "@types/testing-library__jest-dom": "^5.14.9",
"@types/validator": "^13.11.8", "@types/validator": "^13.11.9",
"@typescript-eslint/eslint-plugin": "^6.20.0", "@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.20.0", "@typescript-eslint/parser": "^6.21.0",
"@vitejs/plugin-react": "^4.2.1", "@vitejs/plugin-react": "^4.2.1",
"@vitest/coverage-v8": "^0.32.4", "@vitest/coverage-v8": "^0.32.4",
"autoprefixer": "^10.4.17", "autoprefixer": "^10.4.19",
"babel-loader": "^8.3.0", "babel-loader": "^8.3.0",
"babel-plugin-transform-remove-console": "^6.9.4", "babel-plugin-transform-remove-console": "^6.9.4",
"csstype": "^3.1.3", "csstype": "^3.1.3",
"dotenv": "^16.4.1", "dotenv": "^16.4.5",
"encoding": "^0.1.13", "encoding": "^0.1.13",
"eslint": "^8.56.0", "eslint": "^8.57.0",
"eslint-config-airbnb": "19.0.4", "eslint-config-airbnb": "19.0.4",
"eslint-config-airbnb-typescript": "^17.1.0", "eslint-config-airbnb-typescript": "^17.1.0",
"eslint-config-next": "^13.5.6", "eslint-config-next": "^13.5.6",
"eslint-config-prettier": "^8.10.0", "eslint-config-prettier": "^8.10.0",
"eslint-plugin-import": "^2.29.1", "eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-react": "^7.33.2", "eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"jsdom": "^22.1.0", "jsdom": "^22.1.0",
"lint-staged": "^15.2.1", "lint-staged": "^15.2.2",
"msw": "^1.3.2", "msw": "^1.3.3",
"msw-storybook-addon": "^1.10.0", "msw-storybook-addon": "^1.10.0",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",
"postcss": "^8.4.33", "postcss": "^8.4.38",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"prettier-plugin-organize-imports": "^3.2.4", "prettier-plugin-organize-imports": "^3.2.4",
"prettier-plugin-tailwindcss": "^0.4.1", "prettier-plugin-tailwindcss": "^0.4.1",
@@ -157,11 +157,11 @@
"require-from-string": "^2.0.2", "require-from-string": "^2.0.2",
"snake-case": "^3.0.4", "snake-case": "^3.0.4",
"storybook-addon-next-router": "^4.0.2", "storybook-addon-next-router": "^4.0.2",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.3",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"tsconfig-paths-webpack-plugin": "^4.1.0", "tsconfig-paths-webpack-plugin": "^4.1.0",
"vite": "^5.0.12", "vite": "^5.2.7",
"vite-tsconfig-paths": "^4.3.1", "vite-tsconfig-paths": "^4.3.2",
"vitest": "^0.32.4" "vitest": "^0.32.4"
}, },
"browserslist": { "browserslist": {

View File

@@ -0,0 +1,30 @@
import { useDialog } from '@/components/common/DialogProvider';
import { Button } from '@/components/ui/v2/Button';
import { Text } from '@/components/ui/v2/Text';
export default function ApplyLocalSettingsDialog() {
const { closeDialog } = useDialog();
return (
<div className="flex flex-col gap-4 px-6 pb-6">
<div className="flex flex-col gap-2">
<Text color="secondary">
Run{' '}
<code className="px-1 py-px mx-1 rounded-md bg-slate-500 text-slate-100">
$ nhost up
</code>{' '}
using the cli to apply your changes
</Text>
</div>
<Button
className="w-full"
color="primary"
onClick={() => closeDialog()}
autoFocus
>
OK
</Button>
</div>
);
}

View File

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

View File

@@ -35,7 +35,7 @@ function InsertPlaceholderTableRow({
...props ...props
}: InsertPlaceholderTableRowProps) { }: InsertPlaceholderTableRowProps) {
return ( return (
<Box className="h-12 border-r-1 border-b-1" {...props}> <Box className="h-12 border-b-1 border-r-1" {...props}>
<Button <Button
onClick={onInsertRow} onClick={onInsertRow}
variant="borderless" variant="borderless"
@@ -209,7 +209,7 @@ export default function DataGridBody<T extends object>({
/> />
) : ( ) : (
<Box <Box
className="inline-flex h-12 items-center border-b-1 border-r-1 py-1.5 px-2 text-xs" className="inline-flex h-12 items-center border-b-1 border-r-1 px-2 py-1.5 text-xs"
sx={{ color: 'text.secondary' }} sx={{ color: 'text.secondary' }}
style={{ style={{
width: allowInsertColumn width: allowInsertColumn
@@ -281,8 +281,8 @@ export default function DataGridBody<T extends object>({
}} }}
className={twMerge( className={twMerge(
'h-12 font-display text-xs motion-safe:transition-colors', 'h-12 font-display text-xs motion-safe:transition-colors',
'border-r-1 border-b-1', 'border-b-1 border-r-1',
'scroll-mt-[57px] scroll-ml-8', 'scroll-ml-8 scroll-mt-[57px]',
column.id === 'selection' && column.id === 'selection' &&
'sticky left-0 z-20 justify-center px-0', 'sticky left-0 z-20 justify-center px-0',
)} )}
@@ -296,7 +296,7 @@ export default function DataGridBody<T extends object>({
})} })}
{allowInsertColumn && ( {allowInsertColumn && (
<Box className="h-12 w-25 border-r-1 border-b-1" /> <Box className="h-12 w-25 border-b-1 border-r-1" />
)} )}
</div> </div>

View File

@@ -8,7 +8,15 @@ import type {
DataBrowserGridCellProps, DataBrowserGridCellProps,
} from '@/features/database/dataGrid/types/dataBrowser'; } from '@/features/database/dataGrid/types/dataBrowser';
import { triggerToast } from '@/utils/toast'; import { triggerToast } from '@/utils/toast';
import type { FocusEvent, JSXElementConstructor, KeyboardEvent, MouseEvent, ReactElement, ReactNode, ReactPortal } from 'react'; import type {
FocusEvent,
JSXElementConstructor,
KeyboardEvent,
MouseEvent,
ReactElement,
ReactNode,
ReactPortal,
} from 'react';
import { import {
Children, Children,
cloneElement, cloneElement,
@@ -308,7 +316,7 @@ function DataGridCellContent<TData extends object = {}, TValue = unknown>({
isEditable && isEditable &&
'focus-within:outline-none focus-within:ring-0 focus:ring-0', 'focus-within:outline-none focus-within:ring-0 focus:ring-0',
isSelected && 'shadow-outline', isSelected && 'shadow-outline',
isEditing ? 'p-0.5 shadow-outline-dark' : 'py-1.5 px-2', isEditing ? 'p-0.5 shadow-outline-dark' : 'px-2 py-1.5',
className, className,
)} )}
onFocus={handleFocus} onFocus={handleFocus}
@@ -320,20 +328,28 @@ function DataGridCellContent<TData extends object = {}, TValue = unknown>({
sx={{ backgroundColor: 'transparent' }} sx={{ backgroundColor: 'transparent' }}
{...props} {...props}
> >
{Children.map(children, (child: ReactNode | ReactPortal | ReactElement<unknown, string | JSXElementConstructor<any>>) => { {Children.map(
if (!isValidElement(child)) { children,
return null; (
} child:
| ReactNode
| ReactPortal
| ReactElement<unknown, string | JSXElementConstructor<any>>,
) => {
if (!isValidElement(child)) {
return null;
}
return cloneElement(child, { return cloneElement(child, {
...child.props, ...child.props,
onSave: handleSave, onSave: handleSave,
optimisticValue, optimisticValue,
onOptimisticValueChange: setOptimisticValue, onOptimisticValueChange: setOptimisticValue,
temporaryValue, temporaryValue,
onTemporaryValueChange: setTemporaryValue, onTemporaryValueChange: setTemporaryValue,
}); });
})} },
)}
</Box> </Box>
); );

View File

@@ -96,45 +96,52 @@ export default function DataGridHeader<T extends object>({
}} }}
key={column.id} key={column.id}
> >
<Dropdown.Trigger {column.id === 'selection' ? (
className={twMerge(
'focus:outline-none motion-safe:transition-colors',
)}
disabled={
column.isDisabled ||
column.id === 'selection' ||
(column.disableSortBy && !onRemoveColumn)
}
hideChevron
>
<span <span
{...headerProps} {...headerProps}
className="relative grid w-full grid-flow-col items-center justify-between p-2" className="relative grid w-full grid-flow-col items-center justify-between p-2"
> >
{column.render('Header')} {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> </span>
) : (
{allowResize && !column.disableResizing && ( <Dropdown.Trigger
className={twMerge(
'focus:outline-none motion-safe:transition-colors',
)}
disabled={
column.isDisabled || (column.disableSortBy && !onRemoveColumn)
}
hideChevron
>
<span <span
{...column.getResizerProps({ {...headerProps}
onClick: (event: Event) => event.stopPropagation(), className="relative grid w-full grid-flow-col items-center justify-between p-2"
})} >
className="absolute top-0 bottom-0 -right-0.5 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" {column.render('Header')}
/>
)} {allowSort && (
</Dropdown.Trigger> <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 <Dropdown.Content
menu menu

View File

@@ -11,7 +11,6 @@ import { useNotFoundRedirect } from '@/features/projects/common/hooks/useNotFoun
import { useProjectRoutes } from '@/features/projects/common/hooks/useProjectRoutes'; import { useProjectRoutes } from '@/features/projects/common/hooks/useProjectRoutes';
import { NextSeo } from 'next-seo'; import { NextSeo } from 'next-seo';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useEffect } from 'react';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
export interface ProjectLayoutProps extends AuthenticatedLayoutProps { export interface ProjectLayoutProps extends AuthenticatedLayoutProps {
@@ -48,15 +47,16 @@ function ProjectLayoutContent({
useNotFoundRedirect(); useNotFoundRedirect();
useEffect(() => { // useEffect(() => {
if (isPlatform || !router.isReady) { // if (isPlatform || !router.isReady) {
return; // return;
} // }
if (isRestrictedPath) { // TODO // Double check what restricted path means here
router.push('/local/local'); // if (isRestrictedPath) {
} // router.push('/local/local');
}, [isPlatform, isRestrictedPath, router]); // }
// }, [isPlatform, isRestrictedPath, router]);
if (isRestrictedPath || loading) { if (isRestrictedPath || loading) {
return <LoadingScreen />; return <LoadingScreen />;

View File

@@ -7,6 +7,7 @@ import { List } from '@/components/ui/v2/List';
import type { ListItemButtonProps } from '@/components/ui/v2/ListItem'; import type { ListItemButtonProps } from '@/components/ui/v2/ListItem';
import { ListItem } from '@/components/ui/v2/ListItem'; import { ListItem } from '@/components/ui/v2/ListItem';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import Image from 'next/image'; import Image from 'next/image';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -61,6 +62,7 @@ export default function SettingsSidebar({
className, className,
...props ...props
}: SettingsSidebarProps) { }: SettingsSidebarProps) {
const isPlatform = useIsPlatform();
const [expanded, setExpanded] = useState(false); const [expanded, setExpanded] = useState(false);
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
@@ -95,7 +97,7 @@ export default function SettingsSidebar({
<> <>
<Backdrop <Backdrop
open={expanded} open={expanded}
className="absolute top-0 left-0 bottom-0 right-0 z-[34] md:hidden" className="absolute bottom-0 left-0 right-0 top-0 z-[34] md:hidden"
role="button" role="button"
tabIndex={-1} tabIndex={-1}
onClick={() => setExpanded(false)} onClick={() => setExpanded(false)}
@@ -112,7 +114,7 @@ export default function SettingsSidebar({
<Box <Box
component="aside" component="aside"
className={twMerge( className={twMerge(
'absolute top-0 z-[35] h-full w-full overflow-auto border-r-1 px-2 pt-2 pb-17 motion-safe:transition-transform md:relative md:z-0 md:h-full md:py-2.5 md:transition-none', 'absolute top-0 z-[35] h-full w-full overflow-auto border-r-1 px-2 pb-17 pt-2 motion-safe:transition-transform md:relative md:z-0 md:h-full md:py-2.5 md:transition-none',
expanded ? 'translate-x-0' : '-translate-x-full md:translate-x-0', expanded ? 'translate-x-0' : '-translate-x-full md:translate-x-0',
className, className,
)} )}
@@ -181,7 +183,12 @@ export default function SettingsSidebar({
SMTP SMTP
</SettingsNavLink> </SettingsNavLink>
<SettingsNavLink href="/git" exact={false} onClick={handleSelect}> <SettingsNavLink
href="/git"
exact={false}
onClick={handleSelect}
disabled={!isPlatform}
>
Git Git
</SettingsNavLink> </SettingsNavLink>

View File

@@ -1,6 +1,6 @@
import { PlusCircleIcon } from '@/components/ui/v2/icons/PlusCircleIcon'; import { PlusCircleIcon } from '@/components/ui/v2/icons/PlusCircleIcon';
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon'; import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import type { ComponentMeta, ComponentStory } from '@storybook/react'; import type { Meta, StoryFn } from '@storybook/react';
import type { ButtonProps } from './Button'; import type { ButtonProps } from './Button';
import Button from './Button'; import Button from './Button';
@@ -24,9 +24,9 @@ export default {
control: { type: 'radio' }, control: { type: 'radio' },
}, },
}, },
} as ComponentMeta<typeof Button>; } as Meta<typeof Button>;
const Template: ComponentStory<typeof Button> = function Template( const Template: StoryFn<ButtonProps> = function TemplateFunction(
args: ButtonProps, args: ButtonProps,
) { ) {
return <Button {...args} />; return <Button {...args} />;

View File

@@ -5,7 +5,7 @@ import { XIcon } from '@/components/ui/v2/icons/XIcon';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { getToastBackgroundColor } from '@/utils/constants/settings'; import { getToastBackgroundColor } from '@/utils/constants/settings';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { type ApolloError } from '@apollo/client'; import type { ApolloError } from '@apollo/client';
import { useUserData } from '@nhost/nextjs'; import { useUserData } from '@nhost/nextjs';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
@@ -20,6 +20,44 @@ interface ErrorDetails {
error: any; error: any;
} }
const getInternalErrorMessage = (
error: Error | ApolloError | undefined,
): string | null => {
if (!error) {
return null;
}
if (error.name === 'ApolloError') {
// @ts-ignore
const internalError = error.graphQLErrors?.[0]?.extensions?.internal as {
error: { message: string };
};
return internalError?.error?.message || null;
}
if (error instanceof Error) {
return error.message;
}
return null;
};
const errorToObject = (error: ApolloError | Error) => {
if (error.name === 'ApolloError') {
return error;
}
if (error instanceof Error) {
return {
name: error.name,
message: error.message,
stack: error.stack,
};
}
return {};
};
export default function ErrorToast({ export default function ErrorToast({
isVisible, isVisible,
errorMessage, errorMessage,
@@ -28,7 +66,7 @@ export default function ErrorToast({
}: { }: {
isVisible: boolean; isVisible: boolean;
errorMessage: string; errorMessage: string;
error: ApolloError; error: ApolloError | Error;
close: () => void; close: () => void;
}) { }) {
const userData = useUserData(); const userData = useUserData();
@@ -43,19 +81,10 @@ export default function ErrorToast({
userId: userData?.id || 'local', userId: userData?.id || 'local',
url: asPath, url: asPath,
}, },
error, error: errorToObject(error),
}; };
const internalError = error?.graphQLErrors?.at(0)?.extensions?.internal as { const msg = getInternalErrorMessage(error) || errorMessage;
error: {
message: string;
};
};
const msg =
internalError?.error?.message ||
error?.graphQLErrors?.at(0).message ||
errorMessage;
return ( return (
<AnimatePresence> <AnimatePresence>

View File

@@ -1,5 +1,5 @@
import { Option } from '@/components/ui/v2/Option'; import { Option } from '@/components/ui/v2/Option';
import type { ComponentMeta, ComponentStory } from '@storybook/react'; import type { Meta, StoryFn } from '@storybook/react';
import type { SelectProps } from './Select'; import type { SelectProps } from './Select';
import Select from './Select'; import Select from './Select';
@@ -7,11 +7,9 @@ export default {
title: 'UI Library / Select', title: 'UI Library / Select',
component: Select, component: Select,
argTypes: {}, argTypes: {},
} as ComponentMeta<typeof Select>; } as Meta<typeof Select>;
const Template: ComponentStory<typeof Select> = function Template( const Template: StoryFn<SelectProps<any>> = function TemplateFunction(args) {
args: SelectProps<any>,
) {
return ( return (
<Select className="w-64" {...args}> <Select className="w-64" {...args}>
<Option value="value1">Value 1</Option> <Option value="value1">Value 1</Option>

View File

@@ -1,4 +1,4 @@
import type { ComponentMeta, ComponentStory } from '@storybook/react'; import type { Meta, StoryFn } from '@storybook/react';
import type { SwitchProps } from './Switch'; import type { SwitchProps } from './Switch';
import Switch from './Switch'; import Switch from './Switch';
@@ -6,9 +6,9 @@ export default {
title: 'UI Library / Switch', title: 'UI Library / Switch',
component: Switch, component: Switch,
argTypes: {}, argTypes: {},
} as ComponentMeta<typeof Switch>; } as Meta<typeof Switch>;
const Template: ComponentStory<typeof Switch> = function Template( const Template: StoryFn<SwitchProps> = function TemplateFunction(
args: SwitchProps, args: SwitchProps,
) { ) {
return <Switch label="Accept Rules" {...args} />; return <Switch label="Accept Rules" {...args} />;

View File

@@ -0,0 +1,85 @@
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { Input } from '@/components/ui/v2/Input';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { useUpdateUserDisplayNameMutation } from '@/utils/__generated__/graphql';
import { yupResolver } from '@hookform/resolvers/yup';
import { useUserData } from '@nhost/nextjs';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
const validationSchema = Yup.object({
displayName: Yup.string()
.label('Display Name')
.required('This field is required.'),
});
export type DisplayNameSettingFormValues = Yup.InferType<
typeof validationSchema
>;
export default function DisplayNameSetting() {
const { id: userID, displayName } = useUserData();
const [updateUserDisplayName] = useUpdateUserDisplayNameMutation();
const form = useForm<DisplayNameSettingFormValues>({
reValidateMode: 'onSubmit',
defaultValues: {
displayName,
},
resolver: yupResolver(validationSchema),
});
const { register, formState } = form;
const isDirty = Object.keys(formState.dirtyFields).length > 0;
async function handleSubmit(formValues: DisplayNameSettingFormValues) {
await execPromiseWithErrorToast(
async () => {
await updateUserDisplayName({
variables: {
id: userID,
displayName: formValues.displayName,
},
});
form.reset({ displayName: formValues.displayName });
},
{
loadingMessage: 'Updating your display name...',
successMessage: 'Your display name has been updated successfully.',
errorMessage:
'An error occurred while trying to update your display name. Please try again.',
},
);
}
return (
<FormProvider {...form}>
<Form onSubmit={handleSubmit}>
<SettingsContainer
title="Update your display name"
slotProps={{
submitButton: {
disabled: !isDirty,
loading: formState.isSubmitting,
},
}}
className="grid grid-flow-row lg:grid-cols-5"
>
<Input
{...register('displayName')}
className="col-span-2"
type="text"
id="display-name"
label="Display Name"
fullWidth
helperText={formState.errors.displayName?.message}
error={Boolean(formState.errors.displayName)}
/>
</SettingsContainer>
</Form>
</FormProvider>
);
}

View File

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

View File

@@ -0,0 +1,80 @@
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { Input } from '@/components/ui/v2/Input';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useNhostClient, useUserData } from '@nhost/nextjs';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
const validationSchema = Yup.object({
email: Yup.string().label('Email').email().required(),
});
export type EmailSettingFormValues = Yup.InferType<typeof validationSchema>;
export default function EmailSetting() {
const nhost = useNhostClient();
const { email } = useUserData();
const form = useForm<EmailSettingFormValues>({
reValidateMode: 'onSubmit',
defaultValues: { email },
resolver: yupResolver(validationSchema),
});
const { register, formState } = form;
const isDirty = Object.keys(formState.dirtyFields).length > 0;
async function handleSubmit(formValues: EmailSettingFormValues) {
await execPromiseWithErrorToast(
async () => {
await nhost.auth.changeEmail({
newEmail: formValues.email,
options: {
redirectTo: `${window.location.origin}/account`,
},
});
form.reset({ email: formValues.email });
},
{
loadingMessage: 'Updating your email...',
successMessage:
'Please check your inbox. Follow the link to finalize changing your email.',
errorMessage:
'An error occurred while trying to update your email. Please try again.',
},
);
}
return (
<FormProvider {...form}>
<Form onSubmit={handleSubmit}>
<SettingsContainer
title="Update your email"
slotProps={{
submitButton: {
disabled: !isDirty,
loading: formState.isSubmitting,
},
}}
className="grid grid-flow-row lg:grid-cols-5"
>
<Input
{...register('email')}
className="col-span-2"
id="email"
spellCheck="false"
autoCapitalize="none"
type="email"
label="Email"
hideEmptyHelperText
fullWidth
helperText={formState.errors.email?.message}
error={Boolean(formState.errors.email)}
/>
</SettingsContainer>
</Form>
</FormProvider>
);
}

View File

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

View File

@@ -0,0 +1,76 @@
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { GitHubIcon } from '@/components/ui/v2/icons/GitHubIcon';
import { Text } from '@/components/ui/v2/Text';
import { useGetAuthUserProvidersQuery } from '@/utils/__generated__/graphql';
import { useProviderLink } from '@nhost/nextjs';
import NavLink from 'next/link';
export default function SocialProvidersSettings() {
const { data, loading, error } = useGetAuthUserProvidersQuery();
const isGithubConnected = data?.authUserProviders?.some(
(item) => item.providerId === 'github',
);
const { github } = useProviderLink({
connect: true,
redirectTo: `${window.location.origin}/account`,
});
if (!data && loading) {
return (
<ActivityIndicator
delay={1000}
label="Loading personal access tokens..."
/>
);
}
if (error) {
throw error;
}
return (
<SettingsContainer
title="Authentication providers"
description=""
rootClassName="gap-0 flex flex-col items-start"
className="my-2"
slotProps={{
submitButton: { className: 'hidden' },
footer: { className: 'hidden' },
}}
>
{isGithubConnected ? (
<Box
sx={{ backgroundColor: 'grey.200' }}
className="flex flex-row items-center justify-start space-x-2 rounded-md p-2"
>
<GitHubIcon />
<Text className="font-medium ">Connected</Text>
</Box>
) : (
<Box>
<NavLink
href={github}
passHref
target="_blank"
rel="noreferrer noopener"
legacyBehavior
>
<Button
className=""
variant="outlined"
color="secondary"
startIcon={<GitHubIcon />}
>
Connect with GitHub
</Button>
</NavLink>
</Box>
)}
</SettingsContainer>
);
}

View File

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

View File

@@ -0,0 +1,6 @@
query getAuthUserProviders {
authUserProviders {
id
providerId
}
}

View File

@@ -0,0 +1,6 @@
mutation updateUserDisplayName($id: uuid!, $displayName: String!) {
updateUser(pk_columns: { id: $id }, _set: { displayName: $displayName }) {
id
displayName
}
}

View File

@@ -1,4 +1,5 @@
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
import { ControlledSelect } from '@/components/form/ControlledSelect';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { Box } from '@/components/ui/v2/Box'; import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button'; import { Button } from '@/components/ui/v2/Button';
@@ -6,6 +7,7 @@ import { ArrowsClockwise } from '@/components/ui/v2/icons/ArrowsClockwise';
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon'; import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon'; import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { Option } from '@/components/ui/v2/Option';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip'; import { Tooltip } from '@/components/ui/v2/Tooltip';
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient'; import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
@@ -20,11 +22,18 @@ import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
const AUTO_EMBEDDINGS_MODELS = [
'text-embedding-ada-002',
'text-embedding-3-small',
'text-embedding-3-large',
];
export const validationSchema = Yup.object({ export const validationSchema = Yup.object({
name: Yup.string().required('The name is required.'), name: Yup.string().required('The name field is required.'),
schemaName: Yup.string().required('The schema is required'), model: Yup.string().oneOf(AUTO_EMBEDDINGS_MODELS),
tableName: Yup.string().required('The table is required'), schemaName: Yup.string().required('The schema field is required'),
columnName: Yup.string().required('The column is required'), tableName: Yup.string().required('The table field is required'),
columnName: Yup.string().required('The column field is required'),
query: Yup.string(), query: Yup.string(),
mutation: Yup.string(), mutation: Yup.string(),
}); });
@@ -40,7 +49,7 @@ export interface AutoEmbeddingsFormProps extends DialogFormProps {
/** /**
* if there is initialData then it's an update operation * if there is initialData then it's an update operation
*/ */
initialData?: AutoEmbeddingsFormValues; initialData?: AutoEmbeddingsFormValues & { model: string };
/** /**
* Function to be called when the operation is cancelled. * Function to be called when the operation is cancelled.
@@ -74,7 +83,10 @@ export default function AutoEmbeddingsForm({
}); });
const form = useForm<AutoEmbeddingsFormValues>({ const form = useForm<AutoEmbeddingsFormValues>({
defaultValues: initialData, defaultValues: {
...initialData,
model: initialData?.model ?? 'text-embedding-ada-002',
},
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
@@ -106,7 +118,9 @@ export default function AutoEmbeddingsForm({
} }
await insertGraphiteAutoEmbeddingsConfiguration({ await insertGraphiteAutoEmbeddingsConfiguration({
variables: values, variables: {
...values,
},
}); });
}; };
@@ -129,9 +143,9 @@ export default function AutoEmbeddingsForm({
<FormProvider {...form}> <FormProvider {...form}>
<Form <Form
onSubmit={handleSubmit} onSubmit={handleSubmit}
className="flex h-full flex-col gap-4 overflow-hidden" className="flex flex-col h-full gap-4 overflow-hidden"
> >
<div className="flex flex-1 flex-col space-y-6 overflow-auto px-6"> <div className="flex flex-col flex-1 px-6 space-y-6 overflow-auto">
<Input <Input
{...register('name')} {...register('name')}
id="name" id="name"
@@ -141,7 +155,7 @@ export default function AutoEmbeddingsForm({
<Tooltip title="Name of the Auto-Embeddings"> <Tooltip title="Name of the Auto-Embeddings">
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
@@ -155,6 +169,36 @@ export default function AutoEmbeddingsForm({
autoComplete="off" autoComplete="off"
autoFocus autoFocus
/> />
<ControlledSelect
slotProps={{
popper: { disablePortal: false, className: 'z-[10000]' },
}}
id="model"
name="model"
label={
<Box className="flex flex-row items-center space-x-2">
<Text>Model</Text>
<Tooltip title="Auto-Embeddings Model">
<InfoIcon
aria-label="Info"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
</Box>
}
fullWidth
error={!!errors?.model?.message}
helperText={errors?.model?.message}
>
{AUTO_EMBEDDINGS_MODELS.map((model) => (
<Option key={model} value={model}>
{model}
</Option>
))}
</ControlledSelect>
<Input <Input
{...register('schemaName')} {...register('schemaName')}
id="schemaName" id="schemaName"
@@ -164,7 +208,7 @@ export default function AutoEmbeddingsForm({
<Tooltip title={<span>Schema where the table belongs to</span>}> <Tooltip title={<span>Schema where the table belongs to</span>}>
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
@@ -186,7 +230,7 @@ export default function AutoEmbeddingsForm({
<Tooltip title="Table Name"> <Tooltip title="Table Name">
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
@@ -208,7 +252,7 @@ export default function AutoEmbeddingsForm({
<Tooltip title="Column name"> <Tooltip title="Column name">
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
@@ -230,7 +274,7 @@ export default function AutoEmbeddingsForm({
<Tooltip title="Query"> <Tooltip title="Query">
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
@@ -254,7 +298,7 @@ export default function AutoEmbeddingsForm({
<Tooltip title="Mutation"> <Tooltip title="Mutation">
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
@@ -271,7 +315,7 @@ export default function AutoEmbeddingsForm({
/> />
</div> </div>
<Box className="flex w-full flex-row justify-between rounded border-t px-6 py-4"> <Box className="flex flex-row justify-between w-full px-6 py-4 border-t rounded">
<Button variant="outlined" color="secondary" onClick={onCancel}> <Button variant="outlined" color="secondary" onClick={onCancel}>
Cancel Cancel
</Button> </Button>

View File

@@ -1,3 +1,4 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete'; import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
@@ -12,6 +13,7 @@ import { Switch } from '@/components/ui/v2/Switch';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip'; import { Tooltip } from '@/components/ui/v2/Tooltip';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema'; import { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import { ComputeFormSection } from '@/features/services/components/ServiceForm/components/ComputeFormSection'; import { ComputeFormSection } from '@/features/services/components/ServiceForm/components/ComputeFormSection';
import { import {
@@ -20,14 +22,19 @@ import {
useGetSoftwareVersionsQuery, useGetSoftwareVersionsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common'; import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
import { getToastStyleProps } from '@/utils/constants/settings';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { DisableAIServiceConfirmationDialog } from './DisableAIServiceConfirmationDialog'; import { DisableAIServiceConfirmationDialog } from './DisableAIServiceConfirmationDialog';
const MIN_POSTGRES_VERSION_SUPPORTING_AI = '14.6-20231018-1';
const validationSchema = Yup.object({ const validationSchema = Yup.object({
version: Yup.object({ version: Yup.object({
label: Yup.string().required(), label: Yup.string().required(),
@@ -46,21 +53,28 @@ const validationSchema = Yup.object({
export type AISettingsFormValues = Yup.InferType<typeof validationSchema>; export type AISettingsFormValues = Yup.InferType<typeof validationSchema>;
export default function AISettings() { export default function AISettings() {
const { maintenanceActive } = useUI(); const isPlatform = useIsPlatform();
const { openDialog } = useDialog(); const { openDialog } = useDialog();
const [updateConfig] = useUpdateConfigMutation(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [aiServiceEnabled, setAIServiceEnabled] = useState(true); const [aiServiceEnabled, setAIServiceEnabled] = useState(true);
const { const {
data: { config: { ai } = {} } = {}, data: {
config: { ai, postgres: { version: postgresVersion } = {} } = {},
} = {},
loading: loadingAiSettings, loading: loadingAiSettings,
error: errorGettingAiSettings, error: errorGettingAiSettings,
} = useGetAiSettingsQuery({ } = useGetAiSettingsQuery({
variables: { variables: {
appId: currentProject.id, appId: currentProject.id,
}, },
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data: graphiteVersionsData, loading: loadingGraphiteVersionsData } = const { data: graphiteVersionsData, loading: loadingGraphiteVersionsData } =
@@ -68,6 +82,7 @@ export default function AISettings() {
variables: { variables: {
software: Software_Type_Enum.Graphite, software: Software_Type_Enum.Graphite,
}, },
skip: !isPlatform,
}); });
const graphiteVersions = graphiteVersionsData?.softwareVersions || []; const graphiteVersions = graphiteVersionsData?.softwareVersions || [];
@@ -92,8 +107,8 @@ export default function AISettings() {
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
defaultValues: { defaultValues: {
version: { version: {
label: ai?.version ?? availableVersions?.at(0)?.label, label: ai?.version || availableVersions?.at(0)?.label || '',
value: ai?.version ?? availableVersions?.at(0)?.value, value: ai?.version || availableVersions?.at(0)?.value || '',
}, },
webhookSecret: '', webhookSecret: '',
organization: '', organization: '',
@@ -150,6 +165,17 @@ export default function AISettings() {
]); ]);
const toggleAIService = async (enabled: boolean) => { const toggleAIService = async (enabled: boolean) => {
if (postgresVersion < MIN_POSTGRES_VERSION_SUPPORTING_AI) {
toast.error(
'In order to enable the AI service you need to update your database version to 14.6-20231018-1 or newer.',
{
style: getToastStyleProps().style,
...getToastStyleProps().error,
},
);
return;
}
setAIServiceEnabled(enabled); setAIServiceEnabled(enabled);
if (!enabled && ai) { if (!enabled && ai) {
@@ -208,6 +234,18 @@ export default function AISettings() {
}); });
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'AI settings are being updated...', loadingMessage: 'AI settings are being updated...',
@@ -230,7 +268,7 @@ export default function AISettings() {
return ( return (
<Box className="space-y-4" sx={{ backgroundColor: 'background.default' }}> <Box className="space-y-4" sx={{ backgroundColor: 'background.default' }}>
<Box className="flex flex-row items-center justify-between rounded-lg border-1 p-4"> <Box className="flex flex-row items-center justify-between p-4 rounded-lg border-1">
<Text className="text-lg font-semibold">Enable AI service</Text> <Text className="text-lg font-semibold">Enable AI service</Text>
<Switch <Switch
checked={aiServiceEnabled} checked={aiServiceEnabled}
@@ -253,54 +291,58 @@ export default function AISettings() {
className="flex flex-col" className="flex flex-col"
> >
<Box className="space-y-4"> <Box className="space-y-4">
{availableVersions.length > 0 && ( <Box className="space-y-2">
<Box className="space-y-2"> <Box className="flex flex-row items-center space-x-2">
<Box className="flex flex-row items-center space-x-2"> <Text className="text-lg font-semibold">Version</Text>
<Text className="text-lg font-semibold">Version</Text> <Tooltip title="Version of the service to use.">
<Tooltip title="Version of the service to use."> <InfoIcon
<InfoIcon aria-label="Info"
aria-label="Info" className="w-4 h-4"
className="h-4 w-4" color="primary"
color="primary" />
/> </Tooltip>
</Tooltip>
</Box>
<ControlledAutocomplete
id="version"
name="version"
autoHighlight
isOptionEqualToValue={() => false}
filterOptions={(options, { inputValue }) => {
const inputValueLower = inputValue.toLowerCase();
const matched = [];
const otherOptions = [];
options.forEach((option) => {
const optionLabelLower = option.label.toLowerCase();
if (optionLabelLower.startsWith(inputValueLower)) {
matched.push(option);
} else {
otherOptions.push(option);
}
});
const result = [...matched, ...otherOptions];
return result;
}}
fullWidth
className="col-span-4"
options={availableVersions}
error={!!formState.errors?.version?.message}
helperText={formState.errors?.version?.message}
showCustomOption="auto"
customOptionLabel={(value) =>
`Use custom value: "${value}"`
}
/>
</Box> </Box>
)} <ControlledAutocomplete
id="version"
name="version"
autoHighlight
isOptionEqualToValue={() => false}
filterOptions={(options, { inputValue }) => {
const inputValueLower = inputValue.toLowerCase();
const matched = [];
const otherOptions = [];
options.forEach((option) => {
const optionLabelLower = option.label.toLowerCase();
if (optionLabelLower.startsWith(inputValueLower)) {
matched.push(option);
} else {
otherOptions.push(option);
}
});
const result = [...matched, ...otherOptions];
return result;
}}
fullWidth
className="col-span-4"
options={availableVersions}
error={
!!formState.errors?.version?.message ||
!!formState.errors?.version?.value?.message
}
helperText={
formState.errors?.version?.message ||
formState.errors?.version?.value?.message
}
showCustomOption="auto"
customOptionLabel={(value) =>
`Use custom value: "${value}"`
}
/>
</Box>
<Box className="space-y-2"> <Box className="space-y-2">
<Box className="flex flex-row items-center space-x-2"> <Box className="flex flex-row items-center space-x-2">
@@ -310,7 +352,7 @@ export default function AISettings() {
<Tooltip title="Used to validate requests between postgres and the AI service. The AI service will also include the header X-Graphite-Webhook-Secret with this value set when calling external webhooks so the source of the request can be validated."> <Tooltip title="Used to validate requests between postgres and the AI service. The AI service will also include the header X-Graphite-Webhook-Secret with this value set when calling external webhooks so the source of the request can be validated.">
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
@@ -334,26 +376,28 @@ export default function AISettings() {
<Tooltip title="Dedicated resources allocated for the service."> <Tooltip title="Dedicated resources allocated for the service.">
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
</Box> </Box>
<Alert {isPlatform ? (
severity="info" <Alert
className="flex items-center justify-between space-x-2" severity="info"
> className="flex items-center justify-between space-x-2"
<span>{getAIResourcesCost()}</span> >
<b> <span>{getAIResourcesCost()}</span>
$ <b>
{parseFloat( $
( {parseFloat(
aiSettingsFormValues.compute.cpu * COST_PER_VCPU (
).toFixed(2), aiSettingsFormValues.compute.cpu * COST_PER_VCPU
)} ).toFixed(2),
</b> )}
</Alert> </b>
</Alert>
) : null}
<ComputeFormSection /> <ComputeFormSection />
</Box> </Box>
@@ -372,7 +416,7 @@ export default function AISettings() {
<Tooltip title="Key to use for authenticating API requests to OpenAI"> <Tooltip title="Key to use for authenticating API requests to OpenAI">
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
@@ -395,7 +439,7 @@ export default function AISettings() {
<Tooltip title="Optional. OpenAI organization to use."> <Tooltip title="Optional. OpenAI organization to use.">
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
@@ -423,7 +467,7 @@ export default function AISettings() {
<Tooltip title="How often to run the job that keeps embeddings up to date."> <Tooltip title="How often to run the job that keeps embeddings up to date.">
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>

View File

@@ -1,8 +1,11 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
import { Box } from '@/components/ui/v2/Box'; import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button'; import { Button } from '@/components/ui/v2/Button';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { useUpdateConfigMutation } from '@/utils/__generated__/graphql'; import { useUpdateConfigMutation } from '@/utils/__generated__/graphql';
import { useState } from 'react'; import { useState } from 'react';
@@ -23,11 +26,14 @@ export default function DisableAIServiceConfirmationDialog({
onCancel, onCancel,
onServiceDisabled, onServiceDisabled,
}: DisableAIServiceConfirmationDialogProps) { }: DisableAIServiceConfirmationDialogProps) {
const { closeDialog } = useDialog(); const isPlatform = useIsPlatform();
const { openDialog, closeDialog } = useDialog();
const localMimirClient = useLocalMimirClient();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
const [updateConfig] = useUpdateConfigMutation(); ...(!isPlatform ? { client: localMimirClient } : {}),
});
async function handleClick() { async function handleClick() {
setLoading(true); setLoading(true);
@@ -45,6 +51,18 @@ export default function DisableAIServiceConfirmationDialog({
onServiceDisabled(); onServiceDisabled();
closeDialog(); closeDialog();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Disabling the AI service...', loadingMessage: 'Disabling the AI service...',

View File

@@ -1,5 +1,8 @@
query GetAISettings($appId: uuid!) { query GetAISettings($appId: uuid!) {
config(appID: $appId, resolve: false) { config(appID: $appId, resolve: false) {
postgres {
version
}
ai { ai {
version version
webhookSecret webhookSecret

View File

@@ -1,16 +1,21 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetAuthenticationSettingsDocument, GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery, useGetAuthenticationSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -26,15 +31,19 @@ export type AllowedEmailSettingsFormValues = Yup.InferType<
>; >;
export default function AllowedEmailDomainsSettings() { export default function AllowedEmailDomainsSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument], refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetAuthenticationSettingsQuery({ const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { email, emailDomains } = data?.config?.auth?.user || {}; const { email, emailDomains } = data?.config?.auth?.user || {};
@@ -54,6 +63,17 @@ export default function AllowedEmailDomainsSettings() {
const isDirty = Object.keys(formState.dirtyFields).length > 0; const isDirty = Object.keys(formState.dirtyFields).length > 0;
useEffect(() => {
if (!loading && email && emailDomains) {
form.reset({
enabled:
email?.allowed?.length > 0 || emailDomains?.allowed?.length > 0,
allowedEmails: email?.allowed?.join(', ') || '',
allowedEmailDomains: emailDomains?.allowed?.join(', ') || '',
});
}
}, [loading, form, email, emailDomains]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -105,6 +125,18 @@ export default function AllowedEmailDomainsSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(values); form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Allowed email settings are being updated...', loadingMessage: 'Allowed email settings are being updated...',
@@ -130,7 +162,7 @@ export default function AllowedEmailDomainsSettings() {
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
docsLink="https://docs.nhost.io/authentication#allowed-emails-and-domains" docsLink="https://docs.nhost.io/guides/auth/overview#allowed-emails-and-domains"
switchId="enabled" switchId="enabled"
showSwitch showSwitch
className={twMerge( className={twMerge(

View File

@@ -1,16 +1,21 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetAuthenticationSettingsDocument, GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery, useGetAuthenticationSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -23,15 +28,19 @@ export type AllowedRedirectURLFormValues = Yup.InferType<
>; >;
export default function AllowedRedirectURLsSettings() { export default function AllowedRedirectURLsSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument], refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetAuthenticationSettingsQuery({ const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { allowedUrls } = data?.config?.auth?.redirections || {}; const { allowedUrls } = data?.config?.auth?.redirections || {};
@@ -44,6 +53,14 @@ export default function AllowedRedirectURLsSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading && allowedUrls) {
form.reset({
allowedUrls: allowedUrls?.join(', ') || '',
});
}
}, [loading, allowedUrls, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -82,6 +99,18 @@ export default function AllowedRedirectURLsSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(values); form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Allowed redirect URL settings are being updated...', loadingMessage: 'Allowed redirect URL settings are being updated...',
@@ -105,7 +134,7 @@ export default function AllowedRedirectURLsSettings() {
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
docsLink="https://docs.nhost.io/authentication#allowed-redirect-urls" docsLink="https://docs.nhost.io/guides/auth/overview#allowed-redirect-urls"
className="grid grid-flow-row px-4 lg:grid-cols-5" className="grid grid-flow-row px-4 lg:grid-cols-5"
> >
<Input <Input

View File

@@ -1,15 +1,20 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -20,15 +25,19 @@ const validationSchema = Yup.object({
export type AnonymousSignInFormValues = Yup.InferType<typeof validationSchema>; export type AnonymousSignInFormValues = Yup.InferType<typeof validationSchema>;
export default function AnonymousSignInSettings() { export default function AnonymousSignInSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { enabled } = data?.config?.auth?.method?.anonymous || {}; const { enabled } = data?.config?.auth?.method?.anonymous || {};
@@ -41,6 +50,12 @@ export default function AnonymousSignInSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({ enabled });
}
}, [loading, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -75,6 +90,18 @@ export default function AnonymousSignInSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(values); form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Anonymous sign-in settings are being updated...', loadingMessage: 'Anonymous sign-in settings are being updated...',

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -7,16 +9,19 @@ import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { InputAdornment } from '@/components/ui/v2/InputAdornment'; import { InputAdornment } from '@/components/ui/v2/InputAdornment';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useTheme } from '@mui/material'; import { useTheme } from '@mui/material';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -53,15 +58,19 @@ export type AppleProviderFormValues = Yup.InferType<typeof validationSchema>;
export default function AppleProviderSettings() { export default function AppleProviderSettings() {
const theme = useTheme(); const theme = useTheme();
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { clientId, enabled, keyId, privateKey, teamId } = const { clientId, enabled, keyId, privateKey, teamId } =
@@ -79,6 +88,18 @@ export default function AppleProviderSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
teamId: teamId || '',
keyId: keyId || '',
clientId: clientId || '',
privateKey: privateKey || '',
enabled: enabled || false,
});
}
}, [loading, teamId, keyId, clientId, privateKey, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -119,6 +140,18 @@ export default function AppleProviderSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Apple settings are being updated...', loadingMessage: 'Apple settings are being updated...',
@@ -141,7 +174,7 @@ export default function AppleProviderSettings() {
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
docsLink="https://docs.nhost.io/authentication/sign-in-with-apple" docsLink="https://docs.nhost.io/guides/auth/social/sign-in-apple"
docsTitle="how to sign in users with Apple" docsTitle="how to sign in users with Apple"
icon={ icon={
theme.palette.mode === 'dark' theme.palette.mode === 'dark'
@@ -236,7 +269,7 @@ export default function AppleProviderSettings() {
); );
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
} }

View File

@@ -1,9 +1,12 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete'; import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetAuthenticationSettingsDocument, GetAuthenticationSettingsDocument,
Software_Type_Enum, Software_Type_Enum,
@@ -11,8 +14,10 @@ import {
useGetSoftwareVersionsQuery, useGetSoftwareVersionsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -28,21 +33,26 @@ export type AuthServiceVersionFormValues = Yup.InferType<
>; >;
export default function AuthServiceVersionSettings() { export default function AuthServiceVersionSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument], refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetAuthenticationSettingsQuery({ const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data: authVersionsData } = useGetSoftwareVersionsQuery({ const { data: authVersionsData } = useGetSoftwareVersionsQuery({
variables: { variables: {
software: Software_Type_Enum.Auth, software: Software_Type_Enum.Auth,
}, },
skip: !isPlatform,
}); });
const { version } = data?.config?.auth || {}; const { version } = data?.config?.auth || {};
@@ -59,10 +69,21 @@ export default function AuthServiceVersionSettings() {
const form = useForm<AuthServiceVersionFormValues>({ const form = useForm<AuthServiceVersionFormValues>({
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
defaultValues: { version: { label: version, value: version } }, defaultValues: { version: { label: '', value: '' } },
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading && version) {
form.reset({
version: {
label: version,
value: version,
},
});
}
}, [loading, version, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -97,6 +118,18 @@ export default function AuthServiceVersionSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Auth version is being updated...', loadingMessage: 'Auth version is being updated...',
@@ -120,12 +153,20 @@ export default function AuthServiceVersionSettings() {
}} }}
docsLink="https://github.com/nhost/hasura-auth/releases" docsLink="https://github.com/nhost/hasura-auth/releases"
docsTitle="the latest releases" docsTitle="the latest releases"
className="grid grid-flow-row gap-x-4 gap-y-2 px-4 lg:grid-cols-5" className="grid grid-flow-row px-4 gap-x-4 gap-y-2 lg:grid-cols-5"
> >
<ControlledAutocomplete <ControlledAutocomplete
id="version" id="version"
name="version" name="version"
autoHighlight autoHighlight
freeSolo
getOptionLabel={(option) => {
if (typeof option === 'string') {
return option || '';
}
return option.value;
}}
isOptionEqualToValue={() => false} isOptionEqualToValue={() => false}
filterOptions={(options, { inputValue }) => { filterOptions={(options, { inputValue }) => {
const inputValueLower = inputValue.toLowerCase(); const inputValueLower = inputValue.toLowerCase();

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -8,15 +10,18 @@ import { Input } from '@/components/ui/v2/Input';
import { InputAdornment } from '@/components/ui/v2/InputAdornment'; import { InputAdornment } from '@/components/ui/v2/InputAdornment';
import { BaseProviderSettings } from '@/features/authentication/settings/components/BaseProviderSettings'; import { BaseProviderSettings } from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -46,15 +51,19 @@ const validationSchema = Yup.object({
export type AzureADProviderFormValues = Yup.InferType<typeof validationSchema>; export type AzureADProviderFormValues = Yup.InferType<typeof validationSchema>;
export default function AzureADProviderSettings() { export default function AzureADProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { clientId, clientSecret, tenant, enabled } = const { clientId, clientSecret, tenant, enabled } =
@@ -71,6 +80,17 @@ export default function AzureADProviderSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
tenant: tenant || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, tenant, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -108,6 +128,18 @@ export default function AzureADProviderSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Azure AD settings are being updated...', loadingMessage: 'Azure AD settings are being updated...',
@@ -182,7 +214,7 @@ export default function AzureADProviderSettings() {
); );
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
} }

View File

@@ -1,16 +1,21 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetAuthenticationSettingsDocument, GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery, useGetAuthenticationSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -24,15 +29,19 @@ const validationSchema = Yup.object({
export type BlockedEmailFormValues = Yup.InferType<typeof validationSchema>; export type BlockedEmailFormValues = Yup.InferType<typeof validationSchema>;
export default function BlockedEmailSettings() { export default function BlockedEmailSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument], refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetAuthenticationSettingsQuery({ const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { email, emailDomains } = data?.config?.auth?.user || {}; const { email, emailDomains } = data?.config?.auth?.user || {};
@@ -51,6 +60,17 @@ export default function BlockedEmailSettings() {
const enabled = watch('enabled'); const enabled = watch('enabled');
const isDirty = Object.keys(formState.dirtyFields).length > 0; const isDirty = Object.keys(formState.dirtyFields).length > 0;
useEffect(() => {
if (!loading && email && emailDomains) {
form.reset({
enabled:
email?.blocked?.length > 0 || emailDomains?.blocked?.length > 0,
blockedEmails: email?.blocked?.join(', ') || '',
blockedEmailDomains: emailDomains?.blocked?.join(', ') || '',
});
}
}, [loading, email, emailDomains, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -112,6 +132,18 @@ export default function BlockedEmailSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(values); form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: loadingMessage:
@@ -136,7 +168,7 @@ export default function BlockedEmailSettings() {
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
docsLink="https://docs.nhost.io/authentication#blocked-emails-and-domains" docsLink="https://docs.nhost.io/guides/auth/overview#allowed-emails-and-domains"
switchId="enabled" switchId="enabled"
showSwitch showSwitch
className={twMerge( className={twMerge(

View File

@@ -1,16 +1,21 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetAuthenticationSettingsDocument, GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery, useGetAuthenticationSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -21,15 +26,19 @@ const validationSchema = Yup.object({
export type ClientURLFormValues = Yup.InferType<typeof validationSchema>; export type ClientURLFormValues = Yup.InferType<typeof validationSchema>;
export default function ClientURLSettings() { export default function ClientURLSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument], refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetAuthenticationSettingsQuery({ const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { clientUrl, allowedUrls } = data?.config?.auth?.redirections || {}; const { clientUrl, allowedUrls } = data?.config?.auth?.redirections || {};
@@ -42,6 +51,12 @@ export default function ClientURLSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading && clientUrl) {
form.reset({ clientUrl });
}
}, [loading, clientUrl, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -77,6 +92,18 @@ export default function ClientURLSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(values); form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Client URL is being updated...', loadingMessage: 'Client URL is being updated...',
@@ -99,7 +126,7 @@ export default function ClientURLSettings() {
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
docsLink="https://docs.nhost.io/authentication#client-url" docsLink="https://docs.nhost.io/guides/auth/overview#client-url"
className="grid grid-flow-row lg:grid-cols-5" className="grid grid-flow-row lg:grid-cols-5"
> >
<Input <Input

View File

@@ -1,14 +1,19 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
GetAuthenticationSettingsDocument, GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery, useGetAuthenticationSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/utils/__generated__/graphql'; } from '@/utils/__generated__/graphql';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -19,15 +24,19 @@ const validationSchema = Yup.object({
export type DisableNewUsersFormValues = Yup.InferType<typeof validationSchema>; export type DisableNewUsersFormValues = Yup.InferType<typeof validationSchema>;
export default function DisableNewUsersSettings() { export default function DisableNewUsersSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument], refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetAuthenticationSettingsQuery({ const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const form = useForm<DisableNewUsersFormValues>({ const form = useForm<DisableNewUsersFormValues>({
@@ -37,6 +46,14 @@ export default function DisableNewUsersSettings() {
}, },
}); });
useEffect(() => {
if (!loading) {
form.reset({
disabled: !data?.config?.auth?.signUp?.enabled,
});
}
}, [loading, data, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -73,6 +90,18 @@ export default function DisableNewUsersSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(values); form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Disabling new user sign ups...', loadingMessage: 'Disabling new user sign ups...',
@@ -89,7 +118,7 @@ export default function DisableNewUsersSettings() {
<SettingsContainer <SettingsContainer
title="Disable New Users" title="Disable New Users"
description="If set, newly registered users are disabled and won't be able to sign in." description="If set, newly registered users are disabled and won't be able to sign in."
docsLink="https://docs.nhost.io/authentication#disable-new-users" docsLink="https://docs.nhost.io/guides/auth/overview#disable-new-users"
switchId="disabled" switchId="disabled"
showSwitch showSwitch
slotProps={{ slotProps={{

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,28 +14,35 @@ import {
baseProviderValidationSchema, baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings'; } from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
export default function DiscordProviderSettings() { export default function DiscordProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { clientId, clientSecret, enabled } = const { clientId, clientSecret, enabled } =
@@ -49,6 +58,16 @@ export default function DiscordProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema), resolver: yupResolver(baseProviderValidationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -89,6 +108,18 @@ export default function DiscordProviderSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Discord settings are being updated...', loadingMessage: 'Discord settings are being updated...',
@@ -111,7 +142,7 @@ export default function DiscordProviderSettings() {
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
docsLink="https://docs.nhost.io/platform/authentication/sign-in-with-discord" docsLink="https://docs.nhost.io/guides/auth/social/sign-in-discord"
docsTitle="how to sign in users with Discord" docsTitle="how to sign in users with Discord"
icon="/assets/brands/discord.svg" icon="/assets/brands/discord.svg"
switchId="enabled" switchId="enabled"
@@ -153,7 +184,7 @@ export default function DiscordProviderSettings() {
); );
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
} }

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { ControlledCheckbox } from '@/components/form/ControlledCheckbox'; import { ControlledCheckbox } from '@/components/form/ControlledCheckbox';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
@@ -6,13 +8,16 @@ import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -29,15 +34,19 @@ const validationSchema = Yup.object({
export type EmailAndPasswordFormValues = Yup.InferType<typeof validationSchema>; export type EmailAndPasswordFormValues = Yup.InferType<typeof validationSchema>;
export default function EmailAndPasswordSettings() { export default function EmailAndPasswordSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, error, loading } = useGetSignInMethodsQuery({ const { data, error, loading } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { hibpEnabled, emailVerificationRequired, passwordMinLength } = const { hibpEnabled, emailVerificationRequired, passwordMinLength } =
@@ -53,6 +62,22 @@ export default function EmailAndPasswordSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
hibpEnabled,
emailVerificationRequired,
passwordMinLength,
});
}
}, [
loading,
hibpEnabled,
emailVerificationRequired,
passwordMinLength,
form,
]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -87,6 +112,18 @@ export default function EmailAndPasswordSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: `Email and password sign-in settings are being updated...`, loadingMessage: `Email and password sign-in settings are being updated...`,
@@ -104,7 +141,7 @@ export default function EmailAndPasswordSettings() {
<SettingsContainer <SettingsContainer
title="Email and Password" title="Email and Password"
description="Allow users to sign in with email and password." description="Allow users to sign in with email and password."
docsLink="https://docs.nhost.io/authentication/sign-in-with-email-and-password" docsLink="https://docs.nhost.io/guides/auth/sign-in-email-password"
docsTitle="how to sign in users with email and password" docsTitle="how to sign in users with email and password"
className="grid grid-flow-row" className="grid grid-flow-row"
showSwitch showSwitch

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,28 +14,35 @@ import {
baseProviderValidationSchema, baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings'; } from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
export default function FacebookProviderSettings() { export default function FacebookProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { clientId, clientSecret, enabled } = const { clientId, clientSecret, enabled } =
@@ -49,6 +58,16 @@ export default function FacebookProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema), resolver: yupResolver(baseProviderValidationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -89,6 +108,18 @@ export default function FacebookProviderSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Facebook settings are being updated...', loadingMessage: 'Facebook settings are being updated...',
@@ -111,7 +142,7 @@ export default function FacebookProviderSettings() {
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
docsLink="https://docs.nhost.io/platform/authentication/sign-in-with-facebook" docsLink="https://docs.nhost.io/guides/auth/social/sign-in-facebook"
docsTitle="how to sign in users with Facebook" docsTitle="how to sign in users with Facebook"
icon="/assets/brands/facebook.svg" icon="/assets/brands/facebook.svg"
switchId="enabled" switchId="enabled"
@@ -153,7 +184,7 @@ export default function FacebookProviderSettings() {
); );
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
} }

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,30 +14,37 @@ import {
baseProviderValidationSchema, baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings'; } from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useTheme } from '@mui/material'; import { useTheme } from '@mui/material';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
export default function GitHubProviderSettings() { export default function GitHubProviderSettings() {
const theme = useTheme(); const theme = useTheme();
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { clientId, clientSecret, enabled } = const { clientId, clientSecret, enabled } =
@@ -51,6 +60,16 @@ export default function GitHubProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema), resolver: yupResolver(baseProviderValidationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -91,6 +110,18 @@ export default function GitHubProviderSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'GitHub settings are being updated...', loadingMessage: 'GitHub settings are being updated...',
@@ -113,7 +144,7 @@ export default function GitHubProviderSettings() {
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
docsLink="https://docs.nhost.io/platform/authentication/sign-in-with-github" docsLink="https://docs.nhost.io/guides/auth/social/sign-in-github"
docsTitle="how to sign in users with GitHub" docsTitle="how to sign in users with GitHub"
icon={ icon={
theme.palette.mode === 'dark' theme.palette.mode === 'dark'
@@ -159,7 +190,7 @@ export default function GitHubProviderSettings() {
); );
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
} }

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,28 +14,35 @@ import {
baseProviderValidationSchema, baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings'; } from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
export default function GoogleProviderSettings() { export default function GoogleProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { clientId, clientSecret, enabled } = const { clientId, clientSecret, enabled } =
@@ -49,6 +58,16 @@ export default function GoogleProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema), resolver: yupResolver(baseProviderValidationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -89,6 +108,18 @@ export default function GoogleProviderSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Google settings are being updated...', loadingMessage: 'Google settings are being updated...',
@@ -111,7 +142,7 @@ export default function GoogleProviderSettings() {
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
docsLink="https://docs.nhost.io/platform/authentication/sign-in-with-google" docsLink="https://docs.nhost.io/guides/auth/social/sign-in-google"
docsTitle="how to sign in users with Google" docsTitle="how to sign in users with Google"
icon="/assets/brands/google.svg" icon="/assets/brands/google.svg"
switchId="enabled" switchId="enabled"
@@ -153,7 +184,7 @@ export default function GoogleProviderSettings() {
); );
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
} }

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { ControlledSelect } from '@/components/form/ControlledSelect'; import { ControlledSelect } from '@/components/form/ControlledSelect';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
@@ -5,17 +7,20 @@ import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Option } from '@/components/ui/v2/Option'; import { Option } from '@/components/ui/v2/Option';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetAuthenticationSettingsDocument, GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery, useGetAuthenticationSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { import {
AUTH_GRAVATAR_DEFAULT, AUTH_GRAVATAR_DEFAULT,
AUTH_GRAVATAR_RATING, AUTH_GRAVATAR_RATING,
} from '@/utils/constants/settings'; } from '@/utils/constants/settings';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -29,15 +34,19 @@ const validationSchema = Yup.object({
export type GravatarFormValues = Yup.InferType<typeof validationSchema>; export type GravatarFormValues = Yup.InferType<typeof validationSchema>;
export default function GravatarSettings() { export default function GravatarSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument], refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetAuthenticationSettingsQuery({ const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { const {
@@ -56,6 +65,16 @@ export default function GravatarSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
default: defaultGravatar || '',
rating: rating || '',
enabled: enabled || false,
});
}
}, [loading, defaultGravatar, rating, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -91,6 +110,18 @@ export default function GravatarSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(values); form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Gravatar settings are being updated...', loadingMessage: 'Gravatar settings are being updated...',
@@ -113,7 +144,7 @@ export default function GravatarSettings() {
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
docsLink="https://docs.nhost.io/authentication#gravatar" docsLink="https://docs.nhost.io/guides/auth/overview#gravatar"
switchId="enabled" switchId="enabled"
showSwitch showSwitch
className={twMerge( className={twMerge(

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,28 +14,35 @@ import {
baseProviderValidationSchema, baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings'; } from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
export default function LinkedInProviderSettings() { export default function LinkedInProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { clientId, clientSecret, enabled } = const { clientId, clientSecret, enabled } =
@@ -49,6 +58,16 @@ export default function LinkedInProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema), resolver: yupResolver(baseProviderValidationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -89,6 +108,18 @@ export default function LinkedInProviderSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'LinkedIn settings are being updated...', loadingMessage: 'LinkedIn settings are being updated...',
@@ -111,7 +142,7 @@ export default function LinkedInProviderSettings() {
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
docsLink="https://docs.nhost.io/platform/authentication/sign-in-with-linkedin" docsLink="https://docs.nhost.io/guides/auth/social/sign-in-linkedin"
docsTitle="how to sign in users with LinkedIn" docsTitle="how to sign in users with LinkedIn"
icon="/assets/brands/linkedin.svg" icon="/assets/brands/linkedin.svg"
switchId="enabled" switchId="enabled"
@@ -153,7 +184,7 @@ export default function LinkedInProviderSettings() {
); );
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
} }

View File

@@ -1,16 +1,21 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetAuthenticationSettingsDocument, GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery, useGetAuthenticationSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -23,15 +28,19 @@ const validationSchema = Yup.object({
export type MFASettingsFormValues = Yup.InferType<typeof validationSchema>; export type MFASettingsFormValues = Yup.InferType<typeof validationSchema>;
export default function MFASettings() { export default function MFASettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument], refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetAuthenticationSettingsQuery({ const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { enabled, issuer } = data?.config?.auth?.totp || {}; const { enabled, issuer } = data?.config?.auth?.totp || {};
@@ -45,6 +54,15 @@ export default function MFASettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading && issuer && enabled) {
form.reset({
issuer,
enabled,
});
}
}, [loading, issuer, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -78,6 +96,18 @@ export default function MFASettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(values); form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: loadingMessage:
@@ -102,7 +132,7 @@ export default function MFASettings() {
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
docsLink="https://docs.nhost.io/authentication#multi-factor-authentication" docsLink="https://docs.nhost.io/guides/auth/overview#multi-factor-authentication"
switchId="enabled" switchId="enabled"
showSwitch showSwitch
className={twMerge( className={twMerge(

View File

@@ -1,15 +1,20 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -20,15 +25,19 @@ const validationSchema = Yup.object({
export type MagicLinkFormValues = Yup.InferType<typeof validationSchema>; export type MagicLinkFormValues = Yup.InferType<typeof validationSchema>;
export default function MagicLinkSettings() { export default function MagicLinkSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { enabled } = data?.config?.auth?.method?.emailPasswordless || {}; const { enabled } = data?.config?.auth?.method?.emailPasswordless || {};
@@ -41,6 +50,12 @@ export default function MagicLinkSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({ enabled });
}
}, [loading, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -75,6 +90,18 @@ export default function MagicLinkSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(values); form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Magic Link settings are being updated...', loadingMessage: 'Magic Link settings are being updated...',
@@ -97,7 +124,7 @@ export default function MagicLinkSettings() {
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
docsLink="https://docs.nhost.io/authentication/sign-in-with-magic-link" docsLink="https://docs.nhost.io/guides/auth/sign-in-magic-link"
docsTitle="how to sign in users with Magic Link" docsTitle="how to sign in users with Magic Link"
switchId="enabled" switchId="enabled"
showSwitch showSwitch

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -7,14 +9,17 @@ import { Option } from '@/components/ui/v2/Option';
import { Select } from '@/components/ui/v2/Select'; import { Select } from '@/components/ui/v2/Select';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import Image from 'next/image'; import Image from 'next/image';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -44,15 +49,19 @@ const validationSchema = Yup.object({
export type SMSSettingsFormValues = Yup.InferType<typeof validationSchema>; export type SMSSettingsFormValues = Yup.InferType<typeof validationSchema>;
export default function SMSSettings() { export default function SMSSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, error, loading } = useGetSignInMethodsQuery({ const { data, error, loading } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { accountSid, authToken, messagingServiceId } = const { accountSid, authToken, messagingServiceId } =
@@ -70,6 +79,17 @@ export default function SMSSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
accountSid: accountSid || '',
authToken: authToken || '',
messagingServiceId: messagingServiceId || '',
enabled: enabled || false,
});
}
}, [loading, accountSid, authToken, messagingServiceId, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -114,6 +134,18 @@ export default function SMSSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(values); form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'SMS settings are being updated...', loadingMessage: 'SMS settings are being updated...',
@@ -137,7 +169,7 @@ export default function SMSSettings() {
}} }}
switchId="enabled" switchId="enabled"
showSwitch showSwitch
docsLink="https://docs.nhost.io/authentication/sign-in-with-phone-number-sms" docsLink="https://docs.nhost.io/guides/auth/sign-in-phone-number"
docsTitle="how to sign in users with a phone number (SMS)" docsTitle="how to sign in users with a phone number (SMS)"
className={twMerge( className={twMerge(
'grid grid-flow-col grid-cols-2 grid-rows-4 gap-x-3 gap-y-4 px-4 py-2', 'grid grid-flow-col grid-cols-2 grid-rows-4 gap-x-3 gap-y-4 px-4 py-2',

View File

@@ -1,16 +1,21 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetAuthenticationSettingsDocument, GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery, useGetAuthenticationSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -28,15 +33,19 @@ const validationSchema = Yup.object({
export type SessionFormValues = Yup.InferType<typeof validationSchema>; export type SessionFormValues = Yup.InferType<typeof validationSchema>;
export default function SessionSettings() { export default function SessionSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument], refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetAuthenticationSettingsQuery({ const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { accessToken, refreshToken } = data?.config?.auth?.session || {}; const { accessToken, refreshToken } = data?.config?.auth?.session || {};
@@ -50,6 +59,15 @@ export default function SessionSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading && accessToken && refreshToken) {
form.reset({
accessTokenExpiresIn: accessToken?.expiresIn || 900,
refreshTokenExpiresIn: refreshToken?.expiresIn || 43200,
});
}
}, [loading, accessToken, refreshToken, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -86,6 +104,18 @@ export default function SessionSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Session settings are being updated...', loadingMessage: 'Session settings are being updated...',

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,28 +14,35 @@ import {
baseProviderValidationSchema, baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings'; } from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
export default function SpotifyProviderSettings() { export default function SpotifyProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { clientId, clientSecret, enabled } = const { clientId, clientSecret, enabled } =
@@ -49,6 +58,16 @@ export default function SpotifyProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema), resolver: yupResolver(baseProviderValidationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -89,6 +108,18 @@ export default function SpotifyProviderSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Spotify settings are being updated...', loadingMessage: 'Spotify settings are being updated...',
@@ -111,7 +142,7 @@ export default function SpotifyProviderSettings() {
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
docsLink="https://docs.nhost.io/platform/authentication/sign-in-with-spotify" docsLink="https://docs.nhost.io/guides/auth/social/sign-in-spotify"
docsTitle="how to sign in users with Spotify" docsTitle="how to sign in users with Spotify"
icon="/assets/brands/spotify.svg" icon="/assets/brands/spotify.svg"
switchId="enabled" switchId="enabled"
@@ -153,7 +184,7 @@ export default function SpotifyProviderSettings() {
); );
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
} }

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,30 +14,37 @@ import {
baseProviderValidationSchema, baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings'; } from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useTheme } from '@mui/material'; import { useTheme } from '@mui/material';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
export default function TwitchProviderSettings() { export default function TwitchProviderSettings() {
const theme = useTheme(); const theme = useTheme();
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { clientId, clientSecret, enabled } = const { clientId, clientSecret, enabled } =
@@ -51,6 +60,16 @@ export default function TwitchProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema), resolver: yupResolver(baseProviderValidationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -91,6 +110,18 @@ export default function TwitchProviderSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Twitch settings are being updated...', loadingMessage: 'Twitch settings are being updated...',
@@ -113,7 +144,7 @@ export default function TwitchProviderSettings() {
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
docsLink="https://docs.nhost.io/platform/authentication/sign-in-with-twitch" docsLink="https://docs.nhost.io/guides/auth/social/sign-in-twitch"
docsTitle="how to sign in users with Twitch" docsTitle="how to sign in users with Twitch"
icon={ icon={
theme.palette.mode === 'dark' theme.palette.mode === 'dark'
@@ -159,7 +190,7 @@ export default function TwitchProviderSettings() {
); );
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
} }

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -7,15 +9,18 @@ import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { InputAdornment } from '@/components/ui/v2/InputAdornment'; import { InputAdornment } from '@/components/ui/v2/InputAdornment';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -39,15 +44,19 @@ const validationSchema = Yup.object({
export type TwitterProviderFormValues = Yup.InferType<typeof validationSchema>; export type TwitterProviderFormValues = Yup.InferType<typeof validationSchema>;
export default function TwitterProviderSettings() { export default function TwitterProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { consumerKey, consumerSecret, enabled } = const { consumerKey, consumerSecret, enabled } =
@@ -63,6 +72,16 @@ export default function TwitterProviderSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
consumerSecret: consumerSecret || '',
consumerKey: consumerKey || '',
enabled: enabled || false,
});
}
}, [loading, consumerKey, consumerSecret, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -100,6 +119,18 @@ export default function TwitterProviderSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Twitter settings are being updated...', loadingMessage: 'Twitter settings are being updated...',
@@ -185,7 +216,7 @@ export default function TwitterProviderSettings() {
); );
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
} }

View File

@@ -1,15 +1,20 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -20,15 +25,19 @@ const validationSchema = Yup.object({
export type WebAuthnFormValues = Yup.InferType<typeof validationSchema>; export type WebAuthnFormValues = Yup.InferType<typeof validationSchema>;
export default function WebAuthnSettings() { export default function WebAuthnSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { enabled } = data?.config?.auth?.method?.webauthn || {}; const { enabled } = data?.config?.auth?.method?.webauthn || {};
@@ -41,6 +50,12 @@ export default function WebAuthnSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({ enabled });
}
}, [loading, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -80,6 +95,18 @@ export default function WebAuthnSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(values); form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'WebAuthn settings are being updated...', loadingMessage: 'WebAuthn settings are being updated...',
@@ -101,7 +128,7 @@ export default function WebAuthnSettings() {
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
docsLink="https://docs.nhost.io/authentication/sign-in-with-security-keys" docsLink="https://docs.nhost.io/guides/auth/sign-in-webauthn"
docsTitle="how to sign in users with security keys" docsTitle="how to sign in users with security keys"
switchId="enabled" switchId="enabled"
showSwitch showSwitch

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,28 +14,35 @@ import {
baseProviderValidationSchema, baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings'; } from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
export default function WindowsLiveProviderSettings() { export default function WindowsLiveProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { clientId, clientSecret, enabled } = const { clientId, clientSecret, enabled } =
@@ -49,6 +58,16 @@ export default function WindowsLiveProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema), resolver: yupResolver(baseProviderValidationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -89,6 +108,18 @@ export default function WindowsLiveProviderSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Windows Live settings are being updated...', loadingMessage: 'Windows Live settings are being updated...',
@@ -151,7 +182,7 @@ export default function WindowsLiveProviderSettings() {
); );
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
} }

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -8,15 +10,18 @@ import { Input } from '@/components/ui/v2/Input';
import { InputAdornment } from '@/components/ui/v2/InputAdornment'; import { InputAdornment } from '@/components/ui/v2/InputAdornment';
import { BaseProviderSettings } from '@/features/authentication/settings/components/BaseProviderSettings'; import { BaseProviderSettings } from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -52,15 +57,19 @@ const validationSchema = Yup.object({
export type WorkOsProviderFormValues = Yup.InferType<typeof validationSchema>; export type WorkOsProviderFormValues = Yup.InferType<typeof validationSchema>;
export default function WorkOsProviderSettings() { export default function WorkOsProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { clientId, clientSecret, organization, connection, enabled } = const { clientId, clientSecret, organization, connection, enabled } =
@@ -78,6 +87,26 @@ export default function WorkOsProviderSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
organization: organization || '',
connection: connection || '',
enabled: enabled || false,
});
}
}, [
loading,
clientId,
clientSecret,
organization,
connection,
enabled,
form,
]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -115,6 +144,18 @@ export default function WorkOsProviderSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'WorkOS settings are being updated...', loadingMessage: 'WorkOS settings are being updated...',
@@ -137,7 +178,7 @@ export default function WorkOsProviderSettings() {
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
docsLink="https://docs.nhost.io/authentication/sign-in-with-workos" docsLink="https://docs.nhost.io/guides/auth/social/sign-in-workos"
docsTitle="how to sign in users with WorkOS" docsTitle="how to sign in users with WorkOS"
icon="/assets/brands/workos.svg" icon="/assets/brands/workos.svg"
switchId="enabled" switchId="enabled"
@@ -203,7 +244,7 @@ export default function WorkOsProviderSettings() {
); );
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
} }

View File

@@ -275,7 +275,7 @@ export default function DataBrowserGrid({
() => () =>
columns columns
.map((column) => ({ .map((column) => ({
...createDataGridColumn(column, isSchemaEditable), ...createDataGridColumn(column, true),
onCellEdit: async (variables: UpdateRecordVariables) => { onCellEdit: async (variables: UpdateRecordVariables) => {
const result = await updateRow(variables); const result = await updateRow(variables);
await queryClient.invalidateQueries([currentTablePath]); await queryClient.invalidateQueries([currentTablePath]);
@@ -288,7 +288,6 @@ export default function DataBrowserGrid({
[ [
columns, columns,
currentTablePath, currentTablePath,
isSchemaEditable,
optimisticlyRemovedColumnId, optimisticlyRemovedColumnId,
queryClient, queryClient,
removableColumnId, removableColumnId,
@@ -422,7 +421,7 @@ export default function DataBrowserGrid({
loading={status === 'loading'} loading={status === 'loading'}
sortBy={sortBy} sortBy={sortBy}
className="pb-17 sm:pb-0" className="pb-17 sm:pb-0"
onInsertRow={isSchemaEditable ? handleInsertRowClick : undefined} onInsertRow={handleInsertRowClick}
onInsertColumn={isSchemaEditable ? handleInsertColumnClick : undefined} onInsertColumn={isSchemaEditable ? handleInsertColumnClick : undefined}
onEditColumn={isSchemaEditable ? handleEditColumnClick : undefined} onEditColumn={isSchemaEditable ? handleEditColumnClick : undefined}
onRemoveColumn={isSchemaEditable ? handleColumnRemoveClick : undefined} onRemoveColumn={isSchemaEditable ? handleColumnRemoveClick : undefined}
@@ -445,7 +444,7 @@ export default function DataBrowserGrid({
onInsertColumnClick={ onInsertColumnClick={
isSchemaEditable ? handleInsertColumnClick : undefined isSchemaEditable ? handleInsertColumnClick : undefined
} }
onInsertRowClick={isSchemaEditable ? handleInsertRowClick : undefined} onInsertRowClick={handleInsertRowClick}
paginationProps={{ paginationProps={{
currentPage: Math.max(currentPage, 1), currentPage: Math.max(currentPage, 1),
totalPages: Math.max(numberOfPages, 1), totalPages: Math.max(numberOfPages, 1),

View File

@@ -12,11 +12,9 @@ import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import { RowIcon } from '@/components/ui/v2/icons/RowIcon'; import { RowIcon } from '@/components/ui/v2/icons/RowIcon';
import { useDeleteRecordMutation } from '@/features/database/dataGrid/hooks/useDeleteRecordMutation'; import { useDeleteRecordMutation } from '@/features/database/dataGrid/hooks/useDeleteRecordMutation';
import type { DataBrowserGridColumn } from '@/features/database/dataGrid/types/dataBrowser'; import type { DataBrowserGridColumn } from '@/features/database/dataGrid/types/dataBrowser';
import { isSchemaLocked } from '@/features/database/dataGrid/utils/schemaHelpers/isSchemaLocked';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { triggerToast } from '@/utils/toast'; import { triggerToast } from '@/utils/toast';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import { useState } from 'react'; import { useState } from 'react';
import type { Row } from 'react-table'; import type { Row } from 'react-table';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
@@ -58,11 +56,6 @@ export default function DataBrowserGridControls({
const { className: paginationClassName, ...restPaginationProps } = const { className: paginationClassName, ...restPaginationProps } =
paginationProps || ({} as DataGridPaginationProps); paginationProps || ({} as DataGridPaginationProps);
const {
query: { schemaSlug },
} = useRouter();
const isSchemaEditable = !isSchemaLocked(schemaSlug as string);
const { const {
selectedFlatRows: selectedRows, selectedFlatRows: selectedRows,
columns, columns,
@@ -126,7 +119,7 @@ export default function DataBrowserGridControls({
numberOfSelectedRows > 0 ? 'justify-between' : 'justify-end', numberOfSelectedRows > 0 ? 'justify-between' : 'justify-end',
)} )}
> >
{isSchemaEditable && numberOfSelectedRows > 0 && ( {numberOfSelectedRows > 0 && (
<div className="grid grid-flow-col place-content-start items-center gap-2"> <div className="grid grid-flow-col place-content-start items-center gap-2">
<Chip <Chip
size="small" size="small"

View File

@@ -43,7 +43,13 @@ export default async function deleteRecord({
(row) => (row) =>
`(${primaryOrUniqueColumns `(${primaryOrUniqueColumns
.map((primaryOrUniqueColumn) => .map((primaryOrUniqueColumn) =>
format('%I=%L', primaryOrUniqueColumn, row[primaryOrUniqueColumn]), row[primaryOrUniqueColumn] === null
? format('%I IS NULL', primaryOrUniqueColumn)
: format(
'%I=%L',
primaryOrUniqueColumn,
row[primaryOrUniqueColumn],
),
) )
.join(' AND ')})`, .join(' AND ')})`,
); );

View File

@@ -1,10 +1,13 @@
import { useDatabaseQuery } from '@/features/database/dataGrid/hooks/useDatabaseQuery';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { getToastStyleProps } from '@/utils/constants/settings'; import { getToastStyleProps } from '@/utils/constants/settings';
import { getHasuraAdminSecret } from '@/utils/env'; import { getHasuraAdminSecret } from '@/utils/env';
import { parseIdentifiersFromSQL } from '@/utils/sql'; import { parseIdentifiersFromSQL } from '@/utils/sql';
import toast from 'react-hot-toast'; import { useRouter } from 'next/router';
import { useState } from 'react'; import { useState } from 'react';
import toast from 'react-hot-toast';
export default function useRunSQL( export default function useRunSQL(
sqlCode: string, sqlCode: string,
@@ -15,6 +18,7 @@ export default function useRunSQL(
migrationName: string, migrationName: string,
) { ) {
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const isPlatform = useIsPlatform();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [commandOk, setCommandOk] = useState(false); const [commandOk, setCommandOk] = useState(false);
@@ -22,6 +26,14 @@ export default function useRunSQL(
const [columns, setColumns] = useState<string[]>([]); const [columns, setColumns] = useState<string[]>([]);
const [rows, setRows] = useState<string[][]>([[]]); const [rows, setRows] = useState<string[][]>([[]]);
const router = useRouter();
const {
query: { dataSourceSlug },
} = router;
const { refetch } = useDatabaseQuery([dataSourceSlug as string]);
const appUrl = generateAppServiceUrl( const appUrl = generateAppServiceUrl(
currentProject?.subdomain, currentProject?.subdomain,
currentProject?.region, currentProject?.region,
@@ -169,8 +181,34 @@ export default function useRunSQL(
} }
}; };
const trackAll = async (objects: any[]): Promise<Response[]> => {
const apiPath = isPlatform ? '/v1/metadata' : '/apis/migrate';
const responses: Response[] = await Promise.all(
objects.map((object) =>
fetch(`${appUrl}${apiPath}`, {
method: 'POST',
headers: { 'x-hasura-admin-secret': adminSecret },
body: JSON.stringify(object),
}).then((response) => {
if (!response.ok) {
console.error('failed to track:', response);
}
return response;
}),
),
).catch((error) => {
console.error('Error in trackAll:', error);
throw error;
});
return responses;
};
const updateMetadata = async (inputSQL: string) => { const updateMetadata = async (inputSQL: string) => {
const entities = parseIdentifiersFromSQL(inputSQL); const entities = parseIdentifiersFromSQL(inputSQL);
if (entities.length === 0) {
return;
}
const tablesOrViewEntities = entities.filter( const tablesOrViewEntities = entities.filter(
(entity) => entity.type !== 'function', (entity) => entity.type !== 'function',
@@ -179,47 +217,75 @@ export default function useRunSQL(
(entity) => entity.type === 'function', (entity) => entity.type === 'function',
); );
const trackTablesOrViews = tablesOrViewEntities.map(({ name, schema }) => ({ let trackTablesOrViews: any[] = [];
type: 'pg_track_table', let trackFunctions: any[] = [];
args: { if (isPlatform) {
source: 'default', // use v2/query
table: { trackTablesOrViews = tablesOrViewEntities.map(({ name, schema }) => ({
name, type: 'pg_track_table',
schema, args: {
source: 'default',
table: {
name,
schema,
},
}, },
}, }));
})); trackFunctions = functionEntities.map(({ name, schema }) => ({
type: 'pg_track_function',
const trackFunctions = functionEntities.map(({ name, schema }) => ({ args: {
type: 'pg_track_function', source: 'default',
args: { function: {
source: 'default', name,
function: { schema,
name, configuration: {},
schema, },
configuration: {},
}, },
}, }));
})); } else {
// use apis/migrate
const metaDataPayload = { trackTablesOrViews = tablesOrViewEntities.map(({ name, schema }) => ({
source: 'default', name: `add_existing_table_or_view_${schema}_${name}`,
type: 'bulk', datasource: 'default',
args: [...trackTablesOrViews, ...trackFunctions], down: [],
}; skip_execution: false,
up: [
{
type: 'pg_track_table',
args: {
table: { name, schema },
source: 'default',
},
},
],
}));
trackFunctions = functionEntities.map(({ name, schema }) => ({
name: `add_existing_function_or_view_${schema}_${name}`,
datasource: 'default',
down: [],
skip_execution: false,
up: [
{
type: 'pg_track_function',
args: {
function: { name, schema },
source: 'default',
},
},
],
}));
}
try { try {
if (entities.length > 0) { await trackAll([...trackTablesOrViews, ...trackFunctions]).then(
const metadataApiResponse = await fetch(`${appUrl}/v1/metadata`, { (responses) => {
method: 'POST', responses.forEach((response) => {
headers: { 'x-hasura-admin-secret': adminSecret }, if (!response.ok) {
body: JSON.stringify(metaDataPayload), console.error('Error tracking table or view:', response);
}); }
});
if (!metadataApiResponse.ok) { },
throw new Error('Metadata API call failed'); );
}
}
} catch (error) { } catch (error) {
toast.error('An error happened when calling the metadata API', { toast.error('An error happened when calling the metadata API', {
style: toastStyle.style, style: toastStyle.style,
@@ -269,6 +335,9 @@ export default function useRunSQL(
} }
} }
// refresh the table list after running the sql
await refetch();
setLoading(false); setLoading(false);
}; };

View File

@@ -1,9 +1,12 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete'; import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetPostgresSettingsDocument, GetPostgresSettingsDocument,
Software_Type_Enum, Software_Type_Enum,
@@ -11,8 +14,10 @@ import {
useGetSoftwareVersionsQuery, useGetSoftwareVersionsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -30,24 +35,30 @@ export type DatabaseServiceVersionFormValues = Yup.InferType<
>; >;
export default function DatabaseServiceVersionSettings() { export default function DatabaseServiceVersionSettings() {
const isPlatform = useIsPlatform();
const { openDialog } = useDialog();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetPostgresSettingsDocument], refetchQueries: [GetPostgresSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetPostgresSettingsQuery({ const { data, loading, error } = useGetPostgresSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data: databaseVersionsData } = useGetSoftwareVersionsQuery({ const { data: databaseVersionsData } = useGetSoftwareVersionsQuery({
variables: { variables: {
software: Software_Type_Enum.PostgreSql, software: Software_Type_Enum.PostgreSql,
}, },
skip: !isPlatform,
}); });
const { version } = data?.config?.postgres || {}; const { version } = data?.config?.postgres || {};
const databaseVersions = databaseVersionsData?.softwareVersions || []; const databaseVersions = databaseVersionsData?.softwareVersions || [];
const availableVersions = Array.from( const availableVersions = Array.from(
new Set(databaseVersions.map((el) => el.version)).add(version), new Set(databaseVersions.map((el) => el.version)).add(version),
@@ -61,10 +72,21 @@ export default function DatabaseServiceVersionSettings() {
const form = useForm<DatabaseServiceVersionFormValues>({ const form = useForm<DatabaseServiceVersionFormValues>({
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
defaultValues: { version: { label: version, value: version } }, defaultValues: { version: { label: '', value: '' } },
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading && version) {
form.reset({
version: {
label: version,
value: version,
},
});
}
}, [loading, version, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -99,6 +121,18 @@ export default function DatabaseServiceVersionSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Postgres version is being updated...', loadingMessage: 'Postgres version is being updated...',
@@ -123,12 +157,20 @@ export default function DatabaseServiceVersionSettings() {
}} }}
docsLink="https://hub.docker.com/r/nhost/postgres/tags" docsLink="https://hub.docker.com/r/nhost/postgres/tags"
docsTitle="the latest releases" docsTitle="the latest releases"
className="grid grid-flow-row gap-x-4 gap-y-2 px-4 lg:grid-cols-5" className="grid grid-flow-row px-4 gap-x-4 gap-y-2 lg:grid-cols-5"
> >
<ControlledAutocomplete <ControlledAutocomplete
id="version" id="version"
name="version" name="version"
autoHighlight autoHighlight
freeSolo
getOptionLabel={(option) => {
if (typeof option === 'string') {
return option || '';
}
return option.value;
}}
isOptionEqualToValue={() => false} isOptionEqualToValue={() => false}
filterOptions={(options, { inputValue }) => { filterOptions={(options, { inputValue }) => {
const inputValueLower = inputValue.toLowerCase(); const inputValueLower = inputValue.toLowerCase();

View File

@@ -7,10 +7,12 @@ import { Box } from '@/components/ui/v2/Box';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { UpgradeNotification } from '@/features/projects/common/components/UpgradeNotification'; import { UpgradeNotification } from '@/features/projects/common/components/UpgradeNotification';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
useGetPostgresSettingsQuery, useGetPostgresSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react'; import { useEffect } from 'react';
@@ -18,13 +20,15 @@ import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
const validationSchema = Yup.object({ const validationSchema = Yup.object({
capacity: Yup.number().required(), capacity: Yup.number().required().min(10),
}); });
export type AuthDomainFormValues = Yup.InferType<typeof validationSchema>; export type AuthDomainFormValues = Yup.InferType<typeof validationSchema>;
export default function AuthDomain() { export default function AuthDomain() {
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { const {
@@ -34,14 +38,17 @@ export default function AuthDomain() {
refetch: refetchPostgresSettings, refetch: refetchPostgresSettings,
} = useGetPostgresSettingsQuery({ } = useGetPostgresSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const capacity = const capacity =
data?.config?.postgres?.resources?.storage?.capacity ?? (data?.config?.postgres?.resources?.storage?.capacity ??
currentProject.plan.featureMaxDbSize; currentProject?.plan?.featureMaxDbSize) ||
0;
const [updateConfig] = useUpdateConfigMutation(); const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const form = useForm<Yup.InferType<typeof validationSchema>>({ const form = useForm<Yup.InferType<typeof validationSchema>>({
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
@@ -117,7 +124,7 @@ export default function AuthDomain() {
}} }}
className="flex flex-col" className="flex flex-col"
> >
{currentProject.plan.isFree && ( {currentProject.plan?.isFree && (
<UpgradeNotification message="Unlock by upgrading your project to the Pro plan." /> <UpgradeNotification message="Unlock by upgrading your project to the Pro plan." />
)} )}
<Box className="grid grid-flow-row lg:grid-cols-5"> <Box className="grid grid-flow-row lg:grid-cols-5">
@@ -127,7 +134,7 @@ export default function AuthDomain() {
name="capacity" name="capacity"
type="number" type="number"
fullWidth fullWidth
disabled={currentProject.plan.isFree} disabled={currentProject.plan?.isFree}
className="lg:col-span-2" className="lg:col-span-2"
error={Boolean(formState.errors.capacity?.message)} error={Boolean(formState.errors.capacity?.message)}
helperText={formState.errors.capacity?.message} helperText={formState.errors.capacity?.message}
@@ -138,7 +145,7 @@ export default function AuthDomain() {
}} }}
/> />
</Box> </Box>
{!currentProject.plan.isFree && ( {!currentProject.plan?.isFree && (
<Alert severity="info" className="col-span-6 text-left"> <Alert severity="info" className="col-span-6 text-left">
Note that volumes can only be increased (not decreased). Also, due Note that volumes can only be increased (not decreased). Also, due
to an AWS limitation, the same volume can only be increased once to an AWS limitation, the same volume can only be increased once

View File

@@ -1,15 +1,20 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetHasuraSettingsDocument, GetHasuraSettingsDocument,
useGetHasuraSettingsQuery, useGetHasuraSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -20,16 +25,20 @@ const validationSchema = Yup.object({
export type HasuraAllowListFormValues = Yup.InferType<typeof validationSchema>; export type HasuraAllowListFormValues = Yup.InferType<typeof validationSchema>;
export default function HasuraAllowListSettings() { export default function HasuraAllowListSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument], refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetHasuraSettingsQuery({ const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-first', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { enableAllowList } = data?.config?.hasura.settings || {}; const { enableAllowList } = data?.config?.hasura.settings || {};
@@ -42,6 +51,14 @@ export default function HasuraAllowListSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
enabled: enableAllowList,
});
}
}, [loading, enableAllowList, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -75,6 +92,18 @@ export default function HasuraAllowListSettings() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Allow list settings are being updated...', loadingMessage: 'Allow list settings are being updated...',

View File

@@ -1,15 +1,20 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetHasuraSettingsDocument, GetHasuraSettingsDocument,
useGetHasuraSettingsQuery, useGetHasuraSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -20,16 +25,20 @@ const validationSchema = Yup.object({
export type HasuraConsoleFormValues = Yup.InferType<typeof validationSchema>; export type HasuraConsoleFormValues = Yup.InferType<typeof validationSchema>;
export default function HasuraConsoleSettings() { export default function HasuraConsoleSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument], refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetHasuraSettingsQuery({ const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-first', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { enableConsole } = data?.config?.hasura.settings || {}; const { enableConsole } = data?.config?.hasura.settings || {};
@@ -42,6 +51,14 @@ export default function HasuraConsoleSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
enabled: enableConsole,
});
}
}, [loading, enableConsole, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -75,6 +92,18 @@ export default function HasuraConsoleSettings() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Hasura Console settings are being updated...', loadingMessage: 'Hasura Console settings are being updated...',

View File

@@ -1,14 +1,18 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetHasuraSettingsDocument, GetHasuraSettingsDocument,
useGetHasuraSettingsQuery, useGetHasuraSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
@@ -28,16 +32,20 @@ const validationSchema = Yup.object({
export type HasuraCorsDomainFormValues = Yup.InferType<typeof validationSchema>; export type HasuraCorsDomainFormValues = Yup.InferType<typeof validationSchema>;
export default function HasuraCorsDomainSettings() { export default function HasuraCorsDomainSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument], refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetHasuraSettingsQuery({ const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-first', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { corsDomain } = data?.config?.hasura.settings || {}; const { corsDomain } = data?.config?.hasura.settings || {};
@@ -98,6 +106,18 @@ export default function HasuraCorsDomainSettings() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'CORS domain settings are being updated...', loadingMessage: 'CORS domain settings are being updated...',

View File

@@ -1,15 +1,20 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetHasuraSettingsDocument, GetHasuraSettingsDocument,
useGetHasuraSettingsQuery, useGetHasuraSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -20,16 +25,20 @@ const validationSchema = Yup.object({
export type HasuraDevModeFormValues = Yup.InferType<typeof validationSchema>; export type HasuraDevModeFormValues = Yup.InferType<typeof validationSchema>;
export default function HasuraDevModeSettings() { export default function HasuraDevModeSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument], refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetHasuraSettingsQuery({ const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-first', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { devMode } = data?.config?.hasura.settings || {}; const { devMode } = data?.config?.hasura.settings || {};
@@ -42,6 +51,14 @@ export default function HasuraDevModeSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
enabled: devMode,
});
}
}, [loading, devMode, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -63,7 +80,7 @@ export default function HasuraDevModeSettings() {
config: { config: {
hasura: { hasura: {
settings: { settings: {
enableConsole: formValues.enabled, devMode: formValues.enabled,
}, },
}, },
}, },
@@ -75,6 +92,18 @@ export default function HasuraDevModeSettings() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Dev Mode settings are being updated...', loadingMessage: 'Dev Mode settings are being updated...',

View File

@@ -1,16 +1,21 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete'; import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetHasuraSettingsDocument, GetHasuraSettingsDocument,
useGetHasuraSettingsQuery, useGetHasuraSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -30,31 +35,40 @@ export type HasuraEnabledAPIFormValues = Yup.InferType<typeof validationSchema>;
const AVAILABLE_HASURA_APIS = ['metadata', 'graphql', 'pgdump', 'config']; const AVAILABLE_HASURA_APIS = ['metadata', 'graphql', 'pgdump', 'config'];
export default function HasuraEnabledAPISettings() { export default function HasuraEnabledAPISettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument], refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetHasuraSettingsQuery({ const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { enabledAPIs } = data?.config?.hasura.settings || {}; const { enabledAPIs = [] } = data?.config?.hasura?.settings || {};
const form = useForm<HasuraEnabledAPIFormValues>({ const form = useForm<HasuraEnabledAPIFormValues>({
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
defaultValues: { defaultValues: {
enabledAPIs: enabledAPIs.map((api) => ({ enabledAPIs: [],
label: api,
value: api,
})),
}, },
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (enabledAPIs && !loading) {
form.reset({
enabledAPIs: enabledAPIs.map((api) => ({ label: api, value: api })),
});
}
}, [form, enabledAPIs, loading]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -96,6 +110,18 @@ export default function HasuraEnabledAPISettings() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Enabled APIs are being updated...', loadingMessage: 'Enabled APIs are being updated...',
@@ -117,7 +143,7 @@ export default function HasuraEnabledAPISettings() {
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
className="grid grid-flow-row gap-x-4 gap-y-2 px-4 lg:grid-cols-6" className="grid grid-flow-row px-4 gap-x-4 gap-y-2 lg:grid-cols-6"
> >
<ControlledAutocomplete <ControlledAutocomplete
id="enabledAPIs" id="enabledAPIs"

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete'; import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
@@ -5,13 +7,16 @@ import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { HighlightedText } from '@/components/presentational/HighlightedText'; import { HighlightedText } from '@/components/presentational/HighlightedText';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetHasuraSettingsDocument, GetHasuraSettingsDocument,
useGetHasuraSettingsQuery, useGetHasuraSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -29,16 +34,20 @@ export type HasuraLogLevelFormValues = Yup.InferType<typeof validationSchema>;
const AVAILABLE_HASURA_LOG_LEVELS = ['debug', 'info', 'warn', 'error']; const AVAILABLE_HASURA_LOG_LEVELS = ['debug', 'info', 'warn', 'error'];
export default function HasuraLogLevelSettings() { export default function HasuraLogLevelSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument], refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetHasuraSettingsQuery({ const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-first', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { level } = data?.config?.hasura.logs || {}; const { level } = data?.config?.hasura.logs || {};
@@ -56,6 +65,17 @@ export default function HasuraLogLevelSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading && level) {
form.reset({
logLevel: {
label: level,
value: level,
},
});
}
}, [form, loading, level]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -97,6 +117,18 @@ export default function HasuraLogLevelSettings() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Log level is being updated...', loadingMessage: 'Log level is being updated...',
@@ -128,7 +160,7 @@ export default function HasuraLogLevelSettings() {
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
className="grid grid-flow-row gap-x-4 gap-y-2 px-4 lg:grid-cols-5" className="grid grid-flow-row px-4 gap-x-4 gap-y-2 lg:grid-cols-5"
> >
<ControlledAutocomplete <ControlledAutocomplete
id="logLevel" id="logLevel"

View File

@@ -1,14 +1,18 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetHasuraSettingsDocument, GetHasuraSettingsDocument,
useGetHasuraSettingsQuery, useGetHasuraSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
@@ -26,16 +30,20 @@ const validationSchema = Yup.object({
export type HasuraPoolSizeFormValues = Yup.InferType<typeof validationSchema>; export type HasuraPoolSizeFormValues = Yup.InferType<typeof validationSchema>;
export default function HasuraPoolSizeSettings() { export default function HasuraPoolSizeSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument], refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetHasuraSettingsQuery({ const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-first', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { httpPoolSize } = data?.config?.hasura.events || {}; const { httpPoolSize } = data?.config?.hasura.events || {};
@@ -82,6 +90,18 @@ export default function HasuraPoolSizeSettings() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Pool size is being updated...', loadingMessage: 'Pool size is being updated...',
@@ -104,7 +124,7 @@ export default function HasuraPoolSizeSettings() {
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
className="grid grid-flow-row gap-x-4 gap-y-2 px-4 lg:grid-cols-5" className="grid grid-flow-row px-4 gap-x-4 gap-y-2 lg:grid-cols-5"
> >
<Input <Input
{...register('httpPoolSize')} {...register('httpPoolSize')}

View File

@@ -1,15 +1,20 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetHasuraSettingsDocument, GetHasuraSettingsDocument,
useGetHasuraSettingsQuery, useGetHasuraSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -22,16 +27,20 @@ export type HasuraRemoteSchemaPermissionsFormValues = Yup.InferType<
>; >;
export default function HasuraRemoteSchemaPermissionsSettings() { export default function HasuraRemoteSchemaPermissionsSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument], refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetHasuraSettingsQuery({ const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-first', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { enableRemoteSchemaPermissions } = data?.config?.hasura.settings || {}; const { enableRemoteSchemaPermissions } = data?.config?.hasura.settings || {};
@@ -44,6 +53,14 @@ export default function HasuraRemoteSchemaPermissionsSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
enabled: enableRemoteSchemaPermissions,
});
}
}, [loading, enableRemoteSchemaPermissions, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -67,7 +84,7 @@ export default function HasuraRemoteSchemaPermissionsSettings() {
config: { config: {
hasura: { hasura: {
settings: { settings: {
enableConsole: formValues.enabled, enableRemoteSchemaPermissions: formValues.enabled,
}, },
}, },
}, },
@@ -79,6 +96,18 @@ export default function HasuraRemoteSchemaPermissionsSettings() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: loadingMessage:

View File

@@ -1,9 +1,12 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete'; import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetHasuraSettingsDocument, GetHasuraSettingsDocument,
Software_Type_Enum, Software_Type_Enum,
@@ -11,8 +14,10 @@ import {
useGetSoftwareVersionsQuery, useGetSoftwareVersionsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -30,22 +35,27 @@ export type HasuraServiceVersionFormValues = Yup.InferType<
>; >;
export default function HasuraServiceVersionSettings() { export default function HasuraServiceVersionSettings() {
const isPlatform = useIsPlatform();
const { openDialog } = useDialog();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument], refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetHasuraSettingsQuery({ const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data: hasuraVersionsData } = useGetSoftwareVersionsQuery({ const { data: hasuraVersionsData } = useGetSoftwareVersionsQuery({
variables: { variables: {
software: Software_Type_Enum.Hasura, software: Software_Type_Enum.Hasura,
}, },
skip: !isPlatform,
}); });
const { version } = data?.config?.hasura || {}; const { version } = data?.config?.hasura || {};
@@ -53,6 +63,7 @@ export default function HasuraServiceVersionSettings() {
const availableVersions = Array.from( const availableVersions = Array.from(
new Set(versions.map((el) => el.version)).add(version), new Set(versions.map((el) => el.version)).add(version),
) )
.filter((v) => !!v)
.sort() .sort()
.reverse() .reverse()
.map((availableVersion) => ({ .map((availableVersion) => ({
@@ -60,12 +71,25 @@ export default function HasuraServiceVersionSettings() {
value: availableVersion, value: availableVersion,
})); }));
// TODO make sure the network request is made in the parent component
// also this request should be made against cache only and the data should be available right away
const form = useForm<HasuraServiceVersionFormValues>({ const form = useForm<HasuraServiceVersionFormValues>({
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
defaultValues: { version: { label: version, value: version } }, defaultValues: { version: { label: '', value: '' } },
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading && version) {
form.reset({
version: {
label: version,
value: version,
},
});
}
}, [loading, version, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -99,6 +123,18 @@ export default function HasuraServiceVersionSettings() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Hasura version is being updated...', loadingMessage: 'Hasura version is being updated...',
@@ -123,11 +159,19 @@ export default function HasuraServiceVersionSettings() {
}} }}
docsLink="https://hub.docker.com/r/nhost/graphql-engine/tags" docsLink="https://hub.docker.com/r/nhost/graphql-engine/tags"
docsTitle="the latest releases" docsTitle="the latest releases"
className="grid grid-flow-row gap-x-4 gap-y-2 px-4 lg:grid-cols-5" className="grid grid-flow-row px-4 gap-x-4 gap-y-2 lg:grid-cols-5"
> >
<ControlledAutocomplete <ControlledAutocomplete
id="version" id="version"
name="version" name="version"
freeSolo
getOptionLabel={(option) => {
if (typeof option === 'string') {
return option || '';
}
return option.value;
}}
autoHighlight autoHighlight
isOptionEqualToValue={() => false} isOptionEqualToValue={() => false}
filterOptions={(options, { inputValue }) => { filterOptions={(options, { inputValue }) => {

View File

@@ -47,7 +47,7 @@ export default function ApplicationPaused() {
async function handleTriggerUnpausing() { async function handleTriggerUnpausing() {
await execPromiseWithErrorToast( await execPromiseWithErrorToast(
async () => { async () => {
unpauseApplication({ variables: { appId: currentProject.id } }); await unpauseApplication({ variables: { appId: currentProject.id } });
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
}, },
{ {

View File

@@ -25,12 +25,12 @@ function Plan({ planName, price, setPlan, planId, selectedPlanId }: any) {
return ( return (
<button <button
type="button" type="button"
className="grid items-center justify-between w-full grid-flow-col gap-2 px-1 my-4" className="my-4 grid w-full grid-flow-col items-center justify-between gap-2 px-1"
onClick={setPlan} onClick={setPlan}
tabIndex={-1} tabIndex={-1}
> >
<div className="grid grid-flow-row gap-y-0.5"> <div className="grid grid-flow-row gap-y-0.5">
<div className="grid items-center justify-start grid-flow-col gap-2"> <div className="grid grid-flow-col items-center justify-start gap-2">
<Checkbox <Checkbox
onChange={setPlan} onChange={setPlan}
checked={selectedPlanId === planId} checked={selectedPlanId === planId}
@@ -40,7 +40,7 @@ function Plan({ planName, price, setPlan, planId, selectedPlanId }: any) {
<Text <Text
variant="h3" variant="h3"
component="p" component="p"
className="self-center font-medium text-left" className="self-center text-left font-medium"
> >
Upgrade to {planName} Upgrade to {planName}
</Text> </Text>
@@ -119,7 +119,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
const handleUpdateAppPlan = async () => { const handleUpdateAppPlan = async () => {
await execPromiseWithErrorToast( await execPromiseWithErrorToast(
async () => { async () => {
updateApp({ await updateApp({
variables: { variables: {
appId: app.id, appId: app.id,
app: { app: {
@@ -156,7 +156,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
if (pollingCurrentProject) { if (pollingCurrentProject) {
return ( return (
<Box className="w-full max-w-xl p-6 mx-auto text-left rounded-lg"> <Box className="mx-auto w-full max-w-xl rounded-lg p-6 text-left">
<div className="flex flex-col"> <div className="flex flex-col">
<div className="mx-auto"> <div className="mx-auto">
<Image <Image
@@ -179,7 +179,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
<Button <Button
variant="outlined" variant="outlined"
color="secondary" color="secondary"
className="w-full max-w-sm mx-auto mt-4" className="mx-auto mt-4 w-full max-w-sm"
onClick={() => { onClick={() => {
if (close) { if (close) {
close(); close();
@@ -196,7 +196,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
} }
return ( return (
<Box className="w-full max-w-xl p-6 text-left rounded-lg"> <Box className="w-full max-w-xl rounded-lg p-6 text-left">
<BaseDialog <BaseDialog
open={showPaymentModal} open={showPaymentModal}
onClose={() => setShowPaymentModal(false)} onClose={() => setShowPaymentModal(false)}
@@ -241,7 +241,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
))} ))}
</div> </div>
<div className="grid grid-flow-row gap-2 mt-2"> <div className="mt-2 grid grid-flow-row gap-2">
<Button <Button
onClick={handleChangePlanClick} onClick={handleChangePlanClick}
disabled={!selectedPlan} disabled={!selectedPlan}

View File

@@ -3,12 +3,12 @@ import { Button } from '@/components/ui/v2/Button';
import { Checkbox } from '@/components/ui/v2/Checkbox'; import { Checkbox } from '@/components/ui/v2/Checkbox';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { type RunService } from '@/hooks/useRunServices';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
useDeleteRunServiceConfigMutation, useDeleteRunServiceConfigMutation,
useDeleteRunServiceMutation, useDeleteRunServiceMutation,
} from '@/utils/__generated__/graphql'; } from '@/utils/__generated__/graphql';
import { type RunService } from 'pages/[workspaceSlug]/[appSlug]/services';
import { useState } from 'react'; import { useState } from 'react';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';

View File

@@ -91,7 +91,7 @@ export default function useCurrentWorkspaceAndProject(): UseCurrentWorkspaceAndP
// Return a default project if working locally // Return a default project if working locally
if (!isPlatform) { if (!isPlatform) {
const localProject: Project = { const localProject: Project = {
id: 'local', id: '00000000-0000-0000-0000-000000000000',
slug: 'local', slug: 'local',
name: 'local', name: 'local',
appStates: [ appStates: [

View File

@@ -1,9 +1,12 @@
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useGetPlansQuery } from '@/utils/__generated__/graphql'; import { useGetPlansQuery } from '@/utils/__generated__/graphql';
/** /**
* Returns the Pro plan. * Returns the Pro plan.
*/ */
export default function useProPlan() { export default function useProPlan() {
const isPlatform = useIsPlatform();
const { data, ...rest } = useGetPlansQuery({ const { data, ...rest } = useGetPlansQuery({
variables: { variables: {
where: { where: {
@@ -13,6 +16,7 @@ export default function useProPlan() {
}, },
}, },
fetchPolicy: 'cache-first', fetchPolicy: 'cache-first',
skip: !isPlatform,
}); });
return { data: data?.plans?.at(0), ...rest }; return { data: data?.plans?.at(0), ...rest };

View File

@@ -90,14 +90,6 @@ export default function useProjectRoutes() {
icon: <GaugeIcon />, icon: <GaugeIcon />,
disabled: !isPlatform, disabled: !isPlatform,
}, },
{
relativeMainPath: '/settings',
relativePath: '/settings/general',
exact: false,
label: 'Settings',
icon: <CogIcon />,
disabled: !isPlatform || maintenanceActive,
},
]; ];
const allRoutes: ProjectRoute[] = [ const allRoutes: ProjectRoute[] = [
@@ -143,7 +135,6 @@ export default function useProjectRoutes() {
exact: false, exact: false,
label: 'Run', label: 'Run',
icon: <ServicesIcon />, icon: <ServicesIcon />,
disabled: !isPlatform,
}, },
{ {
relativeMainPath: '/ai', relativeMainPath: '/ai',
@@ -153,6 +144,14 @@ export default function useProjectRoutes() {
icon: <AIIcon />, icon: <AIIcon />,
}, },
...nhostRoutes, ...nhostRoutes,
{
relativeMainPath: '/settings',
relativePath: '/settings/general',
exact: false,
label: 'Settings',
icon: <CogIcon />,
disabled: maintenanceActive,
},
]; ];
return { return {

View File

@@ -1,15 +1,19 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { VerifyDomain } from '@/features/projects/custom-domains/settings/components/VerifyDomain'; import { VerifyDomain } from '@/features/projects/custom-domains/settings/components/VerifyDomain';
import { import {
useGetAuthenticationSettingsQuery, useGetAuthenticationSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
type ConfigIngressUpdateInput, type ConfigIngressUpdateInput,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -23,12 +27,18 @@ const validationSchema = Yup.object({
export type AuthDomainFormValues = Yup.InferType<typeof validationSchema>; export type AuthDomainFormValues = Yup.InferType<typeof validationSchema>;
export default function AuthDomain() { export default function AuthDomain() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const [isVerified, setIsVerified] = useState(false); const [isVerified, setIsVerified] = useState(false);
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation(); const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const form = useForm<Yup.InferType<typeof validationSchema>>({ const form = useForm<Yup.InferType<typeof validationSchema>>({
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
@@ -40,6 +50,7 @@ export default function AuthDomain() {
variables: { variables: {
appId: currentProject.id, appId: currentProject.id,
}, },
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { networking } = data?.config?.auth?.resources || {}; const { networking } = data?.config?.auth?.resources || {};
@@ -94,6 +105,18 @@ export default function AuthDomain() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Auth domain is being updated...', loadingMessage: 'Auth domain is being updated...',
@@ -104,6 +127,14 @@ export default function AuthDomain() {
); );
} }
const isDisabled = () => {
if (!isPlatform) {
return !isDirty || maintenanceActive;
}
return !isDirty || maintenanceActive || (!isVerified && !initialValue);
};
return ( return (
<FormProvider {...form}> <FormProvider {...form}>
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
@@ -112,12 +143,11 @@ export default function AuthDomain() {
description="Enter below your custom domain for the authentication service." description="Enter below your custom domain for the authentication service."
slotProps={{ slotProps={{
submitButton: { submitButton: {
disabled: disabled: isDisabled(),
!isDirty || maintenanceActive || (!isVerified && !initialValue),
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
className="grid grid-flow-row gap-x-4 gap-y-4 px-4 lg:grid-cols-5" className="grid grid-flow-row px-4 gap-x-4 gap-y-4 lg:grid-cols-5"
> >
<Input <Input
{...register('auth_fqdn')} {...register('auth_fqdn')}

View File

@@ -1,15 +1,19 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { VerifyDomain } from '@/features/projects/custom-domains/settings/components/VerifyDomain'; import { VerifyDomain } from '@/features/projects/custom-domains/settings/components/VerifyDomain';
import { import {
useGetHasuraSettingsQuery, useGetHasuraSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
type ConfigIngressUpdateInput, type ConfigIngressUpdateInput,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -23,12 +27,18 @@ const validationSchema = Yup.object({
export type HasuraDomainFormValues = Yup.InferType<typeof validationSchema>; export type HasuraDomainFormValues = Yup.InferType<typeof validationSchema>;
export default function HasuraDomain() { export default function HasuraDomain() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const [isVerified, setIsVerified] = useState(false); const [isVerified, setIsVerified] = useState(false);
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation(); const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const form = useForm<Yup.InferType<typeof validationSchema>>({ const form = useForm<Yup.InferType<typeof validationSchema>>({
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
@@ -40,6 +50,7 @@ export default function HasuraDomain() {
variables: { variables: {
appId: currentProject.id, appId: currentProject.id,
}, },
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { networking } = data?.config?.hasura?.resources || {}; const { networking } = data?.config?.hasura?.resources || {};
@@ -96,6 +107,18 @@ export default function HasuraDomain() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Hasura domain is being updated...', loadingMessage: 'Hasura domain is being updated...',
@@ -106,6 +129,14 @@ export default function HasuraDomain() {
); );
} }
const isDisabled = () => {
if (!isPlatform) {
return !isDirty || maintenanceActive;
}
return !isDirty || maintenanceActive || (!isVerified && !initialValue);
};
return ( return (
<FormProvider {...form}> <FormProvider {...form}>
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
@@ -114,12 +145,11 @@ export default function HasuraDomain() {
description="Enter below your custom domain for the Hasura/GraphQL service." description="Enter below your custom domain for the Hasura/GraphQL service."
slotProps={{ slotProps={{
submitButton: { submitButton: {
disabled: disabled: isDisabled(),
!isDirty || maintenanceActive || (!isVerified && !initialValue),
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
className="grid grid-flow-row gap-x-4 gap-y-4 px-4 lg:grid-cols-5" className="grid grid-flow-row px-4 gap-x-4 gap-y-4 lg:grid-cols-5"
> >
<Input <Input
{...register('hasura_fqdn')} {...register('hasura_fqdn')}

View File

@@ -5,29 +5,12 @@ import { Link } from '@/components/ui/v2/Link';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { RunServicePortDomain } from '@/features/projects/custom-domains/settings/components/RunServicePortDomain'; import { RunServicePortDomain } from '@/features/projects/custom-domains/settings/components/RunServicePortDomain';
import { useGetRunServicesQuery } from '@/utils/__generated__/graphql'; import { useRunServices } from '@/hooks/useRunServices';
import { useMemo } from 'react';
export default function RunServiceDomains() { export default function RunServiceDomains() {
const { currentProject, currentWorkspace } = useCurrentWorkspaceAndProject(); const { currentProject, currentWorkspace } = useCurrentWorkspaceAndProject();
const { const { services, loading } = useRunServices();
data,
loading,
// refetch: refetchServices, // TODO refetch after update
} = useGetRunServicesQuery({
variables: {
appID: currentProject.id,
resolve: false,
limit: 1000, // TODO consider pagination
offset: 0,
},
});
const services = useMemo(
() => data?.app?.runServices.map((service) => service) ?? [],
[data],
);
if (loading) { if (loading) {
return ( return (
@@ -45,7 +28,7 @@ export default function RunServiceDomains() {
.filter((service) => service.config?.ports?.length > 0) .filter((service) => service.config?.ports?.length > 0)
.map((service) => ( .map((service) => (
<SettingsContainer <SettingsContainer
key={service.id} key={service.id ?? service.serviceID}
title={ title={
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
<Text className="text-lg font-semibold"> <Text className="text-lg font-semibold">
@@ -58,7 +41,7 @@ export default function RunServiceDomains() {
underline="hover" underline="hover"
className="font-medium" className="font-medium"
> >
<ArrowSquareOutIcon className="mb-1 ml-1 h-4 w-4" /> <ArrowSquareOutIcon className="w-4 h-4 mb-1 ml-1" />
</Link> </Link>
</div> </div>
} }
@@ -72,7 +55,7 @@ export default function RunServiceDomains() {
className: 'hidden', className: 'hidden',
}, },
}} }}
className="grid gap-y-4 gap-x-4 px-4" className="grid px-4 gap-x-4 gap-y-4"
> >
{service.config?.ports?.map((port) => ( {service.config?.ports?.map((port) => (
<RunServicePortDomain <RunServicePortDomain

View File

@@ -1,14 +1,18 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { Button } from '@/components/ui/v2/Button'; import { Button } from '@/components/ui/v2/Button';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { VerifyDomain } from '@/features/projects/custom-domains/settings/components/VerifyDomain'; import { VerifyDomain } from '@/features/projects/custom-domains/settings/components/VerifyDomain';
import { useUpdateRunServiceConfigMutation } from '@/generated/graphql'; import { useUpdateRunServiceConfigMutation } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { type RunService } from '@/hooks/useRunServices';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { type RunService } from 'pages/[workspaceSlug]/[appSlug]/services';
import { useState } from 'react'; import { useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -28,12 +32,17 @@ export default function RunServicePortDomain({
service, service,
port, port,
}: RunServicePortProps) { }: RunServicePortProps) {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [isVerified, setIsVerified] = useState(false); const [isVerified, setIsVerified] = useState(false);
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateRunServiceConfig] = useUpdateRunServiceConfigMutation(); const [updateRunServiceConfig] = useUpdateRunServiceConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const runServicePort = service.config.ports.find((p) => p.port === port); const runServicePort = service.config.ports.find((p) => p.port === port);
const initialValue = runServicePort?.ingresses?.[0]?.fqdn?.[0]; const initialValue = runServicePort?.ingresses?.[0]?.fqdn?.[0];
@@ -59,7 +68,7 @@ export default function RunServicePortDomain({
await updateRunServiceConfig({ await updateRunServiceConfig({
variables: { variables: {
appID: currentProject.id, appID: currentProject.id,
serviceID: service.id, serviceID: service.id ?? service.serviceID,
config: { config: {
ports: service?.config?.ports?.map((p) => { ports: service?.config?.ports?.map((p) => {
// exclude the `__typename` because the mutation will fail otherwise // exclude the `__typename` because the mutation will fail otherwise
@@ -78,7 +87,7 @@ export default function RunServicePortDomain({
return { return {
...rest, ...rest,
// exclude the `__typename` because the mutation will fail otherwise // exclude the `__typename` because the mutation will fail otherwise
ingresses: rest.ingresses.map((item) => ({ ingresses: rest?.ingresses?.map((item) => ({
fqdn: item.fqdn, fqdn: item.fqdn,
})), })),
}; };
@@ -88,6 +97,18 @@ export default function RunServicePortDomain({
}); });
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: `Port ${port} is being updated...`, loadingMessage: `Port ${port} is being updated...`,
@@ -99,6 +120,16 @@ export default function RunServicePortDomain({
setLoading(false); setLoading(false);
} }
const isDisabled = () => {
if (!isPlatform) {
return loading || !isDirty || maintenanceActive;
}
return (
loading || !isDirty || maintenanceActive || (!isVerified && !initialValue)
);
};
return ( return (
<FormProvider {...form}> <FormProvider {...form}>
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
@@ -121,16 +152,7 @@ export default function RunServicePortDomain({
inputRoot: { min: 1, max: 100 }, inputRoot: { min: 1, max: 100 },
}} }}
/> />
<Button <Button variant="outlined" type="submit" disabled={isDisabled()}>
variant="outlined"
type="submit"
disabled={
loading ||
!isDirty ||
maintenanceActive ||
(!isVerified && !initialValue)
}
>
Save Save
</Button> </Button>
</div> </div>

View File

@@ -1,15 +1,19 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { VerifyDomain } from '@/features/projects/custom-domains/settings/components/VerifyDomain'; import { VerifyDomain } from '@/features/projects/custom-domains/settings/components/VerifyDomain';
import { import {
useGetServerlessFunctionsSettingsQuery, useGetServerlessFunctionsSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
type ConfigIngressUpdateInput, type ConfigIngressUpdateInput,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -25,12 +29,17 @@ export type ServerlessFunctionsDomainFormValues = Yup.InferType<
>; >;
export default function ServerlessFunctionsDomain() { export default function ServerlessFunctionsDomain() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const [isVerified, setIsVerified] = useState(false); const [isVerified, setIsVerified] = useState(false);
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation(); const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const form = useForm<ServerlessFunctionsDomainFormValues>({ const form = useForm<ServerlessFunctionsDomainFormValues>({
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
@@ -42,6 +51,7 @@ export default function ServerlessFunctionsDomain() {
variables: { variables: {
appId: currentProject.id, appId: currentProject.id,
}, },
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { networking } = data?.config?.functions?.resources || {}; const { networking } = data?.config?.functions?.resources || {};
@@ -98,6 +108,18 @@ export default function ServerlessFunctionsDomain() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Serverless Functions domain is being updated...', loadingMessage: 'Serverless Functions domain is being updated...',
@@ -109,6 +131,14 @@ export default function ServerlessFunctionsDomain() {
); );
} }
const isDisabled = () => {
if (!isPlatform) {
return !isDirty || maintenanceActive;
}
return !isDirty || maintenanceActive || (!isVerified && !initialValue);
};
return ( return (
<FormProvider {...form}> <FormProvider {...form}>
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
@@ -117,12 +147,11 @@ export default function ServerlessFunctionsDomain() {
description="Enter below your custom domain for Serverless Functions." description="Enter below your custom domain for Serverless Functions."
slotProps={{ slotProps={{
submitButton: { submitButton: {
disabled: disabled: isDisabled(),
!isDirty || maintenanceActive || (!isVerified && !initialValue),
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
className="grid grid-flow-row gap-x-4 gap-y-4 px-4 lg:grid-cols-5" className="grid grid-flow-row px-4 gap-x-4 gap-y-4 lg:grid-cols-5"
> >
<Input <Input
{...register('functions_fqdn')} {...register('functions_fqdn')}

View File

@@ -3,6 +3,7 @@ import { Button } from '@/components/ui/v2/Button';
import { IconButton } from '@/components/ui/v2/IconButton'; import { IconButton } from '@/components/ui/v2/IconButton';
import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon'; import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { useDnsLookupCnameLazyQuery } from '@/utils/__generated__/graphql'; import { useDnsLookupCnameLazyQuery } from '@/utils/__generated__/graphql';
@@ -21,6 +22,7 @@ export default function VerifyDomain({
value, value,
onHostNameVerified, onHostNameVerified,
}: VerifyDomainProps) { }: VerifyDomainProps) {
const isPlatform = useIsPlatform();
const [verificationFailed, setVerificationFailed] = useState(false); const [verificationFailed, setVerificationFailed] = useState(false);
const [verificationSucceeded, setVerificationSucceeded] = useState(false); const [verificationSucceeded, setVerificationSucceeded] = useState(false);
@@ -72,8 +74,11 @@ export default function VerifyDomain({
backgroundColor: 'success.light', backgroundColor: 'success.light',
color: 'success.dark', color: 'success.dark',
}, },
!isPlatform && {
backgroundColor: 'grey.300',
},
]} ]}
className="flex flex-col space-y-4 rounded-md p-4" className="flex flex-col p-4 space-y-4 rounded-md"
> >
<div className="flex flex-row items-center justify-between"> <div className="flex flex-row items-center justify-between">
{!verificationFailed && !verificationSucceeded && ( {!verificationFailed && !verificationSucceeded && (
@@ -110,23 +115,29 @@ export default function VerifyDomain({
</div> </div>
<div className="flex flex-row space-x-2"> <div className="flex flex-row space-x-2">
<Text>Value:</Text> <Text>Value:</Text>
<Text className="font-bold">{value}</Text> {isPlatform ? (
<IconButton <>
aria-label="Copy Personal Access Token" <Text className="font-bold">{value}</Text>
variant="borderless" <IconButton
color="secondary" aria-label="Copy Personal Access Token"
onClick={() => copy(value, 'CNAME Value')} variant="borderless"
> color="secondary"
<CopyIcon className="h-4 w-4" /> onClick={() => copy(value, 'CNAME Value')}
</IconButton> >
<CopyIcon className="w-4 h-4" />
</IconButton>
</>
) : null}
</div> </div>
<Button {isPlatform ? (
disabled={loading || !hostname} <Button
onClick={handleVerifyDomain} disabled={loading || !hostname || isPlatform}
className="mt-4 sm:absolute sm:bottom-0 sm:right-0 sm:mt-0" onClick={handleVerifyDomain}
> className="mt-4 sm:absolute sm:bottom-0 sm:right-0 sm:mt-0"
Verify >
</Button> Verify
</Button>
) : null}
</div> </div>
</Box> </Box>
); );

View File

@@ -1,5 +1,8 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import type { import type {
BaseEnvironmentVariableFormProps, BaseEnvironmentVariableFormProps,
BaseEnvironmentVariableFormValues, BaseEnvironmentVariableFormValues,
@@ -8,6 +11,7 @@ import {
BaseEnvironmentVariableForm, BaseEnvironmentVariableForm,
baseEnvironmentVariableFormValidationSchema, baseEnvironmentVariableFormValidationSchema,
} from '@/features/projects/environmentVariables/settings/components/BaseEnvironmentVariableForm'; } from '@/features/projects/environmentVariables/settings/components/BaseEnvironmentVariableForm';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
GetEnvironmentVariablesDocument, GetEnvironmentVariablesDocument,
@@ -22,13 +26,17 @@ export interface CreateEnvironmentVariableFormProps
/** /**
* Function to be called when the form is submitted. * Function to be called when the form is submitted.
*/ */
onSubmit?: () => Promise<void>; onSubmit?: () => Promise<any>;
} }
export default function CreateEnvironmentVariableForm({ export default function CreateEnvironmentVariableForm({
onSubmit, onSubmit,
...props ...props
}: CreateEnvironmentVariableFormProps) { }: CreateEnvironmentVariableFormProps) {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const form = useForm<BaseEnvironmentVariableFormValues>({ const form = useForm<BaseEnvironmentVariableFormValues>({
defaultValues: { defaultValues: {
name: '', name: '',
@@ -42,13 +50,14 @@ export default function CreateEnvironmentVariableForm({
const { data, loading, error } = useGetEnvironmentVariablesQuery({ const { data, loading, error } = useGetEnvironmentVariablesQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const availableEnvironmentVariables = data?.config?.global?.environment || []; const availableEnvironmentVariables = data?.config?.global?.environment || [];
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetEnvironmentVariablesDocument], refetchQueries: [GetEnvironmentVariablesDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (loading) { if (loading) {
@@ -103,7 +112,19 @@ export default function CreateEnvironmentVariableForm({
await execPromiseWithErrorToast( await execPromiseWithErrorToast(
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
onSubmit?.(); await onSubmit?.();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Creating environment variable...', loadingMessage: 'Creating environment variable...',

View File

@@ -1,5 +1,8 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import type { import type {
BaseEnvironmentVariableFormProps, BaseEnvironmentVariableFormProps,
BaseEnvironmentVariableFormValues, BaseEnvironmentVariableFormValues,
@@ -8,6 +11,7 @@ import {
BaseEnvironmentVariableForm, BaseEnvironmentVariableForm,
baseEnvironmentVariableFormValidationSchema, baseEnvironmentVariableFormValidationSchema,
} from '@/features/projects/environmentVariables/settings/components/BaseEnvironmentVariableForm'; } from '@/features/projects/environmentVariables/settings/components/BaseEnvironmentVariableForm';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import type { EnvironmentVariable } from '@/types/application'; import type { EnvironmentVariable } from '@/types/application';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
@@ -27,7 +31,7 @@ export interface EditEnvironmentVariableFormProps
/** /**
* Function to be called when the form is submitted. * Function to be called when the form is submitted.
*/ */
onSubmit?: () => Promise<void>; onSubmit?: () => Promise<any>;
} }
export default function EditEnvironmentVariableForm({ export default function EditEnvironmentVariableForm({
@@ -35,6 +39,10 @@ export default function EditEnvironmentVariableForm({
onSubmit, onSubmit,
...props ...props
}: EditEnvironmentVariableFormProps) { }: EditEnvironmentVariableFormProps) {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const form = useForm<BaseEnvironmentVariableFormValues>({ const form = useForm<BaseEnvironmentVariableFormValues>({
defaultValues: { defaultValues: {
id: originalEnvironmentVariable.id || '', id: originalEnvironmentVariable.id || '',
@@ -49,13 +57,14 @@ export default function EditEnvironmentVariableForm({
const { data, loading, error } = useGetEnvironmentVariablesQuery({ const { data, loading, error } = useGetEnvironmentVariablesQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const availableEnvironmentVariables = data?.config?.global?.environment || []; const availableEnvironmentVariables = data?.config?.global?.environment || [];
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetEnvironmentVariablesDocument], refetchQueries: [GetEnvironmentVariablesDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (loading) { if (loading) {
@@ -117,7 +126,19 @@ export default function EditEnvironmentVariableForm({
await execPromiseWithErrorToast( await execPromiseWithErrorToast(
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
onSubmit?.(); await onSubmit?.();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Updating environment variable...', loadingMessage: 'Updating environment variable...',

View File

@@ -3,6 +3,8 @@ import { Form } from '@/components/form/Form';
import { Button } from '@/components/ui/v2/Button'; import { Button } from '@/components/ui/v2/Button';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import type { DialogFormProps } from '@/types/common'; import type { DialogFormProps } from '@/types/common';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
@@ -63,9 +65,12 @@ export default function EditJwtSecretForm({
submitButtonText = 'Save', submitButtonText = 'Save',
location, location,
}: EditJwtSecretFormProps) { }: EditJwtSecretFormProps) {
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetEnvironmentVariablesDocument], refetchQueries: [GetEnvironmentVariablesDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { onDirtyStateChange } = useDialog(); const { onDirtyStateChange } = useDialog();
@@ -118,9 +123,9 @@ export default function EditJwtSecretForm({
<FormProvider {...form}> <FormProvider {...form}>
<Form <Form
onSubmit={handleSubmit} onSubmit={handleSubmit}
className="flex flex-auto flex-col content-between overflow-hidden pb-4" className="flex flex-col content-between flex-auto pb-4 overflow-hidden"
> >
<div className="flex-auto overflow-y-auto px-6"> <div className="flex-auto px-6 overflow-y-auto">
<Input <Input
{...register('jwtSecret')} {...register('jwtSecret')}
error={Boolean(errors.jwtSecret?.message)} error={Boolean(errors.jwtSecret?.message)}

View File

@@ -1,3 +1,4 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -13,8 +14,10 @@ import { List } from '@/components/ui/v2/List';
import { ListItem } from '@/components/ui/v2/ListItem'; import { ListItem } from '@/components/ui/v2/ListItem';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { CreateEnvironmentVariableForm } from '@/features/projects/environmentVariables/settings/components/CreateEnvironmentVariableForm'; import { CreateEnvironmentVariableForm } from '@/features/projects/environmentVariables/settings/components/CreateEnvironmentVariableForm';
import { EditEnvironmentVariableForm } from '@/features/projects/environmentVariables/settings/components/EditEnvironmentVariableForm'; import { EditEnvironmentVariableForm } from '@/features/projects/environmentVariables/settings/components/EditEnvironmentVariableForm';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import type { EnvironmentVariable } from '@/types/application'; import type { EnvironmentVariable } from '@/types/application';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
@@ -33,12 +36,14 @@ export interface EnvironmentVariableSettingsFormValues {
} }
export default function EnvironmentVariableSettings() { export default function EnvironmentVariableSettings() {
const { openDialog, openAlertDialog } = useDialog(); const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { openDialog, openAlertDialog } = useDialog();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { data, loading, error } = useGetEnvironmentVariablesQuery({ const { data, loading, error, refetch } = useGetEnvironmentVariablesQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const availableEnvironmentVariables = [ const availableEnvironmentVariables = [
@@ -57,6 +62,7 @@ export default function EnvironmentVariableSettings() {
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetEnvironmentVariablesDocument], refetchQueries: [GetEnvironmentVariablesDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (loading) { if (loading) {
@@ -92,6 +98,18 @@ export default function EnvironmentVariableSettings() {
await execPromiseWithErrorToast( await execPromiseWithErrorToast(
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Deleting environment variable...', loadingMessage: 'Deleting environment variable...',
@@ -105,7 +123,7 @@ export default function EnvironmentVariableSettings() {
function handleOpenCreator() { function handleOpenCreator() {
openDialog({ openDialog({
title: 'Create Environment Variable', title: 'Create Environment Variable',
component: <CreateEnvironmentVariableForm />, component: <CreateEnvironmentVariableForm onSubmit={refetch} />,
props: { props: {
titleProps: { className: '!pb-0' }, titleProps: { className: '!pb-0' },
PaperProps: { className: 'gap-2 max-w-sm' }, PaperProps: { className: 'gap-2 max-w-sm' },
@@ -119,6 +137,7 @@ export default function EnvironmentVariableSettings() {
component: ( component: (
<EditEnvironmentVariableForm <EditEnvironmentVariableForm
originalEnvironmentVariable={originalVariable} originalEnvironmentVariable={originalVariable}
onSubmit={refetch}
/> />
), ),
props: { props: {
@@ -159,7 +178,7 @@ export default function EnvironmentVariableSettings() {
)} )}
slotProps={{ submitButton: { className: 'hidden' } }} slotProps={{ submitButton: { className: 'hidden' } }}
> >
<Box className="grid grid-cols-2 gap-2 border-b-1 px-4 py-3 lg:grid-cols-3"> <Box className="grid grid-cols-2 gap-2 px-4 py-3 border-b-1 lg:grid-cols-3">
<Text className="font-medium">Variable Name</Text> <Text className="font-medium">Variable Name</Text>
</Box> </Box>
@@ -175,7 +194,7 @@ export default function EnvironmentVariableSettings() {
<Dropdown.Trigger <Dropdown.Trigger
asChild asChild
hideChevron hideChevron
className="absolute right-4 top-1/2 -translate-y-1/2" className="absolute -translate-y-1/2 right-4 top-1/2"
> >
<IconButton <IconButton
variant="borderless" variant="borderless"

View File

@@ -21,11 +21,15 @@ import {
} from '@/features/projects/common/utils/generateAppServiceUrl'; } from '@/features/projects/common/utils/generateAppServiceUrl';
import { EditJwtSecretForm } from '@/features/projects/environmentVariables/settings/components/EditJwtSecretForm'; import { EditJwtSecretForm } from '@/features/projects/environmentVariables/settings/components/EditJwtSecretForm';
import { getJwtSecretsWithoutFalsyValues } from '@/features/projects/environmentVariables/settings/utils/getJwtSecretsWithoutFalsyValues'; import { getJwtSecretsWithoutFalsyValues } from '@/features/projects/environmentVariables/settings/utils/getJwtSecretsWithoutFalsyValues';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { getHasuraConsoleServiceUrl } from '@/utils/env'; import { getHasuraConsoleServiceUrl } from '@/utils/env';
import { useGetEnvironmentVariablesQuery } from '@/utils/__generated__/graphql'; import { useGetEnvironmentVariablesQuery } from '@/utils/__generated__/graphql';
import { Fragment, useState } from 'react'; import { Fragment, useState } from 'react';
export default function SystemEnvironmentVariableSettings() { export default function SystemEnvironmentVariableSettings() {
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const [showAdminSecret, setShowAdminSecret] = useState(false); const [showAdminSecret, setShowAdminSecret] = useState(false);
const [showWebhookSecret, setShowWebhookSecret] = useState(false); const [showWebhookSecret, setShowWebhookSecret] = useState(false);
@@ -34,7 +38,7 @@ export default function SystemEnvironmentVariableSettings() {
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { data, loading, error } = useGetEnvironmentVariablesQuery({ const { data, loading, error } = useGetEnvironmentVariablesQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { jwtSecrets, webhookSecret, adminSecret } = data?.config?.hasura || {}; const { jwtSecrets, webhookSecret, adminSecret } = data?.config?.hasura || {};
@@ -46,8 +50,6 @@ export default function SystemEnvironmentVariableSettings() {
? JSON.stringify(jwtSecretsWithoutFalsyValues[0], null, 2) ? JSON.stringify(jwtSecretsWithoutFalsyValues[0], null, 2)
: JSON.stringify(jwtSecretsWithoutFalsyValues, null, 2); : JSON.stringify(jwtSecretsWithoutFalsyValues, null, 2);
const isPlatform = useIsPlatform();
const appClient = useAppClient(); const appClient = useAppClient();
if (loading) { if (loading) {
@@ -124,10 +126,10 @@ export default function SystemEnvironmentVariableSettings() {
description="System environment variables are automatically generated from the configuration file and your project's subdomain and region." description="System environment variables are automatically generated from the configuration file and your project's subdomain and region."
docsLink="https://docs.nhost.io/platform/environment-variables#system-environment-variables" docsLink="https://docs.nhost.io/platform/environment-variables#system-environment-variables"
rootClassName="gap-0" rootClassName="gap-0"
className="mt-2 mb-2.5 px-0" className="mb-2.5 mt-2 px-0"
slotProps={{ submitButton: { className: 'hidden' } }} slotProps={{ submitButton: { className: 'hidden' } }}
> >
<Box className="grid grid-cols-3 gap-2 border-b-1 px-4 py-3"> <Box className="grid grid-cols-3 gap-2 px-4 py-3 border-b-1">
<Text className="font-medium">Variable Name</Text> <Text className="font-medium">Variable Name</Text>
<Text className="font-medium lg:col-span-2">Value</Text> <Text className="font-medium lg:col-span-2">Value</Text>
</Box> </Box>
@@ -136,7 +138,7 @@ export default function SystemEnvironmentVariableSettings() {
<ListItem.Root className="grid grid-cols-2 gap-2 px-4 lg:grid-cols-3"> <ListItem.Root className="grid grid-cols-2 gap-2 px-4 lg:grid-cols-3">
<ListItem.Text>NHOST_ADMIN_SECRET</ListItem.Text> <ListItem.Text>NHOST_ADMIN_SECRET</ListItem.Text>
<div className="grid grid-flow-col items-center justify-start gap-2 lg:col-span-2"> <div className="grid items-center justify-start grid-flow-col gap-2 lg:col-span-2">
<Text className="truncate" color="secondary"> <Text className="truncate" color="secondary">
{showAdminSecret ? ( {showAdminSecret ? (
<InlineCode className="!text-sm font-medium"> <InlineCode className="!text-sm font-medium">
@@ -156,9 +158,9 @@ export default function SystemEnvironmentVariableSettings() {
onClick={() => setShowAdminSecret((show) => !show)} onClick={() => setShowAdminSecret((show) => !show)}
> >
{showAdminSecret ? ( {showAdminSecret ? (
<EyeOffIcon className="h-5 w-5" /> <EyeOffIcon className="w-5 h-5" />
) : ( ) : (
<EyeIcon className="h-5 w-5" /> <EyeIcon className="w-5 h-5" />
)} )}
</IconButton> </IconButton>
</div> </div>
@@ -169,7 +171,7 @@ export default function SystemEnvironmentVariableSettings() {
<ListItem.Root className="grid grid-cols-2 gap-2 px-4 lg:grid-cols-3"> <ListItem.Root className="grid grid-cols-2 gap-2 px-4 lg:grid-cols-3">
<ListItem.Text>NHOST_WEBHOOK_SECRET</ListItem.Text> <ListItem.Text>NHOST_WEBHOOK_SECRET</ListItem.Text>
<div className="grid grid-flow-col items-center justify-start gap-2 lg:col-span-2"> <div className="grid items-center justify-start grid-flow-col gap-2 lg:col-span-2">
<Text className="truncate" color="secondary"> <Text className="truncate" color="secondary">
{showWebhookSecret ? ( {showWebhookSecret ? (
<InlineCode className="!text-sm font-medium"> <InlineCode className="!text-sm font-medium">
@@ -191,9 +193,9 @@ export default function SystemEnvironmentVariableSettings() {
onClick={() => setShowWebhookSecret((show) => !show)} onClick={() => setShowWebhookSecret((show) => !show)}
> >
{showWebhookSecret ? ( {showWebhookSecret ? (
<EyeOffIcon className="h-5 w-5" /> <EyeOffIcon className="w-5 h-5" />
) : ( ) : (
<EyeIcon className="h-5 w-5" /> <EyeIcon className="w-5 h-5" />
)} )}
</IconButton> </IconButton>
</div> </div>
@@ -217,9 +219,9 @@ export default function SystemEnvironmentVariableSettings() {
</Fragment> </Fragment>
))} ))}
<Divider component="li" className="!mt-4 !mb-2.5" /> <Divider component="li" className="!mb-2.5 !mt-4" />
<ListItem.Root className="grid grid-cols-2 justify-start px-4 lg:grid-cols-3"> <ListItem.Root className="grid justify-start grid-cols-2 px-4 lg:grid-cols-3">
<ListItem.Text>NHOST_JWT_SECRET</ListItem.Text> <ListItem.Text>NHOST_JWT_SECRET</ListItem.Text>
<div className="grid grid-flow-row items-center justify-center gap-1.5 text-center md:grid-flow-col lg:col-span-2 lg:justify-start lg:text-left"> <div className="grid grid-flow-row items-center justify-center gap-1.5 text-center md:grid-flow-col lg:col-span-2 lg:justify-start lg:text-left">

View File

@@ -131,7 +131,7 @@ export default function LogsBody({ logsData, loading, error }: LogsBodyProps) {
count: rows.length, count: rows.length,
getScrollElement: () => tableRef.current, getScrollElement: () => tableRef.current,
estimateSize: () => 63, estimateSize: () => 63,
overscan: 5, overscan: 50,
}); });
if (loading && !error) { if (loading && !error) {
@@ -214,7 +214,7 @@ export default function LogsBody({ logsData, loading, error }: LogsBodyProps) {
<TableCell <TableCell
key={cell.id} key={cell.id}
component="td" component="td"
className="break-words py-2.5 px-2 align-top text-xs- font-normal tracking-tight" className="break-words px-2 py-2.5 align-top text-xs- font-normal tracking-tight"
style={{ style={{
width: cell.column.getSize() || 'auto', width: cell.column.getSize() || 'auto',
minWidth: !cell.column.getSize() ? 300 : 'initial', minWidth: !cell.column.getSize() ? 300 : 'initial',

View File

@@ -52,7 +52,7 @@ function LogsDatePicker({
<Text <Text
htmlFor={label} htmlFor={label}
component="label" component="label"
className="self-center text-sm+ font-normal" className="min-w-14 self-center text-sm+ font-normal"
color="secondary" color="secondary"
> >
{label} {label}

View File

@@ -1,246 +1,240 @@
import { ControlledSelect } from '@/components/form/ControlledSelect';
import { Form } from '@/components/form/Form';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import type { BoxProps } from '@/components/ui/v2/Box'; import type { BoxProps } from '@/components/ui/v2/Box';
import { Box } from '@/components/ui/v2/Box'; import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button'; import { Button } from '@/components/ui/v2/Button';
import { ClockIcon } from '@/components/ui/v2/icons/ClockIcon'; import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
import { SearchIcon } from '@/components/ui/v2/icons/SearchIcon';
import { Input } from '@/components/ui/v2/Input';
import { Link } from '@/components/ui/v2/Link';
import { Option } from '@/components/ui/v2/Option'; import { Option } from '@/components/ui/v2/Option';
import { Select } from '@/components/ui/v2/Select'; import { Tooltip } from '@/components/ui/v2/Tooltip';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { LogsDatePicker } from '@/features/projects/logs/components/LogsDatePicker'; import { LogsRangeSelector } from '@/features/projects/logs/components/LogsRangeSelector';
import type { LogsCustomInterval } from '@/features/projects/logs/utils/constants/intervals'; import { AvailableLogsService } from '@/features/projects/logs/utils/constants/services';
import { LOGS_AVAILABLE_INTERVALS } from '@/features/projects/logs/utils/constants/intervals'; import { MINUTES_TO_DECREASE_FROM_CURRENT_DATE } from '@/utils/constants/common';
import type { AvailableLogsService } from '@/features/projects/logs/utils/constants/services'; import { useGetServiceLabelValuesQuery } from '@/utils/__generated__/graphql';
import { LOGS_AVAILABLE_SERVICES } from '@/features/projects/logs/utils/constants/services'; import { yupResolver } from '@hookform/resolvers/yup';
import { useGetRunServicesQuery } from '@/utils/__generated__/graphql';
import { subMinutes } from 'date-fns'; import { subMinutes } from 'date-fns';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { twMerge } from 'tailwind-merge'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
export interface LogsHeaderProps extends Omit<BoxProps, 'children'> { export const validationSchema = Yup.object({
from: Yup.date(),
to: Yup.date().nullable(),
service: Yup.string().oneOf(Object.values(AvailableLogsService)),
regexFilter: Yup.string(),
});
export type LogsFilterFormValues = Yup.InferType<typeof validationSchema>;
interface LogsHeaderProps extends Omit<BoxProps, 'children'> {
/** /**
* The date to be displayed in the date picker for the from date. * This is used to indicate that a query is currently inflight
*/ */
fromDate: Date; loading: boolean;
/** /**
* The date to be displayed in the date picker for the to date. *
* Function to be called when the user submits the filters form
*/ */
toDate: Date | null; onSubmitFilterValues: (value: LogsFilterFormValues) => void;
/**
* Service to where to fetch logs from.
*/
service: AvailableLogsService;
/**
* Function to be called when the user changes the from date.
*/
onFromDateChange: (value: Date) => void;
/**
* Function to be called when the user changes the `to` date.
*/
onToDateChange: (value: Date) => void;
/**
* Function to be called when the user changes service to which to query logs from.
*/
onServiceChange: (value: AvailableLogsService) => void;
}
type LogsToDatePickerLiveButtonProps = Pick<
LogsHeaderProps,
'fromDate' | 'toDate' | 'onToDateChange'
>;
function LogsToDatePickerLiveButton({
fromDate,
toDate,
onToDateChange,
}: LogsToDatePickerLiveButtonProps) {
const [currentTime, setCurrentTime] = useState(new Date());
const isLive = !toDate;
function handleLiveButtonClick() {
if (isLive) {
return;
}
onToDateChange(null);
setCurrentTime(new Date());
}
// if isLive is true, we want to update the current time every second
// and set the toDate to the current time.
useEffect(() => {
let interval = null;
if (!interval && isLive) {
interval = setInterval(() => {
setCurrentTime(new Date());
}, 1000);
}
return () => {
clearInterval(interval);
};
}, [isLive, onToDateChange]);
return (
<div className="text-greyscaleMedium grid grid-flow-col">
<LogsDatePicker
label="To"
value={!isLive ? toDate : currentTime}
disabled={isLive}
onChange={onToDateChange}
minDate={fromDate}
maxDate={toDate || new Date()}
componentsProps={{
button: {
className: twMerge(
'rounded-r-none pr-3',
isLive ? 'border-r-0 hover:border-r-0 z-0' : 'z-10',
),
color: toDate ? 'inherit' : 'secondary',
},
}}
/>
<Button
variant="outlined"
color={isLive ? 'primary' : 'secondary'}
sx={{
backgroundColor: (theme) =>
!isLive ? `${theme.palette.grey[200]} !important` : 'transparent',
color: !isLive ? 'text.secondary' : undefined,
}}
className={twMerge(
'min-w-[77px] rounded-l-none',
!isLive ? 'z-0 border-l-0 hover:border-l-0' : 'z-10',
)}
startIcon={<ClockIcon className="h-4 w-4 self-center align-middle" />}
onClick={handleLiveButtonClick}
>
Live
</Button>
</div>
);
} }
export default function LogsHeader({ export default function LogsHeader({
fromDate, loading,
toDate, onSubmitFilterValues,
service,
onFromDateChange,
onToDateChange,
onServiceChange,
...props ...props
}: LogsHeaderProps) { }: LogsHeaderProps) {
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const applicationCreationDate = new Date(currentProject.createdAt);
const [runServices, setRunServices] = useState< const [serviceLabels, setServiceLabels] = useState<
{ { label: string; value: string }[]
label: string;
value: string;
}[]
>([]); >([]);
const { data, loading } = useGetRunServicesQuery({ const { data, loading: loadingServiceLabelValues } =
variables: { useGetServiceLabelValuesQuery({
appID: currentProject.id, variables: { appID: currentProject.id },
resolve: false, });
limit: 1000,
offset: 0,
},
});
useEffect(() => { useEffect(() => {
if (!loading) { if (!loadingServiceLabelValues) {
const services = data.app?.runServices ?? []; const labels = data.getServiceLabelValues ?? [];
setServiceLabels(labels.map((l) => ({ label: l, value: l })));
}
}, [loadingServiceLabelValues, data]);
setRunServices( useEffect(() => {
services if (!loadingServiceLabelValues) {
.filter((s) => !!s.config?.name) const labels = data.getServiceLabelValues ?? [];
.map((s) => ({
label: s.config.name, const labelMappings = {
value: `run-${s.config.name}`, 'hasura-auth': 'Auth',
})), 'hasura-storage': 'Storage',
postgres: 'Postgres',
functions: 'Functions',
hasura: 'Hasura',
grafana: 'Grafana',
'job-backup': 'Backup Jobs',
ai: 'AI',
};
setServiceLabels(
labels.map((l) => ({ label: labelMappings[l] ?? l, value: l })),
); );
} }
}, [loading, data]); }, [loadingServiceLabelValues, data]);
/** const form = useForm<LogsFilterFormValues>({
* Will subtract the `customInterval` time in minutes from the current date. defaultValues: {
*/ from: subMinutes(new Date(), MINUTES_TO_DECREASE_FROM_CURRENT_DATE),
function handleIntervalChange({ to: new Date(),
minutesToDecreaseFromCurrentDate, regexFilter: '',
}: LogsCustomInterval) { service: AvailableLogsService.ALL,
onFromDateChange(subMinutes(new Date(), minutesToDecreaseFromCurrentDate)); },
onToDateChange(new Date()); reValidateMode: 'onSubmit',
} resolver: yupResolver(validationSchema),
});
const { register, watch, getValues } = form;
const service = watch('service');
useEffect(() => {
onSubmitFilterValues(getValues());
}, [service, getValues, onSubmitFilterValues]);
const handleSubmit = (values: LogsFilterFormValues) =>
onSubmitFilterValues(values);
return ( return (
<Box <Box
className="sticky top-0 z-10 grid w-full grid-flow-row gap-x-6 gap-y-2 border-b py-2.5 px-4 lg:grid-flow-col lg:justify-between" className="sticky top-0 z-10 grid w-full grid-flow-row gap-x-6 gap-y-2 border-b px-4 py-2.5 lg:grid-flow-col"
{...props} {...props}
> >
<Box className="grid w-full grid-flow-row items-center justify-center gap-2 md:w-[initial] md:grid-flow-col md:gap-3 lg:justify-start"> <FormProvider {...form}>
<div className="grid grid-flow-col items-center gap-3 md:justify-start"> <Form
<LogsDatePicker onSubmit={handleSubmit}
label="From" className="grid w-full grid-flow-row items-center gap-2 md:w-[initial] md:grid-flow-col md:gap-3 lg:justify-end"
value={fromDate} >
onChange={onFromDateChange} <Box className="flex flex-row space-x-2">
minDate={applicationCreationDate} <ControlledSelect
maxDate={toDate || new Date()} {...register('service')}
/> className="w-full text-sm font-normal min-w-fit"
placeholder="All Services"
aria-label="Select service"
hideEmptyHelperText
slotProps={{
root: {
className: 'min-h-[initial] h-10 leading-[initial]',
},
}}
>
{[{ label: 'All services', value: '' }, ...serviceLabels].map(
({ value, label }) => (
<Option
key={value}
value={value}
className="text-sm+ font-medium"
>
{label}
</Option>
),
)}
</ControlledSelect>
<div className="w-full min-w-fit">
<LogsRangeSelector onSubmitFilterValues={onSubmitFilterValues} />
</div>
</Box>
<LogsToDatePickerLiveButton <Input
fromDate={fromDate} {...register('regexFilter')}
toDate={toDate} placeholder="Filter logs with a regular expression"
onToDateChange={onToDateChange}
/>
</div>
<Box className="-my-2.5 px-0 py-2.5 lg:border-l lg:px-3">
<Select
className="w-full text-sm font-normal"
placeholder="All Services"
onChange={(_e, value) => {
if (typeof value !== 'string') {
return;
}
onServiceChange(value as AvailableLogsService);
}}
value={service}
aria-label="Select service"
hideEmptyHelperText hideEmptyHelperText
slotProps={{ autoComplete="off"
root: { className: 'min-h-[initial] h-9 leading-[initial]' }, fullWidth
}} className="min-w-80"
> startAdornment={
{[...LOGS_AVAILABLE_SERVICES, ...runServices].map( <Tooltip
({ value, label }) => ( componentsProps={{
<Option tooltip: {
key={value} sx: {
value={value} maxWidth: '30rem',
className="text-sm+ font-medium" },
> },
{label} }}
</Option> title={
), <div className="p-2 space-y-4">
)} <h2>Here are some useful regular expressions:</h2>
</Select> <ul className="pl-3 space-y-2 list-disc">
</Box> <li>
</Box> use
<code className="px-1 py-px mx-1 rounded-md bg-slate-500 text-slate-100">
(?i)error
</code>
to search for lines with the word <b>error</b> (case
insenstive)
</li>
<li>
use
<code className="px-1 py-px mx-1 rounded-md bg-slate-500 text-slate-100">
error
</code>
to search for lines with the word <b>error</b> (case
sensitive)
</li>
<li>
use
<code className="px-1 py-px mx-1 rounded-md bg-slate-500 text-slate-100">
/metadata.*error
</code>
to search for errors in hasura&apos;s metadata endpoint
</li>
<li>
See
<Link
href="https://github.com/google/re2/wiki/Syntax"
target="_blank"
rel="noopener noreferrer"
underline="hover"
className="mx-1"
>
here
</Link>
for more patterns
</li>
</ul>
</div>
}
>
<Box className="ml-2 rounded-full cursor-pointer">
<InfoIcon
aria-label="Info"
className="w-5 h-5"
color="info"
/>
</Box>
</Tooltip>
}
/>
<Box className="hidden grid-flow-col items-center justify-center gap-3 md:grid lg:justify-end">
{LOGS_AVAILABLE_INTERVALS.map((logInterval) => (
<Button <Button
key={logInterval.label} type="submit"
variant="outlined" className="h-10"
color="secondary" startIcon={
className="self-center" loading ? (
onClick={() => handleIntervalChange(logInterval)} <ActivityIndicator className="w-4 h-4" />
) : (
<SearchIcon />
)
}
disabled={loading}
> >
{logInterval.label} Search
</Button> </Button>
))} </Form>
</Box> </FormProvider>
</Box> </Box>
); );
} }

View File

@@ -1,2 +1 @@
export * from './LogsHeader'; export { default as LogsHeader, type LogsFilterFormValues } from './LogsHeader';
export { default as LogsHeader } from './LogsHeader';

View File

@@ -0,0 +1,170 @@
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { Dropdown, useDropdown } from '@/components/ui/v2/Dropdown';
import { ClockIcon } from '@/components/ui/v2/icons/ClockIcon';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { LogsDatePicker } from '@/features/projects/logs/components/LogsDatePicker';
import type { LogsFilterFormValues } from '@/features/projects/logs/components/LogsHeader';
import {
LOGS_AVAILABLE_INTERVALS,
type LogsCustomInterval,
} from '@/features/projects/logs/utils/constants/intervals';
import { useInterval } from '@/hooks/useInterval';
import { ChevronDownIcon } from '@graphiql/react';
import { formatDistance, subMinutes } from 'date-fns';
import { useState } from 'react';
import { useFormContext, useWatch } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
function LogsToDatePickerLiveButton() {
const [currentTime, setCurrentTime] = useState(new Date());
const { setValue } = useFormContext<LogsFilterFormValues>();
const { from, to } = useWatch<LogsFilterFormValues>();
const isLive = !to;
function handleLiveButtonClick() {
if (isLive) {
setValue('from', subMinutes(new Date(), 20));
setValue('to', new Date());
return;
}
setValue('to', null);
setCurrentTime(new Date());
}
useInterval(() => setCurrentTime(new Date()), isLive ? 1000 : 0);
return (
<div className="text-greyscaleMedium flex flex-col">
{!isLive && (
<LogsDatePicker
label="To"
value={!isLive ? to : currentTime}
disabled={isLive}
onChange={(date: Date) => setValue('to', date)}
minDate={from}
maxDate={new Date()}
componentsProps={{
button: {
className: twMerge('rounded-r-none', isLive ? 'z-0' : 'z-10'),
color: to ? 'inherit' : 'secondary',
},
}}
/>
)}
<Button
variant="outlined"
color={isLive ? 'primary' : 'secondary'}
sx={{
backgroundColor: (theme) =>
!isLive ? `${theme.palette.grey[200]} !important` : 'transparent',
color: !isLive ? 'text.secondary' : undefined,
}}
className={twMerge(!isLive ? 'z-0 mt-4' : 'z-10')}
startIcon={<ClockIcon className="h-4 w-4 self-center align-middle" />}
onClick={handleLiveButtonClick}
>
Live
</Button>
</div>
);
}
interface LogsRangeSelectorProps {
onSubmitFilterValues: (value: LogsFilterFormValues) => void;
}
function LogsRangeSelectorIntervalPickers({
onSubmitFilterValues,
}: LogsRangeSelectorProps) {
const { currentProject } = useCurrentWorkspaceAndProject();
const applicationCreationDate = new Date(currentProject.createdAt);
const { setValue, getValues } = useFormContext<LogsFilterFormValues>();
const { from } = useWatch<LogsFilterFormValues>();
const { handleClose } = useDropdown();
const handleApply = () => {
onSubmitFilterValues(getValues());
handleClose();
};
/**
* Will subtract the `customInterval` time in minutes from the current date.
*/
function handleIntervalChange({
minutesToDecreaseFromCurrentDate,
}: LogsCustomInterval) {
setValue('from', subMinutes(new Date(), minutesToDecreaseFromCurrentDate));
setValue('to', new Date());
}
return (
<Box className="flex flex-col space-y-4">
<div className="flex flex-col space-y-4">
<LogsDatePicker
label="From"
value={from}
onChange={(date) => setValue('from', date)}
minDate={applicationCreationDate}
maxDate={new Date()}
/>
<LogsToDatePickerLiveButton />
</div>
<Box className="grid grid-cols-2 gap-2">
{LOGS_AVAILABLE_INTERVALS.map((logInterval) => (
<Button
key={logInterval.label}
variant="outlined"
color="secondary"
className="self-center"
onClick={() => handleIntervalChange(logInterval)}
>
Last {logInterval.label}
</Button>
))}
</Box>
<Button color="primary" variant="contained" onClick={handleApply}>
Apply
</Button>
</Box>
);
}
export default function LogsRangeSelector({
onSubmitFilterValues,
}: LogsRangeSelectorProps) {
const { from, to } = useWatch<LogsFilterFormValues>();
return (
<Dropdown.Root>
<Dropdown.Trigger hideChevron className="flex w-full rounded-full">
<Button
component="a"
className="h-10 w-full min-w-40 items-center justify-between"
variant="outlined"
>
<span>
{to === null
? 'Live'
: `${formatDistance(to.getTime(), from.getTime())}`}
</span>
<ChevronDownIcon className="h-3 w-3" />
</Button>
</Dropdown.Trigger>
<Dropdown.Content PaperProps={{ className: 'mt-1 max-w-xs w-full p-3' }}>
<LogsRangeSelectorIntervalPickers
onSubmitFilterValues={onSubmitFilterValues}
/>
</Dropdown.Content>
</Dropdown.Root>
);
}

View File

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

View File

@@ -89,7 +89,7 @@ function LogsTimePicker({
</Button> </Button>
<Button variant="contained" color="primary" onClick={handleApply}> <Button variant="contained" color="primary" onClick={handleApply}>
Apply Set
</Button> </Button>
</Box> </Box>
</div> </div>

View File

@@ -26,4 +26,12 @@ export const LOGS_AVAILABLE_INTERVALS: LogsCustomInterval[] = [
label: '60 min', label: '60 min',
minutesToDecreaseFromCurrentDate: 60, minutesToDecreaseFromCurrentDate: 60,
}, },
{
label: '12 hours',
minutesToDecreaseFromCurrentDate: 720,
},
{
label: '24 hours',
minutesToDecreaseFromCurrentDate: 1440,
},
]; ];

View File

@@ -1,5 +1,8 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import type { import type {
BasePermissionVariableFormProps, BasePermissionVariableFormProps,
BasePermissionVariableFormValues, BasePermissionVariableFormValues,
@@ -9,6 +12,7 @@ import {
basePermissionVariableValidationSchema, basePermissionVariableValidationSchema,
} from '@/features/projects/permissions/settings/components/BasePermissionVariableForm'; } from '@/features/projects/permissions/settings/components/BasePermissionVariableForm';
import { getAllPermissionVariables } from '@/features/projects/permissions/settings/utils/getAllPermissionVariables'; import { getAllPermissionVariables } from '@/features/projects/permissions/settings/utils/getAllPermissionVariables';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
GetRolesPermissionsDocument, GetRolesPermissionsDocument,
@@ -23,18 +27,20 @@ export interface CreatePermissionVariableFormProps
/** /**
* Function to be called when the form is submitted. * Function to be called when the form is submitted.
*/ */
onSubmit?: () => Promise<void>; onSubmit?: () => Promise<any>;
} }
export default function CreatePermissionVariableForm({ export default function CreatePermissionVariableForm({
onSubmit, onSubmit,
...props ...props
}: CreatePermissionVariableFormProps) { }: CreatePermissionVariableFormProps) {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { data, error, loading } = useGetRolesPermissionsQuery({ const { data, error, loading } = useGetRolesPermissionsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { customClaims: permissionVariables } = const { customClaims: permissionVariables } =
@@ -51,6 +57,7 @@ export default function CreatePermissionVariableForm({
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetRolesPermissionsDocument], refetchQueries: [GetRolesPermissionsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (loading) { if (loading) {
@@ -106,6 +113,18 @@ export default function CreatePermissionVariableForm({
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
await onSubmit?.(); await onSubmit?.();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Creating permission variable...', loadingMessage: 'Creating permission variable...',

View File

@@ -1,5 +1,8 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import type { import type {
BasePermissionVariableFormProps, BasePermissionVariableFormProps,
BasePermissionVariableFormValues, BasePermissionVariableFormValues,
@@ -9,6 +12,7 @@ import {
basePermissionVariableValidationSchema, basePermissionVariableValidationSchema,
} from '@/features/projects/permissions/settings/components/BasePermissionVariableForm'; } from '@/features/projects/permissions/settings/components/BasePermissionVariableForm';
import { getAllPermissionVariables } from '@/features/projects/permissions/settings/utils/getAllPermissionVariables'; import { getAllPermissionVariables } from '@/features/projects/permissions/settings/utils/getAllPermissionVariables';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import type { PermissionVariable } from '@/types/application'; import type { PermissionVariable } from '@/types/application';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
@@ -28,7 +32,7 @@ export interface EditPermissionVariableFormProps
/** /**
* Function to be called when the form is submitted. * Function to be called when the form is submitted.
*/ */
onSubmit?: () => Promise<void>; onSubmit?: () => any;
} }
export default function EditPermissionVariableForm({ export default function EditPermissionVariableForm({
@@ -36,11 +40,14 @@ export default function EditPermissionVariableForm({
onSubmit, onSubmit,
...props ...props
}: EditPermissionVariableFormProps) { }: EditPermissionVariableFormProps) {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { data, error, loading } = useGetRolesPermissionsQuery({ const { data, error, loading } = useGetRolesPermissionsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { customClaims: permissionVariables } = const { customClaims: permissionVariables } =
@@ -57,6 +64,7 @@ export default function EditPermissionVariableForm({
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetRolesPermissionsDocument], refetchQueries: [GetRolesPermissionsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (loading) { if (loading) {
@@ -132,6 +140,19 @@ export default function EditPermissionVariableForm({
await execPromiseWithErrorToast( await execPromiseWithErrorToast(
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
await onSubmit?.();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Updating permission variable...', loadingMessage: 'Updating permission variable...',
@@ -140,8 +161,6 @@ export default function EditPermissionVariableForm({
'An error occurred while trying to update the permission variable.', 'An error occurred while trying to update the permission variable.',
}, },
); );
await onSubmit?.();
} }
return ( return (

View File

@@ -1,3 +1,4 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -15,9 +16,11 @@ import { ListItem } from '@/components/ui/v2/ListItem';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip'; import { Tooltip } from '@/components/ui/v2/Tooltip';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { CreatePermissionVariableForm } from '@/features/projects/permissions/settings/components/CreatePermissionVariableForm'; import { CreatePermissionVariableForm } from '@/features/projects/permissions/settings/components/CreatePermissionVariableForm';
import { EditPermissionVariableForm } from '@/features/projects/permissions/settings/components/EditPermissionVariableForm'; import { EditPermissionVariableForm } from '@/features/projects/permissions/settings/components/EditPermissionVariableForm';
import { getAllPermissionVariables } from '@/features/projects/permissions/settings/utils/getAllPermissionVariables'; import { getAllPermissionVariables } from '@/features/projects/permissions/settings/utils/getAllPermissionVariables';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import type { PermissionVariable } from '@/types/application'; import type { PermissionVariable } from '@/types/application';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
@@ -29,13 +32,15 @@ import { Fragment } from 'react';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
export default function PermissionVariableSettings() { export default function PermissionVariableSettings() {
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { openDialog, openAlertDialog } = useDialog(); const { openDialog, openAlertDialog } = useDialog();
const { data, loading, error } = useGetRolesPermissionsQuery({ const { data, loading, error, refetch } = useGetRolesPermissionsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { customClaims: permissionVariables } = const { customClaims: permissionVariables } =
@@ -43,6 +48,7 @@ export default function PermissionVariableSettings() {
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetRolesPermissionsDocument], refetchQueries: [GetRolesPermissionsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (loading) { if (loading) {
@@ -55,6 +61,20 @@ export default function PermissionVariableSettings() {
throw error; throw error;
} }
function showApplyChangesDialog() {
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}
async function handleDeleteVariable({ id }: PermissionVariable) { async function handleDeleteVariable({ id }: PermissionVariable) {
const updateConfigPromise = updateConfig({ const updateConfigPromise = updateConfig({
variables: { variables: {
@@ -79,6 +99,7 @@ export default function PermissionVariableSettings() {
await execPromiseWithErrorToast( await execPromiseWithErrorToast(
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
showApplyChangesDialog();
}, },
{ {
loadingMessage: 'Deleting permission variable...', loadingMessage: 'Deleting permission variable...',
@@ -92,7 +113,7 @@ export default function PermissionVariableSettings() {
function handleOpenCreator() { function handleOpenCreator() {
openDialog({ openDialog({
title: 'Create Permission Variable', title: 'Create Permission Variable',
component: <CreatePermissionVariableForm />, component: <CreatePermissionVariableForm onSubmit={refetch} />,
props: { props: {
titleProps: { className: '!pb-0' }, titleProps: { className: '!pb-0' },
PaperProps: { className: 'max-w-sm' }, PaperProps: { className: 'max-w-sm' },
@@ -104,7 +125,10 @@ export default function PermissionVariableSettings() {
openDialog({ openDialog({
title: 'Edit Permission Variable', title: 'Edit Permission Variable',
component: ( component: (
<EditPermissionVariableForm originalVariable={originalVariable} /> <EditPermissionVariableForm
originalVariable={originalVariable}
onSubmit={refetch}
/>
), ),
props: { props: {
titleProps: { className: '!pb-0' }, titleProps: { className: '!pb-0' },
@@ -138,12 +162,12 @@ export default function PermissionVariableSettings() {
<SettingsContainer <SettingsContainer
title="Permission Variables" title="Permission Variables"
description="Permission variables are used to define permission rules in the GraphQL API." description="Permission variables are used to define permission rules in the GraphQL API."
docsLink="https://docs.nhost.io/graphql/permissions" docsLink="https://docs.nhost.io/guides/api/permissions#permission-variables"
rootClassName="gap-0" rootClassName="gap-0"
className="my-2 px-0" className="px-0 my-2"
slotProps={{ submitButton: { className: 'hidden' } }} slotProps={{ submitButton: { className: 'hidden' } }}
> >
<Box className="grid grid-cols-2 border-b-1 px-4 py-3"> <Box className="grid grid-cols-2 px-4 py-3 border-b-1">
<Text className="font-medium">Field name</Text> <Text className="font-medium">Field name</Text>
<Text className="font-medium">Path</Text> <Text className="font-medium">Path</Text>
</Box> </Box>
@@ -167,7 +191,7 @@ export default function PermissionVariableSettings() {
!permissionVariable.isSystemVariable !permissionVariable.isSystemVariable
} }
hasDisabledChildren={permissionVariable.isSystemVariable} hasDisabledChildren={permissionVariable.isSystemVariable}
className="absolute right-4 top-1/2 -translate-y-1/2" className="absolute -translate-y-1/2 right-4 top-1/2"
> >
<Dropdown.Trigger asChild hideChevron> <Dropdown.Trigger asChild hideChevron>
<IconButton <IconButton
@@ -224,7 +248,7 @@ export default function PermissionVariableSettings() {
<> <>
X-Hasura-{permissionVariable.key}{' '} X-Hasura-{permissionVariable.key}{' '}
{permissionVariable.isSystemVariable && ( {permissionVariable.isSystemVariable && (
<LockIcon className="h-4 w-4" /> <LockIcon className="w-4 h-4" />
)} )}
</> </>
} }

View File

@@ -1,3 +1,4 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -7,6 +8,7 @@ import { Box } from '@/components/ui/v2/Box';
import { Divider } from '@/components/ui/v2/Divider'; import { Divider } from '@/components/ui/v2/Divider';
import { Link } from '@/components/ui/v2/Link'; import { Link } from '@/components/ui/v2/Link';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useProPlan } from '@/features/projects/common/hooks/useProPlan'; import { useProPlan } from '@/features/projects/common/hooks/useProPlan';
import { ResourcesConfirmationDialog } from '@/features/projects/resources/settings/components/ResourcesConfirmationDialog'; import { ResourcesConfirmationDialog } from '@/features/projects/resources/settings/components/ResourcesConfirmationDialog';
import { ServiceResourcesFormFragment } from '@/features/projects/resources/settings/components/ServiceResourcesFormFragment'; import { ServiceResourcesFormFragment } from '@/features/projects/resources/settings/components/ServiceResourcesFormFragment';
@@ -14,6 +16,7 @@ import { TotalResourcesFormFragment } from '@/features/projects/resources/settin
import { calculateBillableResources } from '@/features/projects/resources/settings/utils/calculateBillableResources'; import { calculateBillableResources } from '@/features/projects/resources/settings/utils/calculateBillableResources';
import type { ResourceSettingsFormValues } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema'; import type { ResourceSettingsFormValues } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import { resourceSettingsValidationSchema } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema'; import { resourceSettingsValidationSchema } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { import {
RESOURCE_VCPU_MULTIPLIER, RESOURCE_VCPU_MULTIPLIER,
RESOURCE_VCPU_PRICE, RESOURCE_VCPU_PRICE,
@@ -44,6 +47,8 @@ function getInitialServiceResources(
} }
export default function ResourcesForm() { export default function ResourcesForm() {
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { openDialog, closeDialog } = useDialog(); const { openDialog, closeDialog } = useDialog();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
@@ -55,6 +60,7 @@ export default function ResourcesForm() {
variables: { variables: {
appId: currentProject?.id, appId: currentProject?.id,
}, },
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { const {
@@ -65,6 +71,7 @@ export default function ResourcesForm() {
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetResourcesDocument], refetchQueries: [GetResourcesDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const initialDatabaseResources = getInitialServiceResources(data, 'postgres'); const initialDatabaseResources = getInitialServiceResources(data, 'postgres');
@@ -113,7 +120,7 @@ export default function ResourcesForm() {
resolver: yupResolver(resourceSettingsValidationSchema), resolver: yupResolver(resourceSettingsValidationSchema),
}); });
if (!proPlan && !proPlanLoading) { if (isPlatform && !proPlan && !proPlanLoading) {
return ( return (
<Alert severity="error"> <Alert severity="error">
Couldn&apos;t load the plan for this project. Please try again. Couldn&apos;t load the plan for this project. Please try again.
@@ -121,7 +128,7 @@ export default function ResourcesForm() {
); );
} }
if (loading || proPlanLoading) { if (isPlatform && (loading || proPlanLoading)) {
return ( return (
<ActivityIndicator <ActivityIndicator
label="Loading resource settings..." label="Loading resource settings..."
@@ -156,9 +163,10 @@ export default function ResourcesForm() {
}, },
); );
const initialPrice = const initialPrice = isPlatform
proPlan.price + ? proPlan.price +
(billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) * RESOURCE_VCPU_PRICE; (billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) * RESOURCE_VCPU_PRICE
: 0;
async function handleSubmit(formValues: ResourceSettingsFormValues) { async function handleSubmit(formValues: ResourceSettingsFormValues) {
const updateConfigPromise = updateConfig({ const updateConfigPromise = updateConfig({
@@ -217,6 +225,18 @@ export default function ResourcesForm() {
await execPromiseWithErrorToast( await execPromiseWithErrorToast(
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Updating resources...', loadingMessage: 'Updating resources...',
@@ -260,7 +280,12 @@ export default function ResourcesForm() {
} }
} }
function handleConfirm(formValues: ResourceSettingsFormValues) { async function handleConfirm(formValues: ResourceSettingsFormValues) {
if (!isPlatform) {
await handleSubmit(formValues);
return;
}
openDialog({ openDialog({
title: formValues.enabled title: formValues.enabled
? 'Confirm Dedicated Resources' ? 'Confirm Dedicated Resources'

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