Compare commits

...

33 Commits

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


# Releases
## @nhost/hasura-auth-js@2.6.0

### Minor Changes

- 55d8bb5: feat: support custom headers in sign-up and deanonymize
requests

## @nhost/react@3.6.0

### Minor Changes

- 55d8bb5: feat: add `requestOptions` to `signUpEmailPassword` to allow
passing extra headers with the signup request

### Patch Changes

-   @nhost/nhost-js@3.1.10

## @nhost/apollo@7.1.7

### Patch Changes

-   @nhost/nhost-js@3.1.10

## @nhost/react-apollo@13.0.0

### Patch Changes

-   Updated dependencies [55d8bb5]
    -   @nhost/react@3.6.0
    -   @nhost/apollo@7.1.7

## @nhost/react-urql@10.0.0

### Patch Changes

-   Updated dependencies [55d8bb5]
    -   @nhost/react@3.6.0

## @nhost/nextjs@2.1.22

### Patch Changes

-   Updated dependencies [55d8bb5]
    -   @nhost/react@3.6.0

## @nhost/nhost-js@3.1.10

### Patch Changes

-   Updated dependencies [55d8bb5]
    -   @nhost/hasura-auth-js@2.6.0

## @nhost/vue@2.6.7

### Patch Changes

-   @nhost/nhost-js@3.1.10

## @nhost/dashboard@1.29.0

### Minor Changes

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

### Patch Changes

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

## @nhost-examples/cli@0.3.12

### Patch Changes

-   @nhost/nhost-js@3.1.10

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

### Patch Changes

-   Updated dependencies [55d8bb5]
    -   @nhost/react@3.6.0
    -   @nhost/react-apollo@13.0.0

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

### Patch Changes

-   Updated dependencies [55d8bb5]
    -   @nhost/react@3.6.0

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

### Patch Changes

-   Updated dependencies [55d8bb5]
    -   @nhost/react@3.6.0
    -   @nhost/react-urql@10.0.0

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

### Patch Changes

-   @nhost/nhost-js@3.1.10

## @nhost-examples/nextjs@0.3.13

### Patch Changes

-   Updated dependencies [55d8bb5]
    -   @nhost/react@3.6.0
    -   @nhost/react-apollo@13.0.0
    -   @nhost/nextjs@2.1.22

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

### Patch Changes

-   @nhost/nhost-js@3.1.10

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

### Patch Changes

-   @nhost/nhost-js@3.1.10

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

### Patch Changes

-   Updated dependencies [55d8bb5]
    -   @nhost/react@3.6.0
    -   @nhost/react-apollo@13.0.0

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

### Patch Changes

-   Updated dependencies [55d8bb5]
    -   @nhost/react@3.6.0

## @nhost-examples/react-native@0.0.6

### Patch Changes

-   Updated dependencies [55d8bb5]
    -   @nhost/react@3.6.0
    -   @nhost/react-apollo@13.0.0

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

### Patch Changes

-   @nhost/nhost-js@3.1.10
-   @nhost/apollo@7.1.7
-   @nhost/vue@2.6.7

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

### Patch Changes

-   @nhost/apollo@7.1.7
-   @nhost/vue@2.6.7

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-25 16:23:50 +01:00
Hassan Ben Jobrane
55d8bb5a89 feat: turnstile (#2895)
### **PR Type**
Enhancement


___

### **Description**
- Integrated Cloudflare Turnstile for signup verification in the
dashboard
- Added support for custom headers in authentication requests
- Updated signup page to include Turnstile component and handle
verification
- Modified authentication machine and fetch utilities to support extra
headers
- Added Turnstile site key to environment variables
- Included react-turnstile package as a new dependency


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>signup.tsx</strong><dd><code>Integrate Turnstile for
signup verification</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/pages/signup.tsx

<li>Integrated Turnstile for signup verification<br> <li> Added state
management for Turnstile response<br> <li> Updated signUpEmailPassword
function to include Turnstile response in <br>headers<br> <li> Added
Turnstile component to the signup form<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2895/files#diff-fc2b5989e3bbafda1d3d8b2317d24c39ef2b8cec0c4dc410170fa2da13464f68">+29/-5</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>machine.ts</strong><dd><code>Add header support for
authentication requests</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

packages/hasura-auth-js/src/machines/authentication/machine.ts

<li>Modified postRequest function to accept headers parameter<br> <li>
Updated signUpEmailPassword action to include headers in the request<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2895/files#diff-a8fdfee087ad5a72ea0a64667e2a0c7f25baa84eaaf73ebfee3f5a5a1b7584d1">+18/-9</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>options.ts</strong><dd><code>Add headers option to
registration interface</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

packages/hasura-auth-js/src/types/options.ts

- Added headers property to RegistrationOptions interface



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2895/files#diff-087479bfe46a1294bc277b5497fbd3f7638853290b8f521ed5e5cb374786812f">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>fetch.ts</strong><dd><code>Enhance fetch utilities with
extra headers support</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

packages/hasura-auth-js/src/utils/fetch.ts

<li>Modified fetchWrapper function to accept and merge extra headers<br>
<li> Updated postFetch function to include extraHeaders parameter<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2895/files#diff-b1af9daf6c51514d5d514540f2318d87e926c5e8a57079b6e2c258b98a1163a2">+13/-4</a>&nbsp;
&nbsp; </td>

</tr>                    
</table></td></tr><tr><td><strong>Configuration
changes</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>.env.example</strong><dd><code>Add Turnstile site key
to environment variables</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/.env.example

- Added NEXT_PUBLIC_TURNSTILE_SITE_KEY variable



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2895/files#diff-b47cf46119af2f0298d96e5657e53e57161833e8b02d87526ac5c1ed9393d477">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    
</table></td></tr><tr><td><strong>Dependencies</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Add Turnstile React
component dependency</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

dashboard/package.json

- Added @marsidev/react-turnstile dependency



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2895/files#diff-2d8d55c799cd71f1b35e831f075f8178ed1734c4820a2ad548b4dd24d6938d7c">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2024-09-25 16:01:40 +01:00
David BM
18f942f464 fix (dashboard): long error message in error toast overflow (#2892) 2024-09-23 13:42:41 -04:00
David BM
2a2e54c4d8 fix (dashboard): update url to docs in run services tooltip (#2886) 2024-09-21 11:16:21 -04:00
github-actions[bot]
6a735523b4 chore: update versions (#2880)
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/nextjs@2.1.21

### Patch Changes

- 52a38fe: chore: update dependencies to address security
vulnerabilities

## @nhost/dashboard@1.28.2

### Patch Changes

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

## @nhost/docs@2.17.2

### Patch Changes

-   52a38fe: chore: added pg_ivm extension

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

### Patch Changes

- 52a38fe: chore: update dependencies to address security
vulnerabilities

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

### Patch Changes

- 52a38fe: chore: update dependencies to address security
vulnerabilities

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

### Patch Changes

- 52a38fe: chore: update dependencies to address security
vulnerabilities

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

### Patch Changes

- 52a38fe: chore: update dependencies to address security
vulnerabilities

## @nhost-examples/nextjs@0.3.12

### Patch Changes

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

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

### Patch Changes

- 52a38fe: chore: update dependencies to address security
vulnerabilities

## @nhost-examples/sveltekit@0.4.1

### Patch Changes

- 52a38fe: chore: update dependencies to address security
vulnerabilities

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

### Patch Changes

- 4d6b722: fix: add check for elevated permission before deleting a
security key
- 3dcbacf: fix: add elevated permission check before adding a security
key
- 52a38fe: chore: update dependencies to address security
vulnerabilities

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

### Patch Changes

- 52a38fe: chore: update dependencies to address security
vulnerabilities

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

### Patch Changes

- 52a38fe: chore: update dependencies to address security
vulnerabilities

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

### Patch Changes

- 52a38fe: chore: update dependencies to address security
vulnerabilities

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-19 12:23:04 +01:00
Hassan Ben Jobrane
4d6b7228d9 fix(react-apollo): add elevated permission check before deleting a security key (#2883)
### **PR Type**
Enhancement, Bug fix


___

### **Description**
- Enhanced security key management by adding elevated permission checks:
- Implemented `elevatePermission` function to centralize permission
elevation logic
  - Added permission check before deleting a security key
- Modified `onSubmit` function to use `elevatePermission` before adding
a key
- Refactored code structure for improved readability and
maintainability:
  - Moved `removeKey` mutation definition earlier in the component
  - Simplified conditional logic in `onSubmit` function
- Added a changeset file to document the bug fix for elevated permission
check


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>security-keys.tsx</strong><dd><code>Enhance security
key management with permission checks</code>&nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

examples/react-apollo/src/components/profile/security-keys.tsx

<li>Added <code>elevatePermission</code> function to handle permission
elevation logic<br> <li> Modified <code>onSubmit</code> function to use
<code>elevatePermission</code> before adding a <br>key<br> <li> Added
permission check before deleting a security key<br> <li> Refactored code
structure for better readability and maintainability<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2883/files#diff-20c5d7ececb3f500fc179a36ec957b0744197e88ca47d050e29b401967781be3">+43/-31</a>&nbsp;
</td>

</tr>                    
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>lucky-mirrors-relate.md</strong><dd><code>Add changeset
for security key deletion fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

.changeset/lucky-mirrors-relate.md

<li>Added a changeset file to document the bug fix for elevated
permission <br>check<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2883/files#diff-3a2d1f6a2da0ea9311d8ad2c20128f3d06defc1e24ae9c8a2f0d71f82246cb79">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
2024-09-19 11:56:55 +01:00
Hassan Ben Jobrane
3dcbacf188 fix(react-apollo): add elevation check before adding security key (#2882)
### **PR Type**
Enhancement


___

### **Description**
- Implemented an elevation check before adding a security key when there
are existing keys
- Added new imports from '@nhost/react': `useElevateSecurityKeyEmail`
and `useUserEmail`
- Integrated `elevated` and `elevateEmailSecurityKey` from the
`useElevateSecurityKeyEmail` hook
- Added error handling for the elevation process
- Updated the `onSubmit` function to include the elevation check and
process
- Improved security by requiring elevation for adding subsequent
security keys


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>security-keys.tsx</strong><dd><code>Add elevation check
for security key addition</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/profile/security-keys.tsx

<li>Added import for <code>useElevateSecurityKeyEmail</code> and
<code>useUserEmail</code> from <br>'@nhost/react'<br> <li> Implemented
elevation check before adding a security key<br> <li> Added error
handling for elevation process<br> <li> Integrated <code>elevated</code>
and <code>elevateEmailSecurityKey</code> from
<br><code>useElevateSecurityKeyEmail</code> hook<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2882/files#diff-20c5d7ececb3f500fc179a36ec957b0744197e88ca47d050e29b401967781be3">+24/-2</a>&nbsp;
&nbsp; </td>

</tr>                    
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
2024-09-19 11:14:56 +01:00
David Barroso
5c2269ef92 chore (docs): place the grafana files in the right place (#2881)
### **PR Type**
Enhancement, Documentation


___

### **Description**
- Added a comprehensive set of Grafana configuration files and
dashboards for enhanced observability of Nhost projects.
- Introduced new dashboards for Functions metrics, GraphQL metrics,
Ingress metrics, and overall Project metrics.
- Implemented Grafana setup scripts and configuration files for data
sources, contact points, and notification policies.
- Created Nhost-specific alerting rules for critical issues such as high
CPU usage, low disk space, and high error rates.
- Improved documentation by adding detailed comments and descriptions in
configuration files.
- Enhanced customization options by using environment variables and
templates in various configuration files.


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Configuration
changes</strong></td><td><details><summary>6 files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>setup_config.sh</strong><dd><code>Add Grafana setup
configuration script</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

observability/grafana/setup_config.sh

<li>Added a new shell script to set up Grafana configuration<br> <li>
Script creates necessary directories and generates datasources
<br>configuration<br> <li> Uses environment variables and templates for
dynamic configuration<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2881/files#diff-8c8a7932243229ff1b184b232685f85e894dc85cd13fbc7e6203de6826f05f19">+12/-1</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>contact_points.yaml</strong><dd><code>Add Grafana
contact points configuration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

observability/grafana/contact_points.yaml

<li>Added a new YAML file for Grafana contact points configuration<br>
<li> Includes settings for email, Pagerduty, Discord, Slack, and webhook
<br>notifications<br> <li> Uses templating for dynamic configuration
based on user settings<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2881/files#diff-9262f9a9442457b302afbed9a64604007bec161fb8eed794f3e75a79a3e24a8a">+59/-1</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>dashboards_providers.yaml</strong><dd><code>Add Grafana
dashboard providers configuration</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

observability/grafana/dashboards_providers.yaml

<li>Added a new YAML file for Grafana dashboard providers
configuration<br> <li> Configures the source of dashboards to be loaded
into Grafana<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2881/files#diff-bfdc9ab104d266e0cdc3153476252536d83aeb13905c423c95f1fc9a4a135cdf">+11/-1</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>datasources.yaml.tmpl</strong><dd><code>Add Grafana
data sources configuration template</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

observability/grafana/datasources.yaml.tmpl

<li>Added a new YAML template file for Grafana data sources
configuration<br> <li> Configures Prometheus as the default data source
for Grafana<br> <li> Uses environment variables for dynamic
configuration<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2881/files#diff-07d3ef0d2277f144efb00f0e400cbdcb0a1474c9059dfa1ae281bf2388c0992c">+18/-1</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>grafana.ini</strong><dd><code>Add Grafana main
configuration file</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

observability/grafana/grafana.ini

<li>Added a new INI file for Grafana main configuration<br> <li>
Includes settings for analytics, logging, paths, and server options<br>
<li> Configures SMTP if provided in the environment<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2881/files#diff-144fcadb330865a0b6bcbedd327d5f5b3d72c458a7805c2449f7d8bb568e13f5">+24/-1</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>notification_policies.yaml</strong><dd><code>Add
Grafana notification policies configuration</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

observability/grafana/notification_policies.yaml

<li>Added a new YAML file for Grafana notification policies<br> <li>
Configures the default receiver for alerts<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2881/files#diff-b79528bf44b485a4e87a327f2b82b78b6b409e06a6077eb9f6c5eea24653193d">+8/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    

</table></details></td></tr><tr><td><strong>Enhancement</strong></td><td><details><summary>5
files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>dashboard_functions_metrics.json</strong><dd><code>Add
Grafana dashboard for Functions metrics</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

observability/grafana/dashboard_functions_metrics.json

<li>Added a new JSON file for Grafana dashboard configuration<br> <li>
Dashboard focuses on Functions metrics including invocations, response
<br>times, and errors<br> <li> Includes various panels and
visualizations for comprehensive <br>monitoring<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2881/files#diff-b2815589932503d2d05f9027d550d28f3d2a774106d0f3dec61db8678b12c385">+1280/-1</a></td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>dashboard_graphql.json</strong><dd><code>Add Grafana
dashboard for GraphQL metrics</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

observability/grafana/dashboard_graphql.json

<li>Added a new JSON file for Grafana dashboard configuration<br> <li>
Dashboard focuses on GraphQL metrics including resource utilization
<br>and request rates<br> <li> Includes panels for CPU, memory, and
GraphQL-specific metrics<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2881/files#diff-4f591ab80bee38969fd64c20c4ddab89df3d810d41144089fa23bc1d59a89d4d">+696/-1</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>dashboard_ingress_metrics.json</strong><dd><code>Add
Grafana dashboard for Ingress metrics</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

observability/grafana/dashboard_ingress_metrics.json

<li>Added a new JSON file for Grafana dashboard configuration<br> <li>
Dashboard focuses on Ingress metrics including requests, response
<br>status, and errors<br> <li> Includes panels for various
ingress-related metrics and visualizations<br> <br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2881/files#diff-058aae47dde92553225c557b839f7359ad1deefe341f9a119f63b6875352bb55">+777/-1</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>dashboard_project_metrics.json</strong><dd><code>Add
Grafana dashboard for Project-wide metrics</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

observability/grafana/dashboard_project_metrics.json

<li>Added a new JSON file for Grafana dashboard configuration<br> <li>
Dashboard provides an overview of project-wide metrics<br> <li> Includes
panels for CPU, memory, network, and service-specific metrics<br> <br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2881/files#diff-1db1a1e7d7715e17415ec82d96997af4bb235de0f752711e2a6c27420067bb54">+2038/-1</a></td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>rules_nhost.yaml</strong><dd><code>Add Nhost-specific
Grafana alerting rules</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

observability/grafana/rules_nhost.yaml

<li>Added a new YAML file for Grafana alerting rules<br> <li> Includes
rules for high CPU usage, low disk space, low memory, OOM <br>kills, and
high error rates<br> <li> Rules are customized for Nhost services and
include detailed <br>annotations<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2881/files#diff-27165812186176e21d13a35136e43511b837700a599d3a00c61a1f6b36c55af2">+370/-1</a>&nbsp;
</td>

</tr>                    
</table></details></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
2024-09-18 15:36:32 +02:00
David Barroso
52a38feca7 chore (docs): added pg_ivm extension (#2879)
### **PR Type**
Enhancement, Documentation


___

### **Description**
- Added documentation for the pg_ivm (Incremental View Maintenance)
PostgreSQL extension
- Included a new section in the database extensions guide explaining
pg_ivm's purpose and benefits
- Provided SQL commands for installing and uninstalling the pg_ivm
extension
- Added a link to the pg_ivm GitHub repository for further reference
- Created a changeset file to track the documentation update


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>rare-wolves-tease.md</strong><dd><code>Add changeset
for pg_ivm extension documentation</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/rare-wolves-tease.md

<li>Added a new changeset file for documenting the addition of pg_ivm
<br>extension<br> <li> Specified a patch update for '@nhost/docs'<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2879/files#diff-da3e674eeabc0ad81e3cc5534f4c3a67cf1c6ecae9fbed354e5cc0e4d069493b">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>extensions.mdx</strong><dd><code>Add documentation for
pg_ivm extension</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

docs/guides/database/extensions.mdx

<li>Added a new section for the pg_ivm extension<br> <li> Provided an
overview of Incremental View Maintenance (IVM)<br> <li> Included
installation and uninstallation SQL commands<br> <li> Added a link to
the pg_ivm GitHub repository<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2879/files#diff-7a41fa45d84db83a8c01a76ddb42ad614022ad94a4c3a6aa321f5b9a5300da8c">+26/-0</a>&nbsp;
&nbsp; </td>

</tr>                    
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions

---------

Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-09-18 14:49:37 +02:00
github-actions[bot]
f218058c89 chore: update versions (#2869)
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.28.1

### Patch Changes

-   9735fa2: chore: remove broken link

## @nhost/docs@2.17.1

### Patch Changes

-   db2f44d: fix: update rate-limit to reflect reality
- dda0c67: chore: udpate metrics documentation with managed
configuration

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-18 11:40:25 +01:00
David Barroso
dda0c67fa4 chore (docs): udpate metrics documentation with managed configuration (#2871)
### **PR Type**
Enhancement, Documentation


___

### **Description**
- Enhanced metrics documentation with detailed information on Grafana
configuration, contact points, SMTP settings, and alerting.
- Added new configuration files for Grafana, including setup for
datasources, dashboards, contact points, and alerting rules.
- Updated existing dashboard configurations to use the "nhost"
datasource and improve legend formatting.
- Introduced a setup script to automate Grafana configuration
generation.
- Restructured documentation navigation for better organization of
metrics-related content.
- Added README with instructions for contributing new Grafana
dashboards.
- Implemented comprehensive alerting rules for various system metrics
and error conditions.


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Configuration
changes</strong></td><td><details><summary>11 files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>setup_config.sh</strong><dd><code>Add Grafana
configuration setup script</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

observability/setup_config.sh

<li>New script to set up Grafana configuration<br> <li> Creates
datasources directory<br> <li> Retrieves token and app ID<br> <li>
Generates datasources.yaml file<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2871/files#diff-020b65994838bf8f896b973c08c3d1f32fb26df56981eee8feec396adddc0fa6">+11/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>contact_points.yaml</strong><dd><code>Add Grafana
contact points configuration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

observability/contact_points.yaml

<li>New file for configuring Grafana contact points<br> <li> Includes
settings for email, PagerDuty, Discord, Slack, and webhook
<br>notifications<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2871/files#diff-9b849dd13ecd160bb71d0dbd99677bbc8cd455950a49d2a2c5e0faa12d84de62">+58/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>

<summary><strong>dashboard_functions_metrics.json</strong><dd><code>Update
Functions dashboard configuration</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

observability/dashboard_functions_metrics.json

<li>Updated datasource UID from "prometheus" to "nhost"<br> <li>
Modified legend format to use print statements<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2871/files#diff-43ed0168e8291fdeb852ae00ee52f832e6e54b38d7957ad59fb3f3d2bcfa9bb0">+41/-39</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>dashboard_graphql.json</strong><dd><code>Update GraphQL
dashboard configuration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

observability/dashboard_graphql.json

<li>Updated datasource UID from "prometheus" to "nhost"<br> <li>
Modified legend format to use print statements<br> <li> Added "nhost"
tag to dashboard<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2871/files#diff-da87c05a307528ead905fc17fb6d75eb31b44769d06714c66233f489cbdbb1f2">+24/-22</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>

<summary><strong>dashboard_ingress_metrics.json</strong><dd><code>Update
Ingress Metrics dashboard configuration</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

observability/dashboard_ingress_metrics.json

<li>Removed __inputs, __elements, and __requires sections<br> <li>
Updated datasource UID from "prometheus" to "nhost"<br> <li> Modified
legend format to use print statements<br> <li> Added "nhost" tag to
dashboard<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2871/files#diff-de5c4d7cc3aa858822d9243161b50924d04e290013eb4a738f19bc07a79b1ed7">+25/-54</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>

<summary><strong>dashboard_project_metrics.json</strong><dd><code>Update
Project Metrics dashboard configuration</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

observability/dashboard_project_metrics.json

<li>Removed __inputs, __elements, and __requires sections<br> <li>
Updated datasource UID from "${DS_PROMETHEUS}" to "nhost"<br> <li>
Modified legend format to use print statements<br> <li> Updated
schemaVersion and removed templating list<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2871/files#diff-18cfde5980fad509ab3a14485f1ce3e7f89540854f30a9d932a630c7003065f6">+98/-157</a></td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>dashboards_providers.yaml</strong><dd><code>Add Grafana
dashboard providers configuration</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

observability/dashboards_providers.yaml

<li>New file for configuring Grafana dashboard providers<br> <li> Sets
up file-based dashboard provisioning<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2871/files#diff-c6d162b6c4666e2ee121c37b0d5cffc1b760ed68446e65d5556f083e241765b9">+10/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>datasources.yaml.tmpl</strong><dd><code>Add Grafana
datasource configuration template</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

observability/datasources.yaml.tmpl

<li>New template file for Grafana datasource configuration<br> <li> Sets
up Prometheus datasource with custom query parameters and
<br>authorization<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2871/files#diff-883ff1a7b6c26d41604cfb7fbe7444c568f9379d408a140f0e88047b1768468e">+17/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>grafana.ini</strong><dd><code>Add Grafana main
configuration file</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

observability/grafana.ini

<li>New configuration file for Grafana<br> <li> Includes settings for
analytics, logging, paths, and server<br> <li> Conditional SMTP
configuration<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2871/files#diff-69effef5d34dd2b15f66a1ff7eb524de80e14e82b6ffd63ce3a9cf84fcfa2128">+23/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>notification_policies.yaml</strong><dd><code>Add
Grafana notification policies configuration</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

observability/notification_policies.yaml

<li>New file for configuring Grafana notification policies<br> <li> Sets
up a default policy for the "Nhost Managed Contacts" receiver<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2871/files#diff-15f73217844e330f8cbf0b98becf9ba1712ded93168ec52ffd10ad7af58326e9">+7/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>rules_nhost.yaml</strong><dd><code>Add Grafana alerting
rules configuration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

observability/rules_nhost.yaml

<li>New file for configuring Grafana alerting rules<br> <li> Includes
rules for high CPU usage, low disk space, low memory, OOM <br>kills, and
high error rates<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2871/files#diff-2be0d3f7ec2e1a61cf05bbac1c46b6e14a822af4797d27d2c0caaa5205de88ec">+369/-0</a>&nbsp;
</td>

</tr>                    

</table></details></td></tr><tr><td><strong>Documentation</strong></td><td><details><summary>3
files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>mint.json</strong><dd><code>Restructure documentation
navigation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

docs/mint.json

<li>Removed nested "Monitoring" group<br> <li> Moved "platform/metrics"
to main "Platform" group<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2871/files#diff-c91a604899dfef4b2494c317f4fd39a7f22b79986095f580399347293d534deb">+1/-5</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>metrics.mdx</strong><dd><code>Enhance metrics
documentation with configuration details</code>&nbsp; </dd></summary>
<hr>

docs/platform/metrics.mdx

<li>Added info about Pro/Team/Enterprise feature<br> <li> Expanded
sections on accessing and configuring Grafana<br> <li> Added details
about contact points, SMTP, and alerting configuration<br> <li> Included
information about advanced configuration options<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2871/files#diff-433c57c7c7811809819b3683a23368324a93a9eac7a4ab121b54d16414452f6d">+124/-6</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>README.md</strong><dd><code>Add README for Grafana
dashboard contributions</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

observability/grafana/README.md

<li>New README file with instructions for contributing dashboards<br>
<li> Outlines steps to export and save dashboard files<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2871/files#diff-83ce6f1e076f43acbdcb8cfeac5c2caa0a1d87116c25c1cb063ae0b10b7b6885">+9/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    
</table></details></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
2024-09-17 09:43:44 +02:00
David Barroso
db2f44d7c0 fix (docs): update rate-limit to reflect reality (#2870)
### **PR Type**
Documentation


___

### **Description**
- Updated the rate limit for email sending endpoints in the
documentation
- Changed the limit from 50 per hour to 10 per hour for projects without
custom SMTP settings
- This change reflects the actual rate limit implemented in the system
- No other changes were made to the rate limits table or surrounding
text


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>rate-limits.mdx</strong><dd><code>Update email rate
limit in documentation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

docs/platform/rate-limits.mdx

<li>Updated the rate limit for email sending endpoints from 50/hour to
<br>10/hour for projects without custom SMTP settings<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2870/files#diff-d6c7ab75a347c1217107fdcf841312df268708bc7d319f528ea67c7280f00284">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions

---------

Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-09-13 12:19:08 +02:00
David Barroso
9735fa238b chore (dashboard): remove broken link (#2868)
### **PR Type**
Enhancement, Documentation


___

### **Description**
- Removed a broken "Learn More" link from the DataBrowserSidebar
component in the dashboard
- Added a changeset file to document the removal of the broken link
- Introduced a new GitHub Actions workflow for AI-powered pull request
reviews
- The new workflow uses the PR Agent action with specific configurations
for OpenAI and Anthropic models
- Updated the project structure to improve documentation and automate
code review processes


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>DataBrowserSidebar.tsx</strong><dd><code>Remove "Learn
More" link from DataBrowserSidebar</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/database/dataGrid/components/DataBrowserSidebar/DataBrowserSidebar.tsx

<li>Removed a "Learn More" link with an arrow icon<br> <li> The link was
pointing to GitHub integration documentation<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2868/files#diff-6c0c7b86959eb51f0ef884074e8a72725ee505a5759ca4a95126e96f26062e3b">+0/-9</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>gen_ai_review.yaml</strong><dd><code>Add AI-powered PR
review workflow</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.github/workflows/gen_ai_review.yaml

<li>Added a new GitHub Actions workflow for AI-powered PR reviews<br>
<li> Configures the PR Agent action with specific settings and
secrets<br> <li> Sets up triggers for pull request events and issue
comments<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2868/files#diff-d1e4c772e0acb5ce4891df2dd94ba58ffaf6393e8f75493ec7e10cbce1c4992c">+28/-0</a>&nbsp;
&nbsp; </td>

</tr>                    
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>tricky-colts-beg.md</strong><dd><code>Add changeset for
broken link removal</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/tricky-colts-beg.md

<li>Added a new changeset file for @nhost/dashboard<br> <li> Describes
the change as removing a broken link<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2868/files#diff-6564a7547695ab3d9be88cc4977a814f3123f60b2bb10effeb8904997710a950">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions

---------

Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-09-11 19:09:10 +02:00
github-actions[bot]
58dec6e7b2 chore: update versions (#2853)
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@7.1.6

### Patch Changes

-   @nhost/nhost-js@3.1.9

## @nhost/react-apollo@12.0.6

### Patch Changes

-   @nhost/apollo@7.1.6
-   @nhost/react@3.5.6

## @nhost/react-urql@9.0.6

### Patch Changes

-   @nhost/react@3.5.6

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

### Patch Changes

- 8b12426: fix: correct signout to send accessToken when clearing all
session

## @nhost/nextjs@2.1.20

### Patch Changes

-   @nhost/react@3.5.6

## @nhost/nhost-js@3.1.9

### Patch Changes

-   Updated dependencies [8b12426]
    -   @nhost/hasura-auth-js@2.5.6

## @nhost/react@3.5.6

### Patch Changes

-   @nhost/nhost-js@3.1.9

## @nhost/vue@2.6.6

### Patch Changes

-   @nhost/nhost-js@3.1.9

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

### Major Changes

-   cffdec5: feat: rewrite example using shadcn ui components

### Patch Changes

-   @nhost/react@3.5.6
-   @nhost/react-apollo@12.0.6

## @nhost/dashboard@1.28.0

### Minor Changes

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

### Patch Changes

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

## @nhost/docs@2.17.0

### Minor Changes

- cffdec5: feat: update react quickstart guide to use the nhost react
apollo template
-   4cf6677: feat: update list of postgres extensions

## @nhost-examples/cli@0.3.11

### Patch Changes

-   @nhost/nhost-js@3.1.9

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

### Patch Changes

-   @nhost/react@3.5.6
-   @nhost/react-apollo@12.0.6

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

### Patch Changes

-   @nhost/react@3.5.6

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

### Patch Changes

-   @nhost/react@3.5.6
-   @nhost/react-urql@9.0.6

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

### Patch Changes

-   @nhost/nhost-js@3.1.9

## @nhost-examples/nextjs@0.3.11

### Patch Changes

-   @nhost/react@3.5.6
-   @nhost/react-apollo@12.0.6
-   @nhost/nextjs@2.1.20

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

### Patch Changes

-   @nhost/nhost-js@3.1.9

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

### Patch Changes

-   @nhost/nhost-js@3.1.9

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

### Patch Changes

-   @nhost/react@3.5.6

## @nhost-examples/react-native@0.0.5

### Patch Changes

-   @nhost/react@3.5.6
-   @nhost/react-apollo@12.0.6

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

### Patch Changes

-   @nhost/nhost-js@3.1.9
-   @nhost/apollo@7.1.6
-   @nhost/vue@2.6.6

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

### Patch Changes

-   @nhost/apollo@7.1.6
-   @nhost/vue@2.6.6

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-09-09 12:19:53 +01:00
David BM
526183ab88 feat (dashboard): allow filtering users in "make request as" feature of graphql section (#2805)
Resolves #2593
2024-09-09 05:55:49 -04:00
Hassan Ben Jobrane
435b65a65a fix(react-apollo): change relyingParty ID to nhost.io (#2861) 2024-09-04 14:56:54 +01:00
Hassan Ben Jobrane
35a2f1203c fix(e2e): fix run service e2e test (#2859)
### **PR Type**
Bug fix, Enhancement


___

### **Description**
- Fixed the button selector in the end-to-end test for deleting a
service, ensuring the correct button is targeted.
- Refactored the `ServicesList` component to standardize icon sizes and
improve the order of class attributes for better readability.
- Added event stop propagation to the delete service action to prevent
unintended event bubbling.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Bug
fix</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>run.test.ts</strong><dd><code>Fix button selector in
e2e test for service deletion</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

dashboard/e2e/run/run.test.ts

<li>Updated button selector for deleting a service.<br> <li> Removed
unnecessary click action on 'Close' button.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2859/files#diff-3b81821630a8e66e8f580609a834499bdfec9ac228ff07b99f398ec07c329095">+1/-5</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    
</table></td></tr><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>ServicesList.tsx</strong><dd><code>Refactor
ServicesList component for consistency and
readability</code></dd></summary>
<hr>

dashboard/src/features/services/components/ServicesList/ServicesList.tsx

<li>Standardized icon size and order of class attributes.<br> <li> Added
event stop propagation for delete service action.<br> <li> Improved code
readability and consistency.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2859/files#diff-efb3008c23436b2db5bb94de15e91c78cf76ef6481ecb02eb542cf660ba98653">+12/-9</a>&nbsp;
&nbsp; </td>

</tr>                    
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
2024-09-04 11:39:32 +01:00
David BM
be3b85bbc8 feat (dashboard): add conceal errors toggle on auth settings page (#2858) 2024-09-03 10:07:40 -04:00
Hassan Ben Jobrane
cffdec585c feat: templates: react-apollo (#2834)
### **PR Type**
Enhancement, Tests


___

### **Description**
- Added multiple new UI components including `Layout`, `DropdownMenu`,
`Sheet`, `Form`, `Dialog`, `Table`, `Card`, `Button`, `Alert`,
`OAuthLinks`, and more.
- Integrated Apollo Client for GraphQL queries and mutations in various
components.
- Implemented form handling using `react-hook-form` and validation with
`zod`.
- Refactored `App` component to use a new route structure and removed
Mantine components.
- Updated E2E tests for new UI components and improved test reliability
and readability.
- Removed `pnpm-lock.yaml` file.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><details><summary>45
files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>layout.tsx</strong><dd><code>Implement Layout Component
with Navigation and Sign-Out</code>&nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/routes/app/layout.tsx

<li>Added <code>Layout</code> component with navigation and sign-out
functionality.<br> <li> Integrated <code>Tooltip</code>,
<code>DropdownMenu</code>, and <code>Sheet</code> components for UI
<br>elements.<br> <li> Implemented mobile navigation toggle.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-dfb4bf452a609fab7a9d6f9dc7906365ffaed54e830077310f9d505836da2c75">+228/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>dropdown-menu.tsx</strong><dd><code>Add DropdownMenu
Component with Radix UI Integration</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/ui/dropdown-menu.tsx

<li>Added <code>DropdownMenu</code> component with various
sub-components.<br> <li> Integrated Radix UI primitives for dropdown
functionality.<br> <li> Styled dropdown menu items and content.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-8e4a2a51701c3205e7d5199dc9f25d3fe348169ad72d6b4739687b106e7159f2">+198/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>dropdown-menu.tsx</strong><dd><code>Add DropdownMenu
Component with Radix UI Integration</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/ui/dropdown-menu.tsx

<li>Added <code>DropdownMenu</code> component with various
sub-components.<br> <li> Integrated Radix UI primitives for dropdown
functionality.<br> <li> Styled dropdown menu items and content.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-82ecd25c0e1deeffefeb11e66d3b1d625f5cbdaf64934a325360ef519d46734d">+198/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>security-keys.tsx</strong><dd><code>Add SecurityKeys
Component for Managing Security Keys</code>&nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

examples/react-apollo/src/components/profile/security-keys.tsx

<li>Added <code>SecurityKeys</code> component for managing security
keys.<br> <li> Integrated Apollo Client for GraphQL queries and
mutations.<br> <li> Implemented form validation with
<code>react-hook-form</code> and <code>zod</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-20c5d7ececb3f500fc179a36ec957b0744197e88ca47d050e29b401967781be3">+178/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>App.tsx</strong><dd><code>Refactor App Component with
New Route Structure</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/App.tsx

<li>Refactored <code>App</code> component to use new route
structure.<br> <li> Added routes for authentication and application
pages.<br> <li> Removed Mantine components and replaced with custom
layout.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-cce00ef2ed124ca9c4fb6d5a27065cfb227de957db19fee484d79526bd243405">+48/-123</a></td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>layout.tsx</strong><dd><code>Implement Layout Component
with Navigation and Sign-Out</code>&nbsp; &nbsp; </dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/routes/app/layout.tsx

<li>Added <code>Layout</code> component with navigation and sign-out
functionality.<br> <li> Integrated <code>Tooltip</code> and
<code>Sheet</code> components for UI elements.<br> <li> Implemented
mobile navigation toggle.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-c2700d3324f3a2d1b5cb71c45c6599d810d4deb4dedd48a83a1c097ac745546d">+125/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>protected-notes.tsx</strong><dd><code>Add
ProtectedNotes Component for Managing Protected
Notes</code></dd></summary>
<hr>

examples/react-apollo/src/components/routes/app/protected-notes.tsx

<li>Added <code>ProtectedNotes</code> component for managing protected
notes.<br> <li> Integrated Apollo Client for GraphQL queries and
mutations.<br> <li> Implemented permission elevation for secure
actions.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-2ff0d7eccbad3f4bf3769e3b4db6a8c0b2630b5e600a3bae8b565b2ad16df6b3">+196/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sheet.tsx</strong><dd><code>Add Sheet Component with
Radix UI Integration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/ui/sheet.tsx

<li>Added <code>Sheet</code> component with various sub-components.<br>
<li> Integrated Radix UI primitives for sheet functionality.<br> <li>
Styled sheet content and overlay.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-ae92d11cc983a782928cdebe08c27a7b0954a9212e8e60ed339df57cbcca3b71">+138/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sheet.tsx</strong><dd><code>Add Sheet Component with
Radix UI Integration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/ui/sheet.tsx

<li>Added <code>Sheet</code> component with various sub-components.<br>
<li> Integrated Radix UI primitives for sheet functionality.<br> <li>
Styled sheet content and overlay.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-d8caf3ac16eb4b67f59f860323cc131cd02390141f964f9ca2955987d639e8db">+138/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>form.tsx</strong><dd><code>Add Form Component with
React Hook Form Integration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/ui/form.tsx

<li>Added <code>Form</code> component with various sub-components.<br>
<li> Integrated <code>react-hook-form</code> for form handling.<br> <li>
Styled form fields and messages.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-d2f42eb4934b32e1b4572330c17af13138835127adade0989aaa294e78d5b874">+176/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sign-in-email-password.tsx</strong><dd><code>Add
SignInEmailPassword Component for Email/Password
Sign-In</code></dd></summary>
<hr>


examples/react-apollo/src/components/routes/auth/sign-in/sign-in-email-password.tsx

<li>Added <code>SignInEmailPassword</code> component for email/password
sign-in.<br> <li> Integrated <code>react-hook-form</code> for form
handling.<br> <li> Implemented email verification dialog.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-a919b17649003a1b961e1f84a211f7d3913d7c8a57ea5c2c3aed08b9c900b73f">+135/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>form.tsx</strong><dd><code>Add Form Component with
React Hook Form Integration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/ui/form.tsx

<li>Added <code>Form</code> component with various sub-components.<br>
<li> Integrated <code>react-hook-form</code> for form handling.<br> <li>
Styled form fields and messages.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-5367598c9a64936b77080af5c57bd2110e2e835c23b306974b8d84fe8295e7f7">+168/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sign-up-email-password.tsx</strong><dd><code>Add
SignUpEmailPassword Component for Email/Password
Sign-Up</code></dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/routes/auth/sign-up/sign-up-email-password.tsx

<li>Added <code>SignUpEmailPassword</code> component for email/password
sign-up.<br> <li> Integrated <code>react-hook-form</code> for form
handling.<br> <li> Implemented email verification dialog.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-4507fe991337f37a30f72f9411947046df23427c37b97e8c357c609d98288ff4">+143/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sign-up-email-password.tsx</strong><dd><code>Add
SignUpEmailPassword Component for Email/Password
Sign-Up</code></dd></summary>
<hr>


examples/react-apollo/src/components/routes/auth/sign-up/sign-up-email-password.tsx

<li>Added <code>SignUpEmailPassword</code> component for email/password
sign-up.<br> <li> Integrated <code>react-hook-form</code> for form
handling.<br> <li> Implemented email verification dialog.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-4192d80c13ac7212e63c5ad0729fb4b509ac4ff8876fb919be24ab93dbfbdfcc">+143/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>dialog.tsx</strong><dd><code>Add Dialog Component with
Radix UI Integration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/ui/dialog.tsx

<li>Added <code>Dialog</code> component with various sub-components.<br>
<li> Integrated Radix UI primitives for dialog functionality.<br> <li>
Styled dialog content and overlay.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-233c2734414990fe89dc642d88a85cf8964f3c139c4fdb3b8d1845616118d41a">+120/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>dialog.tsx</strong><dd><code>Add Dialog Component with
Radix UI Integration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/ui/dialog.tsx

<li>Added <code>Dialog</code> component with various sub-components.<br>
<li> Integrated Radix UI primitives for dialog functionality.<br> <li>
Styled dialog content and overlay.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-f74c4636490d03c847d91cc3aee450023f28c7ef7a6e3088f900b6498e0bb349">+120/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>todos.tsx</strong><dd><code>Add Todos Component for
Managing To-Do Items</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/routes/app/todos.tsx

<li>Added <code>Todos</code> component for managing to-do items.<br>
<li> Integrated Apollo Client for GraphQL queries and mutations.<br>
<li> Implemented UI for adding and deleting to-dos.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-56a00354330376b2f9bf79a2504fe05450ae972b4206b949f26ac58458c2c6aa">+132/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>upload-multiple-files.tsx</strong><dd><code>Add
UploadMultipleFiles Component for Multiple File
Uploads</code></dd></summary>
<hr>

examples/react-apollo/src/components/storage/upload-multiple-files.tsx

<li>Added <code>UploadMultipleFiles</code> component for uploading
multiple files.<br> <li> Integrated <code>react-dropzone</code> for file
drag-and-drop.<br> <li> Styled file upload progress and status
indicators.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-b8dda6ac0ba5b9703676b38e3ddbbb553c5e1368665c30307ec04e70ef9643ce">+99/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sign-in-email-password.tsx</strong><dd><code>Add
SignInEmailPassword Component for Email/Password
Sign-In</code></dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/routes/auth/sign-in/sign-in-email-password.tsx

<li>Added <code>SignInEmailPassword</code> component for email/password
sign-in.<br> <li> Integrated <code>react-hook-form</code> for form
handling.<br> <li> Implemented email verification dialog.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-b0c349a0e172774781f32b7b2956feadd832e5c25a314858a6de856bb04dc18b">+105/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sign-up-security-key.tsx</strong><dd><code>Add
SignUpSecurityKey Component for Security Key Sign-Up</code>&nbsp;
</dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/routes/auth/sign-up/sign-up-security-key.tsx

<li>Added <code>SignUpSecurityKey</code> component for security key
sign-up.<br> <li> Integrated <code>react-hook-form</code> for form
handling.<br> <li> Implemented email verification dialog.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-d7a694447b86326dc05407d24966d54b3c04749a111fc6f317d2feb9939c05f1">+100/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sign-up-security-key.tsx</strong><dd><code>Add
SignUpSecurityKey Component for Security Key Sign-Up</code>&nbsp;
</dd></summary>
<hr>


examples/react-apollo/src/components/routes/auth/sign-up/sign-up-security-key.tsx

<li>Added <code>SignUpSecurityKey</code> component for security key
sign-up.<br> <li> Integrated <code>react-hook-form</code> for form
handling.<br> <li> Implemented email verification dialog.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-fb97711c3deb9ee7739371997b9f6f5ab4b1ebc926ca37311006c865d9eb97d8">+100/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sign-in-security-key.tsx</strong><dd><code>Add
SignInSecurityKey Component for Security Key Sign-In</code>&nbsp;
</dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/routes/auth/sign-in/sign-in-security-key.tsx

<li>Added <code>SignInSecurityKey</code> component for security key
sign-in.<br> <li> Integrated <code>react-hook-form</code> for form
handling.<br> <li> Implemented email verification dialog.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-95755a5056e7f57caa2eeaec3801e06382792395f7436c60f3e4d0b7382c2703">+99/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sign-in-security-key.tsx</strong><dd><code>Add
SignInSecurityKey Component for Security Key Sign-In</code>&nbsp;
</dd></summary>
<hr>


examples/react-apollo/src/components/routes/auth/sign-in/sign-in-security-key.tsx

<li>Added <code>SignInSecurityKey</code> component for security key
sign-in.<br> <li> Integrated <code>react-hook-form</code> for form
handling.<br> <li> Implemented email verification dialog.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-c53d13936f5d9c0cc31811f17c9721f9d7a2795d0cff4a1333fdb147846f5cd8">+99/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sign-in-magic-link.tsx</strong><dd><code>Add
SignInMagicLink Component for Magic Link Sign-In</code>&nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/routes/auth/sign-in/sign-in-magic-link.tsx

<li>Added <code>SignInMagicLink</code> component for magic link
sign-in.<br> <li> Integrated <code>react-hook-form</code> for form
handling.<br> <li> Implemented email verification dialog.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-6ad692dad5c7240a085ab15146cc515308c295c6118562e81b41db89154a7f2a">+98/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sign-up-magic-link.tsx</strong><dd><code>Add
SignUpMagicLink Component for Magic Link Sign-Up</code>&nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/routes/auth/sign-up/sign-up-magic-link.tsx

<li>Added <code>SignUpMagicLink</code> component for magic link
sign-up.<br> <li> Integrated <code>react-hook-form</code> for form
handling.<br> <li> Implemented email verification dialog.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-756d7107028cebec7b2f6da051f9003fa7689f33083e2388af887dc9de5886c9">+98/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sign-in-magic-link.tsx</strong><dd><code>Add
SignInMagicLink Component for Magic Link Sign-In</code>&nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


examples/react-apollo/src/components/routes/auth/sign-in/sign-in-magic-link.tsx

<li>Added <code>SignInMagicLink</code> component for magic link
sign-in.<br> <li> Integrated <code>react-hook-form</code> for form
handling.<br> <li> Implemented email verification dialog.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-05d6dec736df35bf6b430e76320dff84744131f602c2d3752f82df43180f9660">+98/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>sign-up-magic-link.tsx</strong><dd><code>Add
SignUpMagicLink Component for Magic Link Sign-Up</code>&nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


examples/react-apollo/src/components/routes/auth/sign-up/sign-up-magic-link.tsx

<li>Added <code>SignUpMagicLink</code> component for magic link
sign-up.<br> <li> Integrated <code>react-hook-form</code> for form
handling.<br> <li> Implemented email verification dialog.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-2d9446d69773256ac86513def9db66e8686f9892d74476f6206e86c39ff78176">+98/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>change-password.tsx</strong><dd><code>Refactor
ChangePassword Component with New UI Components</code>&nbsp;
</dd></summary>
<hr>

examples/react-apollo/src/components/profile/change-password.tsx

<li>Refactored <code>ChangePassword</code> component with new UI
components.<br> <li> Integrated Apollo Client for GraphQL queries.<br>
<li> Implemented form validation with <code>react-hook-form</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-d4ea801f2778896b2e41f1f62e1d335b6b77a490a95adac7786bf896b1fd1bac">+40/-33</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>table.tsx</strong><dd><code>Add Table Component with
Styling and Utility Functions</code>&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/ui/table.tsx

<li>Added <code>Table</code> component with various sub-components.<br>
<li> Styled table header, body, and rows.<br> <li> Integrated utility
functions for class names.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-2c2db0130b7a45429d43be2268e27ec92575a83594079a6faad6bb8e1ad7b505">+117/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>forgot-password.tsx</strong><dd><code>Add
ForgotPassword Component for Password Reset</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/routes/auth/forgot-password.tsx

<li>Added <code>ForgotPassword</code> component for password reset.<br>
<li> Integrated <code>react-hook-form</code> for form handling.<br> <li>
Implemented password reset functionality.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-d8be229cc3e22a295099c952ed7553dcbf150e0f4d1e417c20728aee369fc36a">+94/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>mfa.tsx</strong><dd><code>Add Mfa Component for
Managing Multi-Factor Authentication</code></dd></summary>
<hr>

examples/react-apollo/src/components/profile/mfa.tsx

<li>Added <code>Mfa</code> component for managing multi-factor
authentication.<br> <li> Integrated Apollo Client for GraphQL
queries.<br> <li> Implemented QR code generation and activation.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-a64ad0612d2947e40c2d570e7b949c91ec4737d74bbab2a12e5bfffbeb0ff991">+101/-0</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>storage.tsx</strong><dd><code>Add Storage Component for
Managing File Storage</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/routes/app/storage.tsx

<li>Added <code>Storage</code> component for managing file storage.<br>
<li> Integrated Apollo Client for GraphQL queries.<br> <li> Implemented
file download functionality.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-fa67a80832e2e84d6d0bba38a5f241fd5f1c57511b1f652e9557a5d465efc124">+99/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>change-email.tsx</strong><dd><code>Add ChangeEmail
Component for Changing User Email</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/profile/change-email.tsx

<li>Added <code>ChangeEmail</code> component for changing user
email.<br> <li> Integrated Apollo Client for GraphQL queries.<br> <li>
Implemented form validation with <code>react-hook-form</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-96cca643537cb9da5002543183dd894548fc0d7e34945686da8b966de79ec998">+91/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>forgot-password.tsx</strong><dd><code>Add
ForgotPassword Component for Password Reset</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/routes/auth/forgot-password.tsx

<li>Added <code>ForgotPassword</code> component for password reset.<br>
<li> Integrated <code>react-hook-form</code> for form handling.<br> <li>
Implemented password reset functionality.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-3a657829492f9fb34157a7b9bc72f8e2de051710b9a9fe3d52c1f868ae39ff07">+74/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>upload-single-file.tsx</strong><dd><code>Add
UploadSingleFile Component for Single File Upload</code>&nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/storage/upload-single-file.tsx

<li>Added <code>UploadSingleFile</code> component for uploading a single
file.<br> <li> Integrated <code>react-dropzone</code> for file
drag-and-drop.<br> <li> Styled file upload progress and status
indicators.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-e21ae43a38d184164fc3dd791f396fd3a13f6a2c95fee31b1797252a4f16c56e">+69/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>upload-single-file.tsx</strong><dd><code>Add
UploadSingleFile Component for Single File Upload</code>&nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/storage/upload-single-file.tsx

<li>Added <code>UploadSingleFile</code> component for uploading a single
file.<br> <li> Integrated <code>react-dropzone</code> for file
drag-and-drop.<br> <li> Styled file upload progress and status
indicators.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-cf8adab15beb189ea30207b9c4ebc9ed4ef0fe9901a9cc06b1d02aafba9d7d46">+64/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>card.tsx</strong><dd><code>Add Card Component with
Styling and Utility Functions</code>&nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/ui/card.tsx

<li>Added <code>Card</code> component with various sub-components.<br>
<li> Styled card header, content, and footer.<br> <li> Integrated
utility functions for class names.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-6c08563777faac205d377afaf3160c75e80474663f2554e5dc23c691b82f2cfb">+79/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>card.tsx</strong><dd><code>Add Card Component with
Styling and Utility Functions</code>&nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

examples/react-apollo/src/components/ui/card.tsx

<li>Added <code>Card</code> component with various sub-components.<br>
<li> Styled card header, content, and footer.<br> <li> Integrated
utility functions for class names.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-caa6940dfb36b33fb7431d74fc74bd86a0ab549ac07ee4f8931767f2e401f22b">+79/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>connect-github.tsx</strong><dd><code>Add ConnectGithub
Component for Connecting GitHub Account</code></dd></summary>
<hr>

examples/react-apollo/src/components/profile/connect-github.tsx

<li>Added <code>ConnectGithub</code> component for connecting GitHub
account.<br> <li> Integrated Apollo Client for GraphQL queries.<br> <li>
Implemented GitHub connection status and link.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-8fb7c2dd3c82dfc83ad53118b884af3fd7563f7d8694da1afbe170b5e523570a">+63/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>oauth-links.tsx</strong><dd><code>Add OAuthLinks
Component for Social Sign-In Options</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/auth/oauth-links.tsx

<li>Added <code>OAuthLinks</code> component for social sign-in
options.<br> <li> Integrated provider links for GitHub, Google, Apple,
and LinkedIn.<br> <li> Styled social sign-in buttons.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-f5999fa99948c7a83619e69ab669da87ca10146ad5742f93112e21b00932bc0e">+58/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>oauth-links.tsx</strong><dd><code>Add OAuthLinks
Component for Social Sign-In Options</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/auth/oauth-links.tsx

<li>Added <code>OAuthLinks</code> component for social sign-in
options.<br> <li> Integrated provider links for GitHub, Google, Apple,
and LinkedIn.<br> <li> Styled social sign-in buttons.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-97b7775bbdf0f75091067d77b6638f6a81a15467e2ab080a769602c7ab345010">+56/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>App.tsx</strong><dd><code>Add App Component with Route
Structure</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

templates/cra-template-nhost-react-apollo-template/template/src/App.tsx

<li>Added <code>App</code> component with route structure.<br> <li>
Integrated authentication and application routes.<br> <li> Implemented
layout and page components.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-4a551eeb9e74ac9467ea078b159befed8370562b592bb09124200bbbdb146c3d">+51/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>button.tsx</strong><dd><code>Add Button Component with
Styling and Utility Functions</code>&nbsp; &nbsp; </dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/ui/button.tsx

<li>Added <code>Button</code> component with various styles.<br> <li>
Integrated utility functions for class names.<br> <li> Styled button
variants and sizes.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-5f3def0840b2a2d0c618123db3b679f5afa7bfe089301e6df15556ea7ea567de">+56/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>button.tsx</strong><dd><code>Add Button Component with
Styling and Utility Functions</code>&nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/src/components/ui/button.tsx

<li>Added <code>Button</code> component with various styles.<br> <li>
Integrated utility functions for class names.<br> <li> Styled button
variants and sizes.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-66624bef5f7762c254328a05baf2e434d8a431d2778ba5d51de1fb8d603090d2">+50/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>alert.tsx</strong><dd><code>Add Alert Component with
Styling and Utility Functions</code>&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


templates/cra-template-nhost-react-apollo-template/template/src/components/ui/alert.tsx

<li>Added <code>Alert</code> component with various styles.<br> <li>
Integrated utility functions for class names.<br> <li> Styled alert
variants and descriptions.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-223df6c627e0f93e279f9570b192ed98ac8c5cbe0e5dd553ce0e5dd6b8cbff8c">+59/-0</a>&nbsp;
&nbsp; </td>

</tr>                    

</table></details></td></tr><tr><td><strong>Tests</strong></td><td><details><summary>6
files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>utils.ts</strong><dd><code>Update Utility Functions for
E2E Tests</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

examples/react-apollo/e2e/utils.ts

<li>Updated utility functions for E2E tests.<br> <li> Modified selectors
and actions for new UI components.<br> <li> Improved test reliability
and readability.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-3bdd9b675af03a22eb7e8077183e8179504a9c3a085980da4938fd0c5e4b8907">+15/-19</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>file-upload.test.ts</strong><dd><code>Update File
Upload E2E Tests</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/e2e/authenticated/file-upload.test.ts

<li>Updated file upload E2E tests.<br> <li> Modified selectors and
actions for new UI components.<br> <li> Improved test reliability and
readability.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-ad496daecb0035a49fe21b4b54182bcca8188a20475bcc79a21bd433914a9200">+21/-13</a>&nbsp;
</td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>change-email.test.ts</strong><dd><code>Update Change
Email E2E Tests</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

examples/react-apollo/e2e/authenticated/change-email.test.ts

<li>Updated change email E2E tests.<br> <li> Modified selectors and
actions for new UI components.<br> <li> Improved test reliability and
readability.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-5bf556a7e19bcc9932603bd52dd41929f1cabd65924ea88ad4123efcd9daad13">+26/-9</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>apollo.test.ts</strong><dd><code>Update Apollo E2E
Tests</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/e2e/authenticated/apollo.test.ts

<li>Updated Apollo E2E tests.<br> <li> Modified selectors and actions
for new UI components.<br> <li> Improved test reliability and
readability.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-56686466b936643bcf1a1b180a367f11e52a123df8f1298f31a9eb077ddd4ffd">+13/-9</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>change-password.test.ts</strong><dd><code>Update Change
Password E2E Tests</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

examples/react-apollo/e2e/authenticated/change-password.test.ts

<li>Updated change password E2E tests.<br> <li> Modified selectors and
actions for new UI components.<br> <li> Improved test reliability and
readability.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-ab8eb0581f78f98285a7d016170b4f076ecda83ad84a40a4a167ed1601b38874">+19/-6</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>email-password.test.ts</strong><dd><code>Update
Email/Password Sign-In E2E Tests</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/react-apollo/e2e/sign-in/email-password.test.ts

<li>Updated email/password sign-in E2E tests.<br> <li> Modified
selectors and actions for new UI components.<br> <li> Improved test
reliability and readability.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2834/files#diff-ea8f3b725904df5b4d7881bc3e2d66db6e50b2d78c836c04041b387e84e22262">+4/-5</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    
</table></details></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
2024-09-03 09:40:58 +01:00
Hassan Ben Jobrane
8b12426157 fix(hasura-auth-js): update signout to use accessToken when clearing all sessions (#2857)
### **User description**
fixes https://github.com/nhost/nhost/issues/2836


___

### **PR Type**
Bug fix, Enhancement


___

### **Description**
- Enhanced the signout process to utilize the access token when clearing
all sessions.
- Updated the `signingOut` state to manage both access and refresh
tokens.
- Introduced a new function `destroyAccessToken` to clear the access
token.
- Adjusted type definitions to reflect changes in the signout process.
- Added a changeset document to outline the patch changes.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>machine.ts</strong><dd><code>Enhance signout process to
handle access token</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

packages/hasura-auth-js/src/machines/authentication/machine.ts

<li>Updated <code>signingOut</code> state to handle access token.<br>
<li> Modified <code>clearContextExceptTokens</code> to retain access
token.<br> <li> Added <code>destroyAccessToken</code> function to clear
access token.<br> <li> Updated <code>signout</code> function to
optionally use access token.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2857/files#diff-a8fdfee087ad5a72ea0a64667e2a0c7f25baa84eaaf73ebfee3f5a5a1b7584d1">+24/-9</a>&nbsp;
&nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>machine.typegen.ts</strong><dd><code>Update type
definitions for signout enhancements</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

packages/hasura-auth-js/src/machines/authentication/machine.typegen.ts

<li>Updated type definitions for
<code>clearContextExceptTokens</code>.<br> <li> Added type definitions
for <code>destroyAccessToken</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2857/files#diff-b0050ab06a8f00d3ae5decd65565adb1bdae3b4b6d19d4f67b9013ffb14e18ee">+11/-1</a>&nbsp;
&nbsp; </td>

</tr>                    
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>popular-rabbits-bake.md</strong><dd><code>Add changeset
for signout fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

.changeset/popular-rabbits-bake.md

- Added changeset for signout fix using access token.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2857/files#diff-cfd522f2d71d4a7da79f890c87603a0d8e85f066701af747b08712f1fd8890cd">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
2024-09-02 14:28:13 +01:00
David Barroso
4cf6677284 feat (docs): update list of postgres extensions (#2852)
### **PR Type**
Documentation, Enhancement, Other


___

### **Description**
- Updated the documentation to include new PostgreSQL extensions:
`hypopg`, `http`, `pg_hashids`, and `pg_squeeze`.
- Provided detailed installation and uninstallation instructions for
each new extension.
- Updated the `timescaledb` extension information and resources.
- Addressed vulnerabilities by updating the `svelte` dependency in the
SvelteKit quickstart example.
- Added `webpack` as a new dependency in the main `package.json`.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>hungry-news-film.md</strong><dd><code>Add changeset
entry for PostgreSQL extensions update</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

.changeset/hungry-news-film.md

<li>Added changeset entry for updating the list of PostgreSQL
extensions.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2852/files#diff-f070b809ea80a0cd0c3718b71b17f2a46eb6d96fbef75ef5cc19de9d870ccefb">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>extensions.mdx</strong><dd><code>Update and expand
PostgreSQL extensions documentation</code>&nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

docs/guides/database/extensions.mdx

<li>Added new PostgreSQL extensions: <code>hypopg</code>,
<code>http</code>, <code>pg_hashids</code>, <code>pg_squeeze</code>.<br>
<li> Updated information for <code>timescaledb</code>.<br> <li> Provided
installation and uninstallation instructions for each <br>extension.<br>
<li> Included resource links for each extension.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2852/files#diff-7a41fa45d84db83a8c01a76ddb42ad614022ad94a4c3a6aa321f5b9a5300da8c">+73/-23</a>&nbsp;
</td>

</tr>                    
</table></td></tr><tr><td><strong>Dependencies</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Update Svelte dependency
version in package.json</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/quickstarts/sveltekit/package.json

- Updated `svelte` dependency version to `^4.2.19`.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2852/files#diff-6288951fff74ec246c9cc023b7b7e3e9aad31423891bc4ea25b5d84a5f5b061f">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Add webpack dependency
to package.json</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

package.json

- Added `webpack` dependency with version `^5.94.0`.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2852/files#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519">+2/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions

---------

Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-09-02 08:37:56 +02:00
github-actions[bot]
fdaaf19057 chore: update versions (#2844)
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.27.0

### Minor Changes

-   a7cd02c: fix: resolve rate limit query

## @nhost/docs@2.16.0

### Minor Changes

-   ba55c1b: feat: run: added a guide on using a private registry
-   3d70c63: feat: added rate-limiter guide for auth service

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-27 14:51:52 +01:00
David BM
a7cd02c965 fix (dashboard): resolve rate limit query (#2845)
### **PR Type**
Bug fix, Enhancement


___

### **Description**
- Removed the 'Auth' switch from the `AuthLimitingForm` component to
simplify the settings interface.
- Updated the rate limit query in `useGetRateLimits` hook to resolve by
default, fixing a potential issue.
- Added a changeset to document the fix for the rate limit query.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>AuthLimitingForm.tsx</strong><dd><code>Remove 'Auth'
switch from AuthLimitingForm component</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>


dashboard/src/features/projects/rate-limiting/settings/components/AuthLimitingForm/AuthLimitingForm.tsx

<li>Removed the 'Auth' switch from the settings container.<br> <li>
Simplified the form component by removing unused props.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2845/files#diff-cd300f74c3d921fde5b705b25f63e22a3e66dfb9182ca818102cb1a5f508eb5e">+0/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    
</table></td></tr><tr><td><strong>Bug fix</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>useGetRateLimits.ts</strong><dd><code>Update rate limit
query to resolve by default</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/projects/rate-limiting/settings/hooks/useGetRateLimits/useGetRateLimits.ts

- Changed the 'resolve' variable to true in the rate limit query.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2845/files#diff-82e380da100404643bd31504d42eb4d27a406dd9e1cccffc17b9dbcb0df5e8fa">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>smooth-bears-confess.md</strong><dd><code>Add changeset
for rate limit query fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/smooth-bears-confess.md

- Added a changeset for the rate limit query fix.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2845/files#diff-7def3298a6278543c9953ddf387b04c552ddefc4cd5ad9217366c59926b3cf63">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions

---------

Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2024-08-27 14:45:08 +01:00
David Barroso
3d70c63d1b feat (docs): added docs about rate-limits (#2812) 2024-08-27 15:17:03 +02:00
David Barroso
ba55c1b779 feat (docs): run: added a guide on using a private registry (#2843) 2024-08-27 12:36:09 +02:00
github-actions[bot]
852f13b273 chore: update versions (#2824)
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@7.1.5

### Patch Changes

-   @nhost/nhost-js@3.1.8

## @nhost/react-apollo@12.0.5

### Patch Changes

-   @nhost/apollo@7.1.5
-   @nhost/react@3.5.5

## @nhost/react-urql@9.0.5

### Patch Changes

-   @nhost/react@3.5.5

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

### Patch Changes

- caa8bd7: fix: add error handling logic to transition to the signedOut
state when the token is invalid or expired

## @nhost/nextjs@2.1.19

### Patch Changes

-   @nhost/react@3.5.5

## @nhost/nhost-js@3.1.8

### Patch Changes

-   Updated dependencies [caa8bd7]
    -   @nhost/hasura-auth-js@2.5.5

## @nhost/react@3.5.5

### Patch Changes

-   @nhost/nhost-js@3.1.8

## @nhost/vue@2.6.5

### Patch Changes

-   @nhost/nhost-js@3.1.8

## @nhost/dashboard@1.26.0

### Minor Changes

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

### Patch Changes

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

## @nhost/docs@2.15.0

### Minor Changes

-   40c0d7b: │feat: added subdomain/region information
-   a18b545: feat: added postgres upgrade docs

## @nhost-examples/cli@0.3.10

### Patch Changes

-   @nhost/nhost-js@3.1.8

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

### Patch Changes

-   @nhost/react@3.5.5
-   @nhost/react-apollo@12.0.5

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

### Patch Changes

-   @nhost/react@3.5.5

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

### Patch Changes

-   @nhost/react@3.5.5
-   @nhost/react-urql@9.0.5

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

### Patch Changes

-   @nhost/nhost-js@3.1.8

## @nhost-examples/nextjs@0.3.10

### Patch Changes

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

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

### Patch Changes

-   @nhost/nhost-js@3.1.8

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

### Patch Changes

-   @nhost/nhost-js@3.1.8

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

### Patch Changes

-   @nhost/react@3.5.5
-   @nhost/react-apollo@12.0.5

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

### Patch Changes

-   @nhost/react@3.5.5

## @nhost-examples/react-native@0.0.4

### Patch Changes

-   @nhost/react@3.5.5
-   @nhost/react-apollo@12.0.5

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

### Patch Changes

-   @nhost/nhost-js@3.1.8
-   @nhost/apollo@7.1.5
-   @nhost/vue@2.6.5

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

### Patch Changes

-   @nhost/apollo@7.1.5
-   @nhost/vue@2.6.5

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-27 10:48:43 +01:00
David BM
a44a1d48d6 feat (dashboard): change rate limits from dashboard (#2832) 2024-08-26 18:47:06 +02:00
David BM
b63250d1cb fix (dashboard): not allow run service creation form resubmission while creating a run service (#2838) 2024-08-23 15:20:27 +02:00
Hassan Ben Jobrane
caa8bd75ec fix(hasura-auth-js): transition to the signedOut state when the token is invalid or expired (#2835)
### **User description**
fixes https://github.com/nhost/nhost/issues/2817


___

### **PR Type**
Bug fix, Tests, Enhancement


___

### **Description**
- Added error handling logic to transition to the `signedOut` state when
the token is invalid or expired.
- Updated the authentication machine to handle 401 errors by signing out
the user.
- Enhanced test cases to verify the new behavior of signing out on
unauthorized errors.
- Updated Hasura page teardown logic to ensure the first matching
element is clicked.
- Added `micromatch` to the audit-ci allowlist for dependency
management.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Bug
fix</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>machine.ts</strong><dd><code>Add error handling for
unauthorized token refresh</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

packages/hasura-auth-js/src/machines/authentication/machine.ts

<li>Added error handling logic to transition to <code>signedOut</code>
state on <br>unauthorized error.<br> <li> Introduced a new condition
<code>isUnauthorizedError</code> to check for 401 <br>status.<br> <li>
Reordered imports for better organization.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2835/files#diff-a8fdfee087ad5a72ea0a64667e2a0c7f25baa84eaaf73ebfee3f5a5a1b7584d1">+10/-3</a>&nbsp;
&nbsp; </td>

</tr>                    
</table></td></tr><tr><td><strong>Tests</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>refreshToken.test.ts</strong><dd><code>Update token
refresh test for unauthorized error handling</code></dd></summary>
<hr>

packages/hasura-auth-js/tests/refreshToken.test.ts

<li>Updated test to expect sign out on unauthorized error during token
<br>refresh.<br> <li> Adjusted test logic to match new authentication
state transitions.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2835/files#diff-271b5a8899ade50e4876f5a50f06da16954125f50d16f28219598cff4e39344b">+3/-7</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    
</table></td></tr><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>global-teardown.ts</strong><dd><code>Update Hasura
locator to click first matching element</code>&nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

dashboard/global-teardown.ts

<li>Updated locator to click the first matching element for Hasura page
<br>teardown.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2835/files#diff-1ee3d64258c498cdfa30665ec61605ab817622c7dae2a09bd4b6b23606c13e9f">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>machine.typegen.ts</strong><dd><code>Update type
definitions for unauthorized error handling</code>&nbsp; &nbsp;
</dd></summary>
<hr>

packages/hasura-auth-js/src/machines/authentication/machine.typegen.ts

- Added `isUnauthorizedError` to type definitions.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2835/files#diff-b0050ab06a8f00d3ae5decd65565adb1bdae3b4b6d19d4f67b9013ffb14e18ee">+2/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>silent-lies-smoke.md</strong><dd><code>Document bug fix
for invalid token handling</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

.changeset/silent-lies-smoke.md

<li>Documented the bug fix for transitioning to <code>signedOut</code>
state on invalid <br>token.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2835/files#diff-f8d41906481f17db7208e2c154075e8679f222536c7958000e6f50f1f019aa01">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    
</table></td></tr><tr><td><strong>Configuration
changes</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>audit-ci.jsonc</strong><dd><code>Update audit-ci
allowlist with micromatch</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

audit-ci.jsonc

- Added `micromatch` to the audit-ci allowlist.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2835/files#diff-4ede69da2a1704e53e08b8d647a315c202f037cc9277f16c94176d9622d261c6">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
2024-08-23 09:29:33 +01:00
David Barroso
40c0d7b914 feat (docs): added subdomain/region information (#2820) 2024-08-19 14:07:53 +02:00
David BM
3773ad7cca chore (dashboard): update pricing information (#2827)
Resolves #2822
2024-08-15 14:04:10 +02:00
Hassan Ben Jobrane
6f122521e9 fix: eval vulnerabilities (#2828)
### **PR Type**
enhancement, dependencies


___

### **Description**
- Removed `trim-newlines` from the `audit-ci` allowlist to address
potential vulnerabilities.
- Added `axios` version 1.7.4 to `package.json` resolutions to fix a
vulnerability.
- Updated `axios`, `@nhost/hasura-auth-js`, and `@nhost/nhost-js`
versions in `pnpm-lock.yaml` to ensure compatibility and security.



___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Configuration
changes</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>audit-ci.jsonc</strong><dd><code>Update audit-ci
allowlist configuration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

audit-ci.jsonc

- Removed `trim-newlines` from the allowlist.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2828/files#diff-4ede69da2a1704e53e08b8d647a315c202f037cc9277f16c94176d9622d261c6">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    
</table></td></tr><tr><td><strong>Dependencies</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Add axios version to
package resolutions</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

package.json

- Added `axios` version 1.7.4 to resolutions.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2828/files#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519">+2/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>                    

<tr>
  <td>
    <details>
<summary><strong>pnpm-lock.yaml</strong><dd><code>Update dependencies in
pnpm-lock file</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

pnpm-lock.yaml

<li>Updated <code>axios</code> to version 1.7.4.<br> <li> Updated
<code>@nhost/hasura-auth-js</code> to version 2.5.4.<br> <li> Updated
<code>@nhost/nhost-js</code> to version 3.1.7.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/2828/files#diff-32824c984905bb02bc7ffcef96a77addd1f1602cff71a11fbbfdd7f53ee026bb">+11/-10</a>&nbsp;
</td>

</tr>                    
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
2024-08-15 11:39:09 +01:00
David Barroso
a18b545d2a feat (docs): added postgres upgrade docs (#2823) 2024-08-13 10:42:08 +02:00
324 changed files with 72544 additions and 17933 deletions

28
.github/workflows/gen_ai_review.yaml vendored Normal file
View File

@@ -0,0 +1,28 @@
---
name: "gen: AI review"
on:
pull_request:
types: [opened, reopened, ready_for_review]
issue_comment:
jobs:
pr_agent_job:
if: ${{ github.event.sender.type != 'Bot' }}
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
issues: write
pull-requests: write
contents: write
name: Run pr agent on every pull request, respond to user comments
steps:
- name: PR Agent action step
id: pragent
uses: Codium-ai/pr-agent@v0.24
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OPENAI_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
config.max_model_tokens: 100000
config.model: "anthropic/claude-3-5-sonnet-20240620"
config.model_turbo: "anthropic/claude-3-5-sonnet-20240620"
ignore.glob: "['pnpm-lock.yaml','**/pnpm-lock.yaml']"

View File

@@ -2,5 +2,5 @@
// $schema provides code completion hints to IDEs.
"$schema": "https://github.com/IBM/audit-ci/raw/main/docs/schema.json",
"moderate": true,
"allowlist": ["trim-newlines", "vue-template-compiler"]
"allowlist": ["vue-template-compiler", "micromatch", "path-to-regexp"]
}

View File

@@ -24,3 +24,4 @@ NEXT_PUBLIC_ZENDESK_USER_EMAIL=
CODEGEN_GRAPHQL_URL=https://local.graphql.nhost.run/v1
CODEGEN_HASURA_ADMIN_SECRET=nhost-admin-secret
NEXT_PUBLIC_TURNSTILE_SITE_KEY=FIXME

View File

@@ -1 +1,3 @@
link-workspace-packages = false
link-workspace-packages = false
auto-install-peers = false
resolution-mode=highest

View File

@@ -1,5 +1,64 @@
# @nhost/dashboard
## 1.29.0
### Minor Changes
- 55d8bb5: feat: integrate turnstile for signup verification
- 2a2e54c: fix: update docs url in run services form tooltip
- 18f942f: fix: display long error messages in error toast without overflow
### Patch Changes
- @nhost/react-apollo@13.0.0
- @nhost/nextjs@2.1.22
## 1.28.2
### Patch Changes
- 52a38fe: chore: update dependencies to address security vulnerabilities
- Updated dependencies [52a38fe]
- @nhost/nextjs@2.1.21
## 1.28.1
### Patch Changes
- 9735fa2: chore: remove broken link
## 1.28.0
### Minor Changes
- 526183a: feat: allow filtering users in "make request as" in graphql section
- be3b85b: feat: add conceal errors toggle on auth settings page
### Patch Changes
- 35a2f12: fix: prevent run service details from opening when attempting to delete
- @nhost/react-apollo@12.0.6
- @nhost/nextjs@2.1.20
## 1.27.0
### Minor Changes
- a7cd02c: fix: resolve rate limit query
## 1.26.0
### Minor Changes
- 3773ad7: chore: update pricing information
- b63250d: fix: not allow run service creation form resubmission while creating a run service
- a44a1d4: feat: add rate limits settings page
### Patch Changes
- @nhost/react-apollo@12.0.5
- @nhost/nextjs@2.1.19
## 1.25.0
### Minor Changes

View File

@@ -1,6 +1,6 @@
import {
TEST_PROJECT_NAME,
TEST_PROJECT_SLUG,
PRO_TEST_PROJECT_NAME,
PRO_TEST_PROJECT_SLUG,
TEST_WORKSPACE_SLUG,
} from '@/e2e/env';
import { createUser, generateTestEmail, openProject } from '@/e2e/utils';
@@ -12,9 +12,9 @@ test('should be able to ban and unban a user', async ({ page }) => {
await openProject({
page,
projectName: TEST_PROJECT_NAME,
projectName: PRO_TEST_PROJECT_NAME,
workspaceSlug: TEST_WORKSPACE_SLUG,
projectSlug: TEST_PROJECT_SLUG,
projectSlug: PRO_TEST_PROJECT_SLUG,
});
await page
@@ -22,7 +22,9 @@ test('should be able to ban and unban a user', async ({ page }) => {
.getByRole('link', { name: /auth/i })
.click();
await page.waitForURL(`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/users`);
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/users`,
);
const email = generateTestEmail();
const password = faker.internet.password();

View File

@@ -1,6 +1,6 @@
import {
TEST_PROJECT_NAME,
TEST_PROJECT_SLUG,
PRO_TEST_PROJECT_NAME,
PRO_TEST_PROJECT_SLUG,
TEST_WORKSPACE_SLUG,
} from '@/e2e/env';
import { createUser, generateTestEmail, openProject } from '@/e2e/utils';
@@ -19,9 +19,9 @@ test.beforeEach(async () => {
await openProject({
page,
projectName: TEST_PROJECT_NAME,
projectName: PRO_TEST_PROJECT_NAME,
workspaceSlug: TEST_WORKSPACE_SLUG,
projectSlug: TEST_PROJECT_SLUG,
projectSlug: PRO_TEST_PROJECT_SLUG,
});
await page
@@ -29,7 +29,9 @@ test.beforeEach(async () => {
.getByRole('link', { name: /auth/i })
.click();
await page.waitForURL(`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/users`);
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/users`,
);
});
test.afterAll(async () => {

View File

@@ -1,6 +1,6 @@
import {
TEST_PROJECT_NAME,
TEST_PROJECT_SLUG,
PRO_TEST_PROJECT_NAME,
PRO_TEST_PROJECT_SLUG,
TEST_WORKSPACE_SLUG,
} from '@/e2e/env';
import { createUser, generateTestEmail, openProject } from '@/e2e/utils';
@@ -19,9 +19,9 @@ test.beforeEach(async () => {
await openProject({
page,
projectName: TEST_PROJECT_NAME,
projectName: PRO_TEST_PROJECT_NAME,
workspaceSlug: TEST_WORKSPACE_SLUG,
projectSlug: TEST_PROJECT_SLUG,
projectSlug: PRO_TEST_PROJECT_SLUG,
});
await page
@@ -29,7 +29,9 @@ test.beforeEach(async () => {
.getByRole('link', { name: /auth/i })
.click();
await page.waitForURL(`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/users`);
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/users`,
);
});
test.afterAll(async () => {

View File

@@ -1,6 +1,6 @@
import {
TEST_PROJECT_NAME,
TEST_PROJECT_SLUG,
PRO_TEST_PROJECT_NAME,
PRO_TEST_PROJECT_SLUG,
TEST_WORKSPACE_SLUG,
} from '@/e2e/env';
import { createUser, generateTestEmail, openProject } from '@/e2e/utils';
@@ -19,9 +19,9 @@ test.beforeEach(async () => {
await openProject({
page,
projectName: TEST_PROJECT_NAME,
projectName: PRO_TEST_PROJECT_NAME,
workspaceSlug: TEST_WORKSPACE_SLUG,
projectSlug: TEST_PROJECT_SLUG,
projectSlug: PRO_TEST_PROJECT_SLUG,
});
await page
@@ -29,7 +29,9 @@ test.beforeEach(async () => {
.getByRole('link', { name: /auth/i })
.click();
await page.waitForURL(`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/users`);
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/users`,
);
});
test.afterAll(async () => {

View File

@@ -1,6 +1,6 @@
import {
TEST_PROJECT_NAME,
TEST_PROJECT_SLUG,
PRO_TEST_PROJECT_NAME,
PRO_TEST_PROJECT_SLUG,
TEST_WORKSPACE_SLUG,
} from '@/e2e/env';
import { openProject, prepareTable } from '@/e2e/utils';
@@ -20,9 +20,9 @@ test.beforeEach(async () => {
await openProject({
page,
projectName: TEST_PROJECT_NAME,
projectName: PRO_TEST_PROJECT_NAME,
workspaceSlug: TEST_WORKSPACE_SLUG,
projectSlug: TEST_PROJECT_SLUG,
projectSlug: PRO_TEST_PROJECT_SLUG,
});
await page
@@ -55,7 +55,7 @@ test('should create a simple table', async () => {
await page.getByRole('button', { name: /create/i }).click();
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
);
await expect(
@@ -84,7 +84,7 @@ test('should create a table with unique constraints', async () => {
await page.getByRole('button', { name: /create/i }).click();
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
);
await expect(
@@ -113,7 +113,7 @@ test('should create a table with nullable columns', async () => {
await page.getByRole('button', { name: /create/i }).click();
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
);
await expect(
@@ -146,7 +146,7 @@ test('should create a table with an identity column', async () => {
await page.getByRole('button', { name: /create/i }).click();
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
);
await expect(
@@ -174,7 +174,7 @@ test('should create table with foreign key constraint', async () => {
await page.getByRole('button', { name: /create/i }).click();
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${firstTableName}`,
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/database/browser/default/public/${firstTableName}`,
);
await page.getByRole('button', { name: /new table/i }).click();
@@ -219,7 +219,7 @@ test('should create table with foreign key constraint', async () => {
await page.getByRole('button', { name: /create/i }).click();
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${secondTableName}`,
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/database/browser/default/public/${secondTableName}`,
);
await expect(
@@ -247,7 +247,7 @@ test('should not be able to create a table with a name that already exists', asy
await page.getByRole('button', { name: /create/i }).click();
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
);
await page.getByRole('button', { name: /new table/i }).click();

View File

@@ -1,6 +1,6 @@
import {
TEST_PROJECT_NAME,
TEST_PROJECT_SLUG,
PRO_TEST_PROJECT_NAME,
PRO_TEST_PROJECT_SLUG,
TEST_WORKSPACE_SLUG,
} from '@/e2e/env';
import { deleteTable, openProject, prepareTable } from '@/e2e/utils';
@@ -20,9 +20,9 @@ test.beforeEach(async () => {
await openProject({
page,
projectName: TEST_PROJECT_NAME,
projectName: PRO_TEST_PROJECT_NAME,
workspaceSlug: TEST_WORKSPACE_SLUG,
projectSlug: TEST_PROJECT_SLUG,
projectSlug: PRO_TEST_PROJECT_SLUG,
});
await page
@@ -53,7 +53,7 @@ test('should delete a table', async () => {
await page.getByRole('button', { name: /create/i }).click();
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/database/browser/default/public/${tableName}`,
);
await deleteTable({
@@ -63,7 +63,7 @@ test('should delete a table', async () => {
// navigate to next URL
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/**`,
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/database/browser/default/public/**`,
);
await expect(
@@ -91,7 +91,7 @@ test('should not be able to delete a table if other tables have foreign keys ref
await page.getByRole('button', { name: /create/i }).click();
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${firstTableName}`,
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/database/browser/default/public/${firstTableName}`,
);
await page.getByRole('button', { name: /new table/i }).click();
@@ -138,7 +138,7 @@ test('should not be able to delete a table if other tables have foreign keys ref
await page.getByRole('button', { name: /create/i }).click();
await page.waitForURL(
`/${TEST_WORKSPACE_SLUG}/${TEST_PROJECT_SLUG}/database/browser/default/public/${secondTableName}`,
`/${TEST_WORKSPACE_SLUG}/${PRO_TEST_PROJECT_SLUG}/database/browser/default/public/${secondTableName}`,
);
await expect(

View File

@@ -61,12 +61,8 @@ test('should create and delete a run service', async () => {
page.getByRole('heading', { name: /confirm resources/i }),
).toBeVisible();
await page.waitForTimeout(1000);
await page.getByRole('button', { name: /confirm/i }).click();
await expect(page.getByRole('dialog')).toBeVisible();
await expect(
page.getByRole('heading', { name: /service details/i }),
).toBeVisible();
@@ -74,16 +70,14 @@ test('should create and delete a run service', async () => {
await page.getByRole('button', { name: /ok/i }).click();
await expect(page.getByRole('heading', { name: /test/i })).toBeVisible();
await page.getByLabel(/more options/i).click();
await page.getByRole('menuitem', { name: /delete service/i }).click();
await page.getByLabel(/confirm delete project #/i).check();
await page
.getByText(/delete service/i)
.nth(2)
.click();
await page.getByLabel('Close').click();
await page.getByRole('button', { name: /delete service/i }).click();
await expect(
page

View File

@@ -43,7 +43,7 @@ async function globalTeardown() {
await adminSecretInput.press('Enter');
// note: getByRole doesn't work here
await hasuraPage.locator('a', { hasText: /data/i }).click();
await hasuraPage.locator('a', { hasText: /data/i }).nth(0).click();
await hasuraPage.locator('[data-test="sql-link"]').click();
// Set the value of the Ace code editor using JavaScript evaluation in the browser context

View File

@@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/dashboard",
"version": "1.25.0",
"version": "1.29.0",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
@@ -35,6 +35,7 @@
"@heroicons/react": "^1.0.6",
"@hookform/resolvers": "^3.3.4",
"@iarna/toml": "^2.2.5",
"@marsidev/react-turnstile": "^1.0.2",
"@mui/base": "5.0.0-beta.31",
"@mui/material": "^5.15.14",
"@mui/system": "^5.15.14",
@@ -65,7 +66,7 @@
"graphql-ws": "^5.16.0",
"just-kebab-case": "^4.2.0",
"lodash.debounce": "^4.0.8",
"next": "^14.1.4",
"next": "^14.2.10",
"next-seo": "^6.5.0",
"node-pg-format": "^1.3.5",
"pluralize": "^8.0.0",
@@ -165,7 +166,7 @@
"tailwindcss": "^3.4.3",
"ts-node": "^10.9.2",
"tsconfig-paths-webpack-plugin": "^4.1.0",
"vite": "^5.2.7",
"vite": "^5.4.6",
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^0.32.4"
},

View File

@@ -10,10 +10,10 @@ export default defineConfig({
expect: {
timeout: 5000,
},
fullyParallel: true,
fullyParallel: false,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
workers: 1,
reporter: 'html',
globalTeardown: require.resolve('./global-teardown'),
use: {

View File

@@ -221,6 +221,13 @@ export default function SettingsSidebar({
>
Custom Domains
</SettingsNavLink>
<SettingsNavLink
href="/rate-limiting"
exact={false}
onClick={handleSelect}
>
Rate Limiting
</SettingsNavLink>
<SettingsNavLink href="/ai" exact={false} onClick={handleSelect}>
AI
</SettingsNavLink>

View File

@@ -112,18 +112,24 @@ export default function ErrorToast({
bounce: 0.1,
}}
>
<div className="flex w-full flex-row items-center justify-between space-x-4">
<button onClick={close} type="button" aria-label="Close">
<div className="flex w-full flex-row items-center justify-between gap-4">
<button
className="flex-shrink-0"
onClick={close}
type="button"
aria-label="Close"
>
<XIcon className="h-4 w-4 text-white" />
</button>
<span>
<span className="flex-grow overflow-hidden break-words">
{msg ?? 'An unkown error has occured, please try again later!'}
</span>
<button
type="button"
onClick={() => setShowInfo(!showInfo)}
className="flex flex-row items-center justify-center space-x-2 text-white"
className="flex flex-shrink-0 flex-row items-center justify-center space-x-2 text-white"
aria-label="Show error details"
>
<span>Info</span>
{showInfo ? (

View File

@@ -0,0 +1,136 @@
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';
const validationSchema = Yup.object({
enabled: Yup.boolean(),
});
export type ToggleConcealErrorsFormValues = Yup.InferType<
typeof validationSchema
>;
export default function ConcealErrorsSettings() {
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 },
...(!isPlatform ? { client: localMimirClient } : {}),
});
const form = useForm<ToggleConcealErrorsFormValues>({
reValidateMode: 'onSubmit',
defaultValues: {
enabled: data?.config?.auth?.misc?.concealErrors,
},
});
useEffect(() => {
if (!loading) {
form.reset({
enabled: data?.config?.auth?.misc?.concealErrors,
});
}
}, [loading, data, form]);
if (loading) {
return (
<ActivityIndicator
delay={1000}
label="Loading conceal error settings..."
className="justify-center"
/>
);
}
if (error) {
throw error;
}
const { formState } = form;
const handleToggleConcealErrors = async (
values: ToggleConcealErrorsFormValues,
) => {
const updateConfigPromise = updateConfig({
variables: {
appId: currentProject.id,
config: {
auth: {
misc: {
concealErrors: values.enabled,
},
},
},
},
});
await execPromiseWithErrorToast(
async () => {
await updateConfigPromise;
form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Updating conceal error settings...',
successMessage: 'Conceal error settings updated successfully.',
errorMessage:
'Failed to update conceal error settings. Please try again.',
},
);
};
return (
<FormProvider {...form}>
<Form onSubmit={handleToggleConcealErrors}>
<SettingsContainer
title="Conceal errors"
description="If set, conceals sensitive error messages to prevent leaking information about user accounts."
switchId="enabled"
showSwitch
slotProps={{
submitButton: {
disabled: !formState.isDirty || maintenanceActive,
loading: formState.isSubmitting,
},
}}
className="hidden"
/>
</Form>
</FormProvider>
);
}

View File

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

View File

@@ -50,6 +50,9 @@ query GetAuthenticationSettings($appId: uuid!) {
default
}
}
misc {
concealErrors
}
version
}
}

View File

@@ -12,7 +12,6 @@ import { Chip } from '@/components/ui/v2/Chip';
import { Divider } from '@/components/ui/v2/Divider';
import { Dropdown } from '@/components/ui/v2/Dropdown';
import { IconButton } from '@/components/ui/v2/IconButton';
import { ArrowRightIcon } from '@/components/ui/v2/icons/ArrowRightIcon';
import { DotsHorizontalIcon } from '@/components/ui/v2/icons/DotsHorizontalIcon';
import { LockIcon } from '@/components/ui/v2/icons/LockIcon';
import { PencilIcon } from '@/components/ui/v2/icons/PencilIcon';
@@ -20,7 +19,6 @@ import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import { TerminalIcon } from '@/components/ui/v2/icons/TerminalIcon';
import { TrashIcon } from '@/components/ui/v2/icons/TrashIcon';
import { UsersIcon } from '@/components/ui/v2/icons/UsersIcon';
import { Link } from '@/components/ui/v2/Link';
import { List } from '@/components/ui/v2/List';
import { ListItem } from '@/components/ui/v2/ListItem';
import { Option } from '@/components/ui/v2/Option';
@@ -312,15 +310,6 @@ function DataBrowserSidebarContent({
Your project is connected to GitHub. Please use the CLI to make
schema changes.
</Text>
<Link
href="https://docs.nhost.io/platform/github-integration"
target="_blank"
rel="noopener noreferrer"
underline="hover"
className="grid items-center justify-start grid-flow-col gap-1"
>
Learn More <ArrowRightIcon />
</Link>
</Box>
)}
{!isSelectedSchemaLocked && (

View File

@@ -1,11 +1,12 @@
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Option } from '@/components/ui/v2/Option';
import { Select } from '@/components/ui/v2/Select';
import { Autocomplete } from '@/components/ui/v2/Autocomplete';
import { DEFAULT_ROLES } from '@/features/graphql/common/utils/constants';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
import type { RemoteAppGetUsersCustomQuery } from '@/utils/__generated__/graphql';
import { useRemoteAppGetUsersCustomQuery } from '@/utils/__generated__/graphql';
import {
useRemoteAppGetUsersCustomLazyQuery,
type RemoteAppGetUsersCustomQuery,
} from '@/utils/__generated__/graphql';
import { debounce } from '@mui/material/utils';
import { useCallback, useEffect, useMemo, useState } from 'react';
export interface UserSelectProps {
/**
@@ -13,7 +14,7 @@ export interface UserSelectProps {
*/
onUserChange: (userId: string, availableRoles?: string[]) => void;
/**
* Class name to be applied to the `<Select />` element.
* Class name to be applied to the `<Autocomplete />` element.
*/
className?: string;
}
@@ -22,35 +23,87 @@ export default function UserSelect({
onUserChange,
...props
}: UserSelectProps) {
const { currentProject } = useCurrentWorkspaceAndProject();
const [inputValue, setInputValue] = useState('');
const [users, setUsers] = useState([]);
const [active, setActive] = useState(true);
const userApplicationClient = useRemoteApplicationGQLClient();
const { data, loading, error } = useRemoteAppGetUsersCustomQuery({
const [fetchAppUsers, { loading }] = useRemoteAppGetUsersCustomLazyQuery({
client: userApplicationClient,
variables: { where: {}, limit: 250, offset: 0 },
skip: !currentProject,
variables: {
where: {},
limit: 250,
offset: 0,
},
});
if (loading) {
return (
<div className={props.className}>
<ActivityIndicator label="Loading users..." delay={500} />
</div>
);
}
const fetchUsers = useCallback(
async (
request: { input: string },
callback: (results?: RemoteAppGetUsersCustomQuery['users']) => void,
) => {
const ilike = `%${request.input === 'Admin' ? '' : request.input}%`;
const { data } = await fetchAppUsers({
client: userApplicationClient,
variables: {
where: {
displayName: { _ilike: ilike },
},
limit: 250,
offset: 0,
},
});
if (error) {
throw error;
}
callback(data?.users);
},
[fetchAppUsers, userApplicationClient],
);
const fetchOptions = useMemo(() => debounce(fetchUsers, 1000), [fetchUsers]);
useEffect(() => {
fetchOptions({ input: inputValue }, (results) => {
if (active || inputValue === '') {
setUsers(results);
}
});
}, [inputValue, fetchOptions, active]);
const autocompleteOptions = [
{
value: 'admin',
label: 'Admin',
group: 'Admin',
},
...users.map((user) => ({
value: user.id,
label: user.displayName,
group: 'Users',
})),
];
return (
<Select
<Autocomplete
{...props}
id="user-select"
label="Make Request As"
hideEmptyHelperText
defaultValue="admin"
slotProps={{ root: { className: 'truncate' } }}
onChange={(_event, userId) => {
label="Make request as"
options={autocompleteOptions}
defaultValue={{
value: 'admin',
label: 'Admin',
group: 'Admin',
}}
autoComplete
fullWidth
autoSelect
groupBy={(option) => option.group}
autoHighlight
includeInputInList
loading={loading}
onChange={(_event, _value, reason, details) => {
setActive(false);
const userId = details.option.value;
if (typeof userId !== 'string') {
return;
}
@@ -61,22 +114,23 @@ export default function UserSelect({
return;
}
const user: RemoteAppGetUsersCustomQuery['users'][0] = data?.users.find(
const user: RemoteAppGetUsersCustomQuery['users'][0] = users.find(
({ id }) => id === userId,
);
const roles = user?.roles.map(({ role }) => role);
const roles = user?.roles?.map(({ role }) => role);
onUserChange(user.id, roles);
onUserChange(userId, roles ?? DEFAULT_ROLES);
fetchUsers({ input: '' }, (results) => {
if (results) {
setUsers(results);
}
});
}}
>
<Option value="admin">Admin</Option>
{data?.users.map(({ id, displayName, email, phoneNumber }) => (
<Option key={id} value={id}>
{displayName || email || phoneNumber || id}
</Option>
))}
</Select>
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
/>
);
}

View File

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

View File

@@ -0,0 +1,54 @@
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useRemoteApplicationGQLClient } from '@/hooks/useRemoteApplicationGQLClient';
import type {
RemoteAppGetUsersCustomQuery,
RemoteAppGetUsersCustomQueryVariables,
} from '@/utils/__generated__/graphql';
import { useRemoteAppGetUsersCustomQuery } from '@/utils/__generated__/graphql';
import type { QueryHookOptions } from '@apollo/client';
export type UseFilesOptions = {
searchString?: string;
limit?: number;
offset?: number;
/**
* Custom options for the query.
*/
options?: QueryHookOptions<
RemoteAppGetUsersCustomQuery,
RemoteAppGetUsersCustomQueryVariables
>;
};
export default function useGetAppUsers({
searchString,
limit = 250,
offset = 0,
options = {},
}: UseFilesOptions) {
const { currentProject } = useCurrentWorkspaceAndProject();
const userApplicationClient = useRemoteApplicationGQLClient();
const { data, error, loading } = useRemoteAppGetUsersCustomQuery({
...options,
client: userApplicationClient,
variables: {
...options.variables,
where: searchString
? {
displayName: { _ilike: `%${searchString}%` },
}
: {},
limit,
offset,
},
skip: !currentProject,
});
const users = data?.users || [];
return {
users,
loading,
error,
};
}

View File

@@ -2,8 +2,8 @@ import { useDialog } from '@/components/common/DialogProvider';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { Checkbox } from '@/components/ui/v2/Checkbox';
import { BaseDialog } from '@/components/ui/v2/Dialog';
import { Radio } from '@/components/ui/v2/Radio';
import { Text } from '@/components/ui/v2/Text';
import { useAppState } from '@/features/projects/common/hooks/useAppState';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
@@ -31,7 +31,7 @@ function Plan({ planName, price, setPlan, planId, selectedPlanId }: any) {
>
<div className="grid grid-flow-row gap-y-0.5">
<div className="grid grid-flow-col items-center justify-start gap-2">
<Checkbox
<Radio
onChange={setPlan}
checked={selectedPlanId === planId}
aria-label={planName}
@@ -241,7 +241,21 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
))}
</div>
<div className="mt-2 grid grid-flow-row gap-2">
<div className="mt-0">
<Text variant="subtitle2" className="w-full px-1">
For a complete list of features, visit our{' '}
<a
href="https://nhost.io/pricing"
target="_blank"
rel="noopener noreferrer"
className="underline"
>
pricing page
</a>
</Text>
</div>
<div className="mt-6 grid grid-flow-row gap-2">
<Button
onClick={handleChangePlanClick}
disabled={!selectedPlan}

View File

@@ -1,6 +1,6 @@
const planDescriptions = {
Starter: '1 GB database, 5 GB of file storage, 10 GB of network traffic.',
Pro: '10 GB database, 25 GB of file storage, 50 GB of network traffic, and backups.',
Pro: '10 GB database, 50 GB of file storage, 50 GB of network traffic, and backups.',
Team: 'Reach out to us at support@nhost.io to have your private channel set up.',
};

View File

@@ -0,0 +1,303 @@
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 { Divider } from '@/components/ui/v2/Divider';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { rateLimitingItemValidationSchema } from '@/features/projects/rate-limiting/settings/components/validationSchemas';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { useUpdateRateLimitConfigMutation } from '@/utils/__generated__/graphql';
import { yupResolver } from '@hookform/resolvers/yup';
import { RateLimitField } from 'features/projects/rate-limiting/settings/components/RateLimitField';
import { useGetRateLimits } from 'features/projects/rate-limiting/settings/hooks/useGetRateLimits';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
export const validationSchema = Yup.object({
enabled: Yup.boolean().label('Enabled'),
bruteForce: rateLimitingItemValidationSchema,
emails: rateLimitingItemValidationSchema,
global: rateLimitingItemValidationSchema,
signups: rateLimitingItemValidationSchema,
sms: rateLimitingItemValidationSchema,
});
export type AuthLimitingFormValues = Yup.InferType<typeof validationSchema>;
export default function AuthLimitingForm() {
const { openDialog } = useDialog();
const { maintenanceActive } = useUI();
const isPlatform = useIsPlatform();
const { currentProject } = useCurrentWorkspaceAndProject();
const localMimirClient = useLocalMimirClient();
const [updateRateLimitConfig] = useUpdateRateLimitConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { authRateLimit, loading } = useGetRateLimits();
const {
bruteForce,
emails,
global,
signups,
sms,
enabled: authRateEnabled,
} = authRateLimit;
const {
limit: bruteForceLimit,
interval: bruteForceInterval,
intervalUnit: bruteForceIntervalUnit,
} = bruteForce;
const {
limit: emailsLimit,
interval: emailsInterval,
intervalUnit: emailsIntervalUnit,
} = emails;
const {
limit: globalLimit,
interval: globalInterval,
intervalUnit: globalIntervalUnit,
} = global;
const {
limit: signupsLimit,
interval: signupsInterval,
intervalUnit: signupsIntervalUnit,
} = signups;
const {
limit: smsLimit,
interval: smsInterval,
intervalUnit: smsIntervalUnit,
} = sms;
const form = useForm<AuthLimitingFormValues>({
defaultValues: {
enabled: authRateEnabled,
bruteForce: {
limit: bruteForceLimit,
interval: bruteForceInterval,
intervalUnit: bruteForceIntervalUnit,
},
emails: {
limit: emailsLimit,
interval: emailsInterval,
intervalUnit: emailsIntervalUnit,
},
global: {
limit: globalLimit,
interval: globalInterval,
intervalUnit: globalIntervalUnit,
},
signups: {
limit: signupsLimit,
interval: signupsInterval,
intervalUnit: signupsIntervalUnit,
},
sms: {
limit: smsLimit,
interval: smsInterval,
intervalUnit: smsIntervalUnit,
},
},
reValidateMode: 'onSubmit',
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading && authRateEnabled) {
form.reset({
enabled: authRateEnabled,
bruteForce: {
limit: bruteForceLimit,
interval: bruteForceInterval,
intervalUnit: bruteForceIntervalUnit,
},
emails: {
limit: emailsLimit,
interval: emailsInterval,
intervalUnit: emailsIntervalUnit,
},
global: {
limit: globalLimit,
interval: globalInterval,
intervalUnit: globalIntervalUnit,
},
signups: {
limit: signupsLimit,
interval: signupsInterval,
intervalUnit: signupsIntervalUnit,
},
sms: {
limit: smsLimit,
interval: smsInterval,
intervalUnit: smsIntervalUnit,
},
});
}
}, [
loading,
form,
authRateEnabled,
bruteForceLimit,
bruteForceInterval,
bruteForceIntervalUnit,
emailsLimit,
emailsInterval,
emailsIntervalUnit,
globalLimit,
globalInterval,
globalIntervalUnit,
signupsLimit,
signupsInterval,
signupsIntervalUnit,
smsLimit,
smsInterval,
smsIntervalUnit,
]);
if (loading) {
return (
<ActivityIndicator
delay={1000}
label="Loading rate limits..."
className="justify-center"
/>
);
}
const {
register,
formState: { errors },
formState,
watch,
} = form;
const enabled = watch('enabled');
const handleSubmit = async (formValues: AuthLimitingFormValues) => {
const updateConfigPromise = updateRateLimitConfig({
variables: {
appId: currentProject.id,
config: {
auth: {
rateLimit: formValues.enabled
? {
bruteForce: {
limit: formValues.bruteForce.limit,
interval: `${formValues.bruteForce.interval}${formValues.bruteForce.intervalUnit}`,
},
emails: {
limit: formValues.emails.limit,
interval: `${formValues.emails.interval}${formValues.emails.intervalUnit}`,
},
global: {
limit: formValues.global.limit,
interval: `${formValues.global.interval}${formValues.global.intervalUnit}`,
},
signups: {
limit: formValues.signups.limit,
interval: `${formValues.signups.interval}${formValues.signups.intervalUnit}`,
},
sms: {
limit: formValues.sms.limit,
interval: `${formValues.sms.interval}${formValues.sms.intervalUnit}`,
},
}
: null,
},
},
},
});
await execPromiseWithErrorToast(
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Updating Auth rate limit settings...',
successMessage: 'Auth rate limit settings updated successfully',
errorMessage: 'Failed to update Auth rate limit settings',
},
);
};
return (
<FormProvider {...form}>
<Form
onSubmit={handleSubmit}
className="flex h-full flex-col overflow-hidden"
>
<SettingsContainer
title="Auth"
slotProps={{
submitButton: {
disabled: !formState.isDirty || maintenanceActive,
loading: formState.isSubmitting,
},
}}
className="flex flex-col px-0"
>
<Divider />
<RateLimitField
disabled={!enabled}
register={register}
errors={errors.bruteForce}
id="bruteForce"
title="Brute Force"
/>
<Divider />
<RateLimitField
disabled={!enabled}
register={register}
errors={errors.emails}
id="emails"
title="Emails"
/>
<Divider />
<RateLimitField
disabled={!enabled}
register={register}
errors={errors.global}
id="global"
title="Global"
/>
<Divider />
<RateLimitField
disabled={!enabled}
register={register}
errors={errors.signups}
id="signups"
title="Signups"
/>
<Divider />
<RateLimitField
disabled={!enabled}
register={register}
errors={errors.sms}
id="sms"
title="SMS"
/>
</SettingsContainer>
</Form>
</FormProvider>
);
}

View File

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

View File

@@ -0,0 +1,88 @@
import { ControlledSelect } from '@/components/form/ControlledSelect';
import { Box } from '@/components/ui/v2/Box';
import { Input } from '@/components/ui/v2/Input';
import { Option } from '@/components/ui/v2/Option';
import { Text } from '@/components/ui/v2/Text';
import { intervalUnitOptions } from '@/features/projects/rate-limiting/settings/components/validationSchemas';
import type {
FieldError,
FieldErrorsImpl,
Merge,
UseFormRegister,
} from 'react-hook-form';
interface RateLimitFieldProps {
register: UseFormRegister<any>;
errors: Merge<
FieldError,
FieldErrorsImpl<{
limit: number;
interval: number;
intervalUnit: string;
}>
>;
disabled?: boolean;
title?: string;
id: string;
}
export default function RateLimitField({
register,
disabled,
id,
errors,
title,
}: RateLimitFieldProps) {
return (
<Box className="px-4">
{title ? <Text className="py-4 font-semibold">{title}</Text> : null}
<div className="flex flex-col gap-8 lg:flex-row">
<div className="flex flex-row items-center gap-2">
<Text>Limit</Text>
<Input
{...register(`${id}.limit`)}
disabled={disabled}
id={`${id}.limit`}
type="number"
placeholder=""
className="max-w-60"
hideEmptyHelperText
error={!!errors?.limit}
helperText={errors?.limit?.message}
autoComplete="off"
/>
</div>
<div className="flex flex-row items-center gap-2">
<Text>Interval</Text>
<Input
{...register(`${id}.interval`)}
disabled={disabled}
id={`${id}.interval`}
type="number"
placeholder=""
hideEmptyHelperText
className="max-w-32"
error={!!errors?.interval}
helperText={errors?.interval?.message}
autoComplete="off"
/>
<ControlledSelect
{...register(`${id}.intervalUnit`)}
disabled={disabled}
variant="normal"
id={`${id}.intervalUnit`}
className="w-27"
defaultValue="m"
hideEmptyHelperText
>
{intervalUnitOptions.map(({ value, label }) => (
<Option key={`${id}.intervalUnit.${value}`} value={value}>
{label}
</Option>
))}
</ControlledSelect>
</div>
</div>
</Box>
);
}

View File

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

View File

@@ -0,0 +1,169 @@
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 { Divider } from '@/components/ui/v2/Divider';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { rateLimitingItemValidationSchema } from '@/features/projects/rate-limiting/settings/components/validationSchemas';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import {
useUpdateRateLimitConfigMutation,
type ConfigConfigUpdateInput,
} from '@/utils/__generated__/graphql';
import { yupResolver } from '@hookform/resolvers/yup';
import { RateLimitField } from 'features/projects/rate-limiting/settings/components/RateLimitField';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
export const validationSchema = Yup.object({
enabled: Yup.boolean().label('Enabled'),
rateLimit: rateLimitingItemValidationSchema,
});
export interface RateLimitDefaultValues {
enabled: boolean;
rateLimit: { limit: number; interval: number; intervalUnit: string };
}
export interface RateLimitingFormProps {
defaultValues: RateLimitDefaultValues;
serviceName: keyof ConfigConfigUpdateInput;
title: string;
loading: boolean;
}
export type RateLimitingFormValues = Yup.InferType<typeof validationSchema>;
export default function RateLimitingForm({
defaultValues,
serviceName,
title,
loading,
}: RateLimitingFormProps) {
const { openDialog } = useDialog();
const { maintenanceActive } = useUI();
const isPlatform = useIsPlatform();
const { currentProject } = useCurrentWorkspaceAndProject();
const localMimirClient = useLocalMimirClient();
const [updateRateLimitConfig] = useUpdateRateLimitConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const form = useForm<RateLimitingFormValues>({
defaultValues: defaultValues.enabled
? defaultValues
: {
enabled: false,
rateLimit: {
limit: 0,
interval: 0,
intervalUnit: 's',
},
},
reValidateMode: 'onSubmit',
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading && defaultValues.enabled) {
form.reset(defaultValues);
}
}, [loading, defaultValues, form]);
if (loading) {
return (
<ActivityIndicator
delay={1000}
label="Loading rate limits..."
className="justify-center"
/>
);
}
const {
register,
formState: { errors },
formState,
watch,
} = form;
const enabled = watch('enabled');
const handleSubmit = async (formValues: RateLimitingFormValues) => {
const updateConfigPromise = updateRateLimitConfig({
variables: {
appId: currentProject.id,
config: {
[serviceName]: {
rateLimit: formValues.enabled
? {
limit: formValues.rateLimit.limit,
interval: `${formValues.rateLimit.interval}${formValues.rateLimit.intervalUnit}`,
}
: null,
},
},
},
});
await execPromiseWithErrorToast(
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: `Updating ${title} rate limit settings...`,
successMessage: `${title} rate limit settings updated successfully`,
errorMessage: `Failed to update ${title} rate limit settings`,
},
);
};
return (
<FormProvider {...form}>
<Form
onSubmit={handleSubmit}
className="flex h-full flex-col overflow-hidden"
>
<SettingsContainer
title={title}
switchId="enabled"
showSwitch
slotProps={{
submitButton: {
disabled: !formState.isDirty || maintenanceActive,
loading: formState.isSubmitting,
},
}}
className="flex flex-col px-0"
>
<Divider />
<RateLimitField
disabled={!enabled}
register={register}
errors={errors.rateLimit}
id="rateLimit"
/>
</SettingsContainer>
</Form>
</FormProvider>
);
}

View File

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

View File

@@ -0,0 +1,194 @@
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 { Divider } from '@/components/ui/v2/Divider';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { rateLimitingItemValidationSchema } from '@/features/projects/rate-limiting/settings/components/validationSchemas';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { useUpdateRunServiceConfigMutation } from '@/utils/__generated__/graphql';
import { yupResolver } from '@hookform/resolvers/yup';
import { RateLimitField } from 'features/projects/rate-limiting/settings/components/RateLimitField';
import type { UseGetRunServiceRateLimitsReturn } from 'features/projects/rate-limiting/settings/hooks/useGetRunServiceRateLimits/useGetRunServiceRateLimits';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
export const validationSchema = Yup.object({
enabled: Yup.boolean().label('Enabled'),
ports: Yup.array().of(rateLimitingItemValidationSchema),
});
export type RunServiceLimitingFormValues = Yup.InferType<
typeof validationSchema
>;
export interface RunServiceLimitingFormProps {
title?: string;
serviceId?: string;
loading?: boolean;
enabledDefault?: boolean;
ports?: UseGetRunServiceRateLimitsReturn['services'][0]['ports'];
}
export default function RunServiceLimitingForm({
title,
serviceId,
ports,
loading,
enabledDefault,
}: RunServiceLimitingFormProps) {
const { openDialog } = useDialog();
const { maintenanceActive } = useUI();
const isPlatform = useIsPlatform();
const { currentProject } = useCurrentWorkspaceAndProject();
const localMimirClient = useLocalMimirClient();
const [updateRunServiceRateLimit] = useUpdateRunServiceConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const form = useForm<RunServiceLimitingFormValues>({
defaultValues: {
enabled: enabledDefault,
ports: [
...ports.map((port) => ({
limit: port?.rateLimit?.limit,
interval: port?.rateLimit?.interval,
intervalUnit: port?.rateLimit?.intervalUnit,
})),
],
},
reValidateMode: 'onSubmit',
resolver: yupResolver(validationSchema),
});
useEffect(() => {
if (!loading && enabledDefault) {
form.reset({
enabled: enabledDefault,
ports: [
...ports.map((port) => ({
limit: port?.rateLimit?.limit,
interval: port?.rateLimit?.interval,
intervalUnit: port?.rateLimit?.intervalUnit,
})),
],
});
}
}, [loading, enabledDefault, ports, form]);
if (loading) {
return (
<ActivityIndicator
delay={1000}
label="Loading rate limits..."
className="justify-center"
/>
);
}
const {
register,
formState: { errors },
formState,
watch,
} = form;
const enabled = watch('enabled');
const handleSubmit = async (formValues: RunServiceLimitingFormValues) => {
const updateConfigPromise = updateRunServiceRateLimit({
variables: {
appID: currentProject?.id,
serviceID: serviceId,
config: {
ports: ports.map((port, index) => {
const rateLimit = formValues.ports[index];
return {
...port,
rateLimit: enabled
? {
limit: rateLimit.limit,
interval: `${rateLimit.interval}${rateLimit.intervalUnit}`,
}
: null,
};
}),
},
},
});
await execPromiseWithErrorToast(
async () => {
await updateConfigPromise;
form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
},
{
loadingMessage: 'Updating Run service rate limit settings...',
successMessage: 'Run service rate limit settings updated successfully',
errorMessage: 'Failed to update Run service rate limit settings',
},
);
};
return (
<FormProvider {...form}>
<Form
onSubmit={handleSubmit}
className="flex h-full flex-col overflow-hidden"
>
<SettingsContainer
title={title}
switchId="enabled"
showSwitch
slotProps={{
submitButton: {
disabled: !formState.isDirty || maintenanceActive,
loading: formState.isSubmitting,
},
}}
className="flex flex-col px-0"
>
<Divider />
{ports.map((port, index) => {
if (port?.type !== 'http' || !port?.publish) {
return null;
}
const fieldTitle = `${port.type} <-> ${port.port}`.toUpperCase();
const showDivider = index < ports.length - 1;
return (
<div key={`ports.${port.port}`}>
<RateLimitField
title={fieldTitle}
disabled={!enabled}
register={register}
errors={errors.ports}
id={`ports.${index}`}
/>
{showDivider && <Divider />}
</div>
);
})}
</SettingsContainer>
</Form>
</FormProvider>
);
}

View File

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

View File

@@ -0,0 +1,23 @@
import * as Yup from 'yup';
export const rateLimitingItemValidationSchema = Yup.object({
limit: Yup.number()
.required('Limit is required.')
.min(1)
.positive('Limit must be a positive number')
.typeError('Limit must be a number.'),
interval: Yup.number()
.required('Interval is required.')
.min(1)
.positive('Interval must be a positive number')
.typeError('Interval must be a number.'),
intervalUnit: Yup.string()
.required('Interval unit is required.')
.oneOf(['s', 'm', 'h']),
});
export const intervalUnitOptions = [
{ value: 's', label: 'seconds' },
{ value: 'm', label: 'minutes' },
{ value: 'h', label: 'hours' },
];

View File

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

View File

@@ -0,0 +1,120 @@
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { useGetRateLimitConfigQuery } from '@/utils/__generated__/graphql';
import { DEFAULT_RATE_LIMITS } from 'features/projects/rate-limiting/settings/utils/constants';
import { parseIntervalNameUnit } from 'features/projects/rate-limiting/settings/utils/parseIntervalNameUnit';
export default function useGetRateLimits() {
const { currentProject } = useCurrentWorkspaceAndProject();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { data, loading } = useGetRateLimitConfigQuery({
variables: {
appId: currentProject?.id,
resolve: true,
},
skip: !currentProject,
...(!isPlatform ? { client: localMimirClient } : {}),
});
const authRateLimit = data?.config?.auth?.rateLimit;
const hasuraRateLimit = data?.config?.hasura?.rateLimit;
const storageRateLimit = data?.config?.storage?.rateLimit;
const functionsRateLimit = data?.config?.functions?.rateLimit;
const { bruteForce, emails, global, signups, sms } = authRateLimit || {};
const { limit: bruteForceLimit, interval: bruteForceIntervalStr } =
bruteForce || {};
const { interval: bruteForceInterval, intervalUnit: bruteForceIntervalUnit } =
parseIntervalNameUnit(bruteForceIntervalStr);
const { limit: emailsLimit, interval: emailsIntervalStr } = emails || {};
const { interval: emailsInterval, intervalUnit: emailsIntervalUnit } =
parseIntervalNameUnit(emailsIntervalStr);
const { limit: globalLimit, interval: globalIntervalStr } = global || {};
const { interval: globalInterval, intervalUnit: globalIntervalUnit } =
parseIntervalNameUnit(globalIntervalStr);
const { limit: signupsLimit, interval: signupsIntervalStr } = signups || {};
const { interval: signupsInterval, intervalUnit: signupsIntervalUnit } =
parseIntervalNameUnit(signupsIntervalStr);
const { limit: smsLimit, interval: smsIntervalStr } = sms || {};
const { interval: smsInterval, intervalUnit: smsIntervalUnit } =
parseIntervalNameUnit(smsIntervalStr);
const { limit: hasuraLimit, interval: hasuraIntervalStr } =
hasuraRateLimit || {};
const { interval: hasuraInterval, intervalUnit: hasuraIntervalUnit } =
parseIntervalNameUnit(hasuraIntervalStr);
const { limit: storageLimit, interval: storageIntervalStr } =
storageRateLimit || {};
const { interval: storageInterval, intervalUnit: storageIntervalUnit } =
parseIntervalNameUnit(storageIntervalStr);
const { limit: functionsLimit, interval: functionsIntervalStr } =
functionsRateLimit || {};
const { interval: functionsInterval, intervalUnit: functionsIntervalUnit } =
parseIntervalNameUnit(functionsIntervalStr);
return {
authRateLimit: {
enabled: !!authRateLimit,
bruteForce: {
limit: bruteForceLimit || DEFAULT_RATE_LIMITS.limit,
interval: bruteForceInterval || DEFAULT_RATE_LIMITS.interval,
intervalUnit:
bruteForceIntervalUnit || DEFAULT_RATE_LIMITS.intervalUnit,
},
emails: {
limit: emailsLimit || DEFAULT_RATE_LIMITS.limit,
interval: emailsInterval || DEFAULT_RATE_LIMITS.interval,
intervalUnit: emailsIntervalUnit || DEFAULT_RATE_LIMITS.intervalUnit,
},
global: {
limit: globalLimit || DEFAULT_RATE_LIMITS.limit,
interval: globalInterval || DEFAULT_RATE_LIMITS.interval,
intervalUnit: globalIntervalUnit || DEFAULT_RATE_LIMITS.intervalUnit,
},
signups: {
limit: signupsLimit || DEFAULT_RATE_LIMITS.limit,
interval: signupsInterval || DEFAULT_RATE_LIMITS.interval,
intervalUnit: signupsIntervalUnit || DEFAULT_RATE_LIMITS.intervalUnit,
},
sms: {
limit: smsLimit || DEFAULT_RATE_LIMITS.limit,
interval: smsInterval || DEFAULT_RATE_LIMITS.interval,
intervalUnit: smsIntervalUnit || DEFAULT_RATE_LIMITS.intervalUnit,
},
},
hasuraDefaultValues: {
enabled: !!hasuraRateLimit,
rateLimit: {
limit: hasuraLimit || DEFAULT_RATE_LIMITS.limit,
interval: hasuraInterval || DEFAULT_RATE_LIMITS.interval,
intervalUnit: hasuraIntervalUnit || DEFAULT_RATE_LIMITS.intervalUnit,
},
},
storageDefaultValues: {
enabled: !!storageRateLimit,
rateLimit: {
limit: storageLimit || DEFAULT_RATE_LIMITS.limit,
interval: storageInterval || DEFAULT_RATE_LIMITS.interval,
intervalUnit: storageIntervalUnit || DEFAULT_RATE_LIMITS.intervalUnit,
},
},
functionsDefaultValues: {
enabled: !!functionsRateLimit,
rateLimit: {
limit: functionsLimit || DEFAULT_RATE_LIMITS.limit,
interval: functionsInterval || DEFAULT_RATE_LIMITS.interval,
intervalUnit: functionsIntervalUnit || DEFAULT_RATE_LIMITS.intervalUnit,
},
},
loading,
};
}

View File

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

View File

@@ -0,0 +1,107 @@
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import {
useGetLocalRunServiceRateLimitQuery,
useGetRunServicesRateLimitQuery,
type GetRunServicesRateLimitQuery,
} from '@/utils/__generated__/graphql';
import { DEFAULT_RATE_LIMITS } from 'features/projects/rate-limiting/settings/utils/constants';
import { parseIntervalNameUnit } from 'features/projects/rate-limiting/settings/utils/parseIntervalNameUnit';
import { useMemo } from 'react';
type RunService = Pick<
GetRunServicesRateLimitQuery['app']['runServices'][0],
'config'
> & {
id?: string;
serviceID?: string;
createdAt?: string;
updatedAt?: string;
subdomain?: string;
};
export interface UseGetRunServiceRateLimitsReturn {
services: {
name?: string;
id?: string;
enabled?: boolean;
ports?: {
type?: string;
port?: string;
publish?: boolean;
rateLimit?: {
limit?: number;
interval?: number;
intervalUnit?: string;
};
}[];
}[];
loading: boolean;
}
export default function useGetRunServiceRateLimits(): UseGetRunServiceRateLimitsReturn {
const { currentProject } = useCurrentWorkspaceAndProject();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { data, loading: loadingPlatformServices } =
useGetRunServicesRateLimitQuery({
variables: {
appID: currentProject?.id,
resolve: false,
},
skip: !isPlatform,
});
const { loading: loadingLocalServices, data: localServicesData } =
useGetLocalRunServiceRateLimitQuery({
variables: { appID: currentProject?.id, 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 servicesInfo = services.map((service) => {
const enabled = service?.config?.ports?.some(
(port) => port?.rateLimit && port?.type === 'http' && port?.publish,
);
const ports = service?.config?.ports?.map((port) => {
const { interval, intervalUnit } = parseIntervalNameUnit(
port?.rateLimit?.interval,
);
const rateLimit = {
limit: port?.rateLimit?.limit || DEFAULT_RATE_LIMITS.limit,
interval: interval || DEFAULT_RATE_LIMITS.interval,
intervalUnit: intervalUnit || DEFAULT_RATE_LIMITS.intervalUnit,
};
return {
type: port?.type,
publish: port?.publish,
port: port?.port,
rateLimit,
};
});
return {
enabled,
name: service.config?.name,
id: service.id ?? service.serviceID,
ports,
};
});
return { services: servicesInfo, loading };
}

View File

@@ -0,0 +1,5 @@
export const DEFAULT_RATE_LIMITS = {
limit: 1000,
interval: 5,
intervalUnit: 'm',
};

View File

@@ -0,0 +1 @@
export * from './constants';

View File

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

View File

@@ -0,0 +1,18 @@
export default function parseIntervalNameUnit(interval: string) {
if (!interval) {
return {};
}
const regex = /^(\d+)([a-zA-Z])$/;
const match = interval.match(regex);
if (!match) {
return {};
}
const [, intervalValue, intervalUnit] = match;
return {
interval: parseInt(intervalValue, 10),
intervalUnit,
};
}

View File

@@ -102,6 +102,9 @@ export default function ServiceForm({
const getFormattedConfig = (values: ServiceFormValues) => {
// Remove any __typename property from the values
const sanitizedValues = removeTypename(values) as ServiceFormValues;
const sanitizedInitialDataPorts = initialData?.ports
? removeTypename(initialData.ports)
: [];
const config: ConfigRunServiceConfigInsertInput = {
name: sanitizedValues.name,
@@ -130,6 +133,10 @@ export default function ServiceForm({
type: item.type,
publish: item.publish,
ingresses: item.ingresses,
rateLimit:
sanitizedInitialDataPorts.find(
(port) => port.port === item.port && port.type === item.type,
)?.rateLimit ?? null,
})),
healthCheck: sanitizedValues.healthCheck
? {
@@ -309,7 +316,7 @@ export default function ServiceForm({
<Tooltip title="Name of the service, must be unique per project.">
<InfoIcon
aria-label="Info"
className="w-4 h-4"
className="h-4 w-4"
color="primary"
/>
</Tooltip>
@@ -349,7 +356,7 @@ export default function ServiceForm({
>
<InfoIcon
aria-label="Info"
className="w-4 h-4"
className="h-4 w-4"
color="primary"
/>
</Tooltip>
@@ -380,7 +387,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="w-4 h-4"
className="h-4 w-4"
color="primary"
/>
</Tooltip>
@@ -428,7 +435,7 @@ export default function ServiceForm({
{createServiceFormError && (
<Alert
severity="error"
className="grid items-center justify-between grid-flow-col px-4 py-3"
className="grid grid-flow-col items-center justify-between px-4 py-3"
>
<span className="text-left">
<strong>Error:</strong> {createServiceFormError.message}

View File

@@ -69,7 +69,16 @@ export interface ServiceFormProps extends DialogFormProps {
/**
* if there is initialData then it's an update operation
*/
initialData?: ServiceFormValues & { subdomain?: string }; // subdomain is only set on the backend
initialData?: Omit<ServiceFormValues, 'ports'> & {
subdomain?: string;
ports: {
port: number;
type: PortTypes;
publish: boolean;
ingresses?: { fqdn?: string[] }[] | null;
rateLimit?: { limit: number; interval: string } | null;
}[];
}; // subdomain is only set on the backend
/**
* Function to be called when the operation is cancelled.

View File

@@ -70,7 +70,7 @@ export default function ComputeFormSection({
<a
target="_blank"
rel="noopener noreferrer"
href="https://docs.nhost.io/run/resources"
href="https://docs.nhost.io/guides/run/resources"
className="underline"
>
resources

View File

@@ -35,7 +35,7 @@ export default function ReplicasFormSection() {
<a
target="_blank"
rel="noopener noreferrer"
href="https://docs.nhost.io/run/resources"
href="https://docs.nhost.io/guides/run/resources"
className="underline"
>
resources

View File

@@ -7,6 +7,7 @@ import { Tooltip } from '@/components/ui/v2/Tooltip';
import { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import type { ServiceFormValues } from '@/features/services/components/ServiceForm/ServiceFormTypes';
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
import { useState } from 'react';
export interface ServiceConfirmationDialogProps {
/**
@@ -28,10 +29,21 @@ export default function ServiceConfirmationDialog({
onCancel,
onSubmit,
}: ServiceConfirmationDialogProps) {
const [isSubmitting, setIsSubmitting] = useState(false);
const approximatePriceForService = parseFloat(
(formValues.compute.cpu * formValues.replicas * COST_PER_VCPU).toFixed(2),
);
const handleSubmit = async () => {
setIsSubmitting(true);
try {
await onSubmit();
} finally {
setIsSubmitting(false);
}
};
return (
<div className="grid grid-flow-row gap-6 px-6 pb-6">
<Box className="grid grid-flow-row gap-4">
@@ -74,7 +86,12 @@ export default function ServiceConfirmationDialog({
</Box>
<Box className="grid grid-flow-row gap-2">
<Button color="primary" onClick={onSubmit} autoFocus>
<Button
loading={isSubmitting}
color="primary"
onClick={handleSubmit}
autoFocus
>
Confirm
</Button>

View File

@@ -67,6 +67,7 @@ export default function ServicesList({
type: item.type as PortTypes,
publish: item.publish,
ingresses: item.ingresses,
rateLimit: item.rateLimit,
})),
compute: service.config?.resources?.compute ?? {
cpu: 62,
@@ -179,7 +180,10 @@ export default function ServicesList({
<Dropdown.Item
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
sx={{ color: 'error.main' }}
onClick={() => deleteService(service)}
onClick={(e) => {
e.stopPropagation();
deleteService(service);
}}
disabled={!isPlatform}
>
<TrashIcon className="w-4 h-4" />

View File

@@ -0,0 +1,46 @@
query getRateLimitConfig($appId: uuid!, $resolve: Boolean!) {
config(appID: $appId, resolve: $resolve) {
hasura {
rateLimit {
limit
interval
}
}
storage {
rateLimit {
limit
interval
}
}
functions {
rateLimit {
limit
interval
}
}
auth {
rateLimit {
bruteForce {
limit
interval
}
emails {
limit
interval
}
global {
limit
interval
}
signups {
limit
interval
}
sms {
limit
interval
}
}
}
}
}

View File

@@ -0,0 +1,49 @@
mutation UpdateRateLimitConfig(
$appId: uuid!
$config: ConfigConfigUpdateInput!
) {
updateConfig(appID: $appId, config: $config) {
hasura {
rateLimit {
limit
interval
}
}
storage {
rateLimit {
limit
interval
}
}
functions {
rateLimit {
limit
interval
}
}
auth {
rateLimit {
bruteForce {
limit
interval
}
emails {
limit
interval
}
global {
limit
interval
}
signups {
limit
interval
}
sms {
limit
interval
}
}
}
}
}

View File

@@ -27,6 +27,10 @@ fragment RunServiceConfig on ConfigRunServiceConfig {
ingresses {
fqdn
}
rateLimit {
limit
interval
}
}
healthCheck {
port

View File

@@ -0,0 +1,38 @@
fragment RunServiceRateLimit on ConfigRunServiceConfig {
name
ports {
port
type
publish
rateLimit {
limit
interval
}
ingresses {
fqdn
}
}
}
query getRunServicesRateLimit($appID: uuid!, $resolve: Boolean!) {
app(id: $appID) {
runServices {
id
createdAt
updatedAt
subdomain
config(resolve: $resolve) {
...RunServiceRateLimit
}
}
}
}
query getLocalRunServiceRateLimit($appID: uuid!, $resolve: Boolean!) {
runServiceConfigs(appID: $appID, resolve: $resolve) {
serviceID
config {
...RunServiceRateLimit
}
}
}

View File

@@ -6,6 +6,7 @@ import { AllowedRedirectURLsSettings } from '@/features/authentication/settings/
import { AuthServiceVersionSettings } from '@/features/authentication/settings/components/AuthServiceVersionSettings';
import { BlockedEmailSettings } from '@/features/authentication/settings/components/BlockedEmailSettings';
import { ClientURLSettings } from '@/features/authentication/settings/components/ClientURLSettings';
import { ConcealErrorsSettings } from '@/features/authentication/settings/components/ConcealErrorsSettings';
import { DisableNewUsersSettings } from '@/features/authentication/settings/components/DisableNewUsersSettings';
import { GravatarSettings } from '@/features/authentication/settings/components/GravatarSettings';
import { MFASettings } from '@/features/authentication/settings/components/MFASettings';
@@ -43,7 +44,7 @@ export default function SettingsAuthenticationPage() {
return (
<Container
className="grid max-w-5xl grid-flow-row bg-transparent gap-y-6"
className="grid max-w-5xl grid-flow-row gap-y-6 bg-transparent"
rootClassName="bg-transparent"
>
<AuthServiceVersionSettings />
@@ -55,6 +56,7 @@ export default function SettingsAuthenticationPage() {
<SessionSettings />
<GravatarSettings />
<DisableNewUsersSettings />
<ConcealErrorsSettings />
</Container>
);
}

View File

@@ -0,0 +1,90 @@
import { Container } from '@/components/layout/Container';
import { SettingsLayout } from '@/components/layout/SettingsLayout';
import { Box } from '@/components/ui/v2/Box';
import { ArrowSquareOutIcon } from '@/components/ui/v2/icons/ArrowSquareOutIcon';
import { Link } from '@/components/ui/v2/Link';
import { Text } from '@/components/ui/v2/Text';
import { AuthLimitingForm } from '@/features/projects/rate-limiting/settings/components/AuthLimitingForm';
import { RateLimitingForm } from '@/features/projects/rate-limiting/settings/components/RateLimitingForm';
import { RunServiceLimitingForm } from '@/features/projects/rate-limiting/settings/components/RunServiceLimitingForm';
import { useGetRateLimits } from '@/features/projects/rate-limiting/settings/hooks/useGetRateLimits';
import { useGetRunServiceRateLimits } from '@/features/projects/rate-limiting/settings/hooks/useGetRunServiceRateLimits';
import { type ReactElement } from 'react';
export default function RateLimiting() {
const { services, loading } = useGetRunServiceRateLimits();
const {
hasuraDefaultValues,
functionsDefaultValues,
storageDefaultValues,
loading: loadingBaseServices,
} = useGetRateLimits();
return (
<Container
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">
<div className="flex flex-col space-y-2">
<Text className="text-lg font-semibold">Rate Limiting</Text>
<Text color="secondary">
Learn more about
<Link
href="https://docs.nhost.io/platform/rate-limits"
target="_blank"
rel="noopener noreferrer"
underline="hover"
className="ml-1 font-medium"
>
Rate Limiting
<ArrowSquareOutIcon className="ml-1 h-4 w-4" />
</Link>
</Text>
</div>
</Box>
<AuthLimitingForm />
<RateLimitingForm
defaultValues={hasuraDefaultValues}
loading={loadingBaseServices}
serviceName="hasura"
title="Hasura"
/>
<RateLimitingForm
defaultValues={storageDefaultValues}
loading={loadingBaseServices}
serviceName="storage"
title="Storage"
/>
<RateLimitingForm
defaultValues={functionsDefaultValues}
loading={loadingBaseServices}
serviceName="functions"
title="Functions"
/>
{services?.map((service) => {
if (
service?.ports?.some((port) => port?.type === 'http' && port?.publish)
) {
return (
<RunServiceLimitingForm
enabledDefault={service.enabled}
key={service.id}
title={service.name}
serviceId={service.id}
ports={service.ports}
loading={loading}
/>
);
}
return null;
})}
</Container>
);
}
RateLimiting.getLayout = function getLayout(page: ReactElement) {
return <SettingsLayout>{page}</SettingsLayout>;
};

View File

@@ -10,6 +10,7 @@ import { Text } from '@/components/ui/v2/Text';
import { getToastStyleProps } from '@/utils/constants/settings';
import { nhost } from '@/utils/nhost';
import { yupResolver } from '@hookform/resolvers/yup';
import { Turnstile } from '@marsidev/react-turnstile';
import { styled } from '@mui/material';
import { useSignUpEmailPassword } from '@nhost/nextjs';
import { useRouter } from 'next/router';
@@ -39,6 +40,9 @@ export default function SignUpPage() {
const [loading, setLoading] = useState(false);
const router = useRouter();
// x-cf-turnstile-response
const [turnstileResponse, setTurnstileResponse] = useState(null);
const form = useForm<SignUpFormValues>({
reValidateMode: 'onSubmit',
defaultValues: {
@@ -66,11 +70,27 @@ export default function SignUpPage() {
password,
displayName,
}: SignUpFormValues) {
if (!turnstileResponse) {
toast.error(
'Please complete the signup verification challenge to continue.',
getToastStyleProps(),
);
return;
}
try {
const { needsEmailVerification } = await signUpEmailPassword(
email,
password,
{ displayName },
{
displayName,
},
{
headers: {
'x-cf-turnstile-response': turnstileResponse,
},
},
);
if (needsEmailVerification) {
@@ -94,7 +114,7 @@ export default function SignUpPage() {
Sign Up
</Text>
<Box className="grid grid-flow-row gap-4 rounded-md border bg-transparent p-6 lg:p-12">
<Box className="grid grid-flow-row gap-4 p-6 bg-transparent border rounded-md lg:p-12">
<Button
variant="borderless"
className="!bg-white !text-black hover:ring-2 hover:ring-white hover:ring-opacity-50 disabled:!text-black disabled:!text-opacity-60"
@@ -122,7 +142,7 @@ export default function SignUpPage() {
<div className="relative py-2">
<Text
className="absolute left-0 right-0 top-1/2 mx-auto w-12 -translate-y-1/2 bg-black px-2 text-center text-sm"
className="absolute left-0 right-0 w-12 px-2 mx-auto text-sm text-center -translate-y-1/2 bg-black top-1/2"
color="disabled"
>
OR
@@ -172,6 +192,12 @@ export default function SignUpPage() {
helperText={formState.errors.password?.message}
/>
<Turnstile
siteKey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY}
options={{ theme: 'dark', size: 'flexible' }}
onSuccess={setTurnstileResponse}
/>
<Button
variant="outlined"
color="secondary"
@@ -188,7 +214,7 @@ export default function SignUpPage() {
<Divider className="!my-2" />
<Text color="secondary" className="text-center text-sm">
<Text color="secondary" className="text-sm text-center">
By signing up, you agree to our{' '}
<NavLink
href="https://nhost.io/legal/terms-of-service"
@@ -212,7 +238,7 @@ export default function SignUpPage() {
</Text>
</Box>
<Text color="secondary" className="text-center text-base lg:text-lg">
<Text color="secondary" className="text-base text-center lg:text-lg">
Already have an account?{' '}
<NavLink href="/signin" color="white" className="font-medium">
Sign In

View File

@@ -199,6 +199,8 @@ export type ConfigAuth = {
__typename?: 'ConfigAuth';
elevatedPrivileges?: Maybe<ConfigAuthElevatedPrivileges>;
method?: Maybe<ConfigAuthMethod>;
misc?: Maybe<ConfigAuthMisc>;
rateLimit?: Maybe<ConfigAuthRateLimit>;
redirections?: Maybe<ConfigAuthRedirections>;
/** Resources for the service */
resources?: Maybe<ConfigResources>;
@@ -223,6 +225,8 @@ export type ConfigAuthComparisonExp = {
_or?: InputMaybe<Array<ConfigAuthComparisonExp>>;
elevatedPrivileges?: InputMaybe<ConfigAuthElevatedPrivilegesComparisonExp>;
method?: InputMaybe<ConfigAuthMethodComparisonExp>;
misc?: InputMaybe<ConfigAuthMiscComparisonExp>;
rateLimit?: InputMaybe<ConfigAuthRateLimitComparisonExp>;
redirections?: InputMaybe<ConfigAuthRedirectionsComparisonExp>;
resources?: InputMaybe<ConfigResourcesComparisonExp>;
session?: InputMaybe<ConfigAuthSessionComparisonExp>;
@@ -255,6 +259,8 @@ export type ConfigAuthElevatedPrivilegesUpdateInput = {
export type ConfigAuthInsertInput = {
elevatedPrivileges?: InputMaybe<ConfigAuthElevatedPrivilegesInsertInput>;
method?: InputMaybe<ConfigAuthMethodInsertInput>;
misc?: InputMaybe<ConfigAuthMiscInsertInput>;
rateLimit?: InputMaybe<ConfigAuthRateLimitInsertInput>;
redirections?: InputMaybe<ConfigAuthRedirectionsInsertInput>;
resources?: InputMaybe<ConfigResourcesInsertInput>;
session?: InputMaybe<ConfigAuthSessionInsertInput>;
@@ -684,6 +690,62 @@ export type ConfigAuthMethodWebauthnUpdateInput = {
relyingParty?: InputMaybe<ConfigAuthMethodWebauthnRelyingPartyUpdateInput>;
};
export type ConfigAuthMisc = {
__typename?: 'ConfigAuthMisc';
concealErrors?: Maybe<Scalars['Boolean']>;
};
export type ConfigAuthMiscComparisonExp = {
_and?: InputMaybe<Array<ConfigAuthMiscComparisonExp>>;
_not?: InputMaybe<ConfigAuthMiscComparisonExp>;
_or?: InputMaybe<Array<ConfigAuthMiscComparisonExp>>;
concealErrors?: InputMaybe<ConfigBooleanComparisonExp>;
};
export type ConfigAuthMiscInsertInput = {
concealErrors?: InputMaybe<Scalars['Boolean']>;
};
export type ConfigAuthMiscUpdateInput = {
concealErrors?: InputMaybe<Scalars['Boolean']>;
};
export type ConfigAuthRateLimit = {
__typename?: 'ConfigAuthRateLimit';
bruteForce?: Maybe<ConfigRateLimit>;
emails?: Maybe<ConfigRateLimit>;
global?: Maybe<ConfigRateLimit>;
signups?: Maybe<ConfigRateLimit>;
sms?: Maybe<ConfigRateLimit>;
};
export type ConfigAuthRateLimitComparisonExp = {
_and?: InputMaybe<Array<ConfigAuthRateLimitComparisonExp>>;
_not?: InputMaybe<ConfigAuthRateLimitComparisonExp>;
_or?: InputMaybe<Array<ConfigAuthRateLimitComparisonExp>>;
bruteForce?: InputMaybe<ConfigRateLimitComparisonExp>;
emails?: InputMaybe<ConfigRateLimitComparisonExp>;
global?: InputMaybe<ConfigRateLimitComparisonExp>;
signups?: InputMaybe<ConfigRateLimitComparisonExp>;
sms?: InputMaybe<ConfigRateLimitComparisonExp>;
};
export type ConfigAuthRateLimitInsertInput = {
bruteForce?: InputMaybe<ConfigRateLimitInsertInput>;
emails?: InputMaybe<ConfigRateLimitInsertInput>;
global?: InputMaybe<ConfigRateLimitInsertInput>;
signups?: InputMaybe<ConfigRateLimitInsertInput>;
sms?: InputMaybe<ConfigRateLimitInsertInput>;
};
export type ConfigAuthRateLimitUpdateInput = {
bruteForce?: InputMaybe<ConfigRateLimitUpdateInput>;
emails?: InputMaybe<ConfigRateLimitUpdateInput>;
global?: InputMaybe<ConfigRateLimitUpdateInput>;
signups?: InputMaybe<ConfigRateLimitUpdateInput>;
sms?: InputMaybe<ConfigRateLimitUpdateInput>;
};
export type ConfigAuthRedirections = {
__typename?: 'ConfigAuthRedirections';
/** AUTH_ACCESS_CONTROL_ALLOWED_REDIRECT_URLS */
@@ -834,6 +896,8 @@ export type ConfigAuthTotpUpdateInput = {
export type ConfigAuthUpdateInput = {
elevatedPrivileges?: InputMaybe<ConfigAuthElevatedPrivilegesUpdateInput>;
method?: InputMaybe<ConfigAuthMethodUpdateInput>;
misc?: InputMaybe<ConfigAuthMiscUpdateInput>;
rateLimit?: InputMaybe<ConfigAuthRateLimitUpdateInput>;
redirections?: InputMaybe<ConfigAuthRedirectionsUpdateInput>;
resources?: InputMaybe<ConfigResourcesUpdateInput>;
session?: InputMaybe<ConfigAuthSessionUpdateInput>;
@@ -1233,6 +1297,7 @@ export type ConfigFloatComparisonExp = {
export type ConfigFunctions = {
__typename?: 'ConfigFunctions';
node?: Maybe<ConfigFunctionsNode>;
rateLimit?: Maybe<ConfigRateLimit>;
resources?: Maybe<ConfigFunctionsResources>;
};
@@ -1241,11 +1306,13 @@ export type ConfigFunctionsComparisonExp = {
_not?: InputMaybe<ConfigFunctionsComparisonExp>;
_or?: InputMaybe<Array<ConfigFunctionsComparisonExp>>;
node?: InputMaybe<ConfigFunctionsNodeComparisonExp>;
rateLimit?: InputMaybe<ConfigRateLimitComparisonExp>;
resources?: InputMaybe<ConfigFunctionsResourcesComparisonExp>;
};
export type ConfigFunctionsInsertInput = {
node?: InputMaybe<ConfigFunctionsNodeInsertInput>;
rateLimit?: InputMaybe<ConfigRateLimitInsertInput>;
resources?: InputMaybe<ConfigFunctionsResourcesInsertInput>;
};
@@ -1291,6 +1358,7 @@ export type ConfigFunctionsResourcesUpdateInput = {
export type ConfigFunctionsUpdateInput = {
node?: InputMaybe<ConfigFunctionsNodeUpdateInput>;
rateLimit?: InputMaybe<ConfigRateLimitUpdateInput>;
resources?: InputMaybe<ConfigFunctionsResourcesUpdateInput>;
};
@@ -1415,6 +1483,7 @@ export type ConfigHasura = {
/** JWT Secrets configuration */
jwtSecrets?: Maybe<Array<ConfigJwtSecret>>;
logs?: Maybe<ConfigHasuraLogs>;
rateLimit?: Maybe<ConfigRateLimit>;
/** Resources for the service */
resources?: Maybe<ConfigResources>;
/**
@@ -1477,6 +1546,7 @@ export type ConfigHasuraComparisonExp = {
events?: InputMaybe<ConfigHasuraEventsComparisonExp>;
jwtSecrets?: InputMaybe<ConfigJwtSecretComparisonExp>;
logs?: InputMaybe<ConfigHasuraLogsComparisonExp>;
rateLimit?: InputMaybe<ConfigRateLimitComparisonExp>;
resources?: InputMaybe<ConfigResourcesComparisonExp>;
settings?: InputMaybe<ConfigHasuraSettingsComparisonExp>;
version?: InputMaybe<ConfigStringComparisonExp>;
@@ -1510,6 +1580,7 @@ export type ConfigHasuraInsertInput = {
events?: InputMaybe<ConfigHasuraEventsInsertInput>;
jwtSecrets?: InputMaybe<Array<ConfigJwtSecretInsertInput>>;
logs?: InputMaybe<ConfigHasuraLogsInsertInput>;
rateLimit?: InputMaybe<ConfigRateLimitInsertInput>;
resources?: InputMaybe<ConfigResourcesInsertInput>;
settings?: InputMaybe<ConfigHasuraSettingsInsertInput>;
version?: InputMaybe<Scalars['String']>;
@@ -1602,6 +1673,7 @@ export type ConfigHasuraUpdateInput = {
events?: InputMaybe<ConfigHasuraEventsUpdateInput>;
jwtSecrets?: InputMaybe<Array<ConfigJwtSecretUpdateInput>>;
logs?: InputMaybe<ConfigHasuraLogsUpdateInput>;
rateLimit?: InputMaybe<ConfigRateLimitUpdateInput>;
resources?: InputMaybe<ConfigResourcesUpdateInput>;
settings?: InputMaybe<ConfigHasuraSettingsUpdateInput>;
version?: InputMaybe<Scalars['String']>;
@@ -2013,6 +2085,30 @@ export type ConfigProviderUpdateInput = {
smtp?: InputMaybe<ConfigSmtpUpdateInput>;
};
export type ConfigRateLimit = {
__typename?: 'ConfigRateLimit';
interval: Scalars['String'];
limit: Scalars['ConfigUint32'];
};
export type ConfigRateLimitComparisonExp = {
_and?: InputMaybe<Array<ConfigRateLimitComparisonExp>>;
_not?: InputMaybe<ConfigRateLimitComparisonExp>;
_or?: InputMaybe<Array<ConfigRateLimitComparisonExp>>;
interval?: InputMaybe<ConfigStringComparisonExp>;
limit?: InputMaybe<ConfigUint32ComparisonExp>;
};
export type ConfigRateLimitInsertInput = {
interval: Scalars['String'];
limit: Scalars['ConfigUint32'];
};
export type ConfigRateLimitUpdateInput = {
interval?: InputMaybe<Scalars['String']>;
limit?: InputMaybe<Scalars['ConfigUint32']>;
};
/** Resource configuration for a service */
export type ConfigResources = {
__typename?: 'ConfigResources';
@@ -2126,6 +2222,8 @@ export type ConfigRunServiceConfigWithId = {
export type ConfigRunServiceImage = {
__typename?: 'ConfigRunServiceImage';
image: Scalars['String'];
/** content of "auths", i.e., { "auths": $THIS } */
pullCredentials?: Maybe<Scalars['String']>;
};
export type ConfigRunServiceImageComparisonExp = {
@@ -2133,14 +2231,17 @@ export type ConfigRunServiceImageComparisonExp = {
_not?: InputMaybe<ConfigRunServiceImageComparisonExp>;
_or?: InputMaybe<Array<ConfigRunServiceImageComparisonExp>>;
image?: InputMaybe<ConfigStringComparisonExp>;
pullCredentials?: InputMaybe<ConfigStringComparisonExp>;
};
export type ConfigRunServiceImageInsertInput = {
image: Scalars['String'];
pullCredentials?: InputMaybe<Scalars['String']>;
};
export type ConfigRunServiceImageUpdateInput = {
image?: InputMaybe<Scalars['String']>;
pullCredentials?: InputMaybe<Scalars['String']>;
};
export type ConfigRunServiceNameComparisonExp = {
@@ -2155,6 +2256,7 @@ export type ConfigRunServicePort = {
ingresses?: Maybe<Array<ConfigIngress>>;
port: Scalars['ConfigPort'];
publish?: Maybe<Scalars['Boolean']>;
rateLimit?: Maybe<ConfigRateLimit>;
type: Scalars['String'];
};
@@ -2165,6 +2267,7 @@ export type ConfigRunServicePortComparisonExp = {
ingresses?: InputMaybe<ConfigIngressComparisonExp>;
port?: InputMaybe<ConfigPortComparisonExp>;
publish?: InputMaybe<ConfigBooleanComparisonExp>;
rateLimit?: InputMaybe<ConfigRateLimitComparisonExp>;
type?: InputMaybe<ConfigStringComparisonExp>;
};
@@ -2172,6 +2275,7 @@ export type ConfigRunServicePortInsertInput = {
ingresses?: InputMaybe<Array<ConfigIngressInsertInput>>;
port: Scalars['ConfigPort'];
publish?: InputMaybe<Scalars['Boolean']>;
rateLimit?: InputMaybe<ConfigRateLimitInsertInput>;
type: Scalars['String'];
};
@@ -2179,6 +2283,7 @@ export type ConfigRunServicePortUpdateInput = {
ingresses?: InputMaybe<Array<ConfigIngressUpdateInput>>;
port?: InputMaybe<Scalars['ConfigPort']>;
publish?: InputMaybe<Scalars['Boolean']>;
rateLimit?: InputMaybe<ConfigRateLimitUpdateInput>;
type?: InputMaybe<Scalars['String']>;
};
@@ -2386,6 +2491,7 @@ export type ConfigStandardOauthProviderWithScopeUpdateInput = {
export type ConfigStorage = {
__typename?: 'ConfigStorage';
antivirus?: Maybe<ConfigStorageAntivirus>;
rateLimit?: Maybe<ConfigRateLimit>;
/**
* Networking (custom domains at the moment) are not allowed as we need to do further
* configurations in the CDN. We will enable it again in the future.
@@ -2427,18 +2533,21 @@ export type ConfigStorageComparisonExp = {
_not?: InputMaybe<ConfigStorageComparisonExp>;
_or?: InputMaybe<Array<ConfigStorageComparisonExp>>;
antivirus?: InputMaybe<ConfigStorageAntivirusComparisonExp>;
rateLimit?: InputMaybe<ConfigRateLimitComparisonExp>;
resources?: InputMaybe<ConfigResourcesComparisonExp>;
version?: InputMaybe<ConfigStringComparisonExp>;
};
export type ConfigStorageInsertInput = {
antivirus?: InputMaybe<ConfigStorageAntivirusInsertInput>;
rateLimit?: InputMaybe<ConfigRateLimitInsertInput>;
resources?: InputMaybe<ConfigResourcesInsertInput>;
version?: InputMaybe<Scalars['String']>;
};
export type ConfigStorageUpdateInput = {
antivirus?: InputMaybe<ConfigStorageAntivirusUpdateInput>;
rateLimit?: InputMaybe<ConfigRateLimitUpdateInput>;
resources?: InputMaybe<ConfigResourcesUpdateInput>;
version?: InputMaybe<Scalars['String']>;
};
@@ -22805,7 +22914,7 @@ export type GetAuthenticationSettingsQueryVariables = Exact<{
}>;
export type GetAuthenticationSettingsQuery = { __typename?: 'query_root', config?: { __typename: 'ConfigConfig', id: 'ConfigConfig', auth?: { __typename: 'ConfigAuth', version?: string | null, id: 'ConfigAuth', redirections?: { __typename?: 'ConfigAuthRedirections', clientUrl?: any | null, allowedUrls?: Array<string> | null } | null, totp?: { __typename?: 'ConfigAuthTotp', enabled?: boolean | null, issuer?: string | null } | null, signUp?: { __typename?: 'ConfigAuthSignUp', enabled?: boolean | null } | null, session?: { __typename?: 'ConfigAuthSession', accessToken?: { __typename?: 'ConfigAuthSessionAccessToken', expiresIn?: any | null } | null, refreshToken?: { __typename?: 'ConfigAuthSessionRefreshToken', expiresIn?: any | null } | null } | null, resources?: { __typename?: 'ConfigResources', networking?: { __typename?: 'ConfigNetworking', ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null } | null } | null, user?: { __typename?: 'ConfigAuthUser', email?: { __typename?: 'ConfigAuthUserEmail', allowed?: Array<any> | null, blocked?: Array<any> | null } | null, emailDomains?: { __typename?: 'ConfigAuthUserEmailDomains', allowed?: Array<string> | null, blocked?: Array<string> | null } | null, gravatar?: { __typename?: 'ConfigAuthUserGravatar', enabled?: boolean | null, default?: string | null, rating?: string | null } | null, locale?: { __typename?: 'ConfigAuthUserLocale', allowed?: Array<any> | null, default?: any | null } | null } | null } | null } | null };
export type GetAuthenticationSettingsQuery = { __typename?: 'query_root', config?: { __typename: 'ConfigConfig', id: 'ConfigConfig', auth?: { __typename: 'ConfigAuth', version?: string | null, id: 'ConfigAuth', redirections?: { __typename?: 'ConfigAuthRedirections', clientUrl?: any | null, allowedUrls?: Array<string> | null } | null, totp?: { __typename?: 'ConfigAuthTotp', enabled?: boolean | null, issuer?: string | null } | null, signUp?: { __typename?: 'ConfigAuthSignUp', enabled?: boolean | null } | null, session?: { __typename?: 'ConfigAuthSession', accessToken?: { __typename?: 'ConfigAuthSessionAccessToken', expiresIn?: any | null } | null, refreshToken?: { __typename?: 'ConfigAuthSessionRefreshToken', expiresIn?: any | null } | null } | null, resources?: { __typename?: 'ConfigResources', networking?: { __typename?: 'ConfigNetworking', ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null } | null } | null, user?: { __typename?: 'ConfigAuthUser', email?: { __typename?: 'ConfigAuthUserEmail', allowed?: Array<any> | null, blocked?: Array<any> | null } | null, emailDomains?: { __typename?: 'ConfigAuthUserEmailDomains', allowed?: Array<string> | null, blocked?: Array<string> | null } | null, gravatar?: { __typename?: 'ConfigAuthUserGravatar', enabled?: boolean | null, default?: string | null, rating?: string | null } | null, locale?: { __typename?: 'ConfigAuthUserLocale', allowed?: Array<any> | null, default?: any | null } | null } | null, misc?: { __typename?: 'ConfigAuthMisc', concealErrors?: boolean | null } | null } | null } | null };
export type GetPostgresSettingsQueryVariables = Exact<{
appId: Scalars['uuid'];
@@ -23010,6 +23119,22 @@ export type GetConfigRawJsonQueryVariables = Exact<{
export type GetConfigRawJsonQuery = { __typename?: 'query_root', configRawJSON: string };
export type GetRateLimitConfigQueryVariables = Exact<{
appId: Scalars['uuid'];
resolve: Scalars['Boolean'];
}>;
export type GetRateLimitConfigQuery = { __typename?: 'query_root', config?: { __typename?: 'ConfigConfig', hasura: { __typename?: 'ConfigHasura', rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null }, storage?: { __typename?: 'ConfigStorage', rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null } | null, functions?: { __typename?: 'ConfigFunctions', rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null } | null, auth?: { __typename?: 'ConfigAuth', rateLimit?: { __typename?: 'ConfigAuthRateLimit', bruteForce?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null, emails?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null, global?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null, signups?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null, sms?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null } | null } | null } | null };
export type UpdateRateLimitConfigMutationVariables = Exact<{
appId: Scalars['uuid'];
config: ConfigConfigUpdateInput;
}>;
export type UpdateRateLimitConfigMutation = { __typename?: 'mutation_root', updateConfig: { __typename?: 'ConfigConfig', hasura: { __typename?: 'ConfigHasura', rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null }, storage?: { __typename?: 'ConfigStorage', rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null } | null, functions?: { __typename?: 'ConfigFunctions', rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null } | null, auth?: { __typename?: 'ConfigAuth', rateLimit?: { __typename?: 'ConfigAuthRateLimit', bruteForce?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null, emails?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null, global?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null, signups?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null, sms?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null } | null } | null } };
export type ReplaceConfigRawJsonMutationVariables = Exact<{
appID: Scalars['uuid'];
rawJSON: Scalars['String'];
@@ -23393,7 +23518,7 @@ export type GetRunServiceQueryVariables = Exact<{
export type GetRunServiceQuery = { __typename?: 'query_root', runService?: { __typename?: 'run_service', id: any, subdomain: string, config?: { __typename?: 'ConfigRunServiceConfig', name: any, command?: Array<string> | null, image: { __typename?: 'ConfigRunServiceImage', image: string }, resources: { __typename?: 'ConfigRunServiceResources', replicas: any, compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any }, storage?: Array<{ __typename?: 'ConfigRunServiceResourcesStorage', name: any, path: string, capacity: any }> | null }, environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string }> | null, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null }> | null } | null } | null };
export type RunServiceConfigFragment = { __typename?: 'ConfigRunServiceConfig', name: any, command?: Array<string> | null, image: { __typename?: 'ConfigRunServiceImage', image: string }, resources: { __typename?: 'ConfigRunServiceResources', replicas: any, compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any }, storage?: Array<{ __typename?: 'ConfigRunServiceResourcesStorage', name: any, path: string, capacity: any }> | null }, environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string }> | null, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null }> | null, healthCheck?: { __typename?: 'ConfigHealthCheck', port: any, initialDelaySeconds?: number | null, probePeriodSeconds?: number | null } | null };
export type RunServiceConfigFragment = { __typename?: 'ConfigRunServiceConfig', name: any, command?: Array<string> | null, image: { __typename?: 'ConfigRunServiceImage', image: string }, resources: { __typename?: 'ConfigRunServiceResources', replicas: any, compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any }, storage?: Array<{ __typename?: 'ConfigRunServiceResourcesStorage', name: any, path: string, capacity: any }> | null }, environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string }> | null, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null, rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null }> | null, healthCheck?: { __typename?: 'ConfigHealthCheck', port: any, initialDelaySeconds?: number | null, probePeriodSeconds?: number | null } | null };
export type GetRunServicesQueryVariables = Exact<{
appID: Scalars['uuid'];
@@ -23403,7 +23528,7 @@ export type GetRunServicesQueryVariables = Exact<{
}>;
export type GetRunServicesQuery = { __typename?: 'query_root', app?: { __typename?: 'apps', runServices: Array<{ __typename?: 'run_service', id: any, createdAt: any, updatedAt: any, subdomain: string, config?: { __typename?: 'ConfigRunServiceConfig', name: any, command?: Array<string> | null, image: { __typename?: 'ConfigRunServiceImage', image: string }, resources: { __typename?: 'ConfigRunServiceResources', replicas: any, compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any }, storage?: Array<{ __typename?: 'ConfigRunServiceResourcesStorage', name: any, path: string, capacity: any }> | null }, environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string }> | null, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null }> | null, healthCheck?: { __typename?: 'ConfigHealthCheck', port: any, initialDelaySeconds?: number | null, probePeriodSeconds?: number | null } | null } | null }>, runServices_aggregate: { __typename?: 'run_service_aggregate', aggregate?: { __typename?: 'run_service_aggregate_fields', count: number } | null } } | null };
export type GetRunServicesQuery = { __typename?: 'query_root', app?: { __typename?: 'apps', runServices: Array<{ __typename?: 'run_service', id: any, createdAt: any, updatedAt: any, subdomain: string, config?: { __typename?: 'ConfigRunServiceConfig', name: any, command?: Array<string> | null, image: { __typename?: 'ConfigRunServiceImage', image: string }, resources: { __typename?: 'ConfigRunServiceResources', replicas: any, compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any }, storage?: Array<{ __typename?: 'ConfigRunServiceResourcesStorage', name: any, path: string, capacity: any }> | null }, environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string }> | null, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null, rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null }> | null, healthCheck?: { __typename?: 'ConfigHealthCheck', port: any, initialDelaySeconds?: number | null, probePeriodSeconds?: number | null } | null } | null }>, runServices_aggregate: { __typename?: 'run_service_aggregate', aggregate?: { __typename?: 'run_service_aggregate_fields', count: number } | null } } | null };
export type GetLocalRunServiceConfigsQueryVariables = Exact<{
appID: Scalars['uuid'];
@@ -23411,7 +23536,25 @@ export type GetLocalRunServiceConfigsQueryVariables = Exact<{
}>;
export type GetLocalRunServiceConfigsQuery = { __typename?: 'query_root', runServiceConfigs: Array<{ __typename?: 'ConfigRunServiceConfigWithID', serviceID: any, config: { __typename?: 'ConfigRunServiceConfig', name: any, command?: Array<string> | null, image: { __typename?: 'ConfigRunServiceImage', image: string }, resources: { __typename?: 'ConfigRunServiceResources', replicas: any, compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any }, storage?: Array<{ __typename?: 'ConfigRunServiceResourcesStorage', name: any, path: string, capacity: any }> | null }, environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string }> | null, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null }> | null, healthCheck?: { __typename?: 'ConfigHealthCheck', port: any, initialDelaySeconds?: number | null, probePeriodSeconds?: number | null } | null } }> };
export type GetLocalRunServiceConfigsQuery = { __typename?: 'query_root', runServiceConfigs: Array<{ __typename?: 'ConfigRunServiceConfigWithID', serviceID: any, config: { __typename?: 'ConfigRunServiceConfig', name: any, command?: Array<string> | null, image: { __typename?: 'ConfigRunServiceImage', image: string }, resources: { __typename?: 'ConfigRunServiceResources', replicas: any, compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any }, storage?: Array<{ __typename?: 'ConfigRunServiceResourcesStorage', name: any, path: string, capacity: any }> | null }, environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string }> | null, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null, rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null }> | null, healthCheck?: { __typename?: 'ConfigHealthCheck', port: any, initialDelaySeconds?: number | null, probePeriodSeconds?: number | null } | null } }> };
export type RunServiceRateLimitFragment = { __typename?: 'ConfigRunServiceConfig', name: any, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null }> | null };
export type GetRunServicesRateLimitQueryVariables = Exact<{
appID: Scalars['uuid'];
resolve: Scalars['Boolean'];
}>;
export type GetRunServicesRateLimitQuery = { __typename?: 'query_root', app?: { __typename?: 'apps', runServices: Array<{ __typename?: 'run_service', id: any, createdAt: any, updatedAt: any, subdomain: string, config?: { __typename?: 'ConfigRunServiceConfig', name: any, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null }> | null } | null }> } | null };
export type GetLocalRunServiceRateLimitQueryVariables = Exact<{
appID: Scalars['uuid'];
resolve: Scalars['Boolean'];
}>;
export type GetLocalRunServiceRateLimitQuery = { __typename?: 'query_root', runServiceConfigs: Array<{ __typename?: 'ConfigRunServiceConfigWithID', serviceID: any, config: { __typename?: 'ConfigRunServiceConfig', name: any, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null }> | null } }> };
export type InsertRunServiceMutationVariables = Exact<{
object: Run_Service_Insert_Input;
@@ -23874,6 +24017,10 @@ export const RunServiceConfigFragmentDoc = gql`
ingresses {
fqdn
}
rateLimit {
limit
interval
}
}
healthCheck {
port
@@ -23882,6 +24029,23 @@ export const RunServiceConfigFragmentDoc = gql`
}
}
`;
export const RunServiceRateLimitFragmentDoc = gql`
fragment RunServiceRateLimit on ConfigRunServiceConfig {
name
ports {
port
type
publish
rateLimit {
limit
interval
}
ingresses {
fqdn
}
}
}
`;
export const GetWorkspaceMembersWorkspaceMemberFragmentDoc = gql`
fragment getWorkspaceMembersWorkspaceMember on workspaceMembers {
id
@@ -24194,6 +24358,9 @@ export const GetAuthenticationSettingsDocument = gql`
default
}
}
misc {
concealErrors
}
version
}
}
@@ -25371,6 +25538,161 @@ export type GetConfigRawJsonQueryResult = Apollo.QueryResult<GetConfigRawJsonQue
export function refetchGetConfigRawJsonQuery(variables: GetConfigRawJsonQueryVariables) {
return { query: GetConfigRawJsonDocument, variables: variables }
}
export const GetRateLimitConfigDocument = gql`
query getRateLimitConfig($appId: uuid!, $resolve: Boolean!) {
config(appID: $appId, resolve: $resolve) {
hasura {
rateLimit {
limit
interval
}
}
storage {
rateLimit {
limit
interval
}
}
functions {
rateLimit {
limit
interval
}
}
auth {
rateLimit {
bruteForce {
limit
interval
}
emails {
limit
interval
}
global {
limit
interval
}
signups {
limit
interval
}
sms {
limit
interval
}
}
}
}
}
`;
/**
* __useGetRateLimitConfigQuery__
*
* To run a query within a React component, call `useGetRateLimitConfigQuery` and pass it any options that fit your needs.
* When your component renders, `useGetRateLimitConfigQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetRateLimitConfigQuery({
* variables: {
* appId: // value for 'appId'
* resolve: // value for 'resolve'
* },
* });
*/
export function useGetRateLimitConfigQuery(baseOptions: Apollo.QueryHookOptions<GetRateLimitConfigQuery, GetRateLimitConfigQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GetRateLimitConfigQuery, GetRateLimitConfigQueryVariables>(GetRateLimitConfigDocument, options);
}
export function useGetRateLimitConfigLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetRateLimitConfigQuery, GetRateLimitConfigQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetRateLimitConfigQuery, GetRateLimitConfigQueryVariables>(GetRateLimitConfigDocument, options);
}
export type GetRateLimitConfigQueryHookResult = ReturnType<typeof useGetRateLimitConfigQuery>;
export type GetRateLimitConfigLazyQueryHookResult = ReturnType<typeof useGetRateLimitConfigLazyQuery>;
export type GetRateLimitConfigQueryResult = Apollo.QueryResult<GetRateLimitConfigQuery, GetRateLimitConfigQueryVariables>;
export function refetchGetRateLimitConfigQuery(variables: GetRateLimitConfigQueryVariables) {
return { query: GetRateLimitConfigDocument, variables: variables }
}
export const UpdateRateLimitConfigDocument = gql`
mutation UpdateRateLimitConfig($appId: uuid!, $config: ConfigConfigUpdateInput!) {
updateConfig(appID: $appId, config: $config) {
hasura {
rateLimit {
limit
interval
}
}
storage {
rateLimit {
limit
interval
}
}
functions {
rateLimit {
limit
interval
}
}
auth {
rateLimit {
bruteForce {
limit
interval
}
emails {
limit
interval
}
global {
limit
interval
}
signups {
limit
interval
}
sms {
limit
interval
}
}
}
}
}
`;
export type UpdateRateLimitConfigMutationFn = Apollo.MutationFunction<UpdateRateLimitConfigMutation, UpdateRateLimitConfigMutationVariables>;
/**
* __useUpdateRateLimitConfigMutation__
*
* To run a mutation, you first call `useUpdateRateLimitConfigMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useUpdateRateLimitConfigMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [updateRateLimitConfigMutation, { data, loading, error }] = useUpdateRateLimitConfigMutation({
* variables: {
* appId: // value for 'appId'
* config: // value for 'config'
* },
* });
*/
export function useUpdateRateLimitConfigMutation(baseOptions?: Apollo.MutationHookOptions<UpdateRateLimitConfigMutation, UpdateRateLimitConfigMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<UpdateRateLimitConfigMutation, UpdateRateLimitConfigMutationVariables>(UpdateRateLimitConfigDocument, options);
}
export type UpdateRateLimitConfigMutationHookResult = ReturnType<typeof useUpdateRateLimitConfigMutation>;
export type UpdateRateLimitConfigMutationResult = Apollo.MutationResult<UpdateRateLimitConfigMutation>;
export type UpdateRateLimitConfigMutationOptions = Apollo.BaseMutationOptions<UpdateRateLimitConfigMutation, UpdateRateLimitConfigMutationVariables>;
export const ReplaceConfigRawJsonDocument = gql`
mutation ReplaceConfigRawJSON($appID: uuid!, $rawJSON: String!) {
replaceConfigRawJSON(appID: $appID, rawJSON: $rawJSON)
@@ -27572,6 +27894,95 @@ export type GetLocalRunServiceConfigsQueryResult = Apollo.QueryResult<GetLocalRu
export function refetchGetLocalRunServiceConfigsQuery(variables: GetLocalRunServiceConfigsQueryVariables) {
return { query: GetLocalRunServiceConfigsDocument, variables: variables }
}
export const GetRunServicesRateLimitDocument = gql`
query getRunServicesRateLimit($appID: uuid!, $resolve: Boolean!) {
app(id: $appID) {
runServices {
id
createdAt
updatedAt
subdomain
config(resolve: $resolve) {
...RunServiceRateLimit
}
}
}
}
${RunServiceRateLimitFragmentDoc}`;
/**
* __useGetRunServicesRateLimitQuery__
*
* To run a query within a React component, call `useGetRunServicesRateLimitQuery` and pass it any options that fit your needs.
* When your component renders, `useGetRunServicesRateLimitQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetRunServicesRateLimitQuery({
* variables: {
* appID: // value for 'appID'
* resolve: // value for 'resolve'
* },
* });
*/
export function useGetRunServicesRateLimitQuery(baseOptions: Apollo.QueryHookOptions<GetRunServicesRateLimitQuery, GetRunServicesRateLimitQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GetRunServicesRateLimitQuery, GetRunServicesRateLimitQueryVariables>(GetRunServicesRateLimitDocument, options);
}
export function useGetRunServicesRateLimitLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetRunServicesRateLimitQuery, GetRunServicesRateLimitQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetRunServicesRateLimitQuery, GetRunServicesRateLimitQueryVariables>(GetRunServicesRateLimitDocument, options);
}
export type GetRunServicesRateLimitQueryHookResult = ReturnType<typeof useGetRunServicesRateLimitQuery>;
export type GetRunServicesRateLimitLazyQueryHookResult = ReturnType<typeof useGetRunServicesRateLimitLazyQuery>;
export type GetRunServicesRateLimitQueryResult = Apollo.QueryResult<GetRunServicesRateLimitQuery, GetRunServicesRateLimitQueryVariables>;
export function refetchGetRunServicesRateLimitQuery(variables: GetRunServicesRateLimitQueryVariables) {
return { query: GetRunServicesRateLimitDocument, variables: variables }
}
export const GetLocalRunServiceRateLimitDocument = gql`
query getLocalRunServiceRateLimit($appID: uuid!, $resolve: Boolean!) {
runServiceConfigs(appID: $appID, resolve: $resolve) {
serviceID
config {
...RunServiceRateLimit
}
}
}
${RunServiceRateLimitFragmentDoc}`;
/**
* __useGetLocalRunServiceRateLimitQuery__
*
* To run a query within a React component, call `useGetLocalRunServiceRateLimitQuery` and pass it any options that fit your needs.
* When your component renders, `useGetLocalRunServiceRateLimitQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetLocalRunServiceRateLimitQuery({
* variables: {
* appID: // value for 'appID'
* resolve: // value for 'resolve'
* },
* });
*/
export function useGetLocalRunServiceRateLimitQuery(baseOptions: Apollo.QueryHookOptions<GetLocalRunServiceRateLimitQuery, GetLocalRunServiceRateLimitQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GetLocalRunServiceRateLimitQuery, GetLocalRunServiceRateLimitQueryVariables>(GetLocalRunServiceRateLimitDocument, options);
}
export function useGetLocalRunServiceRateLimitLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetLocalRunServiceRateLimitQuery, GetLocalRunServiceRateLimitQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetLocalRunServiceRateLimitQuery, GetLocalRunServiceRateLimitQueryVariables>(GetLocalRunServiceRateLimitDocument, options);
}
export type GetLocalRunServiceRateLimitQueryHookResult = ReturnType<typeof useGetLocalRunServiceRateLimitQuery>;
export type GetLocalRunServiceRateLimitLazyQueryHookResult = ReturnType<typeof useGetLocalRunServiceRateLimitLazyQuery>;
export type GetLocalRunServiceRateLimitQueryResult = Apollo.QueryResult<GetLocalRunServiceRateLimitQuery, GetLocalRunServiceRateLimitQueryVariables>;
export function refetchGetLocalRunServiceRateLimitQuery(variables: GetLocalRunServiceRateLimitQueryVariables) {
return { query: GetLocalRunServiceRateLimitDocument, variables: variables }
}
export const InsertRunServiceDocument = gql`
mutation insertRunService($object: run_service_insert_input!) {
insertRunService(object: $object) {

View File

@@ -1,5 +1,39 @@
# @nhost/docs
## 2.17.2
### Patch Changes
- 52a38fe: chore: added pg_ivm extension
## 2.17.1
### Patch Changes
- db2f44d: fix: update rate-limit to reflect reality
- dda0c67: chore: udpate metrics documentation with managed configuration
## 2.17.0
### Minor Changes
- cffdec5: feat: update react quickstart guide to use the nhost react apollo template
- 4cf6677: feat: update list of postgres extensions
## 2.16.0
### Minor Changes
- ba55c1b: feat: run: added a guide on using a private registry
- 3d70c63: feat: added rate-limiter guide for auth service
## 2.15.0
### Minor Changes
- 40c0d7b: │feat: added subdomain/region information
- a18b545: feat: added postgres upgrade docs
## 2.14.3
### Patch Changes

View File

@@ -1,121 +0,0 @@
---
title: Connect Devices to Local Nhost Project
description: Configuring dnsmasq for network device connectivity to a local Nhost project
icon: ethernet
---
## Introduction
If you want to connect to your local environment from other devices on the same network, such as Android emulators
or iPhone devices, you can use **dnsmasq**. Follow this guide for the necessary configuration steps to enable this
functionality for your local Nhost project running on your machine.
<Note>
Make sure to install **dnsmasq**. If you're using another OS, please refer to the [dnsmasq website](https://thekelleys.org.uk/dnsmasq/doc.html).
<Tabs>
<Tab title="macOS">
```shell Terminal
brew install dnsmasq
```
</Tab>
<Tab title="Debian">
```shell Terminal
apt-get install dnsmasq
```
</Tab>
<Tab title="Nix">
```shell Terminal
nix-env -iA nixpkgs.dnsmasq
```
</Tab>
</Tabs>
</Note>
# Configure dnsmasq for Android
<Warning>These steps are necessary when running on both an **Android emulator** or **physical Android device**</Warning>
<Steps>
<Step title="Configure dnsmasq">
Configure `dnsmasq` to resolve nhost service urls to your machine's special [loopback address](https://developer.android.com/studio/run/emulator-networking) `10.0.2.2`
```shell Terminal
sudo dnsmasq -d \
--address=/local.auth.nhost.run/10.0.2.2 \
--address=/local.graphql.nhost.run/10.0.2.2 \
--address=/local.storage.nhost.run/10.0.2.2 \
--address=/local.functions.nhost.run/10.0.2.2
```
</Step>
<Step title="Restart dnsmasq">
If you're using another OS, please refer to the [dnsmasq website](https://thekelleys.org.uk/dnsmasq/doc.html).
<Tabs>
<Tab title="macOS">
```shell Terminal
sudo brew services restart dnsmasq
```
</Tab>
<Tab title="Debian">
```shell Terminal
sudo systemctl restart dnsmasq
```
</Tab>
</Tabs>
</Step>
<Step title="Configure the android device/emulator's DNS settings">
1. Edit your network settings: Settings > Network & Internet > Internet > AndroidWifi
2. set `IP settings` to `Static`
3. set `DNS 1` and `DNS 2` to `10.0.2.2`
4. Save
</Step>
</Steps>
# Configure dnsmasq for iOS
<Warning>These steps are only necessary when running on physical iOS device, the iOS simulator uses the host machine's network, so no additional configuration is typically needed.</Warning>
<Steps>
<Step title="Inspect your machine's IP address on your network">
<Tabs>
<Tab title="macOS">
```shell Terminal
ipconfig getifaddr en0
```
</Tab>
<Tab title="Debian">
```shell Terminal
ip addr show dev en0 | grep 'inet ' | awk '{print $2}' | cut -d/ -f1
```
</Tab>
</Tabs>
</Step>
<Step title="Configure dnsmasq">
Configure `dnsmasq` to resolve nhost service urls to your machine's ip address.
<Warning>Make sure to replace every occurrence of **[your-machine-s-up-address]** with the address printed in Step `1`</Warning>
```shell Terminal
sudo dnsmasq -d \
--address=/local.auth.nhost.run/[your-machine-s-up-address] \
--address=/local.graphql.nhost.run/[your-machine-s-up-address] \
--address=/local.storage.nhost.run/[your-machine-s-up-address] \
--address=/local.functions.nhost.run/[your-machine-s-up-address]
```
</Step>
<Step title="Restart dnsmasq">
If you're using another OS, please refer to the [dnsmasq website](https://thekelleys.org.uk/dnsmasq/doc.html).
<Tabs>
<Tab title="macOS">
```shell Terminal
sudo brew services restart dnsmasq
```
</Tab>
<Tab title="Debian">
```shell Terminal
sudo systemctl restart dnsmasq
```
</Tab>
</Tabs>
</Step>
<Step title="Configure the iPhone's DNS settings">
1. Select the wifi you're connected and select `Configure DNS`
2. Select `Manual`
3. Click on `Add server` and type your local machine's IP address printed in Step `1`
4. Save
</Step>
</Steps>

View File

@@ -0,0 +1,119 @@
---
title: Subdomain/Region
description: Connecting to your local environment
icon: compass
---
When you start the CLI the services are exposed similarly to the way they are exposed in the [cloud](/platform/subdomain). For instance, the following information is shown on your terminal after running `nhost up`
```
> nhost up
...
URLs:
- Postgres: postgres://postgres:postgres@localhost:5432/local
- Hasura: https://local.hasura.local.nhost.run
- GraphQL: https://local.graphql.local.nhost.run
- Auth: https://local.auth.local.nhost.run
- Storage: https://local.storage.local.nhost.run
- Functions: https://local.functions.local.nhost.run
- Dashboard: https://local.dashboard.local.nhost.run
- Mailhog: https://local.mailhog.local.nhost.run
SDK Configuration:
Subdomain: local
Region: local
```
There you can see the various URLs you can use to access each service plus the region and subdomain you can use to configure the SDK:
```ts
// Create a new Nhost client for local development.
const nhost = new NhostClient(
{ region: 'local', subdomain: 'local' }
)
```
The domains in the URLs above will all return the IP address for localhost, `127.0.0.1`, which should suffice for most development environments. For instance:
```
> host local.auth.local.nhost.run
local.auth.local.nhost.run has address 127.0.0.1
```
However, those URLs are powered by a dynamic DNS that can return any IPv4 address you need, you just need to replace the subdomain `local` with a `subdomain` that contains the 4 octets of the IPv4 adress you want separated by `-`. For instance:
```
> host 192-168-100-1.auth.local.nhost.run
192-168-100-1.auth.local.nhost.run has address 192.168.100.1
> host 10-10-1-108.auth.local.nhost.run
10-10-1-108.auth.local.nhost.run has address 10.10.1.108
```
This is useful if you need to connect to your environment from a different device, a VM or a mobile device emulator.
To make use of this functionality you can start your development environment after setting the environment variable `NHOST_LOCAL_SUBDOMAIN` or passing the flag `--local-subdomain` :
```
> export NHOST_LOCAL_SUBDOMAIN=192-168-1-1-8 # either this or --local-subdomain 192-168-1-108
> nhost --local-subdomain 192-168-1-108 up
...
Nhost development environment started.
URLs:
- Postgres: postgres://postgres:postgres@localhost:5432/local
- Hasura: https://192-168-1-108.hasura.local.nhost.run
- GraphQL: https://192-168-1-108.graphql.local.nhost.run
- Auth: https://192-168-1-108.auth.local.nhost.run
- Storage: https://192-168-1-108.storage.local.nhost.run
- Functions: https://192-168-1-108.functions.local.nhost.run
- Dashboard: https://192-168-1-108.dashboard.local.nhost.run
- Mailhog: https://192-168-1-108.mailhog.local.nhost.run
SDK Configuration:
Subdomain: 192-168-1-108
Region: local
Run `nhost up` to reload the development environment
Run `nhost down` to stop the development environment
Run `nhost logs` to watch the logs
```
Now you can configure the SDK with:
```ts
// Create a new Nhost client for local development.
const nhost = new NhostClient(
{ region: 'local', subdomain: '192-168-1-108' }
)
```
<Warning>
If you are trying to connect to your local environment from an external device or VM make sure that:
- The IP address you are using is reachable from this device/VM
- That your firewall isn't blocking requests
</Warning>
<Warning>
If you are testing a social provider don't forget you will need to configure the callback URL to match the subdomain/region you are using. The dashboard should be able to provide this information in settings page.
</Warning>
## Offline access
All the URLs in this document are resolved by a public DNS, which means you need Internet access to resolve them. If you need to use any of those URLs without Internet access you can add them to your `/etc/hosts` file. For instance:
```
> cat /etc/hosts
##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting. Do not change this entry.
##
127.0.0.1 localhost
255.255.255.255 broadcasthost
# ::1 localhost
127.0.0.1 local.auth.local.nhost.run local.storage.local.nhost.run ...
```
Just start with the IP you want to resolve followed by all the entries you need separated by spaces.

View File

@@ -4,6 +4,57 @@ description: List of available extensions with Nhost Postgres.
icon: grid
---
## hypopg
HypoPG is a PostgreSQL extension adding support for hypothetical indexes.
An hypothetical -- or virtual -- index is an index that doesn't really exists, and thus doesn't cost CPU, disk or any resource to create. They're useful to know if specific indexes can increase performance for problematic queries, since you can know if PostgreSQL will use these indexes or not without having to spend resources to create them.
### Managing
To install the extension you can create a migration with the following contents:
```sql SQL
SET ROLE postgres;
CREATE EXTENSION hypopg;
```
To uninstall it, you can use the following migration:
```sql SQL
SET ROLE postgres;
DROP EXTENSION hypopg;
```
### Resources
- [GitHub](https://github.com/HypoPG/hypopg)
- [Documentation](https://hypopg.readthedocs.io/)
## http
HTTP client for PostgreSQL, retrieve a web page from inside the database.
### Managing
To install the extension you can create a migration with the following contents:
```sql SQL
SET ROLE postgres;
CREATE EXTENSION http;
```
To uninstall it, you can use the following migration:
```sql SQL
SET ROLE postgres;
DROP EXTENSION http;
```
### Resources
- [GitHub](https://github.com/pramsey/pgsql-http)
## postgis
PostGIS extends the capabilities of the PostgreSQL relational database by adding support storing, indexing and querying geographic data.
@@ -84,11 +135,9 @@ DROP EXTENSION pg_cron;
- [GitHub](https://github.com/citusdata/pg_cron)
## hypopg
## pg_hashids
HypoPG is a PostgreSQL extension adding support for hypothetical indexes.
An hypothetical -- or virtual -- index is an index that doesn't really exists, and thus doesn't cost CPU, disk or any resource to create. They're useful to know if specific indexes can increase performance for problematic queries, since you can know if PostgreSQL will use these indexes or not without having to spend resources to create them.
Hashids is a small open-source library that generates short, unique, non-sequential ids from numbers. It converts numbers like 347 into strings like “yr8”. You can also decode those ids back. This is useful in bundling several parameters into one or simply using them as short UIDs.
### Managing
@@ -96,20 +145,94 @@ To install the extension you can create a migration with the following contents:
```sql SQL
SET ROLE postgres;
CREATE EXTENSION hypopg;
CREATE EXTENSION pg_hashids;
```
To uninstall it, you can use the following migration:
```sql SQL
SET ROLE postgres;
DROP EXTENSION hypopg;
DROP EXTENSION pg_hashids;
```
### Resources
- [GitHub](https://github.com/HypoPG/hypopg)
- [Documentation](https://hypopg.readthedocs.io/)
- [GitHub](https://github.com/iCyberon/pg_hashids)
## pg_ivm
The pg_ivm module provides Incremental View Maintenance (IVM) feature for PostgreSQL.
Incremental View Maintenance (IVM) is a way to make materialized views up-to-date in which only incremental changes are computed and applied on views rather than recomputing the contents from scratch as REFRESH MATERIALIZED VIEW does. IVM can update materialized views more efficiently than recomputation when only small parts of the view are changed.
### Managing
To install the extension you can create a migration with the following contents:
```sql SQL
SET ROLE postgres;
CREATE EXTENSION pg_ivm;
```
To uninstall it, you can use the following migration:
```sql SQL
SET ROLE postgres;
DROP EXTENSION pg_ivm;
```
### Resources
- [GitHub](https://github.com/sraoss/pg_ivm)
## pg_squeeze
PostgreSQL extension that removes unused space from a table and optionally sorts tuples according to particular index (as if CLUSTER command was executed concurrently with regular reads / writes). In fact we try to replace pg_repack extension.
### Managing
To install the extension you can create a migration with the following contents:
```sql SQL
SET ROLE postgres;
CREATE EXTENSION pg_squeeze;
```
In addition, you may need to configure the WAL level and replication slots. Check the official documentation for details.
To uninstall it, you can use the following migration:
```sql SQL
SET ROLE postgres;
DROP EXTENSION pg_squeeze;
```
### Resources
- [GitHub](https://github.com/cybertec-postgresql/pg_squeeze)
## pg_stat_statements
The pg_stat_statements module provides a means for tracking planning and execution statistics of all SQL statements executed by a server.
### Managing
To install the extension you can create a migration with the following contents:
```sql SQL
SET ROLE postgres;
CREATE EXTENSION pg_stat_statements;
```
To uninstall it, you can use the following migration:
```sql SQL
SET ROLE postgres;
DROP EXTENSION pg_stat_statements;
```
### Resources
- [Documentation](https://www.postgresql.org/docs/14/pgstatstatements.html)
## timescaledb
@@ -140,50 +263,3 @@ DROP EXTENSION timescaledb;
- [Documentation](https://docs.timescale.com/)
- [Website](https://www.timescale.com/)
## pg_stat_statements
The pg_stat_statements module provides a means for tracking planning and execution statistics of all SQL statements executed by a server.
### Managing
To install the extension you can create a migration with the following contents:
```sql SQL
SET ROLE postgres;
CREATE EXTENSION pg_stat_statements;
```
To uninstall it, you can use the following migration:
```sql SQL
SET ROLE postgres;
DROP EXTENSION pg_stat_statements;
```
### Resources
- [Documentation](https://www.postgresql.org/docs/14/pgstatstatements.html)
## http
HTTP client for PostgreSQL, retrieve a web page from inside the database.
### Managing
To install the extension you can create a migration with the following contents:
```sql SQL
SET ROLE postgres;
CREATE EXTENSION http;
```
To uninstall it, you can use the following migration:
```sql SQL
SET ROLE postgres;
DROP EXTENSION http;
```
### Resources
- [GitHub](https://github.com/pramsey/pgsql-http)

View File

@@ -0,0 +1,37 @@
---
title: "Upgrade Major Version"
description: Upgrade to Postgres 15.x or 16.y
icon: circle-up
---
# Upgrade process
<Info>
This document only applies when changing Postgres major version (i.e. from 14 to 15/16 or from 15 to 16). It doesn'e apply when upgrading minor versions (i.e. from 14.5 to 14.11).
</Info>
While new cloud projects ship with Postgres 14 by default, versions 15 and 16 are also supported. To change your major version you can go to Settings -> Database, select the new major version and start the process:
![dashboard settings](/images/guides/database/upgrade_01.png)
<Warning>
Keep in mind that the upgrade process requires downtime. Pay attention to all the information provided to you in the settings page.
</Warning>
After starting the process you can follow it on the same page:
![logs](/images/guides/database/upgrade_02.png)
Finally, you can confirm the upgrade by executing the SQL query `SELECT version();`
![select version()](/images/guides/database/upgrade_03.png)
## Projects with connected repos
This process can only be triggered from the dashboard. If you have a project with a connected repository and want to upgrade postgres to either 15 or 16 you will have to follow the steps below:
1. Upgrade the major version using the dashboard
2. Run `nhost config pull` or edit the `nhost.toml` by hand.
3. Push to git (this step should be a NOOP and can be skipped)
If you attempt to change major versions via a deployment the deployment will fail. This is done on purpose to avoid unintended upgrades which can lead to downtime.

View File

@@ -1,149 +1,301 @@
---
title: Setup Nhost with React
title: Get up and running with Nhost and React
sidebarTitle: React
description: Get up and running with Nhost and React
icon: react
---
<Steps>
<Step title="Create Project">
If you haven't, please create a project through the [Nhost Dashboard](https://app.nhost.io/new).
<Step title="Create Nhost Project">
Create your project through the [Nhost Dashboard](https://app.nhost.io/new).
</Step>
<Step title="Setup Database">
Navigate to the **SQL Editor** of the database and run the following SQL to create a new table `movies` with some great movies.
```sql SQL Editor
CREATE TABLE movies (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
director VARCHAR(255),
release_year INTEGER,
genre VARCHAR(100),
rating FLOAT
);
INSERT INTO movies (title, director, release_year, genre, rating) VALUES
('Inception', 'Christopher Nolan', 2010, 'Sci-Fi', 8.8),
('The Godfather', 'Francis Ford Coppola', 1972, 'Crime', 9.2),
('Forrest Gump', 'Robert Zemeckis', 1994, 'Drama', 8.8),
('The Matrix', 'Lana Wachowski, Lilly Wachowski', 1999, 'Action', 8.7);
```
Navigate to the **SQL Editor** of the database and run the following SQL to create a new table `todos`.
<Warning>Make sure the option `Track this` is enabled</Warning>
![SQL Editor](/images/guides/quickstarts/react/sql-editor.png)
</Step>
<Step title="permissions">
Select the new table `movies` just created, and click in **Edit Permissions** to set the following permissions for the `public` role and `select` action.
![Permission Rules](/images/guides/quickstarts/react/permissions.png)
</Step>
<Step title="Setup a React Application">
Create a React application using Vite.
```bash Terminal
npm create vite@latest nhost-react-quickstart -- --template react
```sql SQL Editor
CREATE TABLE todos (
id uuid NOT NULL DEFAULT gen_random_uuid(),
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
user_id uuid NOT NULL,
contents text NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (user_id) REFERENCES auth.users(id) ON UPDATE cascade ON DELETE cascade
);
```
<Frame caption="Create Todos Table">
<img src="/images/guides/quickstarts/react-native/create-table-todos.png" />
</Frame>
</Step>
<Step title="Install the Nhost package for React">
Navidate to the React application and install `@nhost/react`.
<Step title="Configure the todos table permissions">
To set permissions for the new `todos` table, select the table, click on the `...` to open the actions dialog,
then click on **Edit Permissions**. Set the following permissions for the `user` role:
1. `Insert`
- Set `Row insert permissions` to `Without any checks`
- Select all columns except `user_id` on `Column insert permissions`
- Add a new `Column preset` and set `Column Name` to `user_id` and `Column Value` to `X-Hasura-User-Id`
- Save
<Frame caption="Insert Permissions">
<img src="/images/guides/quickstarts/react-native/todos-insert-permissions.png" />
</Frame>
2. `Select`
- Set `Row select permissions` to `With custom check` and fill in the following rule:
- Set `Where` to `todos.user_id`
- Set the operator to `_eq`
- Set the value to `X-Hasura-User-Id`
- Select all columns except `user_id` on `Column select permissions`
- Save
<Frame caption="Select Permissions">
<img src="/images/guides/quickstarts/react-native/todos-select-permissions.png" />
</Frame>
3. `Update`
- Set `Row update permissions` to `With custom check` and fill in the following rule:
- Set `Where` to `todos.user_id`
- Set the operator to `_eq`
- Set the value to `X-Hasura-User-Id`
- Select all columns except `user_id` on `Column select permissions`
- Save
<Frame caption="Update permissions">
<img src="/images/guides/quickstarts/react-native/todos-update-permissions.png" />
</Frame>
4. `Delete`
- Set `Row delete permissions` to `With custom check` and fill in the following rule:
- Set `Where` to `todos.user_id`
- Set the operator to `_eq`
- Set the value to `X-Hasura-User-Id`
- Save
<Frame caption="Delete permissions">
<img src="/images/guides/quickstarts/react-native/todos-delete-permissions.png" />
</Frame>
</Step>
<Step title="Configure permissions to enable user file uploads">
To enable file uploads by users, set the permissions as follows:
1. Edit the **files** table permissions
1. Navigate to the files table within the [Database tab](https://app.nhost.io/_/_/database/browser/default/storage/files)
2. Click on the three dots (...) next to the files table
3. Click on **Edit Permissions**
2. Modify the `Insert` permission for the `user` role:
1. Set `Row insert permissions` to `Without any checks`
2. Select all columns on `Column insert permissions`
4. Save
<Frame caption="Insert Permissions">
<img src="/images/guides/quickstarts/react-native/files-insert-permissions.png" />
</Frame>
3. `Select`
- Set `Row select permissions` to `With custom check` and fill in the following rule:
- Set `Where` to `files.uploaded_by_user_id`
- Set the operator to `_eq`
- Set the value to `X-Hasura-User-Id`
- Select all columns on `Column select permissions`
- Save
<Frame caption="Select permissions">
<img src="/images/guides/quickstarts/react-native/files-select-permissions.png" />
</Frame>
</Step>
<Step title="Bootstrap your React app">
Intialize a new React project using the template [`@nhost/react-apollo`](https://www.npmjs.com/package/@nhost/cra-template-react-apollo)
```bash Terminal
cd nhost-react-quickstart && npm install @nhost/react
npx create-react-app myapp --template @nhost/react-apollo
```
</Step>
<Step title="Configure the Nhost client and fetch the list of movies">
<Step title="Connect your React app to the Nhost project">
Copy your project's `<subdomain>` and `<region>` values available on the dashboard overview
Create a new file with the following code to creates the Nhost client.
```js ./src/lib/nhost.js
import { NhostClient } from "@nhost/react";
export const nhost = new NhostClient({
subdomain: "<subdomain>",
region: "<region>",
})
```tsx src/index.tsx
const nhost = new NhostClient({
subdomain: "<subdomain>", // replace the subdomain value e.g. "hjcuuqweqwezolpolrep"
region: "<region>", // replace the region value e.g. "eu-central-1"
});
```
<Note>Replace `<subdomain>` and `<region>` with the subdomain and region for the project</Note>
</Step>
Finally, update `./src/App.jsx` to fetch the list of movies.
<Step title="Create the Todos Page and Add It to the Sidebar Navigation">
<CodeGroup>
```tsx src/components/routes/app/todos.tsx
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { gql, useMutation } from '@apollo/client'
import { useAuthQuery } from '@nhost/react-apollo'
import { Check, Info, Plus, Trash } from 'lucide-react'
import { useState } from 'react'
import { toast } from 'sonner'
```js src/App.jsx
import { useEffect, useState } from "react";
import { NhostProvider } from "@nhost/react";
import { nhost } from './lib/nhost'
export default function Todos() {
const { data, refetch: refetchTodos } = useAuthQuery<{
todos: Array<{
id: string
contents: string
}>
}>(gql`
query {
todos(order_by: { createdAt: desc }) {
id
contents
}
}
`)
const getMovies = `
query {
movies {
title
genre
rating
const [contents, setContents] = useState('')
const [addTodo] = useMutation<{
insertTodo?: {
id: string
contents: string
}
}>(gql`
mutation ($contents: String!) {
insertTodo(object: { contents: $contents }) {
id
contents
}
}
`)
const [deleteTodo] = useMutation<{
deleteNote?: {
id: string
content: string
}
}>(gql`
mutation deleteTodo($todoId: uuid!) {
deleteTodo(id: $todoId) {
id
contents
}
}
`)
const handleAddTodo = () => {
if (contents) {
addTodo({
variables: { contents },
onCompleted: async () => {
setContents('')
await refetchTodos()
},
onError: (error) => {
toast.error(error.message)
}
})
}
}
const handleDeleteTodo = async (todoId: string) => {
await deleteTodo({
variables: { todoId },
onCompleted: async () => {
await refetchTodos()
},
onError: (error) => {
toast.error(error.message)
}
})
await refetchTodos()
}
return (
<div className="w-full">
<Card className="mb-4">
<CardHeader>
<CardTitle>Todos</CardTitle>
</CardHeader>
</Card>
<Card className="w-full pt-6">
<CardContent className="flex flex-col gap-4">
<div className="flex flex-row gap-4">
<Input
value={contents}
onChange={(e) => setContents(e.target.value)}
onKeyDown={(e) => e.code === 'Enter' && handleAddTodo()}
/>
<Button className="m-0" onClick={handleAddTodo}>
<Plus />
Add
</Button>
</div>
<div>
{data?.todos.length === 0 && (
<Alert className="w-full">
<Info className="w-4 h-4" />
<AlertTitle>Empty</AlertTitle>
<AlertDescription className="mt-2">Start by adding a todo</AlertDescription>
</Alert>
)}
{data?.todos.map((todo) => (
<div
key={todo.id}
className="flex flex-row items-center justify-between w-full p-4 border-b last:pb-0 last:border-b-0"
>
<div className="flex flex-row gap-2">
<Check className="w-5 h-5" />
<span>{todo.contents}</span>
</div>
<Button variant="ghost" onClick={() => handleDeleteTodo(todo.id)}>
<Trash className="w-5 h-5" />
</Button>
</div>
))}
</div>
</CardContent>
</Card>
</div>
)
}
`;
```
function App() {
return (
<NhostProvider nhost={nhost}>
<Home />
</NhostProvider>
);
}
```tsx src/components/routes/app/layout.tsx
<nav className="flex flex-col items-center gap-4 px-2 sm:py-5">
--
<Tooltip>
<TooltipTrigger asChild>
<NavLink
to="/todos"
className="flex items-center justify-center transition-colors rounded-lg h-9 w-9 text-muted-foreground hover:text-foreground md:h-8 md:w-8 aria-[current]:bg-accent aria-[current]:text-accent-foreground"
>
<SquareCheckBig className="w-5 h-5" />
<span className="sr-only">Todos</span>
</NavLink>
</TooltipTrigger>
<TooltipContent side="right">Todos</TooltipContent>
</Tooltip>
--
</nav>
```
</CodeGroup>
function Home() {
const [loading, setLoading] = useState(true);
const [movies, setMovies] = useState([]);
useEffect(() => {
async function fetchMovies() {
setLoading(true);
const { data, error } = await nhost.graphql.request(getMovies);
setMovies(data.movies);
setLoading(false);
}
fetchMovies();
}, []);
return (
<div>
{loading ? (
<p>Loading...</p>
) : (
<table>
<tbody>
{movies.map((movie, index) => (
<tr key={index}>
<td>{movie.title}</td>
<td>{movie.genre}</td>
<td>{movie.rating}</td>
</tr>
))}
</tbody>
</table>
)}
</div>
);
}
export default App;
```
</Step>
<Step title="The end">
Run your project with `npm run dev -- --open --port 3000` and enter `http://localhost:3000` in your browser.
<Step title="Run the project">
Run your project with `npm start` and enter `http://localhost:3000` in your browser.
</Step>
</Steps>
```
```

View File

@@ -93,3 +93,56 @@ Wait a few seconds until the project is done updating the new service and visit
![visit url](/images/guides/run/registry_7.png)
## Using your own private registry
If you are publishing your images in your own private registry you can add pull credentials to your Run configuration so the image can be pulled successfully. To do so follow the next steps:
1. Figure out the credentials you need. This might depend on your registry. For instructions on various registries see the next section.
2. The credentials will be similar to:
```json
{
"auths": {
"https://myregistry.com/v1": {
"username": "myuser",
"password": "mypassword"
}
}
}
```
3. Create a secret under Settings -> Secrets with the contents of the auth section. For instance:
![pull secret](/images/guides/run/registry_8.png)
Pay attention that **only** the object inside "auths" is to be added.
4. Configure the `pullCredentials` in your run configuration.
```toml
[image]
image = 'myprivaterepo/myservice:1.0.1'
pullCredentials = '{{ secrets.CONTAINER_REGISTRY_CREDENTIALS }}'
```
Pulling your image should work now.
### Docker Hub Credentials
To create a credential that allows you to pull private images from Docker hub follow the next steps:
1. Login to https://hub.docker.com with a user that can pull the image you want.
2. Head to "Account Settings" -> "Personal access tokens"
3. Create a new token with "Read Only" access permissions
4. Copy the token you got
Your credentials will be:
```json
{
"https://index.docker.io/v1/": {
"username":"<yourusername>",
"password":"<the_token_you_just_got>"
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 892 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 971 KiB

View File

@@ -73,17 +73,15 @@
{
"group": "Platform",
"pages": [
"platform/subdomain",
"platform/compute-resources",
"platform/service-replicas",
{
"group": "Monitoring",
"icon": "monitor-waveform",
"pages": ["platform/metrics"]
},
"platform/metrics",
"platform/environment-variables",
"platform/secrets",
"platform/deployments",
"platform/custom-domains"
"platform/custom-domains",
"platform/rate-limits"
]
},
{
@@ -117,7 +115,8 @@
"guides/database/configuring-postgres",
"guides/database/access",
"guides/database/extensions",
"guides/database/performance"
"guides/database/performance",
"guides/database/upgrade-major"
]
},
{
@@ -197,11 +196,11 @@
"group": "CLI",
"pages": [
"guides/cli/local-development",
"guides/cli/subdomain",
"guides/cli/migrate-config",
"guides/cli/multiple-projects",
"guides/cli/configuration-overlays",
"guides/cli/seeds",
"guides/cli/connect-devices-to-local-nhost-project"
"guides/cli/seeds"
]
},
{

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/docs",
"version": "2.14.3",
"version": "2.17.2",
"private": true,
"scripts": {
"start": "mintlify dev"

View File

@@ -4,6 +4,10 @@ description: 'Grafana Instance configured and tailored to your project'
icon: monitor-waveform
---
<Info>
This is a Pro/Team/Enterprise feature. This is not available on Starter projects.
</Info>
Insights such as response times, resource usage, and error rates, to help you assess the **performance** and **health** of your services.
Metrics helps you analyze the performance of your infrastructure, while identifying bottlenecks and optimizing your applications.
@@ -23,16 +27,130 @@ Your Grafana instance comes pre-defined with dashboards that cover backend servi
![Grafana](/images/platform/metrics/grafana.png)
### Nhost Dashboard
## Accessing Grafana
You can find the link to Grafana in your project's dashboard, under **Metrics**.
You can find the link to Grafana in your project's dashboard, under **Metrics**.
![Project Metrics](/images/platform/metrics/nhost-dashboard-metrics.png)
## Configuring Grafana
Grafana comes pre-configured with a datasource with your project's metrics plus a few useful dashboards to observe your projects. In addition, you can enable alerting by configuring one or more contact points and enabling alerts in your configuration file.
<Info>
The configuration below is open source and can be found [here](https://github.com/nhost/nhost/tree/main/observability/grafana). If you want to see improvements, more rules, better dashboards, more options, etc., don't hesitate to contribute them or open an issue.
</Info>
### Configure contact points
Contact points in Grafana are lists of integrations that send notifications to specific channels or services when alerts are triggered. Supported contact points are:
- email
- pagerduty
- discord
- slack
- webhooks
To configure them include one ore more sections in your configuration file:
```toml
[observability.grafana.contacts]
emails = ['engineering@acme.com']
[[observability.grafana.contacts.pagerduty]]
integrationKey = 'integration-key'
severity = 'critical'
class = 'infra'
component = 'backend'
group = 'group'
[[observability.grafana.contacts.discord]]
url = 'https://discord.com/api/webhooks/...'
avatarUrl = 'https://discord.com/api/avatar/...'
[[observability.grafana.contacts.slack]]
recipient = 'recipient'
token = 'token'
username = 'username'
iconEmoji = 'danger'
iconURL = 'https://...'
mentionUsers = ['user1', 'user2']
mentionGroups = ['group1', 'group2']
mentionChannel = 'channel'
url = 'https://slack.com/api/webhooks/...'
endpointURL = 'https://slack.com/api/endpoint/...'
[[observability.grafana.contacts.webhook]]
url = 'https://webhook.example.com'
httpMethod = 'POST'
username = 'user'
password = 'password'
authorizationScheme = 'Bearer'
authorizationCredentials = 'token'
maxAlerts = 10
```
Once you have added them to your configuration and deployed them you should be able to see them in your grafana dashboard under "Settings" -> "Contact points" -> "Nhost Managed Contacts":
![contact points](/images/platform/metrics/contact_points.png)
If you click on "View" you should be able to see a test button you can use to ensure your contacts are properly configured.
### SMTP
If you are planning to send emails as part of your alerting, you need to configure some SMTP settings as well. To do so add to your configuration:
```toml
[observability.grafana.smtp]
host = 'localhost'
port = 25
sender = 'admin@localhost'
user = 'smtpUser'
password = 'smtpPassword'
```
### Alerting
To enable alerting simply add to your configuration:
```toml
[observability.grafana.alerting]
enabled = true
```
This will enable the following rules, which you can find in your grafana dashboard under "Alert rules":
![alert rules](/images/platform/metrics/alert_rules.png)
1. **High CPU usage**
- Trigger: CPU usage > 75%
- Duration: Sustained for 5-10 minutes
2. **Low disk space**
- Trigger: Disk utilization > 75%
- Duration: Persistent for 5-10 minutes
3. **Low free memory**
- Trigger: Memory usage > 75%
- Duration: Continuous for 5-10 minutes
4. **Service restarted due to lack of memory**
- Trigger: Any service restart due to memory exhaustion
- Duration: Immediate upon occurrence
5. **High request error rate**
- Trigger: Request error rate > 25%
- Duration: Maintained for 5-10 minutes
After they have been enabling they will start notifying your contact points when the conditions are met. For instance, here is an email sent due to a high error rate:
![email_notification](/images/platform/metrics/email_notification.png)
## Advanced configuration
In addition, Team and Enterprise projects can perform any changes they want. For instance you can add users, configure an OAuth provider for user authentication, add datasources, you can configure your own alerts, etc.
## Beta
Metrics is in beta, its functionality and pricing might change.
### Limitations
- Dashboards can be updated or created, but they won't persist after a deployment.

View File

@@ -0,0 +1,112 @@
---
title: Rate Limits
sidebarTitle: Rate Limits
description: Protecting your service against abuse
icon: shield
---
Rate limits in an HTTP API are essential for protecting services against abuse and brute force attacks by restricting the number of requests a client can make within a specified time period. By enforcing rate limits, we can mitigate the risk of unauthorized access, denial of service attacks, and excessive consumption of resources.
Limits work by setting a maximum number of requests (burst amount) allowed for a key within a specified time frame (recovery time). For example, with a limit of 30 requests and a recovery time of 5 minutes, a user can make up to 30 requests before hitting the limit. Additionally, the user receives an extra request every 10 seconds (5 * 60 / 30) until reaching the limit.
## GraphQL/Storage/Functions
You can rate-limit the GraphQL, Storage, and Functions services independently of each other. These rate limits are based on the client IP, and requests made to one service do not count toward the rate limits of another service.
### Configuration
<Tabs>
<Tab title="Dashboard">
**Project Dashboard -> Settings -> Rate Limiting**
![Rate limit services](/images/platform/rate-limiting/misc.png)
</Tab>
<Tab title="Config">
```toml
[hasura.rateLimit]
limit = 100
interval = '15m'
[functions.rateLimit]
limit = 100
interval = '15m'
[storage.rateLimit]
limit = 100
interval = '15m'
```
</Tab>
</Tabs>
## Auth
Given that not all endpoints are equally sensitive, Auth supports more complex rate-limiting rules, allowing you to set different configurations depending on the properties of each endpoint.
| Endpoints | Key | Limits | Description | Minimum version |
| ----------------------|-----|--------|-------------|-----------------|
| Any that sends emails<sup>1</sup> | Global | 10 / hour | Not configurable. This limit applies to any project without custom SMTP settings | 0.33.0 |
| Any that sends emails<sup>1</sup> | Client IP | 10 / hour | Configurable. This limit applies to any project with custom SMTP settings and is configurable | 0.33.0 |
| Any that sends SMS<sup>2</sup> | Client IP | 10 / hour | Configurable. | 0.33.0 |
| Any endpoint that an attacker may try to brute-force. This includes sign-in and verify endpoints<sup>3</sup> | Client IP | 10 / 5 minutes | Configurable | 0.33.0 |
| Signup endpoints<sup>4</sup> | Client IP | 10 / 5 minutes | Configurable | 0.33.0 |
| Any | Client IP | 100 / minute | The total sum of requests to any endpoint (including previous ones) can not exceed this limit | 0.33.0 |
<Note>
Limits are grouped within a given category. For instance, with a limit of 10 per hour for the sign-in/verify category, if a user attempts to sign in 10 times and then tries to verify an OTP code, the latter will be rate-limited alongside the sign-in attempts.
</Note>
<sup>1</sup> Paths included:
- `/signin/passwordless/email`
- `/user/email/change`
- `/user/email/send-verification-email`
- `/user/password/reset`
- `/signup/email-password` - If email verification enabled
- `/user/deanonymize` - If email verification enabled
<sup>2</sup> Paths included:
- `/signin/passwordless/sms`
<sup>3</sup> Paths included:
- `/signin/*`
- `*/verify`
- `*/otp`
<sup>4</sup> Paths included:
- `/signup/*`
### Configuration
<Tabs>
<Tab title="Dashboard">
**Project Dashboard -> Settings -> Rate Limiting**
![Rate limit Auth](/images/platform/rate-limiting/auth.png)
</Tab>
<Tab title="Config">
```toml
[auth.rateLimit]
[auth.rateLimit.emails]
limit = 10
interval = '1h'
[auth.rateLimit.sms]
limit = 10
interval = '1h'
[auth.rateLimit.bruteForce]
limit = 10
interval = '5m'
[auth.rateLimit.signups]
limit = 10
interval = '5m'
[auth.rateLimit.global]
limit = 100
interval = '1m'
```
</Tab>
</Tabs>

View File

@@ -0,0 +1,40 @@
---
title: Subdomain/Region
description: Connecting to your Cloud project
icon: compass
---
Services in the Nhost Cloud adhere to the following format:
- https://&lt;subdomain&gt;.&lt;service&gt;.&lt;region&gt;.nhost.run
When you set up a new project in Nhost Cloud, you're assigned a subdomain that, along with the region where the project was created, allows you to generate the different URLs for your services. For example:
![dashboard](/images/platform/subdomain.png)
With that information at hand generating the URLs for the various services is trivial:
- https://xglwkhjnufblhgtwtfwz.auth.us-west-2.nhost.run
- https://xglwkhjnufblhgtwtfwz.db.us-west-2.nhost.run
- https://xglwkhjnufblhgtwtfwz.functions.us-west-2.nhost.run
- https://xglwkhjnufblhgtwtfwz.graphql.us-west-2.nhost.run
- https://xglwkhjnufblhgtwtfwz.hasura.us-west-2.nhost.run
- https://xglwkhjnufblhgtwtfwz.storage.us-west-2.nhost.run
If you are using our SDK you can just specify the region and subdomain and the SDK will automatically generate these URLs for you. For instance:
```ts
// Create a new Nhost client for local development.
const nhost = new NhostClient(
{ region: 'xglwkhjnufblhgtwtfwz', subdomain: 'us-west-2' }
)
```
<Note>
If you want to use your own domain you can head to the [custom domains documentation](/platform/custom-domains)
</Note>
<Note>
For information on how to access your local environment check the [cli documentation](/guides/cli/subdomain)
</Note>

View File

@@ -22,7 +22,7 @@ const nhost = new NhostClient({
```ts
// Create a new Nhost client for local development.
const nhost = new NhostClient({ subdomain: 'local' })
const nhost = new NhostClient({ region: 'local', subdomain: 'local' })
```
## Parameters

View File

@@ -1,5 +1,23 @@
# @nhost-examples/cli
## 0.3.12
### Patch Changes
- @nhost/nhost-js@3.1.10
## 0.3.11
### Patch Changes
- @nhost/nhost-js@3.1.9
## 0.3.10
### Patch Changes
- @nhost/nhost-js@3.1.8
## 0.3.9
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/cli",
"version": "0.3.9",
"version": "0.3.12",
"main": "src/index.mjs",
"private": true,
"scripts": {

View File

@@ -1,5 +1,33 @@
# @nhost-examples/codegen-react-apollo
## 0.4.13
### Patch Changes
- Updated dependencies [55d8bb5]
- @nhost/react@3.6.0
- @nhost/react-apollo@13.0.0
## 0.4.12
### Patch Changes
- 52a38fe: chore: update dependencies to address security vulnerabilities
## 0.4.11
### Patch Changes
- @nhost/react@3.5.6
- @nhost/react-apollo@12.0.6
## 0.4.10
### Patch Changes
- @nhost/react@3.5.5
- @nhost/react-apollo@12.0.5
## 0.4.9
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/codegen-react-apollo",
"version": "0.4.9",
"version": "0.4.13",
"private": true,
"scripts": {
"codegen": "graphql-codegen",
@@ -36,6 +36,6 @@
"postcss": "^8.4.38",
"tailwindcss": "^3.4.3",
"typescript": "^4.9.5",
"vite": "^5.2.7"
"vite": "^5.4.6"
}
}

View File

@@ -1,5 +1,30 @@
# @nhost-examples/codegen-react-query
## 0.4.13
### Patch Changes
- Updated dependencies [55d8bb5]
- @nhost/react@3.6.0
## 0.4.12
### Patch Changes
- 52a38fe: chore: update dependencies to address security vulnerabilities
## 0.4.11
### Patch Changes
- @nhost/react@3.5.6
## 0.4.10
### Patch Changes
- @nhost/react@3.5.5
## 0.4.9
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/codegen-react-query",
"version": "0.4.9",
"version": "0.4.13",
"private": true,
"scripts": {
"codegen": "graphql-codegen",
@@ -37,6 +37,6 @@
"postcss": "^8.4.38",
"tailwindcss": "^3.4.3",
"typescript": "^4.9.5",
"vite": "^5.2.7"
"vite": "^5.4.6"
}
}

View File

@@ -1,5 +1,33 @@
# @nhost-examples/react-urql
## 0.3.13
### Patch Changes
- Updated dependencies [55d8bb5]
- @nhost/react@3.6.0
- @nhost/react-urql@10.0.0
## 0.3.12
### Patch Changes
- 52a38fe: chore: update dependencies to address security vulnerabilities
## 0.3.11
### Patch Changes
- @nhost/react@3.5.6
- @nhost/react-urql@9.0.6
## 0.3.10
### Patch Changes
- @nhost/react@3.5.5
- @nhost/react-urql@9.0.5
## 0.3.9
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@nhost-examples/codegen-react-urql",
"private": true,
"version": "0.3.9",
"version": "0.3.13",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
@@ -15,7 +15,7 @@
"graphql": "16.8.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"urql": "^3.0.4"
"urql": "^3.0.3"
},
"devDependencies": {
"@graphql-codegen/cli": "^5.0.2",
@@ -30,6 +30,6 @@
"postcss": "^8.4.38",
"tailwindcss": "^3.4.3",
"typescript": "^4.9.5",
"vite": "^5.2.7"
"vite": "^5.4.6"
}
}

View File

@@ -1,5 +1,29 @@
# @nhost-examples/multi-tenant-one-to-many
## 2.2.13
### Patch Changes
- @nhost/nhost-js@3.1.10
## 2.2.12
### Patch Changes
- 52a38fe: chore: update dependencies to address security vulnerabilities
## 2.2.11
### Patch Changes
- @nhost/nhost-js@3.1.9
## 2.2.10
### Patch Changes
- @nhost/nhost-js@3.1.8
## 2.2.9
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@nhost-examples/multi-tenant-one-to-many",
"private": true,
"version": "2.2.9",
"version": "2.2.13",
"description": "",
"main": "index.js",
"scripts": {},
@@ -10,7 +10,7 @@
"license": "ISC",
"devDependencies": {
"@types/express": "^4.17.21",
"express": "^4.19.2",
"express": "^4.20.0",
"typescript": "^4.9.5"
},
"dependencies": {

View File

@@ -1,5 +1,38 @@
# @nhost-examples/nextjs
## 0.3.13
### Patch Changes
- Updated dependencies [55d8bb5]
- @nhost/react@3.6.0
- @nhost/react-apollo@13.0.0
- @nhost/nextjs@2.1.22
## 0.3.12
### Patch Changes
- 52a38fe: chore: update dependencies to address security vulnerabilities
- Updated dependencies [52a38fe]
- @nhost/nextjs@2.1.21
## 0.3.11
### Patch Changes
- @nhost/react@3.5.6
- @nhost/react-apollo@12.0.6
- @nhost/nextjs@2.1.20
## 0.3.10
### Patch Changes
- @nhost/react@3.5.5
- @nhost/react-apollo@12.0.5
- @nhost/nextjs@2.1.19
## 0.3.9
### Patch Changes

View File

@@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/nextjs",
"version": "0.3.9",
"version": "0.3.13",
"private": true,
"scripts": {
"dev": "next dev",
@@ -24,7 +24,7 @@
"@nhost/react": "workspace:^",
"@nhost/react-apollo": "workspace:^",
"graphql": "16.8.1",
"next": "^14.1.4",
"next": "^14.2.10",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "^4.12.0"

View File

@@ -1,5 +1,23 @@
# @nhost-examples/node-storage
## 0.2.12
### Patch Changes
- @nhost/nhost-js@3.1.10
## 0.2.11
### Patch Changes
- @nhost/nhost-js@3.1.9
## 0.2.10
### Patch Changes
- @nhost/nhost-js@3.1.8
## 0.2.9
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/node-storage",
"version": "0.2.9",
"version": "0.2.12",
"private": true,
"description": "This is an example of how to use the Storage with Node.js",
"main": "src/index.mjs",

View File

@@ -1,5 +1,29 @@
# @nhost-examples/nextjs-server-components
## 0.4.14
### Patch Changes
- @nhost/nhost-js@3.1.10
## 0.4.13
### Patch Changes
- 52a38fe: chore: update dependencies to address security vulnerabilities
## 0.4.12
### Patch Changes
- @nhost/nhost-js@3.1.9
## 0.4.11
### Patch Changes
- @nhost/nhost-js@3.1.8
## 0.4.10
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/nextjs-server-components",
"version": "0.4.10",
"version": "0.4.14",
"private": true,
"scripts": {
"dev": "next dev",
@@ -18,7 +18,7 @@
"form-data": "^4.0.0",
"graphql": "16.8.1",
"js-cookie": "^3.0.5",
"next": "^14.1.4",
"next": "^14.2.10",
"postcss": "^8.4.38",
"react": "^18.2.0",
"react-dom": "^18.2.0",

View File

@@ -1 +1 @@
hoist-pattern=[]
hoist-pattern[]=!@nhost/nhost-js

View File

@@ -1,5 +1,10 @@
---
## 0.4.1
### Patch Changes
- 52a38fe: chore: update dependencies to address security vulnerabilities
## 0.4.0
### Minor Changes

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