Compare commits

...

29 Commits

Author SHA1 Message Date
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
252 changed files with 8372 additions and 5144 deletions

View File

@@ -1,5 +1,55 @@
# @nhost/dashboard
## 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

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_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_CONFIGSERVER_URL __NEXT_PUBLIC_NHOST_CONFIGSERVER_URL__
RUN yarn global add pnpm@8.10.5
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_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_CONFIGSERVER_URL__~${NEXT_PUBLIC_NHOST_CONFIGSERVER_URL}~g" {} +
exec "$@"

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/dashboard",
"version": "1.11.2",
"version": "1.13.2",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
@@ -19,34 +19,34 @@
"e2e": "pnpm install-browsers && pnpm playwright test"
},
"dependencies": {
"@apollo/client": "^3.9.5",
"@codemirror/lang-sql": "^6.6.0",
"@apollo/client": "^3.9.9",
"@codemirror/lang-sql": "^6.6.2",
"@emotion/cache": "^11.11.0",
"@emotion/react": "^11.11.4",
"@emotion/server": "^11.11.0",
"@emotion/styled": "^11.11.0",
"@fontsource/inter": "^5.0.16",
"@fontsource/roboto-mono": "^5.0.16",
"@emotion/styled": "^11.11.5",
"@fontsource/inter": "^5.0.17",
"@fontsource/roboto-mono": "^5.0.17",
"@graphiql/react": "^0.20.3",
"@graphiql/toolkit": "^0.9.1",
"@headlessui/react": "^1.7.18",
"@heroicons/react": "^1.0.6",
"@hookform/resolvers": "^3.3.4",
"@mui/base": "5.0.0-beta.31",
"@mui/material": "^5.15.11",
"@mui/system": "^5.15.11",
"@mui/material": "^5.15.14",
"@mui/system": "^5.15.14",
"@mui/x-date-pickers": "^5.0.20",
"@nhost/nextjs": "workspace:*",
"@nhost/react-apollo": "workspace:*",
"@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",
"@tailwindcss/forms": "^0.5.7",
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-table": "^8.13.2",
"@tanstack/react-virtual": "^3.1.3",
"@uiw/codemirror-theme-github": "^4.21.24",
"@uiw/react-codemirror": "^4.21.24",
"@tanstack/react-table": "^8.15.3",
"@tanstack/react-virtual": "^3.2.0",
"@uiw/codemirror-theme-github": "^4.21.25",
"@uiw/react-codemirror": "^4.21.25",
"analytics-node": "^6.2.0",
"bcryptjs": "^2.4.3",
"clsx": "^1.2.1",
@@ -57,10 +57,10 @@
"graphql": "16.8.1",
"graphql-request": "^6.1.0",
"graphql-tag": "^2.12.6",
"graphql-ws": "^5.15.0",
"graphql-ws": "^5.16.0",
"just-kebab-case": "^4.2.0",
"lodash.debounce": "^4.0.8",
"next": "^14.1.0",
"next": "^14.1.4",
"next-seo": "^6.5.0",
"node-pg-format": "^1.3.5",
"pluralize": "^8.0.0",
@@ -68,7 +68,7 @@
"react-children-utilities": "^2.10.0",
"react-dom": "18.2.0",
"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-intersection-observer": "^9.8.1",
"react-is": "18.2.0",
@@ -87,11 +87,11 @@
"tailwind-merge": "^1.14.0",
"utility-types": "^3.11.0",
"validator": "^13.11.0",
"yup": "^1.3.3",
"yup": "^1.4.0",
"yup-password": "^0.2.2"
},
"devDependencies": {
"@babel/core": "^7.24.0",
"@babel/core": "^7.24.3",
"@faker-js/faker": "^7.6.0",
"@graphql-codegen/cli": "^5.0.2",
"@graphql-codegen/typescript": "^3.0.4",
@@ -108,20 +108,20 @@
"@storybook/manager-webpack5": "^6.5.16",
"@storybook/react": "^7.6.17",
"@storybook/testing-library": "^0.2.2",
"@tailwindcss/typography": "^0.5.10",
"@tailwindcss/typography": "^0.5.12",
"@testing-library/dom": "^9.3.4",
"@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",
"@types/ace": "^0.0.48",
"@types/bcryptjs": "^2.4.6",
"@types/jest": "^29.5.12",
"@types/lodash.debounce": "^4.0.9",
"@types/node": "^16.18.86",
"@types/node": "^16.18.93",
"@types/pluralize": "^0.0.30",
"@types/react": "^18.2.61",
"@types/react-dom": "^18.2.19",
"@types/react-table": "^7.7.19",
"@types/react": "^18.2.73",
"@types/react-dom": "^18.2.23",
"@types/react-table": "^7.7.20",
"@types/shell-quote": "^1.7.5",
"@types/testing-library__jest-dom": "^5.14.9",
"@types/validator": "^13.11.9",
@@ -129,7 +129,7 @@
"@typescript-eslint/parser": "^6.21.0",
"@vitejs/plugin-react": "^4.2.1",
"@vitest/coverage-v8": "^0.32.4",
"autoprefixer": "^10.4.17",
"autoprefixer": "^10.4.19",
"babel-loader": "^8.3.0",
"babel-plugin-transform-remove-console": "^6.9.4",
"csstype": "^3.1.3",
@@ -142,14 +142,14 @@
"eslint-config-prettier": "^8.10.0",
"eslint-plugin-import": "^2.29.1",
"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",
"jsdom": "^22.1.0",
"lint-staged": "^15.2.2",
"msw": "^1.3.2",
"msw": "^1.3.3",
"msw-storybook-addon": "^1.10.0",
"node-fetch": "^3.3.2",
"postcss": "^8.4.35",
"postcss": "^8.4.38",
"prettier": "^2.8.8",
"prettier-plugin-organize-imports": "^3.2.4",
"prettier-plugin-tailwindcss": "^0.4.1",
@@ -157,11 +157,11 @@
"require-from-string": "^2.0.2",
"snake-case": "^3.0.4",
"storybook-addon-next-router": "^4.0.2",
"tailwindcss": "^3.4.1",
"tailwindcss": "^3.4.3",
"ts-node": "^10.9.2",
"tsconfig-paths-webpack-plugin": "^4.1.0",
"vite": "^5.1.4",
"vite-tsconfig-paths": "^4.3.1",
"vite": "^5.2.7",
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^0.32.4"
},
"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 { NextSeo } from 'next-seo';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
import { twMerge } from 'tailwind-merge';
export interface ProjectLayoutProps extends AuthenticatedLayoutProps {
@@ -48,15 +47,16 @@ function ProjectLayoutContent({
useNotFoundRedirect();
useEffect(() => {
if (isPlatform || !router.isReady) {
return;
}
// useEffect(() => {
// if (isPlatform || !router.isReady) {
// return;
// }
if (isRestrictedPath) {
router.push('/local/local');
}
}, [isPlatform, isRestrictedPath, router]);
// TODO // Double check what restricted path means here
// if (isRestrictedPath) {
// router.push('/local/local');
// }
// }, [isPlatform, isRestrictedPath, router]);
if (isRestrictedPath || loading) {
return <LoadingScreen />;

View File

@@ -7,6 +7,7 @@ import { List } from '@/components/ui/v2/List';
import type { ListItemButtonProps } from '@/components/ui/v2/ListItem';
import { ListItem } from '@/components/ui/v2/ListItem';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import Image from 'next/image';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
@@ -61,6 +62,7 @@ export default function SettingsSidebar({
className,
...props
}: SettingsSidebarProps) {
const isPlatform = useIsPlatform();
const [expanded, setExpanded] = useState(false);
const { currentProject } = useCurrentWorkspaceAndProject();
@@ -95,7 +97,7 @@ export default function SettingsSidebar({
<>
<Backdrop
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"
tabIndex={-1}
onClick={() => setExpanded(false)}
@@ -112,7 +114,7 @@ export default function SettingsSidebar({
<Box
component="aside"
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',
className,
)}
@@ -181,7 +183,12 @@ export default function SettingsSidebar({
SMTP
</SettingsNavLink>
<SettingsNavLink href="/git" exact={false} onClick={handleSelect}>
<SettingsNavLink
href="/git"
exact={false}
onClick={handleSelect}
disabled={!isPlatform}
>
Git
</SettingsNavLink>

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

@@ -1,4 +1,5 @@
import { useDialog } from '@/components/common/DialogProvider';
import { ControlledSelect } from '@/components/form/ControlledSelect';
import { Form } from '@/components/form/Form';
import { Box } from '@/components/ui/v2/Box';
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 { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import { Input } from '@/components/ui/v2/Input';
import { Option } from '@/components/ui/v2/Option';
import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip';
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
@@ -20,11 +22,18 @@ import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
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({
name: Yup.string().required('The name is required.'),
schemaName: Yup.string().required('The schema is required'),
tableName: Yup.string().required('The table is required'),
columnName: Yup.string().required('The column is required'),
name: Yup.string().required('The name field is required.'),
model: Yup.string().oneOf(AUTO_EMBEDDINGS_MODELS),
schemaName: Yup.string().required('The schema field is required'),
tableName: Yup.string().required('The table field is required'),
columnName: Yup.string().required('The column field is required'),
query: Yup.string(),
mutation: Yup.string(),
});
@@ -40,7 +49,7 @@ export interface AutoEmbeddingsFormProps extends DialogFormProps {
/**
* 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.
@@ -74,7 +83,10 @@ export default function AutoEmbeddingsForm({
});
const form = useForm<AutoEmbeddingsFormValues>({
defaultValues: initialData,
defaultValues: {
...initialData,
model: initialData?.model ?? 'text-embedding-ada-002',
},
reValidateMode: 'onSubmit',
resolver: yupResolver(validationSchema),
});
@@ -106,7 +118,9 @@ export default function AutoEmbeddingsForm({
}
await insertGraphiteAutoEmbeddingsConfiguration({
variables: values,
variables: {
...values,
},
});
};
@@ -129,9 +143,9 @@ export default function AutoEmbeddingsForm({
<FormProvider {...form}>
<Form
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
{...register('name')}
id="name"
@@ -141,7 +155,7 @@ export default function AutoEmbeddingsForm({
<Tooltip title="Name of the Auto-Embeddings">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -155,6 +169,36 @@ export default function AutoEmbeddingsForm({
autoComplete="off"
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
{...register('schemaName')}
id="schemaName"
@@ -164,7 +208,7 @@ export default function AutoEmbeddingsForm({
<Tooltip title={<span>Schema where the table belongs to</span>}>
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -186,7 +230,7 @@ export default function AutoEmbeddingsForm({
<Tooltip title="Table Name">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -208,7 +252,7 @@ export default function AutoEmbeddingsForm({
<Tooltip title="Column name">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -230,7 +274,7 @@ export default function AutoEmbeddingsForm({
<Tooltip title="Query">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -254,7 +298,7 @@ export default function AutoEmbeddingsForm({
<Tooltip title="Mutation">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -271,7 +315,7 @@ export default function AutoEmbeddingsForm({
/>
</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}>
Cancel
</Button>

View File

@@ -1,3 +1,4 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
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 { Tooltip } from '@/components/ui/v2/Tooltip';
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 { ComputeFormSection } from '@/features/services/components/ServiceForm/components/ComputeFormSection';
import {
@@ -20,6 +22,7 @@ import {
useGetSoftwareVersionsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
import { getToastStyleProps } from '@/utils/constants/settings';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
@@ -50,9 +53,13 @@ const validationSchema = Yup.object({
export type AISettingsFormValues = Yup.InferType<typeof validationSchema>;
export default function AISettings() {
const { maintenanceActive } = useUI();
const isPlatform = useIsPlatform();
const { openDialog } = useDialog();
const [updateConfig] = useUpdateConfigMutation();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { currentProject } = useCurrentWorkspaceAndProject();
const [aiServiceEnabled, setAIServiceEnabled] = useState(true);
@@ -67,6 +74,7 @@ export default function AISettings() {
variables: {
appId: currentProject.id,
},
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data: graphiteVersionsData, loading: loadingGraphiteVersionsData } =
@@ -74,6 +82,7 @@ export default function AISettings() {
variables: {
software: Software_Type_Enum.Graphite,
},
skip: !isPlatform,
});
const graphiteVersions = graphiteVersionsData?.softwareVersions || [];
@@ -98,8 +107,8 @@ export default function AISettings() {
reValidateMode: 'onSubmit',
defaultValues: {
version: {
label: ai?.version ?? availableVersions?.at(0)?.label,
value: ai?.version ?? availableVersions?.at(0)?.value,
label: ai?.version || availableVersions?.at(0)?.label || '',
value: ai?.version || availableVersions?.at(0)?.value || '',
},
webhookSecret: '',
organization: '',
@@ -225,6 +234,18 @@ export default function AISettings() {
});
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'AI settings are being updated...',
@@ -247,7 +268,7 @@ export default function AISettings() {
return (
<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>
<Switch
checked={aiServiceEnabled}
@@ -270,54 +291,58 @@ export default function AISettings() {
className="flex flex-col"
>
<Box className="space-y-4">
{availableVersions.length > 0 && (
<Box className="space-y-2">
<Box className="flex flex-row items-center space-x-2">
<Text className="text-lg font-semibold">Version</Text>
<Tooltip title="Version of the service to use.">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
color="primary"
/>
</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 className="space-y-2">
<Box className="flex flex-row items-center space-x-2">
<Text className="text-lg font-semibold">Version</Text>
<Tooltip title="Version of the service to use.">
<InfoIcon
aria-label="Info"
className="w-4 h-4"
color="primary"
/>
</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 ||
!!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="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.">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -351,26 +376,28 @@ export default function AISettings() {
<Tooltip title="Dedicated resources allocated for the service.">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
</Box>
<Alert
severity="info"
className="flex items-center justify-between space-x-2"
>
<span>{getAIResourcesCost()}</span>
<b>
$
{parseFloat(
(
aiSettingsFormValues.compute.cpu * COST_PER_VCPU
).toFixed(2),
)}
</b>
</Alert>
{isPlatform ? (
<Alert
severity="info"
className="flex items-center justify-between space-x-2"
>
<span>{getAIResourcesCost()}</span>
<b>
$
{parseFloat(
(
aiSettingsFormValues.compute.cpu * COST_PER_VCPU
).toFixed(2),
)}
</b>
</Alert>
) : null}
<ComputeFormSection />
</Box>
@@ -389,7 +416,7 @@ export default function AISettings() {
<Tooltip title="Key to use for authenticating API requests to OpenAI">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -412,7 +439,7 @@ export default function AISettings() {
<Tooltip title="Optional. OpenAI organization to use.">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -440,7 +467,7 @@ export default function AISettings() {
<Tooltip title="How often to run the job that keeps embeddings up to date.">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>

View File

@@ -1,8 +1,11 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { Text } from '@/components/ui/v2/Text';
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 { useUpdateConfigMutation } from '@/utils/__generated__/graphql';
import { useState } from 'react';
@@ -23,11 +26,14 @@ export default function DisableAIServiceConfirmationDialog({
onCancel,
onServiceDisabled,
}: DisableAIServiceConfirmationDialogProps) {
const { closeDialog } = useDialog();
const isPlatform = useIsPlatform();
const { openDialog, closeDialog } = useDialog();
const localMimirClient = useLocalMimirClient();
const [loading, setLoading] = useState(false);
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation();
const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
async function handleClick() {
setLoading(true);
@@ -45,6 +51,18 @@ export default function DisableAIServiceConfirmationDialog({
onServiceDisabled();
closeDialog();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup';
@@ -26,15 +31,19 @@ export type AllowedEmailSettingsFormValues = Yup.InferType<
>;
export default function AllowedEmailDomainsSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { email, emailDomains } = data?.config?.auth?.user || {};
@@ -54,6 +63,17 @@ export default function AllowedEmailDomainsSettings() {
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) {
return (
<ActivityIndicator
@@ -105,6 +125,18 @@ export default function AllowedEmailDomainsSettings() {
async () => {
await updateConfigPromise;
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...',

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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -23,15 +28,19 @@ export type AllowedRedirectURLFormValues = Yup.InferType<
>;
export default function AllowedRedirectURLsSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { allowedUrls } = data?.config?.auth?.redirections || {};
@@ -44,6 +53,14 @@ export default function AllowedRedirectURLsSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading && allowedUrls) {
form.reset({
allowedUrls: allowedUrls?.join(', ') || '',
});
}
}, [loading, allowedUrls, form]);
if (loading) {
return (
<ActivityIndicator
@@ -82,6 +99,18 @@ export default function AllowedRedirectURLsSettings() {
async () => {
await updateConfigPromise;
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...',

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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -20,15 +25,19 @@ const validationSchema = Yup.object({
export type AnonymousSignInFormValues = Yup.InferType<typeof validationSchema>;
export default function AnonymousSignInSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { enabled } = data?.config?.auth?.method?.anonymous || {};
@@ -41,6 +50,12 @@ export default function AnonymousSignInSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({ enabled });
}
}, [loading, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -75,6 +90,18 @@ export default function AnonymousSignInSettings() {
async () => {
await updateConfigPromise;
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...',

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 { Form } from '@/components/form/Form';
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 { InputAdornment } from '@/components/ui/v2/InputAdornment';
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 {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useTheme } from '@mui/material';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup';
@@ -53,15 +58,19 @@ export type AppleProviderFormValues = Yup.InferType<typeof validationSchema>;
export default function AppleProviderSettings() {
const theme = useTheme();
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { clientId, enabled, keyId, privateKey, teamId } =
@@ -79,6 +88,18 @@ export default function AppleProviderSettings() {
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) {
return (
<ActivityIndicator
@@ -119,6 +140,18 @@ export default function AppleProviderSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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>
</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 { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetAuthenticationSettingsDocument,
Software_Type_Enum,
@@ -11,8 +14,10 @@ import {
useGetSoftwareVersionsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -28,21 +33,26 @@ export type AuthServiceVersionFormValues = Yup.InferType<
>;
export default function AuthServiceVersionSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data: authVersionsData } = useGetSoftwareVersionsQuery({
variables: {
software: Software_Type_Enum.Auth,
},
skip: !isPlatform,
});
const { version } = data?.config?.auth || {};
@@ -59,10 +69,21 @@ export default function AuthServiceVersionSettings() {
const form = useForm<AuthServiceVersionFormValues>({
reValidateMode: 'onSubmit',
defaultValues: { version: { label: version, value: version } },
defaultValues: { version: { label: '', value: '' } },
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading && version) {
form.reset({
version: {
label: version,
value: version,
},
});
}
}, [loading, version, form]);
if (loading) {
return (
<ActivityIndicator
@@ -97,6 +118,18 @@ export default function AuthServiceVersionSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Auth version is being updated...',
@@ -120,12 +153,20 @@ export default function AuthServiceVersionSettings() {
}}
docsLink="https://github.com/nhost/hasura-auth/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
id="version"
name="version"
autoHighlight
freeSolo
getOptionLabel={(option) => {
if (typeof option === 'string') {
return option || '';
}
return option.value;
}}
isOptionEqualToValue={() => false}
filterOptions={(options, { inputValue }) => {
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 { Form } from '@/components/form/Form';
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 { BaseProviderSettings } from '@/features/authentication/settings/components/BaseProviderSettings';
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 {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup';
@@ -46,15 +51,19 @@ const validationSchema = Yup.object({
export type AzureADProviderFormValues = Yup.InferType<typeof validationSchema>;
export default function AzureADProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { clientId, clientSecret, tenant, enabled } =
@@ -71,6 +80,17 @@ export default function AzureADProviderSettings() {
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) {
return (
<ActivityIndicator
@@ -108,6 +128,18 @@ export default function AzureADProviderSettings() {
async () => {
await updateConfigPromise;
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...',
@@ -182,7 +214,7 @@ export default function AzureADProviderSettings() {
);
}}
>
<CopyIcon className="h-4 w-4" />
<CopyIcon className="w-4 h-4" />
</IconButton>
</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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup';
@@ -24,15 +29,19 @@ const validationSchema = Yup.object({
export type BlockedEmailFormValues = Yup.InferType<typeof validationSchema>;
export default function BlockedEmailSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { email, emailDomains } = data?.config?.auth?.user || {};
@@ -51,6 +60,17 @@ export default function BlockedEmailSettings() {
const enabled = watch('enabled');
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) {
return (
<ActivityIndicator
@@ -112,6 +132,18 @@ export default function BlockedEmailSettings() {
async () => {
await updateConfigPromise;
form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -21,15 +26,19 @@ const validationSchema = Yup.object({
export type ClientURLFormValues = Yup.InferType<typeof validationSchema>;
export default function ClientURLSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { clientUrl, allowedUrls } = data?.config?.auth?.redirections || {};
@@ -42,6 +51,12 @@ export default function ClientURLSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading && clientUrl) {
form.reset({ clientUrl });
}
}, [loading, clientUrl, form]);
if (loading) {
return (
<ActivityIndicator
@@ -77,6 +92,18 @@ export default function ClientURLSettings() {
async () => {
await updateConfigPromise;
form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
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 {
GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery,
useUpdateConfigMutation,
} from '@/utils/__generated__/graphql';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -19,15 +24,19 @@ const validationSchema = Yup.object({
export type DisableNewUsersFormValues = Yup.InferType<typeof validationSchema>;
export default function DisableNewUsersSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
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) {
return (
<ActivityIndicator
@@ -73,6 +90,18 @@ export default function DisableNewUsersSettings() {
async () => {
await updateConfigPromise;
form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,28 +14,35 @@ import {
baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings';
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 {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
export default function DiscordProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { clientId, clientSecret, enabled } =
@@ -49,6 +58,16 @@ export default function DiscordProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -89,6 +108,18 @@ export default function DiscordProviderSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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>
</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 { ControlledCheckbox } from '@/components/form/ControlledCheckbox';
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 { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -29,15 +34,19 @@ const validationSchema = Yup.object({
export type EmailAndPasswordFormValues = Yup.InferType<typeof validationSchema>;
export default function EmailAndPasswordSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, error, loading } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { hibpEnabled, emailVerificationRequired, passwordMinLength } =
@@ -53,6 +62,22 @@ export default function EmailAndPasswordSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
hibpEnabled,
emailVerificationRequired,
passwordMinLength,
});
}
}, [
loading,
hibpEnabled,
emailVerificationRequired,
passwordMinLength,
form,
]);
if (loading) {
return (
<ActivityIndicator
@@ -87,6 +112,18 @@ export default function EmailAndPasswordSettings() {
async () => {
await updateConfigPromise;
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...`,

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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,28 +14,35 @@ import {
baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings';
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 {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
export default function FacebookProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { clientId, clientSecret, enabled } =
@@ -49,6 +58,16 @@ export default function FacebookProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -89,6 +108,18 @@ export default function FacebookProviderSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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>
</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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,30 +14,37 @@ import {
baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings';
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 {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useTheme } from '@mui/material';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
export default function GitHubProviderSettings() {
const theme = useTheme();
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { clientId, clientSecret, enabled } =
@@ -51,6 +60,16 @@ export default function GitHubProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -91,6 +110,18 @@ export default function GitHubProviderSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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>
</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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,28 +14,35 @@ import {
baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings';
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 {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
export default function GoogleProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { clientId, clientSecret, enabled } =
@@ -49,6 +58,16 @@ export default function GoogleProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -89,6 +108,18 @@ export default function GoogleProviderSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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>
</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 { ControlledSelect } from '@/components/form/ControlledSelect';
import { Form } from '@/components/form/Form';
@@ -5,17 +7,20 @@ import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Option } from '@/components/ui/v2/Option';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import {
AUTH_GRAVATAR_DEFAULT,
AUTH_GRAVATAR_RATING,
} from '@/utils/constants/settings';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup';
@@ -29,15 +34,19 @@ const validationSchema = Yup.object({
export type GravatarFormValues = Yup.InferType<typeof validationSchema>;
export default function GravatarSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const {
@@ -56,6 +65,16 @@ export default function GravatarSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
default: defaultGravatar || '',
rating: rating || '',
enabled: enabled || false,
});
}
}, [loading, defaultGravatar, rating, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -91,6 +110,18 @@ export default function GravatarSettings() {
async () => {
await updateConfigPromise;
form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,28 +14,35 @@ import {
baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings';
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 {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
export default function LinkedInProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { clientId, clientSecret, enabled } =
@@ -49,6 +58,16 @@ export default function LinkedInProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -89,6 +108,18 @@ export default function LinkedInProviderSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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>
</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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup';
@@ -23,15 +28,19 @@ const validationSchema = Yup.object({
export type MFASettingsFormValues = Yup.InferType<typeof validationSchema>;
export default function MFASettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { enabled, issuer } = data?.config?.auth?.totp || {};
@@ -45,6 +54,15 @@ export default function MFASettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading && issuer && enabled) {
form.reset({
issuer,
enabled,
});
}
}, [loading, issuer, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -78,6 +96,18 @@ export default function MFASettings() {
async () => {
await updateConfigPromise;
form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -20,15 +25,19 @@ const validationSchema = Yup.object({
export type MagicLinkFormValues = Yup.InferType<typeof validationSchema>;
export default function MagicLinkSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { enabled } = data?.config?.auth?.method?.emailPasswordless || {};
@@ -41,6 +50,12 @@ export default function MagicLinkSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({ enabled });
}
}, [loading, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -75,6 +90,18 @@ export default function MagicLinkSettings() {
async () => {
await updateConfigPromise;
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...',

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 { Form } from '@/components/form/Form';
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 { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import Image from 'next/image';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup';
@@ -44,15 +49,19 @@ const validationSchema = Yup.object({
export type SMSSettingsFormValues = Yup.InferType<typeof validationSchema>;
export default function SMSSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, error, loading } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { accountSid, authToken, messagingServiceId } =
@@ -70,6 +79,17 @@ export default function SMSSettings() {
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) {
return (
<ActivityIndicator
@@ -114,6 +134,18 @@ export default function SMSSettings() {
async () => {
await updateConfigPromise;
form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -28,15 +33,19 @@ const validationSchema = Yup.object({
export type SessionFormValues = Yup.InferType<typeof validationSchema>;
export default function SessionSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { accessToken, refreshToken } = data?.config?.auth?.session || {};
@@ -50,6 +59,15 @@ export default function SessionSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading && accessToken && refreshToken) {
form.reset({
accessTokenExpiresIn: accessToken?.expiresIn || 900,
refreshTokenExpiresIn: refreshToken?.expiresIn || 43200,
});
}
}, [loading, accessToken, refreshToken, form]);
if (loading) {
return (
<ActivityIndicator
@@ -86,6 +104,18 @@ export default function SessionSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,28 +14,35 @@ import {
baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings';
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 {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
export default function SpotifyProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { clientId, clientSecret, enabled } =
@@ -49,6 +58,16 @@ export default function SpotifyProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -89,6 +108,18 @@ export default function SpotifyProviderSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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>
</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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,30 +14,37 @@ import {
baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings';
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 {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useTheme } from '@mui/material';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
export default function TwitchProviderSettings() {
const theme = useTheme();
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { clientId, clientSecret, enabled } =
@@ -51,6 +60,16 @@ export default function TwitchProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -91,6 +110,18 @@ export default function TwitchProviderSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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>
</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 { Form } from '@/components/form/Form';
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 { InputAdornment } from '@/components/ui/v2/InputAdornment';
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 {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup';
@@ -39,15 +44,19 @@ const validationSchema = Yup.object({
export type TwitterProviderFormValues = Yup.InferType<typeof validationSchema>;
export default function TwitterProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { consumerKey, consumerSecret, enabled } =
@@ -63,6 +72,16 @@ export default function TwitterProviderSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
consumerSecret: consumerSecret || '',
consumerKey: consumerKey || '',
enabled: enabled || false,
});
}
}, [loading, consumerKey, consumerSecret, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -100,6 +119,18 @@ export default function TwitterProviderSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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>
</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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -20,15 +25,19 @@ const validationSchema = Yup.object({
export type WebAuthnFormValues = Yup.InferType<typeof validationSchema>;
export default function WebAuthnSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { enabled } = data?.config?.auth?.method?.webauthn || {};
@@ -41,6 +50,12 @@ export default function WebAuthnSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({ enabled });
}
}, [loading, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -80,6 +95,18 @@ export default function WebAuthnSettings() {
async () => {
await updateConfigPromise;
form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,28 +14,35 @@ import {
baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings';
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 {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
export default function WindowsLiveProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { clientId, clientSecret, enabled } =
@@ -49,6 +58,16 @@ export default function WindowsLiveProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) {
return (
<ActivityIndicator
@@ -89,6 +108,18 @@ export default function WindowsLiveProviderSettings() {
async () => {
await updateConfigPromise;
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...',
@@ -151,7 +182,7 @@ export default function WindowsLiveProviderSettings() {
);
}}
>
<CopyIcon className="h-4 w-4" />
<CopyIcon className="w-4 h-4" />
</IconButton>
</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 { Form } from '@/components/form/Form';
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 { BaseProviderSettings } from '@/features/authentication/settings/components/BaseProviderSettings';
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 {
GetSignInMethodsDocument,
useGetSignInMethodsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup';
@@ -52,15 +57,19 @@ const validationSchema = Yup.object({
export type WorkOsProviderFormValues = Yup.InferType<typeof validationSchema>;
export default function WorkOsProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { clientId, clientSecret, organization, connection, enabled } =
@@ -78,6 +87,26 @@ export default function WorkOsProviderSettings() {
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) {
return (
<ActivityIndicator
@@ -115,6 +144,18 @@ export default function WorkOsProviderSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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>
</InputAdornment>
}

View File

@@ -1,5 +1,6 @@
import { useDatabaseQuery } from '@/features/database/dataGrid/hooks/useDatabaseQuery';
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 { getToastStyleProps } from '@/utils/constants/settings';
import { getHasuraAdminSecret } from '@/utils/env';
@@ -17,6 +18,7 @@ export default function useRunSQL(
migrationName: string,
) {
const { currentProject } = useCurrentWorkspaceAndProject();
const isPlatform = useIsPlatform();
const [loading, setLoading] = 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 entities = parseIdentifiersFromSQL(inputSQL);
if (entities.length === 0) {
return;
}
const tablesOrViewEntities = entities.filter(
(entity) => entity.type !== 'function',
@@ -189,47 +217,75 @@ export default function useRunSQL(
(entity) => entity.type === 'function',
);
const trackTablesOrViews = tablesOrViewEntities.map(({ name, schema }) => ({
type: 'pg_track_table',
args: {
source: 'default',
table: {
name,
schema,
let trackTablesOrViews: any[] = [];
let trackFunctions: any[] = [];
if (isPlatform) {
// use v2/query
trackTablesOrViews = tablesOrViewEntities.map(({ name, schema }) => ({
type: 'pg_track_table',
args: {
source: 'default',
table: {
name,
schema,
},
},
},
}));
const trackFunctions = functionEntities.map(({ name, schema }) => ({
type: 'pg_track_function',
args: {
source: 'default',
function: {
name,
schema,
configuration: {},
}));
trackFunctions = functionEntities.map(({ name, schema }) => ({
type: 'pg_track_function',
args: {
source: 'default',
function: {
name,
schema,
configuration: {},
},
},
},
}));
const metaDataPayload = {
source: 'default',
type: 'bulk',
args: [...trackTablesOrViews, ...trackFunctions],
};
}));
} else {
// use apis/migrate
trackTablesOrViews = tablesOrViewEntities.map(({ name, schema }) => ({
name: `add_existing_table_or_view_${schema}_${name}`,
datasource: 'default',
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 {
if (entities.length > 0) {
const metadataApiResponse = await fetch(`${appUrl}/v1/metadata`, {
method: 'POST',
headers: { 'x-hasura-admin-secret': adminSecret },
body: JSON.stringify(metaDataPayload),
});
if (!metadataApiResponse.ok) {
throw new Error('Metadata API call failed');
}
}
await trackAll([...trackTablesOrViews, ...trackFunctions]).then(
(responses) => {
responses.forEach((response) => {
if (!response.ok) {
console.error('Error tracking table or view:', response);
}
});
},
);
} catch (error) {
toast.error('An error happened when calling the metadata API', {
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 { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetPostgresSettingsDocument,
Software_Type_Enum,
@@ -11,8 +14,10 @@ import {
useGetSoftwareVersionsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -30,24 +35,30 @@ export type DatabaseServiceVersionFormValues = Yup.InferType<
>;
export default function DatabaseServiceVersionSettings() {
const isPlatform = useIsPlatform();
const { openDialog } = useDialog();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetPostgresSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetPostgresSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data: databaseVersionsData } = useGetSoftwareVersionsQuery({
variables: {
software: Software_Type_Enum.PostgreSql,
},
skip: !isPlatform,
});
const { version } = data?.config?.postgres || {};
const databaseVersions = databaseVersionsData?.softwareVersions || [];
const availableVersions = Array.from(
new Set(databaseVersions.map((el) => el.version)).add(version),
@@ -61,10 +72,21 @@ export default function DatabaseServiceVersionSettings() {
const form = useForm<DatabaseServiceVersionFormValues>({
reValidateMode: 'onSubmit',
defaultValues: { version: { label: version, value: version } },
defaultValues: { version: { label: '', value: '' } },
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading && version) {
form.reset({
version: {
label: version,
value: version,
},
});
}
}, [loading, version, form]);
if (loading) {
return (
<ActivityIndicator
@@ -99,6 +121,18 @@ export default function DatabaseServiceVersionSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Postgres version is being updated...',
@@ -123,12 +157,20 @@ export default function DatabaseServiceVersionSettings() {
}}
docsLink="https://hub.docker.com/r/nhost/postgres/tags"
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
id="version"
name="version"
autoHighlight
freeSolo
getOptionLabel={(option) => {
if (typeof option === 'string') {
return option || '';
}
return option.value;
}}
isOptionEqualToValue={() => false}
filterOptions={(options, { inputValue }) => {
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 { UpgradeNotification } from '@/features/projects/common/components/UpgradeNotification';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
useGetPostgresSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
@@ -18,13 +20,15 @@ import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
const validationSchema = Yup.object({
capacity: Yup.number().required(),
capacity: Yup.number().required().min(10),
});
export type AuthDomainFormValues = Yup.InferType<typeof validationSchema>;
export default function AuthDomain() {
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const {
@@ -34,14 +38,17 @@ export default function AuthDomain() {
refetch: refetchPostgresSettings,
} = useGetPostgresSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const capacity =
data?.config?.postgres?.resources?.storage?.capacity ??
currentProject.plan.featureMaxDbSize;
(data?.config?.postgres?.resources?.storage?.capacity ??
currentProject?.plan?.featureMaxDbSize) ||
0;
const [updateConfig] = useUpdateConfigMutation();
const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const form = useForm<Yup.InferType<typeof validationSchema>>({
reValidateMode: 'onSubmit',
@@ -117,7 +124,7 @@ export default function AuthDomain() {
}}
className="flex flex-col"
>
{currentProject.plan.isFree && (
{currentProject.plan?.isFree && (
<UpgradeNotification message="Unlock by upgrading your project to the Pro plan." />
)}
<Box className="grid grid-flow-row lg:grid-cols-5">
@@ -127,7 +134,7 @@ export default function AuthDomain() {
name="capacity"
type="number"
fullWidth
disabled={currentProject.plan.isFree}
disabled={currentProject.plan?.isFree}
className="lg:col-span-2"
error={Boolean(formState.errors.capacity?.message)}
helperText={formState.errors.capacity?.message}
@@ -138,7 +145,7 @@ export default function AuthDomain() {
}}
/>
</Box>
{!currentProject.plan.isFree && (
{!currentProject.plan?.isFree && (
<Alert severity="info" className="col-span-6 text-left">
Note that volumes can only be increased (not decreased). Also, due
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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetHasuraSettingsDocument,
useGetHasuraSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -20,16 +25,20 @@ const validationSchema = Yup.object({
export type HasuraAllowListFormValues = Yup.InferType<typeof validationSchema>;
export default function HasuraAllowListSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-first',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { enableAllowList } = data?.config?.hasura.settings || {};
@@ -42,6 +51,14 @@ export default function HasuraAllowListSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
enabled: enableAllowList,
});
}
}, [loading, enableAllowList, form]);
if (loading) {
return (
<ActivityIndicator
@@ -75,6 +92,18 @@ export default function HasuraAllowListSettings() {
await updateConfigPromise;
form.reset(formValues);
await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetHasuraSettingsDocument,
useGetHasuraSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -20,16 +25,20 @@ const validationSchema = Yup.object({
export type HasuraConsoleFormValues = Yup.InferType<typeof validationSchema>;
export default function HasuraConsoleSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-first',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { enableConsole } = data?.config?.hasura.settings || {};
@@ -42,6 +51,14 @@ export default function HasuraConsoleSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
enabled: enableConsole,
});
}
}, [loading, enableConsole, form]);
if (loading) {
return (
<ActivityIndicator
@@ -75,6 +92,18 @@ export default function HasuraConsoleSettings() {
await updateConfigPromise;
form.reset(formValues);
await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetHasuraSettingsDocument,
useGetHasuraSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { FormProvider, useForm } from 'react-hook-form';
@@ -28,16 +32,20 @@ const validationSchema = Yup.object({
export type HasuraCorsDomainFormValues = Yup.InferType<typeof validationSchema>;
export default function HasuraCorsDomainSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-first',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { corsDomain } = data?.config?.hasura.settings || {};
@@ -98,6 +106,18 @@ export default function HasuraCorsDomainSettings() {
await updateConfigPromise;
form.reset(formValues);
await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetHasuraSettingsDocument,
useGetHasuraSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -20,16 +25,20 @@ const validationSchema = Yup.object({
export type HasuraDevModeFormValues = Yup.InferType<typeof validationSchema>;
export default function HasuraDevModeSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-first',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { devMode } = data?.config?.hasura.settings || {};
@@ -42,6 +51,14 @@ export default function HasuraDevModeSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
enabled: devMode,
});
}
}, [loading, devMode, form]);
if (loading) {
return (
<ActivityIndicator
@@ -63,7 +80,7 @@ export default function HasuraDevModeSettings() {
config: {
hasura: {
settings: {
enableConsole: formValues.enabled,
devMode: formValues.enabled,
},
},
},
@@ -75,6 +92,18 @@ export default function HasuraDevModeSettings() {
await updateConfigPromise;
form.reset(formValues);
await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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 { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetHasuraSettingsDocument,
useGetHasuraSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -30,31 +35,40 @@ export type HasuraEnabledAPIFormValues = Yup.InferType<typeof validationSchema>;
const AVAILABLE_HASURA_APIS = ['metadata', 'graphql', 'pgdump', 'config'];
export default function HasuraEnabledAPISettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetHasuraSettingsQuery({
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>({
reValidateMode: 'onSubmit',
defaultValues: {
enabledAPIs: enabledAPIs.map((api) => ({
label: api,
value: api,
})),
enabledAPIs: [],
},
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (enabledAPIs && !loading) {
form.reset({
enabledAPIs: enabledAPIs.map((api) => ({ label: api, value: api })),
});
}
}, [form, enabledAPIs, loading]);
if (loading) {
return (
<ActivityIndicator
@@ -96,6 +110,18 @@ export default function HasuraEnabledAPISettings() {
await updateConfigPromise;
form.reset(formValues);
await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Enabled APIs are being updated...',
@@ -117,7 +143,7 @@ export default function HasuraEnabledAPISettings() {
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
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 { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
import { Form } from '@/components/form/Form';
@@ -5,13 +7,16 @@ import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { HighlightedText } from '@/components/presentational/HighlightedText';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetHasuraSettingsDocument,
useGetHasuraSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
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'];
export default function HasuraLogLevelSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { maintenanceActive } = useUI();
const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-first',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { level } = data?.config?.hasura.logs || {};
@@ -56,6 +65,17 @@ export default function HasuraLogLevelSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading && level) {
form.reset({
logLevel: {
label: level,
value: level,
},
});
}
}, [form, loading, level]);
if (loading) {
return (
<ActivityIndicator
@@ -97,6 +117,18 @@ export default function HasuraLogLevelSettings() {
await updateConfigPromise;
form.reset(formValues);
await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Log level is being updated...',
@@ -128,7 +160,7 @@ export default function HasuraLogLevelSettings() {
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
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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetHasuraSettingsDocument,
useGetHasuraSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { FormProvider, useForm } from 'react-hook-form';
@@ -26,16 +30,20 @@ const validationSchema = Yup.object({
export type HasuraPoolSizeFormValues = Yup.InferType<typeof validationSchema>;
export default function HasuraPoolSizeSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-first',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { httpPoolSize } = data?.config?.hasura.events || {};
@@ -82,6 +90,18 @@ export default function HasuraPoolSizeSettings() {
await updateConfigPromise;
form.reset(formValues);
await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Pool size is being updated...',
@@ -104,7 +124,7 @@ export default function HasuraPoolSizeSettings() {
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
{...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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetHasuraSettingsDocument,
useGetHasuraSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -22,16 +27,20 @@ export type HasuraRemoteSchemaPermissionsFormValues = Yup.InferType<
>;
export default function HasuraRemoteSchemaPermissionsSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-first',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { enableRemoteSchemaPermissions } = data?.config?.hasura.settings || {};
@@ -44,6 +53,14 @@ export default function HasuraRemoteSchemaPermissionsSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
enabled: enableRemoteSchemaPermissions,
});
}
}, [loading, enableRemoteSchemaPermissions, form]);
if (loading) {
return (
<ActivityIndicator
@@ -67,7 +84,7 @@ export default function HasuraRemoteSchemaPermissionsSettings() {
config: {
hasura: {
settings: {
enableConsole: formValues.enabled,
enableRemoteSchemaPermissions: formValues.enabled,
},
},
},
@@ -79,6 +96,18 @@ export default function HasuraRemoteSchemaPermissionsSettings() {
await updateConfigPromise;
form.reset(formValues);
await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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 { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetHasuraSettingsDocument,
Software_Type_Enum,
@@ -11,8 +14,10 @@ import {
useGetSoftwareVersionsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -30,22 +35,27 @@ export type HasuraServiceVersionFormValues = Yup.InferType<
>;
export default function HasuraServiceVersionSettings() {
const isPlatform = useIsPlatform();
const { openDialog } = useDialog();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data: hasuraVersionsData } = useGetSoftwareVersionsQuery({
variables: {
software: Software_Type_Enum.Hasura,
},
skip: !isPlatform,
});
const { version } = data?.config?.hasura || {};
@@ -53,6 +63,7 @@ export default function HasuraServiceVersionSettings() {
const availableVersions = Array.from(
new Set(versions.map((el) => el.version)).add(version),
)
.filter((v) => !!v)
.sort()
.reverse()
.map((availableVersion) => ({
@@ -60,12 +71,25 @@ export default function HasuraServiceVersionSettings() {
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>({
reValidateMode: 'onSubmit',
defaultValues: { version: { label: version, value: version } },
defaultValues: { version: { label: '', value: '' } },
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading && version) {
form.reset({
version: {
label: version,
value: version,
},
});
}
}, [loading, version, form]);
if (loading) {
return (
<ActivityIndicator
@@ -99,6 +123,18 @@ export default function HasuraServiceVersionSettings() {
await updateConfigPromise;
form.reset(formValues);
await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Hasura version is being updated...',
@@ -123,11 +159,19 @@ export default function HasuraServiceVersionSettings() {
}}
docsLink="https://hub.docker.com/r/nhost/graphql-engine/tags"
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
id="version"
name="version"
freeSolo
getOptionLabel={(option) => {
if (typeof option === 'string') {
return option || '';
}
return option.value;
}}
autoHighlight
isOptionEqualToValue={() => false}
filterOptions={(options, { inputValue }) => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -90,14 +90,6 @@ export default function useProjectRoutes() {
icon: <GaugeIcon />,
disabled: !isPlatform,
},
{
relativeMainPath: '/settings',
relativePath: '/settings/general',
exact: false,
label: 'Settings',
icon: <CogIcon />,
disabled: !isPlatform || maintenanceActive,
},
];
const allRoutes: ProjectRoute[] = [
@@ -143,7 +135,6 @@ export default function useProjectRoutes() {
exact: false,
label: 'Run',
icon: <ServicesIcon />,
disabled: !isPlatform,
},
{
relativeMainPath: '/ai',
@@ -153,6 +144,14 @@ export default function useProjectRoutes() {
icon: <AIIcon />,
},
...nhostRoutes,
{
relativeMainPath: '/settings',
relativePath: '/settings/general',
exact: false,
label: 'Settings',
icon: <CogIcon />,
disabled: maintenanceActive,
},
];
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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input';
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 {
useGetAuthenticationSettingsQuery,
useUpdateConfigMutation,
type ConfigIngressUpdateInput,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect, useState } from 'react';
@@ -23,12 +27,18 @@ const validationSchema = Yup.object({
export type AuthDomainFormValues = Yup.InferType<typeof validationSchema>;
export default function AuthDomain() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const [isVerified, setIsVerified] = useState(false);
const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation();
const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const form = useForm<Yup.InferType<typeof validationSchema>>({
reValidateMode: 'onSubmit',
@@ -40,6 +50,7 @@ export default function AuthDomain() {
variables: {
appId: currentProject.id,
},
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { networking } = data?.config?.auth?.resources || {};
@@ -94,6 +105,18 @@ export default function AuthDomain() {
await updateConfigPromise;
form.reset(formValues);
await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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 (
<FormProvider {...form}>
<Form onSubmit={handleSubmit}>
@@ -112,12 +143,11 @@ export default function AuthDomain() {
description="Enter below your custom domain for the authentication service."
slotProps={{
submitButton: {
disabled:
!isDirty || maintenanceActive || (!isVerified && !initialValue),
disabled: isDisabled(),
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
{...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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input';
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 {
useGetHasuraSettingsQuery,
useUpdateConfigMutation,
type ConfigIngressUpdateInput,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect, useState } from 'react';
@@ -23,12 +27,18 @@ const validationSchema = Yup.object({
export type HasuraDomainFormValues = Yup.InferType<typeof validationSchema>;
export default function HasuraDomain() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const [isVerified, setIsVerified] = useState(false);
const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation();
const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const form = useForm<Yup.InferType<typeof validationSchema>>({
reValidateMode: 'onSubmit',
@@ -40,6 +50,7 @@ export default function HasuraDomain() {
variables: {
appId: currentProject.id,
},
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { networking } = data?.config?.hasura?.resources || {};
@@ -96,6 +107,18 @@ export default function HasuraDomain() {
await updateConfigPromise;
form.reset(formValues);
await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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 (
<FormProvider {...form}>
<Form onSubmit={handleSubmit}>
@@ -114,12 +145,11 @@ export default function HasuraDomain() {
description="Enter below your custom domain for the Hasura/GraphQL service."
slotProps={{
submitButton: {
disabled:
!isDirty || maintenanceActive || (!isVerified && !initialValue),
disabled: isDisabled(),
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
{...register('hasura_fqdn')}

View File

@@ -5,29 +5,12 @@ import { Link } from '@/components/ui/v2/Link';
import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { RunServicePortDomain } from '@/features/projects/custom-domains/settings/components/RunServicePortDomain';
import { useGetRunServicesQuery } from '@/utils/__generated__/graphql';
import { useMemo } from 'react';
import { useRunServices } from '@/hooks/useRunServices';
export default function RunServiceDomains() {
const { currentProject, currentWorkspace } = useCurrentWorkspaceAndProject();
const {
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],
);
const { services, loading } = useRunServices();
if (loading) {
return (
@@ -45,7 +28,7 @@ export default function RunServiceDomains() {
.filter((service) => service.config?.ports?.length > 0)
.map((service) => (
<SettingsContainer
key={service.id}
key={service.id ?? service.serviceID}
title={
<div className="flex flex-row items-center">
<Text className="text-lg font-semibold">
@@ -58,7 +41,7 @@ export default function RunServiceDomains() {
underline="hover"
className="font-medium"
>
<ArrowSquareOutIcon className="mb-1 ml-1 h-4 w-4" />
<ArrowSquareOutIcon className="w-4 h-4 mb-1 ml-1" />
</Link>
</div>
}
@@ -72,7 +55,7 @@ export default function RunServiceDomains() {
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) => (
<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 { Form } from '@/components/form/Form';
import { Button } from '@/components/ui/v2/Button';
import { Input } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text';
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 { useUpdateRunServiceConfigMutation } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { type RunService } from '@/hooks/useRunServices';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { type RunService } from 'pages/[workspaceSlug]/[appSlug]/services';
import { useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -28,12 +32,17 @@ export default function RunServicePortDomain({
service,
port,
}: RunServicePortProps) {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const [loading, setLoading] = useState(false);
const [isVerified, setIsVerified] = useState(false);
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateRunServiceConfig] = useUpdateRunServiceConfigMutation();
const [updateRunServiceConfig] = useUpdateRunServiceConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const runServicePort = service.config.ports.find((p) => p.port === port);
const initialValue = runServicePort?.ingresses?.[0]?.fqdn?.[0];
@@ -59,7 +68,7 @@ export default function RunServicePortDomain({
await updateRunServiceConfig({
variables: {
appID: currentProject.id,
serviceID: service.id,
serviceID: service.id ?? service.serviceID,
config: {
ports: service?.config?.ports?.map((p) => {
// exclude the `__typename` because the mutation will fail otherwise
@@ -78,7 +87,7 @@ export default function RunServicePortDomain({
return {
...rest,
// exclude the `__typename` because the mutation will fail otherwise
ingresses: rest.ingresses.map((item) => ({
ingresses: rest?.ingresses?.map((item) => ({
fqdn: item.fqdn,
})),
};
@@ -88,6 +97,18 @@ export default function RunServicePortDomain({
});
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: `Port ${port} is being updated...`,
@@ -99,6 +120,16 @@ export default function RunServicePortDomain({
setLoading(false);
}
const isDisabled = () => {
if (!isPlatform) {
return loading || !isDirty || maintenanceActive;
}
return (
loading || !isDirty || maintenanceActive || (!isVerified && !initialValue)
);
};
return (
<FormProvider {...form}>
<Form onSubmit={handleSubmit}>
@@ -121,16 +152,7 @@ export default function RunServicePortDomain({
inputRoot: { min: 1, max: 100 },
}}
/>
<Button
variant="outlined"
type="submit"
disabled={
loading ||
!isDirty ||
maintenanceActive ||
(!isVerified && !initialValue)
}
>
<Button variant="outlined" type="submit" disabled={isDisabled()}>
Save
</Button>
</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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input';
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 {
useGetServerlessFunctionsSettingsQuery,
useUpdateConfigMutation,
type ConfigIngressUpdateInput,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect, useState } from 'react';
@@ -25,12 +29,17 @@ export type ServerlessFunctionsDomainFormValues = Yup.InferType<
>;
export default function ServerlessFunctionsDomain() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const [isVerified, setIsVerified] = useState(false);
const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation();
const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const form = useForm<ServerlessFunctionsDomainFormValues>({
reValidateMode: 'onSubmit',
@@ -42,6 +51,7 @@ export default function ServerlessFunctionsDomain() {
variables: {
appId: currentProject.id,
},
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { networking } = data?.config?.functions?.resources || {};
@@ -98,6 +108,18 @@ export default function ServerlessFunctionsDomain() {
await updateConfigPromise;
form.reset(formValues);
await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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 (
<FormProvider {...form}>
<Form onSubmit={handleSubmit}>
@@ -117,12 +147,11 @@ export default function ServerlessFunctionsDomain() {
description="Enter below your custom domain for Serverless Functions."
slotProps={{
submitButton: {
disabled:
!isDirty || maintenanceActive || (!isVerified && !initialValue),
disabled: isDisabled(),
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
{...register('functions_fqdn')}

View File

@@ -3,6 +3,7 @@ import { Button } from '@/components/ui/v2/Button';
import { IconButton } from '@/components/ui/v2/IconButton';
import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
import { Text } from '@/components/ui/v2/Text';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { useDnsLookupCnameLazyQuery } from '@/utils/__generated__/graphql';
@@ -21,6 +22,7 @@ export default function VerifyDomain({
value,
onHostNameVerified,
}: VerifyDomainProps) {
const isPlatform = useIsPlatform();
const [verificationFailed, setVerificationFailed] = useState(false);
const [verificationSucceeded, setVerificationSucceeded] = useState(false);
@@ -72,8 +74,11 @@ export default function VerifyDomain({
backgroundColor: 'success.light',
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">
{!verificationFailed && !verificationSucceeded && (
@@ -110,23 +115,29 @@ export default function VerifyDomain({
</div>
<div className="flex flex-row space-x-2">
<Text>Value:</Text>
<Text className="font-bold">{value}</Text>
<IconButton
aria-label="Copy Personal Access Token"
variant="borderless"
color="secondary"
onClick={() => copy(value, 'CNAME Value')}
>
<CopyIcon className="h-4 w-4" />
</IconButton>
{isPlatform ? (
<>
<Text className="font-bold">{value}</Text>
<IconButton
aria-label="Copy Personal Access Token"
variant="borderless"
color="secondary"
onClick={() => copy(value, 'CNAME Value')}
>
<CopyIcon className="w-4 h-4" />
</IconButton>
</>
) : null}
</div>
<Button
disabled={loading || !hostname}
onClick={handleVerifyDomain}
className="mt-4 sm:absolute sm:bottom-0 sm:right-0 sm:mt-0"
>
Verify
</Button>
{isPlatform ? (
<Button
disabled={loading || !hostname || isPlatform}
onClick={handleVerifyDomain}
className="mt-4 sm:absolute sm:bottom-0 sm:right-0 sm:mt-0"
>
Verify
</Button>
) : null}
</div>
</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 { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import type {
BaseEnvironmentVariableFormProps,
BaseEnvironmentVariableFormValues,
@@ -8,6 +11,7 @@ import {
BaseEnvironmentVariableForm,
baseEnvironmentVariableFormValidationSchema,
} from '@/features/projects/environmentVariables/settings/components/BaseEnvironmentVariableForm';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import {
GetEnvironmentVariablesDocument,
@@ -22,13 +26,17 @@ export interface CreateEnvironmentVariableFormProps
/**
* Function to be called when the form is submitted.
*/
onSubmit?: () => Promise<void>;
onSubmit?: () => Promise<any>;
}
export default function CreateEnvironmentVariableForm({
onSubmit,
...props
}: CreateEnvironmentVariableFormProps) {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const form = useForm<BaseEnvironmentVariableFormValues>({
defaultValues: {
name: '',
@@ -42,13 +50,14 @@ export default function CreateEnvironmentVariableForm({
const { data, loading, error } = useGetEnvironmentVariablesQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const availableEnvironmentVariables = data?.config?.global?.environment || [];
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetEnvironmentVariablesDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
if (loading) {
@@ -103,7 +112,19 @@ export default function CreateEnvironmentVariableForm({
await execPromiseWithErrorToast(
async () => {
await updateConfigPromise;
onSubmit?.();
await onSubmit?.();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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 { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import type {
BaseEnvironmentVariableFormProps,
BaseEnvironmentVariableFormValues,
@@ -8,6 +11,7 @@ import {
BaseEnvironmentVariableForm,
baseEnvironmentVariableFormValidationSchema,
} from '@/features/projects/environmentVariables/settings/components/BaseEnvironmentVariableForm';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import type { EnvironmentVariable } from '@/types/application';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import {
@@ -27,7 +31,7 @@ export interface EditEnvironmentVariableFormProps
/**
* Function to be called when the form is submitted.
*/
onSubmit?: () => Promise<void>;
onSubmit?: () => Promise<any>;
}
export default function EditEnvironmentVariableForm({
@@ -35,6 +39,10 @@ export default function EditEnvironmentVariableForm({
onSubmit,
...props
}: EditEnvironmentVariableFormProps) {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const form = useForm<BaseEnvironmentVariableFormValues>({
defaultValues: {
id: originalEnvironmentVariable.id || '',
@@ -49,13 +57,14 @@ export default function EditEnvironmentVariableForm({
const { data, loading, error } = useGetEnvironmentVariablesQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const availableEnvironmentVariables = data?.config?.global?.environment || [];
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetEnvironmentVariablesDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
if (loading) {
@@ -117,7 +126,19 @@ export default function EditEnvironmentVariableForm({
await execPromiseWithErrorToast(
async () => {
await updateConfigPromise;
onSubmit?.();
await onSubmit?.();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Updating environment variable...',

View File

@@ -3,6 +3,8 @@ import { Form } from '@/components/form/Form';
import { Button } from '@/components/ui/v2/Button';
import { Input } from '@/components/ui/v2/Input';
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 { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import {
@@ -63,9 +65,12 @@ export default function EditJwtSecretForm({
submitButtonText = 'Save',
location,
}: EditJwtSecretFormProps) {
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetEnvironmentVariablesDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { onDirtyStateChange } = useDialog();
@@ -118,9 +123,9 @@ export default function EditJwtSecretForm({
<FormProvider {...form}>
<Form
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
{...register('jwtSecret')}
error={Boolean(errors.jwtSecret?.message)}

View File

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

View File

@@ -21,11 +21,15 @@ import {
} from '@/features/projects/common/utils/generateAppServiceUrl';
import { EditJwtSecretForm } from '@/features/projects/environmentVariables/settings/components/EditJwtSecretForm';
import { getJwtSecretsWithoutFalsyValues } from '@/features/projects/environmentVariables/settings/utils/getJwtSecretsWithoutFalsyValues';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { getHasuraConsoleServiceUrl } from '@/utils/env';
import { useGetEnvironmentVariablesQuery } from '@/utils/__generated__/graphql';
import { Fragment, useState } from 'react';
export default function SystemEnvironmentVariableSettings() {
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const [showAdminSecret, setShowAdminSecret] = useState(false);
const [showWebhookSecret, setShowWebhookSecret] = useState(false);
@@ -34,7 +38,7 @@ export default function SystemEnvironmentVariableSettings() {
const { currentProject } = useCurrentWorkspaceAndProject();
const { data, loading, error } = useGetEnvironmentVariablesQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { jwtSecrets, webhookSecret, adminSecret } = data?.config?.hasura || {};
@@ -46,8 +50,6 @@ export default function SystemEnvironmentVariableSettings() {
? JSON.stringify(jwtSecretsWithoutFalsyValues[0], null, 2)
: JSON.stringify(jwtSecretsWithoutFalsyValues, null, 2);
const isPlatform = useIsPlatform();
const appClient = useAppClient();
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."
docsLink="https://docs.nhost.io/platform/environment-variables#system-environment-variables"
rootClassName="gap-0"
className="mt-2 mb-2.5 px-0"
className="mb-2.5 mt-2 px-0"
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 lg:col-span-2">Value</Text>
</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.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">
{showAdminSecret ? (
<InlineCode className="!text-sm font-medium">
@@ -156,9 +158,9 @@ export default function SystemEnvironmentVariableSettings() {
onClick={() => setShowAdminSecret((show) => !show)}
>
{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>
</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.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">
{showWebhookSecret ? (
<InlineCode className="!text-sm font-medium">
@@ -191,9 +193,9 @@ export default function SystemEnvironmentVariableSettings() {
onClick={() => setShowWebhookSecret((show) => !show)}
>
{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>
</div>
@@ -217,9 +219,9 @@ export default function SystemEnvironmentVariableSettings() {
</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>
<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 { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import type {
BasePermissionVariableFormProps,
BasePermissionVariableFormValues,
@@ -9,6 +12,7 @@ import {
basePermissionVariableValidationSchema,
} from '@/features/projects/permissions/settings/components/BasePermissionVariableForm';
import { getAllPermissionVariables } from '@/features/projects/permissions/settings/utils/getAllPermissionVariables';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import {
GetRolesPermissionsDocument,
@@ -23,18 +27,20 @@ export interface CreatePermissionVariableFormProps
/**
* Function to be called when the form is submitted.
*/
onSubmit?: () => Promise<void>;
onSubmit?: () => Promise<any>;
}
export default function CreatePermissionVariableForm({
onSubmit,
...props
}: CreatePermissionVariableFormProps) {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const { data, error, loading } = useGetRolesPermissionsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { customClaims: permissionVariables } =
@@ -51,6 +57,7 @@ export default function CreatePermissionVariableForm({
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetRolesPermissionsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
if (loading) {
@@ -106,6 +113,18 @@ export default function CreatePermissionVariableForm({
async () => {
await updateConfigPromise;
await onSubmit?.();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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 { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import type {
BasePermissionVariableFormProps,
BasePermissionVariableFormValues,
@@ -9,6 +12,7 @@ import {
basePermissionVariableValidationSchema,
} from '@/features/projects/permissions/settings/components/BasePermissionVariableForm';
import { getAllPermissionVariables } from '@/features/projects/permissions/settings/utils/getAllPermissionVariables';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import type { PermissionVariable } from '@/types/application';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import {
@@ -28,7 +32,7 @@ export interface EditPermissionVariableFormProps
/**
* Function to be called when the form is submitted.
*/
onSubmit?: () => Promise<void>;
onSubmit?: () => any;
}
export default function EditPermissionVariableForm({
@@ -36,11 +40,14 @@ export default function EditPermissionVariableForm({
onSubmit,
...props
}: EditPermissionVariableFormProps) {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const { data, error, loading } = useGetRolesPermissionsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { customClaims: permissionVariables } =
@@ -57,6 +64,7 @@ export default function EditPermissionVariableForm({
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetRolesPermissionsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
if (loading) {
@@ -132,6 +140,19 @@ export default function EditPermissionVariableForm({
await execPromiseWithErrorToast(
async () => {
await updateConfigPromise;
await onSubmit?.();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Updating permission variable...',
@@ -140,8 +161,6 @@ export default function EditPermissionVariableForm({
'An error occurred while trying to update the permission variable.',
},
);
await onSubmit?.();
}
return (

View File

@@ -1,3 +1,4 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
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 { Tooltip } from '@/components/ui/v2/Tooltip';
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 { EditPermissionVariableForm } from '@/features/projects/permissions/settings/components/EditPermissionVariableForm';
import { getAllPermissionVariables } from '@/features/projects/permissions/settings/utils/getAllPermissionVariables';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import type { PermissionVariable } from '@/types/application';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import {
@@ -29,13 +32,15 @@ import { Fragment } from 'react';
import { twMerge } from 'tailwind-merge';
export default function PermissionVariableSettings() {
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const { openDialog, openAlertDialog } = useDialog();
const { data, loading, error } = useGetRolesPermissionsQuery({
const { data, loading, error, refetch } = useGetRolesPermissionsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { customClaims: permissionVariables } =
@@ -43,6 +48,7 @@ export default function PermissionVariableSettings() {
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetRolesPermissionsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
if (loading) {
@@ -55,6 +61,20 @@ export default function PermissionVariableSettings() {
throw error;
}
function showApplyChangesDialog() {
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}
async function handleDeleteVariable({ id }: PermissionVariable) {
const updateConfigPromise = updateConfig({
variables: {
@@ -79,6 +99,7 @@ export default function PermissionVariableSettings() {
await execPromiseWithErrorToast(
async () => {
await updateConfigPromise;
showApplyChangesDialog();
},
{
loadingMessage: 'Deleting permission variable...',
@@ -92,7 +113,7 @@ export default function PermissionVariableSettings() {
function handleOpenCreator() {
openDialog({
title: 'Create Permission Variable',
component: <CreatePermissionVariableForm />,
component: <CreatePermissionVariableForm onSubmit={refetch} />,
props: {
titleProps: { className: '!pb-0' },
PaperProps: { className: 'max-w-sm' },
@@ -104,7 +125,10 @@ export default function PermissionVariableSettings() {
openDialog({
title: 'Edit Permission Variable',
component: (
<EditPermissionVariableForm originalVariable={originalVariable} />
<EditPermissionVariableForm
originalVariable={originalVariable}
onSubmit={refetch}
/>
),
props: {
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."
docsLink="https://docs.nhost.io/guides/api/permissions#permission-variables"
rootClassName="gap-0"
className="my-2 px-0"
className="px-0 my-2"
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">Path</Text>
</Box>
@@ -167,7 +191,7 @@ export default function PermissionVariableSettings() {
!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>
<IconButton
@@ -224,7 +248,7 @@ export default function PermissionVariableSettings() {
<>
X-Hasura-{permissionVariable.key}{' '}
{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 { Form } from '@/components/form/Form';
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 { Link } from '@/components/ui/v2/Link';
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 { ResourcesConfirmationDialog } from '@/features/projects/resources/settings/components/ResourcesConfirmationDialog';
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 type { ResourceSettingsFormValues } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import { resourceSettingsValidationSchema } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import {
RESOURCE_VCPU_MULTIPLIER,
RESOURCE_VCPU_PRICE,
@@ -44,6 +47,8 @@ function getInitialServiceResources(
}
export default function ResourcesForm() {
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { openDialog, closeDialog } = useDialog();
const { currentProject } = useCurrentWorkspaceAndProject();
@@ -55,6 +60,7 @@ export default function ResourcesForm() {
variables: {
appId: currentProject?.id,
},
...(!isPlatform ? { client: localMimirClient } : {}),
});
const {
@@ -65,6 +71,7 @@ export default function ResourcesForm() {
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetResourcesDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const initialDatabaseResources = getInitialServiceResources(data, 'postgres');
@@ -113,7 +120,7 @@ export default function ResourcesForm() {
resolver: yupResolver(resourceSettingsValidationSchema),
});
if (!proPlan && !proPlanLoading) {
if (isPlatform && !proPlan && !proPlanLoading) {
return (
<Alert severity="error">
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 (
<ActivityIndicator
label="Loading resource settings..."
@@ -156,9 +163,10 @@ export default function ResourcesForm() {
},
);
const initialPrice =
proPlan.price +
(billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) * RESOURCE_VCPU_PRICE;
const initialPrice = isPlatform
? proPlan.price +
(billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) * RESOURCE_VCPU_PRICE
: 0;
async function handleSubmit(formValues: ResourceSettingsFormValues) {
const updateConfigPromise = updateConfig({
@@ -217,6 +225,18 @@ export default function ResourcesForm() {
await execPromiseWithErrorToast(
async () => {
await updateConfigPromise;
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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({
title: formValues.enabled
? '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 { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useProPlan } from '@/features/projects/common/hooks/useProPlan';
import { calculateBillableResources } from '@/features/projects/resources/settings/utils/calculateBillableResources';
import type { ResourceSettingsFormValues } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
@@ -16,6 +17,8 @@ import {
import { useFormState, useWatch } from 'react-hook-form';
export default function ResourcesFormFooter() {
const isPlatform = useIsPlatform();
const {
data: proPlan,
loading: proPlanLoading,
@@ -63,17 +66,27 @@ export default function ResourcesFormFooter() {
},
);
const updatedPrice = enabled
? Math.max(
priceForTotalAvailableVCPU,
(billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) *
RESOURCE_VCPU_PRICE,
) + proPlan.price
: proPlan.price;
const computeUpdatedPrice = () => {
if (!isPlatform) {
return 0;
}
if (enabled) {
return (
Math.max(
priceForTotalAvailableVCPU,
(billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) *
RESOURCE_VCPU_PRICE,
) + proPlan.price
);
}
return proPlan.price;
};
return (
<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"
>
<Text>
@@ -86,20 +99,22 @@ export default function ResourcesFormFooter() {
className="font-medium"
>
Compute Resources
<ArrowSquareOutIcon className="ml-1 h-4 w-4" />
<ArrowSquareOutIcon className="w-4 h-4 ml-1" />
</Link>
</Text>
{(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">
<Text>
Approximate cost:{' '}
<span className="font-medium">${updatedPrice.toFixed(2)}/mo</span>
<span className="font-medium">
${computeUpdatedPrice().toFixed(2)}/mo
</span>
</Text>
<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>
</Box>

View File

@@ -3,6 +3,7 @@ import { Box } from '@/components/ui/v2/Box';
import { ArrowRightIcon } from '@/components/ui/v2/icons/ArrowRightIcon';
import { Slider, sliderClasses } from '@/components/ui/v2/Slider';
import { Text } from '@/components/ui/v2/Text';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useProPlan } from '@/features/projects/common/hooks/useProPlan';
import { getAllocatedResources } from '@/features/projects/resources/settings/utils/getAllocatedResources';
import { prettifyMemory } from '@/features/projects/resources/settings/utils/prettifyMemory';
@@ -38,6 +39,8 @@ const StyledAvailableCpuSlider = styled(Slider)(({ theme }) => ({
export default function TotalResourcesFormFragment({
initialPrice,
}: TotalResourcesFormFragmentProps) {
const isPlatform = useIsPlatform();
const {
data: proPlan,
error: proPlanError,
@@ -46,7 +49,7 @@ export default function TotalResourcesFormFragment({
const { setValue } = useFormContext<ResourceSettingsFormValues>();
const formValues = useWatch<ResourceSettingsFormValues>();
if (!proPlan && !proPlanLoading) {
if (isPlatform && !proPlan && !proPlanLoading) {
return (
<Alert severity="error">
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) *
RESOURCE_VCPU_PRICE;
const updatedPrice = priceForTotalAvailableVCPU + proPlan.price;
const updatedPrice = isPlatform
? priceForTotalAvailableVCPU + proPlan.price
: 0;
const { vcpu: allocatedVCPU, memory: allocatedMemory } =
getAllocatedResources(formValues);
@@ -102,8 +107,8 @@ export default function TotalResourcesFormFragment({
return (
<Box className="px-4 pb-4">
<Box className="rounded-md border">
<Box className="flex flex-col gap-4 bg-transparent p-4">
<Box className="border rounded-md">
<Box className="flex flex-col gap-4 p-4 bg-transparent">
<Box className="flex flex-row items-center justify-between gap-4">
<Text color="secondary">
Total available compute for your project:
@@ -151,7 +156,7 @@ export default function TotalResourcesFormFragment({
severity={
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 && (
<>

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 { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import type {
BaseRoleFormProps,
BaseRoleFormValues,
@@ -9,6 +12,7 @@ import {
baseRoleFormValidationSchema,
} from '@/features/projects/roles/settings/components/BaseRoleForm';
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import {
GetRolesPermissionsDocument,
@@ -23,17 +27,20 @@ export interface CreateRoleFormProps
/**
* Function to be called when the form is submitted.
*/
onSubmit?: () => Promise<void>;
onSubmit?: () => Promise<any>;
}
export default function CreateRoleForm({
onSubmit,
...props
}: CreateRoleFormProps) {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const { data, loading, error } = useGetRolesPermissionsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { allowed: allowedRoles } = data?.config?.auth?.user?.roles || {};
@@ -45,6 +52,7 @@ export default function CreateRoleForm({
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetRolesPermissionsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
if (loading) {
@@ -85,7 +93,19 @@ export default function CreateRoleForm({
await execPromiseWithErrorToast(
async () => {
await updateConfigPromise;
onSubmit?.();
await onSubmit?.();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
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 { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import type {
BaseRoleFormProps,
BaseRoleFormValues,
@@ -9,6 +12,7 @@ import {
baseRoleFormValidationSchema,
} from '@/features/projects/roles/settings/components/BaseRoleForm';
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import type { Role } from '@/types/application';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import {
@@ -28,7 +32,7 @@ export interface EditRoleFormProps
/**
* Function to be called when the form is submitted.
*/
onSubmit?: () => Promise<void>;
onSubmit?: () => Promise<any>;
}
export default function EditRoleForm({
@@ -36,10 +40,13 @@ export default function EditRoleForm({
onSubmit,
...props
}: EditRoleFormProps) {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const { data, loading, error } = useGetRolesPermissionsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { allowed: allowedRoles, default: defaultRole } =
@@ -55,6 +62,7 @@ export default function EditRoleForm({
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetRolesPermissionsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
if (loading) {
@@ -114,7 +122,19 @@ export default function EditRoleForm({
await execPromiseWithErrorToast(
async () => {
await updateConfigPromise;
onSubmit?.();
await onSubmit?.();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Updating role...',

View File

@@ -1,3 +1,4 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
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 { Text } from '@/components/ui/v2/Text';
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 { EditRoleForm } from '@/features/projects/roles/settings/components/EditRoleForm';
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import type { Role } from '@/types/application';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import {
@@ -40,13 +43,15 @@ export interface RoleSettingsFormValues {
}
export default function RoleSettings() {
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const { openDialog, openAlertDialog } = useDialog();
const { data, loading, error } = useGetRolesPermissionsQuery({
const { data, loading, error, refetch } = useGetRolesPermissionsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { allowed: allowedRoles, default: defaultRole } =
@@ -54,6 +59,7 @@ export default function RoleSettings() {
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetRolesPermissionsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
if (loading) {
@@ -64,6 +70,20 @@ export default function RoleSettings() {
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) {
const updateConfigPromise = updateConfig({
variables: {
@@ -84,6 +104,7 @@ export default function RoleSettings() {
await execPromiseWithErrorToast(
async () => {
await updateConfigPromise;
showApplyChangesDialog();
},
{
loadingMessage: 'Updating default role...',
@@ -114,6 +135,7 @@ export default function RoleSettings() {
await execPromiseWithErrorToast(
async () => {
await updateConfigPromise;
showApplyChangesDialog();
},
{
loadingMessage: 'Deleting allowed role...',
@@ -127,7 +149,7 @@ export default function RoleSettings() {
function handleOpenCreator() {
openDialog({
title: 'Create Allowed Role',
component: <CreateRoleForm />,
component: <CreateRoleForm onSubmit={refetch} />,
props: {
titleProps: { className: '!pb-0' },
PaperProps: { className: 'max-w-sm' },
@@ -138,7 +160,9 @@ export default function RoleSettings() {
function handleOpenEditor(originalRole: Role) {
openDialog({
title: 'Edit Allowed Role',
component: <EditRoleForm originalRole={originalRole} />,
component: (
<EditRoleForm originalRole={originalRole} onSubmit={refetch} />
),
props: {
titleProps: { className: '!pb-0' },
PaperProps: { className: 'max-w-sm' },
@@ -177,7 +201,7 @@ export default function RoleSettings() {
)}
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>
</Box>
@@ -193,7 +217,7 @@ export default function RoleSettings() {
<Dropdown.Trigger
asChild
hideChevron
className="absolute right-4 top-1/2 -translate-y-1/2"
className="absolute -translate-y-1/2 right-4 top-1/2"
>
<IconButton
variant="borderless"
@@ -252,7 +276,7 @@ export default function RoleSettings() {
<>
{role.name}
{role.isSystemRole && <LockIcon className="h-4 w-4" />}
{role.isSystemRole && <LockIcon className="w-4 h-4" />}
{defaultRole === role.name && (
<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 { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import type {
BaseSecretFormProps,
BaseSecretFormValues,
@@ -7,6 +10,7 @@ import {
BaseSecretForm,
baseSecretFormValidationSchema,
} from '@/features/projects/secrets/settings/components/BaseSecretForm';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import {
GetSecretsDocument,
@@ -20,13 +24,17 @@ export interface CreateSecretFormProps
/**
* Function to be called when the form is submitted.
*/
onSubmit?: () => Promise<void>;
onSubmit?: () => Promise<any>;
}
export default function CreateSecretForm({
onSubmit,
...props
}: CreateSecretFormProps) {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const form = useForm<BaseSecretFormValues>({
defaultValues: {
name: '',
@@ -39,6 +47,7 @@ export default function CreateSecretForm({
const { currentProject } = useCurrentWorkspaceAndProject();
const [insertSecret] = useInsertSecretMutation({
refetchQueries: [GetSecretsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
async function handleSubmit({ name, value }: BaseSecretFormValues) {
@@ -56,7 +65,19 @@ export default function CreateSecretForm({
await execPromiseWithErrorToast(
async () => {
await insertSecretPromise;
onSubmit?.();
await onSubmit?.();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Creating secret...',

View File

@@ -1,4 +1,5 @@
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import type {
BaseSecretFormProps,
BaseSecretFormValues,
@@ -7,6 +8,7 @@ import {
BaseSecretForm,
baseSecretFormValidationSchema,
} from '@/features/projects/secrets/settings/components/BaseSecretForm';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import type { Secret } from '@/types/application';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import {
@@ -33,6 +35,9 @@ export default function EditSecretForm({
onSubmit,
...props
}: EditSecretFormProps) {
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const form = useForm<BaseSecretFormValues>({
defaultValues: {
name: originalSecret.name,
@@ -45,6 +50,7 @@ export default function EditSecretForm({
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateSecret] = useUpdateSecretMutation({
refetchQueries: [GetSecretsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
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 { Form } from '@/components/form/Form';
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 { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
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 { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import { ComputeFormSection } from '@/features/services/components/ServiceForm/components/ComputeFormSection';
@@ -25,6 +27,7 @@ import {
type ServiceFormProps,
type ServiceFormValues,
} from '@/features/services/components/ServiceForm/ServiceFormTypes';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
@@ -50,11 +53,15 @@ export default function ServiceForm({
location,
}: ServiceFormProps) {
const hostName = useHostName();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { onDirtyStateChange, openDialog, closeDialog } = useDialog();
const [insertRunService] = useInsertRunServiceMutation();
const { currentProject } = useCurrentWorkspaceAndProject();
const [insertRunServiceConfig] = useInsertRunServiceConfigMutation();
const [replaceRunServiceConfig] = useReplaceRunServiceConfigMutation();
const [replaceRunServiceConfig] = useReplaceRunServiceConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const [detailsServiceId, setDetailsServiceId] = useState('');
const [detailsServiceSubdomain, setDetailsServiceSubdomain] = useState(
initialData?.subdomain,
@@ -145,6 +152,18 @@ export default function ServiceForm({
});
setDetailsServiceId(serviceID);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
} else {
// Insert service config
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({
title: 'Confirm Resources',
component: (
@@ -213,26 +237,34 @@ export default function ServiceForm({
};
useEffect(() => {
(async () => {
if (detailsServiceId) {
openDialog({
title: 'Service Details',
component: (
<ServiceDetailsDialog
serviceID={detailsServiceId}
subdomain={detailsServiceSubdomain}
ports={formValues.ports}
/>
),
props: {
PaperProps: {
className: 'max-w-2xl',
},
if (!isPlatform) {
return;
}
if (detailsServiceId) {
openDialog({
title: 'Service Details',
component: (
<ServiceDetailsDialog
serviceID={detailsServiceId}
subdomain={detailsServiceSubdomain}
ports={formValues.ports}
/>
),
props: {
PaperProps: {
className: 'max-w-2xl',
},
});
}
})();
}, [detailsServiceId, detailsServiceSubdomain, formValues, openDialog]);
},
});
}
}, [
detailsServiceId,
detailsServiceSubdomain,
formValues,
openDialog,
isPlatform,
]);
const pricingExplanation = () => {
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.">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -311,7 +343,7 @@ export default function ServiceForm({
>
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -325,8 +357,8 @@ export default function ServiceForm({
autoComplete="off"
/>
{/* This shows only when trying to edit a service */}
{serviceID && serviceImage && (
{/* This shows only when trying to edit a service and when running against the nhost platform */}
{isPlatform && serviceID && serviceImage && (
<InfoCard
title="Private registry"
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.">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -356,22 +388,24 @@ export default function ServiceForm({
autoComplete="off"
/>
<Alert
severity="info"
className="flex items-center justify-between space-x-2"
>
<span>{pricingExplanation()}</span>
<b>
$
{parseFloat(
(
formValues.compute.cpu *
formValues.replicas *
COST_PER_VCPU
).toFixed(2),
)}
</b>
</Alert>
{isPlatform ? (
<Alert
severity="info"
className="flex items-center justify-between space-x-2"
>
<span>{pricingExplanation()}</span>
<b>
$
{parseFloat(
(
formValues.compute.cpu *
formValues.replicas *
COST_PER_VCPU
).toFixed(2),
)}
</b>
</Alert>
) : null}
<ComputeFormSection showTooltip />
@@ -388,7 +422,7 @@ export default function ServiceForm({
{createServiceFormError && (
<Alert
severity="error"
className="grid grid-flow-col items-center justify-between px-4 py-3"
className="grid items-center justify-between grid-flow-col px-4 py-3"
>
<span className="text-left">
<strong>Error:</strong> {createServiceFormError.message}

View File

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

View File

@@ -1,9 +1,12 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider';
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetStorageSettingsDocument,
Software_Type_Enum,
@@ -11,8 +14,10 @@ import {
useGetStorageSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -30,21 +35,26 @@ export type StorageServiceVersionFormValues = Yup.InferType<
>;
export default function StorageServiceVersionSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetStorageSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetStorageSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data: storageVersionsData } = useGetSoftwareVersionsQuery({
variables: {
software: Software_Type_Enum.Storage,
},
skip: !isPlatform,
});
const { version } = data?.config?.storage || {};
@@ -52,6 +62,7 @@ export default function StorageServiceVersionSettings() {
const availableVersions = Array.from(
new Set(versions.map((el) => el.version)).add(version),
)
.filter((v) => !!v)
.sort()
.reverse()
.map((availableVersion) => ({
@@ -61,10 +72,21 @@ export default function StorageServiceVersionSettings() {
const form = useForm<StorageServiceVersionFormValues>({
reValidateMode: 'onSubmit',
defaultValues: { version: { label: version, value: version } },
defaultValues: { version: { label: '', value: '' } },
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
version: {
label: version,
value: version,
},
});
}
}, [loading, version, form]);
if (loading) {
return (
<ActivityIndicator
@@ -99,6 +121,18 @@ export default function StorageServiceVersionSettings() {
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Storage version is being updated...',
@@ -123,12 +157,20 @@ export default function StorageServiceVersionSettings() {
}}
docsLink="https://github.com/nhost/hasura-storage/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
id="version"
name="version"
autoHighlight
freeSolo
getOptionLabel={(option) => {
if (typeof option === 'string') {
return option || '';
}
return option.value;
}}
isOptionEqualToValue={() => false}
filterOptions={(options, { inputValue }) => {
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 { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetHasuraSettingsDocument,
useGetStorageSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
@@ -20,16 +25,20 @@ const validationSchema = Yup.object({
export type HasuraStorageAVFormValues = Yup.InferType<typeof validationSchema>;
export default function HasuraStorageAVSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetStorageSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-first',
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { server } = data?.config?.storage?.antivirus || {};
@@ -42,6 +51,14 @@ export default function HasuraStorageAVSettings() {
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading) {
form.reset({
enabled: !!server,
});
}
}, [loading, server, form]);
if (loading) {
return (
<ActivityIndicator
@@ -81,6 +98,18 @@ export default function HasuraStorageAVSettings() {
await updateConfigPromise;
form.reset(formValues);
await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Antivirus settings are being updated...',

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
mutation updateGraphiteAutoEmbeddingsConfiguration(
$id: uuid!
$name: String
$model: embedding_model_enum
$schemaName: String
$tableName: String
$columnName: String
@@ -11,6 +12,7 @@ mutation updateGraphiteAutoEmbeddingsConfiguration(
pk_columns: { id: $id }
_set: {
name: $name
model: $model
schemaName: $schemaName
tableName: $tableName
columnName: $columnName
@@ -20,6 +22,7 @@ mutation updateGraphiteAutoEmbeddingsConfiguration(
) {
id
name
model
schemaName
tableName
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(
$appID: uuid!
$resolve: Boolean!
@@ -11,40 +48,7 @@ query getRunServices(
updatedAt
subdomain
config(resolve: $resolve) {
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
}
...RunServiceConfig
}
}
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,
};
}

View File

@@ -9,75 +9,34 @@ import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import { ServicesIcon } from '@/components/ui/v2/icons/ServicesIcon';
import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import type { GetRunServicesQuery } from '@/utils/__generated__/graphql';
import { useGetRunServicesQuery } from '@/utils/__generated__/graphql';
import { UpgradeNotification } from '@/features/projects/common/components/UpgradeNotification';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { ServiceForm } from '@/features/services/components/ServiceForm';
import { type PortTypes } from '@/features/services/components/ServiceForm/components/PortsFormSection/PortsFormSectionTypes';
import ServicesList from '@/features/services/components/ServicesList/ServicesList';
import { useRunServices, type RunServiceConfig } from '@/hooks/useRunServices';
import { useRouter } from 'next/router';
import {
useCallback,
useEffect,
useMemo,
useRef,
useState,
type ReactElement,
} from 'react';
export type RunService = Omit<
GetRunServicesQuery['app']['runServices'][0],
'__typename'
>;
export type RunServiceConfig = Omit<
GetRunServicesQuery['app']['runServices'][0]['config'],
'__typename'
>;
import { useCallback, useEffect, type ReactElement } from 'react';
export default function ServicesPage() {
const limit = useRef(25);
const router = useRouter();
const isPlatform = useIsPlatform();
const { openDrawer, openAlertDialog } = useDialog();
const { currentProject } = useCurrentWorkspaceAndProject();
const isPlanFree = currentProject?.plan?.isFree;
const [currentPage, setCurrentPage] = useState(
parseInt(router.query.page as string, 10) || 1,
);
const [nrOfPages, setNrOfPages] = useState(0);
const offset = useMemo(() => currentPage - 1, [currentPage]);
const {
data,
loading,
refetch: refetchServices,
} = useGetRunServicesQuery({
variables: {
appID: currentProject.id,
resolve: false,
limit: limit.current,
offset,
},
});
services,
totalServicesCount,
limit,
nrOfPages,
currentPage,
setCurrentPage,
refetch,
} = useRunServices();
useEffect(() => {
if (loading) {
return;
}
const userCount = data?.app?.runServices_aggregate.aggregate.count ?? 0;
setNrOfPages(Math.ceil(userCount / limit.current));
}, [data, loading]);
const services = useMemo(
() => data?.app?.runServices.map((service) => service) ?? [],
[data],
);
const isPlanFree = currentProject?.plan?.isFree;
const checkConfigFromQuery = useCallback(
(base64Config: string) => {
@@ -89,7 +48,7 @@ export default function ServicesPage() {
openDrawer({
title: (
<Box className="flex flex-row items-center space-x-2">
<CubeIcon className="h-5 w-5" />
<CubeIcon className="w-5 h-5" />
<Text>Create a new run service</Text>
</Box>
),
@@ -111,7 +70,7 @@ export default function ServicesPage() {
replicas: parsedConfig?.resources?.replicas,
storage: parsedConfig?.resources?.storage,
}}
onSubmit={refetchServices}
onSubmit={refetch}
/>
),
});
@@ -127,7 +86,7 @@ export default function ServicesPage() {
}
}
},
[router.query.config, openDrawer, refetchServices, openAlertDialog],
[router.query.config, openDrawer, refetch, openAlertDialog],
);
useEffect(() => {
@@ -137,18 +96,23 @@ export default function ServicesPage() {
}, [checkConfigFromQuery, router.query]);
const openCreateServiceDialog = () => {
// creating services using the local dashboard is not supported
if (!isPlatform) {
return;
}
openDrawer({
title: (
<Box className="flex flex-row items-center space-x-2">
<CubeIcon className="h-5 w-5" />
<CubeIcon className="w-5 h-5" />
<Text>Create a new service</Text>
</Box>
),
component: <ServiceForm onSubmit={refetchServices} />,
component: <ServiceForm onSubmit={refetch} />,
});
};
if (isPlanFree) {
if (isPlatform && isPlanFree) {
return (
<Container>
<UpgradeNotification
@@ -159,41 +123,44 @@ export default function ServicesPage() {
);
}
if (data?.app.runServices.length === 0 && !loading) {
if (services.length === 0 && !loading) {
return (
<Container className="mx-auto max-w-9xl space-y-5 overflow-x-hidden">
<Container className="mx-auto space-y-5 overflow-x-hidden max-w-9xl">
<div className="flex flex-row place-content-end">
<Button
variant="contained"
color="primary"
onClick={openCreateServiceDialog}
startIcon={<PlusIcon className="h-4 w-4" />}
startIcon={<PlusIcon className="w-4 h-4" />}
disabled={!isPlatform}
>
Add service
</Button>
</div>
<Box className="flex flex-col items-center justify-center space-y-5 rounded-lg border px-48 py-12 shadow-sm">
<ServicesIcon className="h-10 w-10" />
<Box className="flex flex-col items-center justify-center px-48 py-12 space-y-5 border rounded-lg shadow-sm">
<ServicesIcon className="w-10 h-10" />
<div className="flex flex-col space-y-1">
<Text className="text-center font-medium" variant="h3">
<Text className="font-medium text-center" variant="h3">
No custom services are available
</Text>
<Text variant="subtitle1" className="text-center">
All your projects custom services will be listed here.
All your project&apos;s custom services will be listed here.
</Text>
</div>
<div className="flex flex-row place-content-between rounded-lg ">
<Button
variant="contained"
color="primary"
className="w-full"
onClick={openCreateServiceDialog}
startIcon={<PlusIcon className="h-4 w-4" />}
>
Add service
</Button>
</div>
{isPlatform ? (
<div className="flex flex-row rounded-lg place-content-between ">
<Button
variant="contained"
color="primary"
className="w-full"
onClick={openCreateServiceDialog}
startIcon={<PlusIcon className="w-4 h-4" />}
>
Add service
</Button>
</div>
) : null}
</Box>
</Container>
);
@@ -201,12 +168,13 @@ export default function ServicesPage() {
return (
<div className="flex flex-col">
<Box className="flex flex-row place-content-end border-b-1 p-4">
<Box className="flex flex-row p-4 place-content-end border-b-1">
<Button
variant="contained"
color="primary"
onClick={openCreateServiceDialog}
startIcon={<PlusIcon className="h-4 w-4" />}
startIcon={<PlusIcon className="w-4 h-4" />}
disabled={!isPlatform}
>
Add service
</Button>
@@ -214,42 +182,42 @@ export default function ServicesPage() {
<Box className="space-y-4">
<ServicesList
services={services}
onDelete={() => refetchServices()}
onCreateOrUpdate={() => refetchServices()}
onDelete={() => refetch()}
onCreateOrUpdate={() => refetch()}
/>
<Pagination
className="px-2"
totalNrOfPages={nrOfPages}
currentPageNumber={currentPage}
totalNrOfElements={
data?.app?.runServices_aggregate.aggregate.count ?? 0
}
itemsLabel="services"
elementsPerPage={limit.current}
onPrevPageClick={async () => {
setCurrentPage((page) => page - 1);
if (currentPage - 1 !== 1) {
{isPlatform ? (
<Pagination
className="px-2"
totalNrOfPages={nrOfPages}
currentPageNumber={currentPage}
totalNrOfElements={totalServicesCount}
itemsLabel="services"
elementsPerPage={limit.current}
onPrevPageClick={async () => {
setCurrentPage((page) => page - 1);
if (currentPage - 1 !== 1) {
await router.push({
pathname: router.pathname,
query: { ...router.query, page: currentPage - 1 },
});
}
}}
onNextPageClick={async () => {
setCurrentPage((page) => page + 1);
await router.push({
pathname: router.pathname,
query: { ...router.query, page: currentPage - 1 },
query: { ...router.query, page: currentPage + 1 },
});
}
}}
onNextPageClick={async () => {
setCurrentPage((page) => page + 1);
await router.push({
pathname: router.pathname,
query: { ...router.query, page: currentPage + 1 },
});
}}
onPageChange={async (page) => {
setCurrentPage(page);
await router.push({
pathname: router.pathname,
query: { ...router.query, page },
});
}}
/>
}}
onPageChange={async (page) => {
setCurrentPage(page);
await router.push({
pathname: router.pathname,
query: { ...router.query, page },
});
}}
/>
) : null}
</Box>
</div>
);

View File

@@ -11,7 +11,7 @@ import type { ReactElement } from 'react';
export default function StorageSettingsPage() {
const { currentProject, loading, error } = useCurrentWorkspaceAndProject();
if (currentProject.plan.isFree) {
if (currentProject?.plan?.isFree) {
return (
<Box className="p-4" sx={{ backgroundColor: 'background.default' }}>
<UpgradeToProBanner
@@ -43,7 +43,7 @@ export default function StorageSettingsPage() {
return (
<Container
className="grid max-w-5xl grid-flow-row gap-y-6 bg-transparent"
className="grid max-w-5xl grid-flow-row bg-transparent gap-y-6"
rootClassName="bg-transparent"
>
<AISettings />

View File

@@ -11,15 +11,20 @@ import { GravatarSettings } from '@/features/authentication/settings/components/
import { MFASettings } from '@/features/authentication/settings/components/MFASettings';
import { SessionSettings } from '@/features/authentication/settings/components/SessionSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { useGetAuthenticationSettingsQuery } from '@/utils/__generated__/graphql';
import type { ReactElement } from 'react';
export default function SettingsAuthenticationPage() {
const { currentProject } = useCurrentWorkspaceAndProject();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id },
skip: !currentProject,
...(!isPlatform ? { client: localMimirClient } : {}),
});
if (!data && loading) {
@@ -38,7 +43,7 @@ export default function SettingsAuthenticationPage() {
return (
<Container
className="grid max-w-5xl grid-flow-row gap-y-6 bg-transparent"
className="grid max-w-5xl grid-flow-row bg-transparent gap-y-6"
rootClassName="bg-transparent"
>
<AuthServiceVersionSettings />

View File

@@ -16,7 +16,7 @@ import { type ReactElement } from 'react';
export default function CustomDomains() {
const { currentProject } = useCurrentWorkspaceAndProject();
if (currentProject.plan.isFree) {
if (currentProject?.plan?.isFree) {
return (
<Container
className="grid grid-flow-row gap-6 bg-transparent"
@@ -35,7 +35,7 @@ export default function CustomDomains() {
className="grid max-w-5xl grid-flow-row gap-6 bg-transparent"
rootClassName="bg-transparent"
>
<Box className="flex flex-row items-center gap-4 overflow-hidden rounded-lg border-1 p-4">
<Box className="flex flex-row items-center gap-4 p-4 overflow-hidden rounded-lg border-1">
<div className="flex flex-col space-y-2">
<Text className="text-lg font-semibold">Custom Domains</Text>
@@ -50,7 +50,7 @@ export default function CustomDomains() {
className="ml-1 font-medium"
>
Custom Domains
<ArrowSquareOutIcon className="ml-1 h-4 w-4" />
<ArrowSquareOutIcon className="w-4 h-4 ml-1" />
</Link>
</Text>
</div>

View File

@@ -6,15 +6,20 @@ import { DatabaseServiceVersionSettings } from '@/features/database/settings/com
import { DatabaseStorageCapacity } from '@/features/database/settings/components/DatabaseStorageCapacity';
import { ResetDatabasePasswordSettings } from '@/features/database/settings/components/ResetDatabasePasswordSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useGetPostgresSettingsQuery } from '@/generated/graphql';
import useLocalMimirClient from '@/hooks/useLocalMimirClient/useLocalMimirClient';
import type { ReactElement } from 'react';
export default function DatabaseSettingsPage() {
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const { loading, error } = useGetPostgresSettingsQuery({
variables: { appId: currentProject?.id },
skip: !currentProject,
...(!isPlatform ? { client: localMimirClient } : {}),
});
if (loading) {
@@ -38,8 +43,13 @@ export default function DatabaseSettingsPage() {
>
<DatabaseServiceVersionSettings />
<DatabaseStorageCapacity />
<DatabaseConnectionInfo />
<ResetDatabasePasswordSettings />
{isPlatform && (
<>
<DatabaseConnectionInfo />
<ResetDatabasePasswordSettings />
</>
)}
</Container>
);
}

View File

@@ -2,15 +2,20 @@ import { Container } from '@/components/layout/Container';
import { SettingsLayout } from '@/components/layout/SettingsLayout';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { EnvironmentVariableSettings } from '@/features/projects/environmentVariables/settings/components/EnvironmentVariableSettings';
import { SystemEnvironmentVariableSettings } from '@/features/projects/environmentVariables/settings/components/SystemEnvironmentVariableSettings';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { useGetEnvironmentVariablesQuery } from '@/utils/__generated__/graphql';
import type { ReactElement } from 'react';
export default function EnvironmentVariablesPage() {
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const { loading, error } = useGetEnvironmentVariablesQuery({
variables: { appId: currentProject?.id },
...(!isPlatform ? { client: localMimirClient } : {}),
});
if (loading) {

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