Compare commits

...

48 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
291 changed files with 9741 additions and 5725 deletions

View File

@@ -1,5 +1,88 @@
# @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 ## 1.10.0
### Minor Changes ### Minor 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

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/dashboard", "name": "@nhost/dashboard",
"version": "1.10.0", "version": "1.13.3",
"private": true, "private": true,
"scripts": { "scripts": {
"preinstall": "npx only-allow pnpm", "preinstall": "npx only-allow pnpm",
@@ -19,34 +19,34 @@
"e2e": "pnpm install-browsers && pnpm playwright test" "e2e": "pnpm install-browsers && pnpm playwright test"
}, },
"dependencies": { "dependencies": {
"@apollo/client": "^3.9.5", "@apollo/client": "^3.9.9",
"@codemirror/lang-sql": "^6.6.0", "@codemirror/lang-sql": "^6.6.2",
"@emotion/cache": "^11.11.0", "@emotion/cache": "^11.11.0",
"@emotion/react": "^11.11.4", "@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.3", "@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.11", "@mui/material": "^5.15.14",
"@mui/system": "^5.15.11", "@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.5.1", "@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.13.2", "@tanstack/react-table": "^8.15.3",
"@tanstack/react-virtual": "^3.1.3", "@tanstack/react-virtual": "^3.2.0",
"@uiw/codemirror-theme-github": "^4.21.24", "@uiw/codemirror-theme-github": "^4.21.25",
"@uiw/react-codemirror": "^4.21.24", "@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",
@@ -57,10 +57,10 @@
"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.15.0", "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.5.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",
@@ -68,7 +68,7 @@
"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.13", "react-error-boundary": "^4.0.13",
"react-hook-form": "^7.50.1", "react-hook-form": "^7.51.2",
"react-hot-toast": "^2.4.1", "react-hot-toast": "^2.4.1",
"react-intersection-observer": "^9.8.1", "react-intersection-observer": "^9.8.1",
"react-is": "18.2.0", "react-is": "18.2.0",
@@ -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.24.0", "@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",
@@ -108,20 +108,20 @@
"@storybook/manager-webpack5": "^6.5.16", "@storybook/manager-webpack5": "^6.5.16",
"@storybook/react": "^7.6.17", "@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.1", "@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.12", "@types/jest": "^29.5.12",
"@types/lodash.debounce": "^4.0.9", "@types/lodash.debounce": "^4.0.9",
"@types/node": "^16.18.86", "@types/node": "^16.18.93",
"@types/pluralize": "^0.0.30", "@types/pluralize": "^0.0.30",
"@types/react": "^18.2.61", "@types/react": "^18.2.73",
"@types/react-dom": "^18.2.19", "@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.9", "@types/validator": "^13.11.9",
@@ -129,7 +129,7 @@
"@typescript-eslint/parser": "^6.21.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",
@@ -142,14 +142,14 @@
"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.2", "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.35", "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.1.4", "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

@@ -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

@@ -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,6 +22,7 @@ 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 { getToastStyleProps } from '@/utils/constants/settings';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
@@ -50,9 +53,13 @@ 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);
@@ -67,6 +74,7 @@ export default function AISettings() {
variables: { variables: {
appId: currentProject.id, appId: currentProject.id,
}, },
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data: graphiteVersionsData, loading: loadingGraphiteVersionsData } = const { data: graphiteVersionsData, loading: loadingGraphiteVersionsData } =
@@ -74,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 || [];
@@ -98,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: '',
@@ -225,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...',
@@ -247,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}
@@ -270,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">
@@ -327,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>
@@ -351,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>
@@ -389,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>
@@ -412,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>
@@ -440,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,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...',

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...',

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...',
@@ -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:

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...',

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...',

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...',
@@ -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...`,

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...',
@@ -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...',
@@ -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...',
@@ -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...',

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...',
@@ -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:

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...',

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...',

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...',
@@ -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...',
@@ -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...',

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...',
@@ -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

@@ -1,5 +1,6 @@
import { useDatabaseQuery } from '@/features/database/dataGrid/hooks/useDatabaseQuery'; 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';
@@ -17,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);
@@ -179,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',
@@ -189,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,

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

@@ -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' },
@@ -140,10 +164,10 @@ export default function PermissionVariableSettings() {
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/guides/api/permissions#permission-variables" 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'

View File

@@ -6,6 +6,7 @@ import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
import { Link } from '@/components/ui/v2/Link'; import { Link } from '@/components/ui/v2/Link';
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 { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useProPlan } from '@/features/projects/common/hooks/useProPlan'; import { useProPlan } from '@/features/projects/common/hooks/useProPlan';
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';
@@ -16,6 +17,8 @@ import {
import { useFormState, useWatch } from 'react-hook-form'; import { useFormState, useWatch } from 'react-hook-form';
export default function ResourcesFormFooter() { export default function ResourcesFormFooter() {
const isPlatform = useIsPlatform();
const { const {
data: proPlan, data: proPlan,
loading: proPlanLoading, loading: proPlanLoading,
@@ -63,17 +66,27 @@ export default function ResourcesFormFooter() {
}, },
); );
const updatedPrice = enabled const computeUpdatedPrice = () => {
? Math.max( if (!isPlatform) {
priceForTotalAvailableVCPU, return 0;
(billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) * }
RESOURCE_VCPU_PRICE,
) + proPlan.price if (enabled) {
: proPlan.price; return (
Math.max(
priceForTotalAvailableVCPU,
(billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) *
RESOURCE_VCPU_PRICE,
) + proPlan.price
);
}
return proPlan.price;
};
return ( return (
<Box <Box
className="grid items-center gap-4 border-t px-4 pt-4 lg:grid-flow-col lg:justify-between lg:gap-2" className="grid items-center gap-4 px-4 pt-4 border-t lg:grid-flow-col lg:justify-between lg:gap-2"
component="footer" component="footer"
> >
<Text> <Text>
@@ -86,20 +99,22 @@ export default function ResourcesFormFooter() {
className="font-medium" className="font-medium"
> >
Compute Resources Compute Resources
<ArrowSquareOutIcon className="ml-1 h-4 w-4" /> <ArrowSquareOutIcon className="w-4 h-4 ml-1" />
</Link> </Link>
</Text> </Text>
{(enabled || isDirty) && ( {(enabled || isDirty) && (
<Box className="grid grid-flow-col items-center justify-between gap-4"> <Box className="grid items-center justify-between grid-flow-col gap-4">
<Box className="grid grid-flow-col items-center gap-1.5"> <Box className="grid grid-flow-col items-center gap-1.5">
<Text> <Text>
Approximate cost:{' '} Approximate cost:{' '}
<span className="font-medium">${updatedPrice.toFixed(2)}/mo</span> <span className="font-medium">
${computeUpdatedPrice().toFixed(2)}/mo
</span>
</Text> </Text>
<Tooltip title="$0.0012/minute for every 1 vCPU and 2 GiB of RAM"> <Tooltip title="$0.0012/minute for every 1 vCPU and 2 GiB of RAM">
<InfoIcon aria-label="Info" className="h-4 w-4" color="primary" /> <InfoIcon aria-label="Info" className="w-4 h-4" color="primary" />
</Tooltip> </Tooltip>
</Box> </Box>

View File

@@ -3,6 +3,7 @@ import { Box } from '@/components/ui/v2/Box';
import { ArrowRightIcon } from '@/components/ui/v2/icons/ArrowRightIcon'; import { ArrowRightIcon } from '@/components/ui/v2/icons/ArrowRightIcon';
import { Slider, sliderClasses } from '@/components/ui/v2/Slider'; import { Slider, sliderClasses } from '@/components/ui/v2/Slider';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
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 { getAllocatedResources } from '@/features/projects/resources/settings/utils/getAllocatedResources'; import { getAllocatedResources } from '@/features/projects/resources/settings/utils/getAllocatedResources';
import { prettifyMemory } from '@/features/projects/resources/settings/utils/prettifyMemory'; import { prettifyMemory } from '@/features/projects/resources/settings/utils/prettifyMemory';
@@ -38,6 +39,8 @@ const StyledAvailableCpuSlider = styled(Slider)(({ theme }) => ({
export default function TotalResourcesFormFragment({ export default function TotalResourcesFormFragment({
initialPrice, initialPrice,
}: TotalResourcesFormFragmentProps) { }: TotalResourcesFormFragmentProps) {
const isPlatform = useIsPlatform();
const { const {
data: proPlan, data: proPlan,
error: proPlanError, error: proPlanError,
@@ -46,7 +49,7 @@ export default function TotalResourcesFormFragment({
const { setValue } = useFormContext<ResourceSettingsFormValues>(); const { setValue } = useFormContext<ResourceSettingsFormValues>();
const formValues = useWatch<ResourceSettingsFormValues>(); const formValues = useWatch<ResourceSettingsFormValues>();
if (!proPlan && !proPlanLoading) { if (isPlatform && !proPlan && !proPlanLoading) {
return ( return (
<Alert severity="error"> <Alert severity="error">
Couldn&apos;t load the plan for this projectee. Please try again. Couldn&apos;t load the plan for this projectee. Please try again.
@@ -62,7 +65,9 @@ export default function TotalResourcesFormFragment({
(formValues.totalAvailableVCPU / RESOURCE_VCPU_MULTIPLIER) * (formValues.totalAvailableVCPU / RESOURCE_VCPU_MULTIPLIER) *
RESOURCE_VCPU_PRICE; RESOURCE_VCPU_PRICE;
const updatedPrice = priceForTotalAvailableVCPU + proPlan.price; const updatedPrice = isPlatform
? priceForTotalAvailableVCPU + proPlan.price
: 0;
const { vcpu: allocatedVCPU, memory: allocatedMemory } = const { vcpu: allocatedVCPU, memory: allocatedMemory } =
getAllocatedResources(formValues); getAllocatedResources(formValues);
@@ -102,8 +107,8 @@ export default function TotalResourcesFormFragment({
return ( return (
<Box className="px-4 pb-4"> <Box className="px-4 pb-4">
<Box className="rounded-md border"> <Box className="border rounded-md">
<Box className="flex flex-col gap-4 bg-transparent p-4"> <Box className="flex flex-col gap-4 p-4 bg-transparent">
<Box className="flex flex-row items-center justify-between gap-4"> <Box className="flex flex-row items-center justify-between gap-4">
<Text color="secondary"> <Text color="secondary">
Total available compute for your project: Total available compute for your project:
@@ -151,7 +156,7 @@ export default function TotalResourcesFormFragment({
severity={ severity={
hasUnusedResources || hasOverallocatedResources ? 'warning' : 'info' hasUnusedResources || hasOverallocatedResources ? 'warning' : 'info'
} }
className="grid grid-flow-row gap-2 rounded-t-none rounded-b-[5px] text-left" className="grid grid-flow-row gap-2 rounded-b-[5px] rounded-t-none text-left"
> >
{hasUnusedResources && !hasOverallocatedResources && ( {hasUnusedResources && !hasOverallocatedResources && (
<> <>

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 {
BaseRoleFormProps, BaseRoleFormProps,
BaseRoleFormValues, BaseRoleFormValues,
@@ -9,6 +12,7 @@ import {
baseRoleFormValidationSchema, baseRoleFormValidationSchema,
} from '@/features/projects/roles/settings/components/BaseRoleForm'; } from '@/features/projects/roles/settings/components/BaseRoleForm';
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles'; import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
GetRolesPermissionsDocument, GetRolesPermissionsDocument,
@@ -23,17 +27,20 @@ export interface CreateRoleFormProps
/** /**
* 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 CreateRoleForm({ export default function CreateRoleForm({
onSubmit, onSubmit,
...props ...props
}: CreateRoleFormProps) { }: CreateRoleFormProps) {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { data, loading, error } = useGetRolesPermissionsQuery({ const { data, loading, error } = useGetRolesPermissionsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { allowed: allowedRoles } = data?.config?.auth?.user?.roles || {}; const { allowed: allowedRoles } = data?.config?.auth?.user?.roles || {};
@@ -45,6 +52,7 @@ export default function CreateRoleForm({
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetRolesPermissionsDocument], refetchQueries: [GetRolesPermissionsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (loading) { if (loading) {
@@ -85,7 +93,19 @@ export default function CreateRoleForm({
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 role...', loadingMessage: 'Creating role...',

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 {
BaseRoleFormProps, BaseRoleFormProps,
BaseRoleFormValues, BaseRoleFormValues,
@@ -9,6 +12,7 @@ import {
baseRoleFormValidationSchema, baseRoleFormValidationSchema,
} from '@/features/projects/roles/settings/components/BaseRoleForm'; } from '@/features/projects/roles/settings/components/BaseRoleForm';
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles'; import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import type { Role } from '@/types/application'; import type { Role } from '@/types/application';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
@@ -28,7 +32,7 @@ export interface EditRoleFormProps
/** /**
* 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 EditRoleForm({ export default function EditRoleForm({
@@ -36,10 +40,13 @@ export default function EditRoleForm({
onSubmit, onSubmit,
...props ...props
}: EditRoleFormProps) { }: EditRoleFormProps) {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { data, loading, error } = useGetRolesPermissionsQuery({ const { data, loading, error } = useGetRolesPermissionsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { allowed: allowedRoles, default: defaultRole } = const { allowed: allowedRoles, default: defaultRole } =
@@ -55,6 +62,7 @@ export default function EditRoleForm({
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetRolesPermissionsDocument], refetchQueries: [GetRolesPermissionsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (loading) { if (loading) {
@@ -114,7 +122,19 @@ export default function EditRoleForm({
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 role...', loadingMessage: 'Updating role...',

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 { 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 { CreateRoleForm } from '@/features/projects/roles/settings/components/CreateRoleForm'; import { CreateRoleForm } from '@/features/projects/roles/settings/components/CreateRoleForm';
import { EditRoleForm } from '@/features/projects/roles/settings/components/EditRoleForm'; import { EditRoleForm } from '@/features/projects/roles/settings/components/EditRoleForm';
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles'; import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import type { Role } from '@/types/application'; import type { Role } from '@/types/application';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
@@ -40,13 +43,15 @@ export interface RoleSettingsFormValues {
} }
export default function RoleSettings() { export default function RoleSettings() {
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 { allowed: allowedRoles, default: defaultRole } = const { allowed: allowedRoles, default: defaultRole } =
@@ -54,6 +59,7 @@ export default function RoleSettings() {
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetRolesPermissionsDocument], refetchQueries: [GetRolesPermissionsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (loading) { if (loading) {
@@ -64,6 +70,20 @@ export default function RoleSettings() {
throw error; throw error;
} }
async function showApplyChangesDialog() {
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}
async function handleSetAsDefault({ name }: Role) { async function handleSetAsDefault({ name }: Role) {
const updateConfigPromise = updateConfig({ const updateConfigPromise = updateConfig({
variables: { variables: {
@@ -84,6 +104,7 @@ export default function RoleSettings() {
await execPromiseWithErrorToast( await execPromiseWithErrorToast(
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
showApplyChangesDialog();
}, },
{ {
loadingMessage: 'Updating default role...', loadingMessage: 'Updating default role...',
@@ -114,6 +135,7 @@ export default function RoleSettings() {
await execPromiseWithErrorToast( await execPromiseWithErrorToast(
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
showApplyChangesDialog();
}, },
{ {
loadingMessage: 'Deleting allowed role...', loadingMessage: 'Deleting allowed role...',
@@ -127,7 +149,7 @@ export default function RoleSettings() {
function handleOpenCreator() { function handleOpenCreator() {
openDialog({ openDialog({
title: 'Create Allowed Role', title: 'Create Allowed Role',
component: <CreateRoleForm />, component: <CreateRoleForm onSubmit={refetch} />,
props: { props: {
titleProps: { className: '!pb-0' }, titleProps: { className: '!pb-0' },
PaperProps: { className: 'max-w-sm' }, PaperProps: { className: 'max-w-sm' },
@@ -138,7 +160,9 @@ export default function RoleSettings() {
function handleOpenEditor(originalRole: Role) { function handleOpenEditor(originalRole: Role) {
openDialog({ openDialog({
title: 'Edit Allowed Role', title: 'Edit Allowed Role',
component: <EditRoleForm originalRole={originalRole} />, component: (
<EditRoleForm originalRole={originalRole} onSubmit={refetch} />
),
props: { props: {
titleProps: { className: '!pb-0' }, titleProps: { className: '!pb-0' },
PaperProps: { className: 'max-w-sm' }, PaperProps: { className: 'max-w-sm' },
@@ -177,7 +201,7 @@ export default function RoleSettings() {
)} )}
slotProps={{ submitButton: { className: 'hidden' } }} slotProps={{ submitButton: { className: 'hidden' } }}
> >
<Box className="border-b-1 px-4 py-3"> <Box className="px-4 py-3 border-b-1">
<Text className="font-medium">Name</Text> <Text className="font-medium">Name</Text>
</Box> </Box>
@@ -193,7 +217,7 @@ export default function RoleSettings() {
<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"
@@ -252,7 +276,7 @@ export default function RoleSettings() {
<> <>
{role.name} {role.name}
{role.isSystemRole && <LockIcon className="h-4 w-4" />} {role.isSystemRole && <LockIcon className="w-4 h-4" />}
{defaultRole === role.name && ( {defaultRole === role.name && (
<Chip <Chip

View File

@@ -1,4 +1,7 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
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 {
BaseSecretFormProps, BaseSecretFormProps,
BaseSecretFormValues, BaseSecretFormValues,
@@ -7,6 +10,7 @@ import {
BaseSecretForm, BaseSecretForm,
baseSecretFormValidationSchema, baseSecretFormValidationSchema,
} from '@/features/projects/secrets/settings/components/BaseSecretForm'; } from '@/features/projects/secrets/settings/components/BaseSecretForm';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
GetSecretsDocument, GetSecretsDocument,
@@ -20,13 +24,17 @@ export interface CreateSecretFormProps
/** /**
* 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 CreateSecretForm({ export default function CreateSecretForm({
onSubmit, onSubmit,
...props ...props
}: CreateSecretFormProps) { }: CreateSecretFormProps) {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const form = useForm<BaseSecretFormValues>({ const form = useForm<BaseSecretFormValues>({
defaultValues: { defaultValues: {
name: '', name: '',
@@ -39,6 +47,7 @@ export default function CreateSecretForm({
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [insertSecret] = useInsertSecretMutation({ const [insertSecret] = useInsertSecretMutation({
refetchQueries: [GetSecretsDocument], refetchQueries: [GetSecretsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
async function handleSubmit({ name, value }: BaseSecretFormValues) { async function handleSubmit({ name, value }: BaseSecretFormValues) {
@@ -56,7 +65,19 @@ export default function CreateSecretForm({
await execPromiseWithErrorToast( await execPromiseWithErrorToast(
async () => { async () => {
await insertSecretPromise; await insertSecretPromise;
onSubmit?.(); await onSubmit?.();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Creating secret...', loadingMessage: 'Creating secret...',

View File

@@ -1,4 +1,5 @@
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 {
BaseSecretFormProps, BaseSecretFormProps,
BaseSecretFormValues, BaseSecretFormValues,
@@ -7,6 +8,7 @@ import {
BaseSecretForm, BaseSecretForm,
baseSecretFormValidationSchema, baseSecretFormValidationSchema,
} from '@/features/projects/secrets/settings/components/BaseSecretForm'; } from '@/features/projects/secrets/settings/components/BaseSecretForm';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import type { Secret } from '@/types/application'; import type { Secret } from '@/types/application';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
@@ -33,6 +35,9 @@ export default function EditSecretForm({
onSubmit, onSubmit,
...props ...props
}: EditSecretFormProps) { }: EditSecretFormProps) {
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const form = useForm<BaseSecretFormValues>({ const form = useForm<BaseSecretFormValues>({
defaultValues: { defaultValues: {
name: originalSecret.name, name: originalSecret.name,
@@ -45,6 +50,7 @@ export default function EditSecretForm({
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateSecret] = useUpdateSecretMutation({ const [updateSecret] = useUpdateSecretMutation({
refetchQueries: [GetSecretsDocument], refetchQueries: [GetSecretsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
async function handleSubmit({ name, value }: BaseSecretFormValues) { async function handleSubmit({ name, value }: BaseSecretFormValues) {

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 { Alert } from '@/components/ui/v2/Alert'; import { Alert } from '@/components/ui/v2/Alert';
@@ -12,6 +13,7 @@ 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 { useHostName } from '@/features/projects/common/hooks/useHostName'; import { useHostName } from '@/features/projects/common/hooks/useHostName';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { InfoCard } from '@/features/projects/overview/components/InfoCard'; import { InfoCard } from '@/features/projects/overview/components/InfoCard';
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';
@@ -25,6 +27,7 @@ import {
type ServiceFormProps, type ServiceFormProps,
type ServiceFormValues, type ServiceFormValues,
} from '@/features/services/components/ServiceForm/ServiceFormTypes'; } from '@/features/services/components/ServiceForm/ServiceFormTypes';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common'; import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
@@ -50,11 +53,15 @@ export default function ServiceForm({
location, location,
}: ServiceFormProps) { }: ServiceFormProps) {
const hostName = useHostName(); const hostName = useHostName();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { onDirtyStateChange, openDialog, closeDialog } = useDialog(); const { onDirtyStateChange, openDialog, closeDialog } = useDialog();
const [insertRunService] = useInsertRunServiceMutation(); const [insertRunService] = useInsertRunServiceMutation();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [insertRunServiceConfig] = useInsertRunServiceConfigMutation(); const [insertRunServiceConfig] = useInsertRunServiceConfigMutation();
const [replaceRunServiceConfig] = useReplaceRunServiceConfigMutation(); const [replaceRunServiceConfig] = useReplaceRunServiceConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const [detailsServiceId, setDetailsServiceId] = useState(''); const [detailsServiceId, setDetailsServiceId] = useState('');
const [detailsServiceSubdomain, setDetailsServiceSubdomain] = useState( const [detailsServiceSubdomain, setDetailsServiceSubdomain] = useState(
initialData?.subdomain, initialData?.subdomain,
@@ -145,6 +152,18 @@ export default function ServiceForm({
}); });
setDetailsServiceId(serviceID); setDetailsServiceId(serviceID);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
} else { } else {
// Insert service config // Insert service config
const { const {
@@ -197,7 +216,12 @@ export default function ServiceForm({
); );
}; };
const handleConfirm = (values: ServiceFormValues) => { const handleConfirm = async (values: ServiceFormValues) => {
if (!isPlatform) {
await handleSubmit(formValues);
return;
}
openDialog({ openDialog({
title: 'Confirm Resources', title: 'Confirm Resources',
component: ( component: (
@@ -213,26 +237,34 @@ export default function ServiceForm({
}; };
useEffect(() => { useEffect(() => {
(async () => { if (!isPlatform) {
if (detailsServiceId) { return;
openDialog({ }
title: 'Service Details',
component: ( if (detailsServiceId) {
<ServiceDetailsDialog openDialog({
serviceID={detailsServiceId} title: 'Service Details',
subdomain={detailsServiceSubdomain} component: (
ports={formValues.ports} <ServiceDetailsDialog
/> serviceID={detailsServiceId}
), subdomain={detailsServiceSubdomain}
props: { ports={formValues.ports}
PaperProps: { />
className: 'max-w-2xl', ),
}, props: {
PaperProps: {
className: 'max-w-2xl',
}, },
}); },
} });
})(); }
}, [detailsServiceId, detailsServiceSubdomain, formValues, openDialog]); }, [
detailsServiceId,
detailsServiceSubdomain,
formValues,
openDialog,
isPlatform,
]);
const pricingExplanation = () => { const pricingExplanation = () => {
const vCPUs = `${formValues.compute.cpu / RESOURCE_VCPU_MULTIPLIER} vCPUs`; const vCPUs = `${formValues.compute.cpu / RESOURCE_VCPU_MULTIPLIER} vCPUs`;
@@ -271,7 +303,7 @@ export default function ServiceForm({
<Tooltip title="Name of the service, must be unique per project."> <Tooltip title="Name of the service, must be unique per project.">
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
@@ -311,7 +343,7 @@ export default function ServiceForm({
> >
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
@@ -325,8 +357,8 @@ export default function ServiceForm({
autoComplete="off" autoComplete="off"
/> />
{/* This shows only when trying to edit a service */} {/* This shows only when trying to edit a service and when running against the nhost platform */}
{serviceID && serviceImage && ( {isPlatform && serviceID && serviceImage && (
<InfoCard <InfoCard
title="Private registry" title="Private registry"
value={`registry.${currentProject.region.awsName}.${currentProject.region.domain}/${serviceID}`} value={`registry.${currentProject.region.awsName}.${currentProject.region.domain}/${serviceID}`}
@@ -342,7 +374,7 @@ export default function ServiceForm({
<Tooltip title="Command to run when to start the service. This is optional as the image may already have a baked-in command."> <Tooltip title="Command to run when to start the service. This is optional as the image may already have a baked-in command.">
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
@@ -356,22 +388,24 @@ export default function ServiceForm({
autoComplete="off" autoComplete="off"
/> />
<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>{pricingExplanation()}</span> >
<b> <span>{pricingExplanation()}</span>
$ <b>
{parseFloat( $
( {parseFloat(
formValues.compute.cpu * (
formValues.replicas * formValues.compute.cpu *
COST_PER_VCPU formValues.replicas *
).toFixed(2), COST_PER_VCPU
)} ).toFixed(2),
</b> )}
</Alert> </b>
</Alert>
) : null}
<ComputeFormSection showTooltip /> <ComputeFormSection showTooltip />
@@ -388,7 +422,7 @@ export default function ServiceForm({
{createServiceFormError && ( {createServiceFormError && (
<Alert <Alert
severity="error" severity="error"
className="grid grid-flow-col items-center justify-between px-4 py-3" className="grid items-center justify-between grid-flow-col px-4 py-3"
> >
<span className="text-left"> <span className="text-left">
<strong>Error:</strong> {createServiceFormError.message} <strong>Error:</strong> {createServiceFormError.message}

View File

@@ -47,7 +47,8 @@ export const validationSchema = Yup.object({
initialDelaySeconds: Yup.number().required(), initialDelaySeconds: Yup.number().required(),
probePeriodSeconds: Yup.number().required(), probePeriodSeconds: Yup.number().required(),
}) })
.nullable(), .nullable()
.default(undefined),
}); });
export type ServiceFormValues = Yup.InferType<typeof validationSchema>; export type ServiceFormValues = Yup.InferType<typeof validationSchema>;

View File

@@ -11,11 +11,12 @@ import { UserIcon } from '@/components/ui/v2/icons/UserIcon';
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 { DeleteServiceModal } from '@/features/projects/common/components/DeleteServiceModal'; import { DeleteServiceModal } from '@/features/projects/common/components/DeleteServiceModal';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { ServiceForm } from '@/features/services/components/ServiceForm'; import { ServiceForm } from '@/features/services/components/ServiceForm';
import { type PortTypes } from '@/features/services/components/ServiceForm/components/PortsFormSection/PortsFormSectionTypes'; import { type PortTypes } from '@/features/services/components/ServiceForm/components/PortsFormSection/PortsFormSectionTypes';
import { type RunService } from '@/hooks/useRunServices';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { formatDistanceToNow } from 'date-fns'; import { formatDistanceToNow } from 'date-fns';
import type { RunService } from 'pages/[workspaceSlug]/[appSlug]/services';
interface ServicesListProps { interface ServicesListProps {
/** /**
@@ -42,19 +43,20 @@ export default function ServicesList({
onCreateOrUpdate, onCreateOrUpdate,
onDelete, onDelete,
}: ServicesListProps) { }: ServicesListProps) {
const isPlatform = useIsPlatform();
const { openDrawer, openDialog, closeDialog } = useDialog(); const { openDrawer, openDialog, closeDialog } = useDialog();
const viewService = async (service: RunService) => { const viewService = async (service: RunService) => {
openDrawer({ openDrawer({
title: ( title: (
<Box className="flex flex-row items-center space-x-2"> <Box className="flex flex-row items-center space-x-2">
<CubeIcon className="h-5 w-5" /> <CubeIcon className="w-5 h-5" />
<Text>Edit {service.config?.name ?? 'unset'}</Text> <Text>Edit {service.config?.name ?? 'unset'}</Text>
</Box> </Box>
), ),
component: ( component: (
<ServiceForm <ServiceForm
serviceID={service.id} serviceID={service.id ?? service.serviceID}
initialData={{ initialData={{
...service.config, ...service.config,
image: service.config?.image?.image, image: service.config?.image?.image,
@@ -94,50 +96,52 @@ export default function ServicesList({
<Box className="flex flex-col"> <Box className="flex flex-col">
{services.map((service) => ( {services.map((service) => (
<Box <Box
key={service.id} key={service.id ?? service.serviceID}
className="flex h-[64px] w-full cursor-pointer items-center justify-between space-x-4 border-b-1 px-4 py-2 transition-colors" className="flex h-[64px] w-full cursor-pointer items-center justify-between space-x-4 border-b-1 px-4 py-2 transition-colors"
sx={{ sx={{
[`&:hover`]: { [`&:hover`]: {
backgroundColor: 'action.hover', backgroundColor: 'action.hover',
}, },
}} }}
onClick={() => viewService(service)}
> >
<Box <Box
onClick={() => viewService(service)} className="flex flex-row justify-between w-full"
className="flex w-full flex-row justify-between"
sx={{ sx={{
backgroundColor: 'transparent', backgroundColor: 'transparent',
}} }}
> >
<div className="flex flex-1 flex-row items-center space-x-4"> <div className="flex flex-row items-center flex-1 space-x-4">
<CubeIcon className="h-5 w-5" /> <CubeIcon className="w-5 h-5" />
<div className="flex flex-col"> <div className="flex flex-col">
<Text variant="h4" className="font-semibold"> <Text variant="h4" className="font-semibold">
{service.config?.name ?? 'unset'} {service.config?.name ?? 'unset'}
</Text> </Text>
<Tooltip title={service.updatedAt}> {isPlatform ? (
<span className="hidden cursor-pointer text-sm text-slate-500 xs+:flex"> <Tooltip title={service.updatedAt}>
Deployed {formatDistanceToNow(new Date(service.updatedAt))}{' '} <span className="hidden cursor-pointer text-sm text-slate-500 xs+:flex">
ago Deployed{' '}
</span> {formatDistanceToNow(new Date(service.updatedAt))} ago
</Tooltip> </span>
</Tooltip>
) : null}
</div> </div>
</div> </div>
<div className="hidden flex-row items-center space-x-2 md:flex"> <div className="flex-row items-center hidden space-x-2 md:flex">
<Text variant="subtitle1" className="font-mono text-xs"> <Text variant="subtitle1" className="font-mono text-xs">
{service.id} {service.id ?? service.serviceID}
</Text> </Text>
<IconButton <IconButton
variant="borderless" variant="borderless"
color="secondary" color="secondary"
onClick={(event) => { onClick={(event) => {
copy(service.id, 'Service Id'); copy(service.id ?? service.serviceID, 'Service Id');
event.stopPropagation(); event.stopPropagation();
}} }}
aria-label="Service Id" aria-label="Service Id"
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</div> </div>
</Box> </Box>
@@ -167,7 +171,7 @@ export default function ServicesList({
onClick={() => viewService(service)} onClick={() => viewService(service)}
className="z-50 grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium" className="z-50 grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
> >
<UserIcon className="h-4 w-4" /> <UserIcon className="w-4 h-4" />
<Text className="font-medium">View Service</Text> <Text className="font-medium">View Service</Text>
</Dropdown.Item> </Dropdown.Item>
<Divider component="li" /> <Divider component="li" />
@@ -175,8 +179,9 @@ export default function ServicesList({
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium" className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
sx={{ color: 'error.main' }} sx={{ color: 'error.main' }}
onClick={() => deleteService(service)} onClick={() => deleteService(service)}
disabled={!isPlatform}
> >
<TrashIcon className="h-4 w-4" /> <TrashIcon className="w-4 h-4" />
<Text className="font-medium" color="error"> <Text className="font-medium" color="error">
Delete Service Delete Service
</Text> </Text>

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 {
GetStorageSettingsDocument, GetStorageSettingsDocument,
Software_Type_Enum, Software_Type_Enum,
@@ -11,8 +14,10 @@ import {
useGetStorageSettingsQuery, useGetStorageSettingsQuery,
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,21 +35,26 @@ export type StorageServiceVersionFormValues = Yup.InferType<
>; >;
export default function StorageServiceVersionSettings() { export default function StorageServiceVersionSettings() {
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: [GetStorageSettingsDocument], refetchQueries: [GetStorageSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetStorageSettingsQuery({ const { data, loading, error } = useGetStorageSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data: storageVersionsData } = useGetSoftwareVersionsQuery({ const { data: storageVersionsData } = useGetSoftwareVersionsQuery({
variables: { variables: {
software: Software_Type_Enum.Storage, software: Software_Type_Enum.Storage,
}, },
skip: !isPlatform,
}); });
const { version } = data?.config?.storage || {}; const { version } = data?.config?.storage || {};
@@ -52,6 +62,7 @@ export default function StorageServiceVersionSettings() {
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) => ({
@@ -61,10 +72,21 @@ export default function StorageServiceVersionSettings() {
const form = useForm<StorageServiceVersionFormValues>({ const form = useForm<StorageServiceVersionFormValues>({
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
defaultValues: { version: { label: version, value: version } }, defaultValues: { version: { label: '', value: '' } },
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
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 StorageServiceVersionSettings() {
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: 'Storage version is being updated...', loadingMessage: 'Storage version is being updated...',
@@ -123,12 +157,20 @@ export default function StorageServiceVersionSettings() {
}} }}
docsLink="https://github.com/nhost/hasura-storage/releases" docsLink="https://github.com/nhost/hasura-storage/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,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,
useGetStorageSettingsQuery, useGetStorageSettingsQuery,
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 HasuraStorageAVFormValues = Yup.InferType<typeof validationSchema>; export type HasuraStorageAVFormValues = Yup.InferType<typeof validationSchema>;
export default function HasuraStorageAVSettings() { export default function HasuraStorageAVSettings() {
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 } = useGetStorageSettingsQuery({ const { data, loading, error } = useGetStorageSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-first', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { server } = data?.config?.storage?.antivirus || {}; const { server } = data?.config?.storage?.antivirus || {};
@@ -42,6 +51,14 @@ export default function HasuraStorageAVSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
enabled: !!server,
});
}
}, [loading, server, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -81,6 +98,18 @@ export default function HasuraStorageAVSettings() {
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: 'Antivirus settings are being updated...', loadingMessage: 'Antivirus settings are being updated...',

View File

@@ -1,4 +1,4 @@
fragment EnvironmentVariable on ConfigEnvironmentVariable { fragment EnvironmentVariable on ConfigGlobalEnvironmentVariable {
id: name id: name
name name
value value

View File

@@ -12,6 +12,7 @@ query GetSmtpSettings($appId: uuid!) {
secure secure
sender sender
user user
password
} }
} }
} }

View File

@@ -2,6 +2,7 @@ query getGraphiteAutoEmbeddingsConfigurations($limit: Int!, $offset: Int!) {
graphiteAutoEmbeddingsConfigurations(limit: $limit, offset: $offset) { graphiteAutoEmbeddingsConfigurations(limit: $limit, offset: $offset) {
id id
name name
model
schemaName schemaName
tableName tableName
columnName columnName

View File

@@ -1,5 +1,6 @@
mutation insertGraphiteAutoEmbeddingsConfiguration( mutation insertGraphiteAutoEmbeddingsConfiguration(
$name: String $name: String
$model: String
$schemaName: String $schemaName: String
$tableName: String $tableName: String
$columnName: String $columnName: String
@@ -9,6 +10,7 @@ mutation insertGraphiteAutoEmbeddingsConfiguration(
insertGraphiteAutoEmbeddingsConfiguration( insertGraphiteAutoEmbeddingsConfiguration(
object: { object: {
name: $name name: $name
model: $model
schemaName: $schemaName schemaName: $schemaName
tableName: $tableName tableName: $tableName
columnName: $columnName columnName: $columnName

View File

@@ -1,6 +1,7 @@
mutation updateGraphiteAutoEmbeddingsConfiguration( mutation updateGraphiteAutoEmbeddingsConfiguration(
$id: uuid! $id: uuid!
$name: String $name: String
$model: String
$schemaName: String $schemaName: String
$tableName: String $tableName: String
$columnName: String $columnName: String
@@ -11,6 +12,7 @@ mutation updateGraphiteAutoEmbeddingsConfiguration(
pk_columns: { id: $id } pk_columns: { id: $id }
_set: { _set: {
name: $name name: $name
model: $model
schemaName: $schemaName schemaName: $schemaName
tableName: $tableName tableName: $tableName
columnName: $columnName columnName: $columnName
@@ -20,6 +22,7 @@ mutation updateGraphiteAutoEmbeddingsConfiguration(
) { ) {
id id
name name
model
schemaName schemaName
tableName tableName
columnName columnName

View File

@@ -1,3 +1,40 @@
fragment RunServiceConfig on ConfigRunServiceConfig {
name
image {
image
}
command
resources {
compute {
cpu
memory
}
storage {
name
path
capacity
}
replicas
}
environment {
name
value
}
ports {
port
type
publish
ingresses {
fqdn
}
}
healthCheck {
port
initialDelaySeconds
probePeriodSeconds
}
}
query getRunServices( query getRunServices(
$appID: uuid! $appID: uuid!
$resolve: Boolean! $resolve: Boolean!
@@ -11,40 +48,7 @@ query getRunServices(
updatedAt updatedAt
subdomain subdomain
config(resolve: $resolve) { config(resolve: $resolve) {
name ...RunServiceConfig
image {
image
}
command
resources {
compute {
cpu
memory
}
storage {
name
path
capacity
}
replicas
}
environment {
name
value
}
ports {
port
type
publish
ingresses {
fqdn
}
}
healthCheck {
port
initialDelaySeconds
probePeriodSeconds
}
} }
} }
runServices_aggregate { runServices_aggregate {
@@ -54,3 +58,12 @@ query getRunServices(
} }
} }
} }
query getLocalRunServiceConfigs($appID: uuid!, $resolve: Boolean!) {
runServiceConfigs(appID: $appID, resolve: $resolve) {
serviceID
config {
...RunServiceConfig
}
}
}

View File

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

View File

@@ -0,0 +1,20 @@
import { getConfigServerUrl } from '@/utils/env';
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
import { useMemo } from 'react';
/**
* It creates a new Apollo Client instance that connects to the local mimir when running the a local nhost project
* @returns A function that returns a new ApolloClient instance.
*/
export default function useLocalMimirClient() {
return useMemo(
() =>
new ApolloClient({
cache: new InMemoryCache(),
link: new HttpLink({
uri: getConfigServerUrl(),
}),
}),
[],
);
}

View File

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

View File

@@ -0,0 +1,109 @@
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import {
useGetLocalRunServiceConfigsQuery,
useGetRunServicesQuery,
type GetRunServicesQuery,
} from '@/utils/__generated__/graphql';
import { useRouter } from 'next/router';
import { useEffect, useMemo, useRef, useState } from 'react';
export type RunService = Pick<
GetRunServicesQuery['app']['runServices'][0],
'config'
> & {
id?: string;
serviceID?: string;
createdAt?: string;
updatedAt?: string;
subdomain?: string;
};
export type RunServiceConfig = Omit<
GetRunServicesQuery['app']['runServices'][0]['config'],
'__typename'
>;
export default function useRunServices() {
const limit = useRef(25);
const router = useRouter();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [nrOfPages, setNrOfPages] = useState(0);
const [totalServicesCount, setTotalServicesCount] = useState(0);
const [currentPage, setCurrentPage] = useState(
parseInt(router.query.page as string, 10) || 1,
);
const offset = useMemo(() => currentPage - 1, [currentPage]);
const {
data,
loading: loadingPlatformServices,
refetch: refetchPlatformServices,
} = useGetRunServicesQuery({
variables: {
appID: currentProject.id,
resolve: false,
limit: limit.current,
offset,
},
skip: !isPlatform,
});
const {
loading: loadingLocalServices,
data: localServicesData,
refetch: refetchLocalServices,
} = useGetLocalRunServiceConfigsQuery({
variables: { appID: currentProject.id as any, resolve: false },
skip: isPlatform,
client: localMimirClient,
});
const platformServices = useMemo(
() => data?.app?.runServices.map((service) => service) ?? [],
[data],
);
const localServices = useMemo(
() => localServicesData?.runServiceConfigs.map((service) => service) ?? [],
[localServicesData],
);
const services: RunService[] = isPlatform ? platformServices : localServices;
const loading = isPlatform ? loadingPlatformServices : loadingLocalServices;
const refetch = isPlatform ? refetchPlatformServices : refetchLocalServices;
useEffect(() => {
if (!isPlatform) {
return;
}
if (loading) {
return;
}
const userCount = data?.app?.runServices_aggregate.aggregate.count ?? 0;
setTotalServicesCount(
data?.app?.runServices_aggregate.aggregate.count ?? 0,
);
setNrOfPages(Math.ceil(userCount / limit.current));
}, [data, loading, isPlatform]);
return {
services,
loading,
refetch,
limit,
totalServicesCount,
nrOfPages,
currentPage,
setCurrentPage,
};
}

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