Compare commits

...

46 Commits

Author SHA1 Message Date
github-actions[bot]
2e3357b7a3 chore: update versions (#3143)
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-storage-js@2.7.0

### Minor Changes

-   5c6ff6e: fix: correct StorageErrorPayload TypeScript typing

## @nhost/apollo@8.0.4

### Patch Changes

-   @nhost/nhost-js@3.2.4

## @nhost/react-apollo@16.0.1

### Patch Changes

-   @nhost/apollo@8.0.4
-   @nhost/react@3.9.1

## @nhost/react-urql@13.0.1

### Patch Changes

-   @nhost/react@3.9.1

## @nhost/nextjs@2.2.2

### Patch Changes

-   @nhost/react@3.9.1

## @nhost/nhost-js@3.2.4

### Patch Changes

-   Updated dependencies [5c6ff6e]
    -   @nhost/hasura-storage-js@2.7.0

## @nhost/react@3.9.1

### Patch Changes

-   @nhost/nhost-js@3.2.4

## @nhost/vue@2.9.1

### Patch Changes

-   @nhost/nhost-js@3.2.4

## @nhost/dashboard@2.16.0

### Minor Changes

-   f8e6b61: fix: can add rule groups in table permissions
-   9e404c8: fix: not redirect to 404 page if using local Nhost backend
-   ac4aa01: fix: can delete column in database page
-   4385524: fix: update url to check service health in local dashboard

### Patch Changes

-   @nhost/react-apollo@16.0.1
-   @nhost/nextjs@2.2.2

## @nhost/docs@2.27.0

### Minor Changes

-   81cc9b3: chore: add missing images to permissions API

### Patch Changes

-   af34015: chore: add note about encryption at rest
-   1956ed2: chore: added pgmq extension to postgres docs
-   88919a3: chore: added support for nodejs22 to functions

## @nhost-examples/cli@0.3.17

### Patch Changes

-   @nhost/nhost-js@3.2.4

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

### Patch Changes

-   @nhost/react@3.9.1
-   @nhost/react-apollo@16.0.1

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

### Patch Changes

-   @nhost/react@3.9.1

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

### Patch Changes

-   @nhost/react@3.9.1
-   @nhost/react-urql@13.0.1

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

### Patch Changes

-   @nhost/nhost-js@3.2.4

## @nhost-examples/nextjs@0.4.2

### Patch Changes

-   @nhost/react@3.9.1
-   @nhost/react-apollo@16.0.1
-   @nhost/nextjs@2.2.2

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

### Patch Changes

-   @nhost/nhost-js@3.2.4

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

### Patch Changes

-   @nhost/nhost-js@3.2.4

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

### Patch Changes

-   @nhost/react@3.9.1
-   @nhost/react-apollo@16.0.1

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

### Patch Changes

-   @nhost/react@3.9.1

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

### Patch Changes

-   @nhost/react@3.9.1
-   @nhost/react-apollo@16.0.1

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

### Patch Changes

-   @nhost/nhost-js@3.2.4
-   @nhost/apollo@8.0.4
-   @nhost/vue@2.9.1

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

### Patch Changes

-   @nhost/apollo@8.0.4
-   @nhost/vue@2.9.1

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-27 13:08:19 +01:00
David BM
4385524311 fix (dashboard): update url to check service health in local dashboard (#3158)
### **PR Type**
Bug fix, Enhancement


___

### **Description**
- Update health check URL from '/healthz' to '/v1/version'

- Change service from 'auth' to 'hasura' for app URL

- Modify query key to match new endpoint

- Remove duplicate hook file


___



### **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>useIsHealthy.ts</strong><dd><code>Update health check
endpoint and service</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/common/hooks/useIsHealthy/useIsHealthy.ts

<li>Changed app URL service from 'auth' to 'hasura'<br> <li> Updated
health check endpoint from '/healthz' to '/v1/version'<br> <li> Modified
query key to match new endpoint


</details>


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

</tr>
</table></td></tr><tr><td><strong>Miscellaneous</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>index.ts</strong><dd><code>No changes to index
file</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; </dd></summary>
<hr>

dashboard/src/features/projects/common/hooks/useIsHealthy/index.ts

- No changes made to file content


</details>


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

</tr>
</table></td></tr><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>useIsHealthy.ts</strong><dd><code>Remove duplicate
useIsHealthy hook</code>&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>


dashboard/src/features/projects/common/hooks/useIsHealthy/useIsHealthy.ts

- Removed entire file content


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3158/files#diff-4803f83d063ff897b658a3afa833f068ee4d1d1b9457ff89f02b8c8bf11525a0">+0/-31</a>&nbsp;
&nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>two-llamas-arrive.md</strong><dd><code>Add changeset
for dashboard package update</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

.changeset/two-llamas-arrive.md

<li>Added changeset file for '@nhost/dashboard' package<br> <li>
Described fix for service health check URL


</details>


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

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

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
2025-01-24 13:45:11 -05:00
David BM
9e404c8fc9 fix (dashboard): not redirect to 404 page if using local Nhost backend (#3159)
### **PR Type**
Bug fix, Enhancement


___

### **Description**
- Fix 404 redirect for local Nhost backend

- Improve useNotFoundRedirect hook functionality

- Add isPlatform check for conditional routing

- Update documentation for useNotFoundRedirect hook


___



### **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>useNotFoundRedirect.ts</strong><dd><code>Enhance
useNotFoundRedirect hook for local backend support</code></dd></summary>
<hr>


dashboard/src/features/projects/common/hooks/useNotFoundRedirect/useNotFoundRedirect.ts

<li>Added isPlatform check to prevent 404 redirect for local backend<br>
<li> Updated useNotFoundRedirect hook documentation<br> <li> Imported
useIsPlatform hook and added it to dependencies<br> <li> Modified
conditional logic for pathname validation


</details>


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

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>olive-camels-laugh.md</strong><dd><code>Add changeset
for dashboard version update</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

.changeset/olive-camels-laugh.md

<li>Added changeset file for version bump<br> <li> Described fix for 404
redirect issue


</details>


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

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

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
2025-01-24 13:22:49 -05:00
David BM
f8e6b615dd fix (dashboard): postgres add rule group in table permissions (#3157)
### **User description**
Fixes #3156


___

### **PR Type**
Bug fix


___

### **Description**
- Fixed issue with adding rule groups in table permissions

- Updated validation schema for rule group operator

- Replaced useCurrentWorkspaceAndProject with useProject hook

- Added changeset for the bug fix


___



### **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>validationSchemas.ts</strong><dd><code>Simplify rule
group operator validation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/database/dataGrid/components/EditPermissionsForm/validationSchemas.ts

<li>Simplified operator validation in ruleGroupSchema<br> <li> Removed
complex test function for operator<br> <li> Made operator field required
with error message


</details>


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

</tr>
</table></td></tr><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>RuleGroupEditor.tsx</strong><dd><code>Update hooks and
imports in RuleGroupEditor</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>


dashboard/src/features/orgs/projects/database/dataGrid/components/RuleGroupEditor/RuleGroupEditor.tsx

<li>Replaced useCurrentWorkspaceAndProject with useProject hook<br> <li>
Updated import for generateAppServiceUrl<br> <li> Adjusted references
from currentProject to project


</details>


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

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>lovely-moose-study.md</strong><dd><code>Add changeset
for table permissions fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/lovely-moose-study.md

<li>Added changeset file for the bug fix<br> <li> Specified minor
version bump for @nhost/dashboard


</details>


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

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

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
2025-01-24 08:41:20 -05:00
David BM
ac4aa01ec9 fix (dashboard): can delete column in database page (#3153)
### **User description**
FIxes #3151


___

### **PR Type**
Bug fix


___

### **Description**
- Fixed column deletion in database page

- Updated imports for project and platform hooks

- Replaced currentProject with project in useDeleteColumnMutation

- Added changeset for minor version bump


___



### **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>useDeleteColumnMutation.ts</strong><dd><code>Update
project-related imports and references</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/database/dataGrid/hooks/useDeleteColumnMutation/useDeleteColumnMutation.ts

<li>Updated imports for useIsPlatform and useProject<br> <li> Replaced
useCurrentWorkspaceAndProject with useProject<br> <li> Changed
currentProject references to project<br> <li> Updated appUrl and
adminSecret generation


</details>


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

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>two-dodos-jam.md</strong><dd><code>Add changeset for
minor version bump</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>

.changeset/two-dodos-jam.md

<li>Added new changeset file<br> <li> Specified minor version bump for
@nhost/dashboard<br> <li> Included fix description for column deletion


</details>


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

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

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
2025-01-23 13:23:13 -05:00
robertkasza
e515e71c8b chore: upgrade nodejs and pnpm version in nix config (#3152)
### **PR Type**
Enhancement


___

### **Description**
- Upgrade Node.js from v18 to v20

- Update PNPM from nodePackages.pnpm to pnpm_9

- Modify nix configuration files for dependencies


___



### **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>flake.nix</strong><dd><code>Update PNPM version in
build inputs and dev shells</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

flake.nix

<li>Replace <code>nodePackages.pnpm</code> with <code>pnpm_9</code> in
nativeBuildInputs<br> <li> Update devShells to use <code>pnpm_9</code>
instead of <code>nodePackages.pnpm</code>


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>overlay.nix</strong><dd><code>Upgrade Node.js version
in Nix overlay</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

nix/overlay.nix

- Upgrade Node.js from `nodejs-18_x` to `nodejs_20`


</details>


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

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

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
2025-01-23 15:04:31 +01:00
David BM
1246e0024a chore: update actions/upload-artifact to v4 (#3138)
### **PR Type**
Enhancement


___

### **Description**
- Update actions/upload-artifact from v3 to v4


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Dependencies</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>ci.yaml</strong><dd><code>Upgrade
actions/upload-artifact to latest version</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.github/workflows/ci.yaml

- Updated actions/upload-artifact from v3 to v4


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3138/files#diff-944291df2c9c06359d37cc8833d182d705c9e8c3108e7cfe132d61a06e9133dd">+1/-1</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
2025-01-22 12:02:31 -05:00
David BM
81cc9b3810 chore (docs): add missing images in permissions API (#3144)
### **PR Type**
Documentation


___

### **Description**
- Update image paths in permissions API documentation

- Add changeset for minor version bump

- Improve readability of permissions guide


___



### **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>five-panthers-swim.md</strong><dd><code>Add changeset
for minor docs update</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>

.changeset/five-panthers-swim.md

<li>Add new changeset file for minor version bump<br> <li> Specify
changes for '@nhost/docs' package


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>permissions.mdx</strong><dd><code>Update image paths in
permissions guide</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

docs/guides/api/permissions.mdx

<li>Update image paths for insert and select permissions<br> <li>
Replace '/img/graphql/permissions/' with '/images/guides/graphql/'


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3144/files#diff-7f5a067d373275df87600734dd9be5e8b8287e105eb1159ea6e6503760c48758">+2/-2</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
2025-01-20 06:41:58 -05:00
David BM
5c6ff6efc8 fix (dashboard): correct StorageErrorPayload TypeScript typing (#3137)
### **User description**
Resolves #2440


___

### **PR Type**
Bug fix


___

### **Description**
- Corrected StorageErrorPayload TypeScript typing

- Refactored error handling in file upload

- Improved consistency of error object structure

- Added changeset for version bump


___



### **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>upload.ts</strong><dd><code>Refactor error handling in
file upload</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

packages/hasura-storage-js/src/utils/upload.ts

<li>Refactored error handling in <code>fetchUpload</code> function<br>
<li> Created consistent <code>StorageErrorPayload</code> object<br> <li>
Updated error object structure in both success and error callbacks<br>
<li> Improved type safety and error message handling


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3137/files#diff-806cf228c0fefb0c00e2b79108f101e2a26776c19578512ffc3d47ecafe59a5a">+12/-6</a>&nbsp;
&nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>wet-pants-promise.md</strong><dd><code>Add changeset
for StorageErrorPayload fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

.changeset/wet-pants-promise.md

<li>Added new changeset file<br> <li> Specified minor version bump for
'@nhost/hasura-storage-js'<br> <li> Described fix for
StorageErrorPayload TypeScript typing


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3137/files#diff-db6f7027a4f2f3488291bc300959e7ba746107dbac4ac36aed6872d84d9039e9">+5/-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
2025-01-20 06:40:50 -05:00
David Barroso
1956ed23f8 chore (docs): added pgmq extension to postgres docs (#3141)
Fixes #3131 

### **PR Type**
Enhancement, Documentation


___

### **Description**
- Added pgmq extension to Postgres docs

- Updated extensions table with pgmq details

- Included pgmq installation and uninstallation instructions

- Added GitHub resource link for pgmq


___



### **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>cyan-suits-provide.md</strong><dd><code>Add changeset
for pgmq documentation update</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

.changeset/cyan-suits-provide.md

<li>Added changeset file for documentation update<br> <li> Specified
patch version bump for @nhost/docs


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>extensions.mdx</strong><dd><code>Add pgmq extension
details and instructions</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

docs/guides/database/extensions.mdx

<li>Added pgmq to the extensions table<br> <li> Included detailed
section on pgmq extension<br> <li> Provided installation and
uninstallation SQL commands<br> <li> Added GitHub resource link for pgmq


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3141/files#diff-7a41fa45d84db83a8c01a76ddb42ad614022ad94a4c3a6aa321f5b9a5300da8c">+25/-0</a>&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
2025-01-17 14:48:28 +01:00
David Barroso
af34015dbe chore (docs): add note about encryption at rest (#3142)
### **PR Type**
Enhancement, Documentation


___

### **Description**
- Add note about encryption at rest

- Explain encryption for storage, database, and Run services

- Provide warning about volume encryption status

- Include instructions for enabling encryption on older volumes


___



### **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>brown-suits-drive.md</strong><dd><code>Add changeset
for encryption at rest documentation</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/brown-suits-drive.md

<li>Add changeset file for documentation update<br> <li> Specify patch
version bump for '@nhost/docs'


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>compute-resources.mdx</strong><dd><code>Add Encryption
at Rest section to compute resources documentation</code></dd></summary>
<hr>

docs/platform/compute-resources.mdx

<li>Add new section on Encryption at Rest<br> <li> Explain encryption
for storage, database, and Run services<br> <li> Include warning about
volume encryption status<br> <li> Provide instructions for enabling
encryption on older volumes


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3142/files#diff-29f75c751db0e116dad1013260a350ba0105f5b2ef169bc0988d9aeb2803a562">+8/-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
2025-01-17 14:46:38 +01:00
David Barroso
88919a3d99 chore (docs): added support for nodejs22 to functions (#3140)
### **PR Type**
Enhancement, Documentation


___

### **Description**
- Added support for Node.js 22 runtime in functions

- Updated documentation to include Node.js 22 option

- Added changeset for patch version bump


___



### **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>forty-keys-buy.md</strong><dd><code>Add changeset for
Node.js 22 support</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>

.changeset/forty-keys-buy.md

<li>Added new changeset file for patch version bump<br> <li> Specified
'@nhost/docs' package for the change


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>runtimes.mdx</strong><dd><code>Update documentation
with Node.js 22 runtime support</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

docs/guides/functions/runtimes.mdx

<li>Added Node.js 22 to the list of supported runtimes<br> <li> Included
configuration example for Node.js 22 in <code>nhost.toml</code>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3140/files#diff-b028de07c3232c8aed96f6e73b38ec996daa13e250beab1009cdda95df5091c7">+9/-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
2025-01-17 14:46:26 +01:00
github-actions[bot]
ab26a57d05 chore: update versions (#3134)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@2.15.0

### Minor Changes

- f1052a8: fix: improve stability of the dashboard when pausing projects
-   30daa41: fix: update links to docs in overview page
-   7537237: feat: add image preview toggle in storage

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-16 08:24:00 -05:00
David BM
f1052a8826 fix (dashboard): undefined is not an object issue with paused projects (#3136)
### **User description**
Fixes #3127


___

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


___

### **Description**
- Fix undefined errors in paused projects

- Improve loading states across components

- Handle project and org loading separately

- Update project.id usage for consistency


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Bug
fix</strong></td><td><details><summary>1 files</summary><table>
<tr>
<td><strong>SubscriptionPlan.tsx</strong><dd><code>Fix undefined error
in org plan handling</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3136/files#diff-2a5f070869055286b669e382b18d656935752803b9a1ef13390ac028c2a48ac4">+3/-3</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

</table></details></td></tr><tr><td><strong>Enhancement</strong></td><td><details><summary>13
files</summary><table>
<tr>
<td><strong>TransferProjectDialog.tsx</strong><dd><code>Add loading
state for project and orgs</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3136/files#diff-b68d4641a67e07a8bf8c14e1f705059c564e1bca53e591783581af27a488d86e">+7/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>AISettings.tsx</strong><dd><code>Improve loading handling in
AI settings</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3136/files#diff-6ec092fc4af4c9acd11edb4ae69ff6ad6e8e984c761148836c9fde8daaa6e9a4">+12/-11</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>AppLoader.tsx</strong><dd><code>Add loading screen for
project data</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3136/files#diff-fdef910b2c808595c77cb3c0ae573db3ff57cdb4a8161db2e36e86ec548b9b6f">+18/-13</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>AuthDomain.tsx</strong><dd><code>Handle project loading in
Auth Domain</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3136/files#diff-4b8e1e15fc7df8fe284298d5ab47dbc3f554888f98e39f84c4ac995f35c10c86">+8/-4</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>HasuraDomain.tsx</strong><dd><code>Add project loading check
in Hasura Domain</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3136/files#diff-334177cc61035493cfca775de96635b58d98a28856067048dcfba6cd7f255978">+8/-4</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>ServerlessFunctionsDomain.tsx</strong><dd><code>Include
project loading in Serverless Functions Domain</code>&nbsp; &nbsp;
&nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3136/files#diff-e7cf7b49535f816a9c2c60cf1f8b975036bd6a988e4295529e999075d72044ef">+8/-4</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>MetricsSettings.tsx</strong><dd><code>Improve loading state
in Metrics Settings</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3136/files#diff-957bb404fee8d18aa45af9e878837d311b69d9805ac16fe8d2c0e9d3b431e906">+15/-6</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>backups.tsx</strong><dd><code>Refactor Backups page for
better loading</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3136/files#diff-11c24d569a8109344819d2cc9ce6ffbcf3b75abfba604e299c01289690d322f9">+8/-8</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>authentication.tsx</strong><dd><code>Enhance loading logic
in Authentication Settings</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3136/files#diff-d7d59ce72b8bf8a15db18d8dd5132db73cd00c6f99dd1cf58bc2eca676ea1e23">+3/-3</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>custom-domains.tsx</strong><dd><code>Add loading indicator
for Custom Domains</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3136/files#diff-e35b13396a4aa0b96e35dd7a0b1a27d188c0d45fe20cbda99e2fd59b83da5574">+6/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>database.tsx</strong><dd><code>Improve loading state in
Database Settings</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3136/files#diff-00045ae38a73178045bcda39c80a03a0cb46413641586896a628c3a2a22c7855">+3/-3</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>index.tsx</strong><dd><code>Enhance loading and form reset
in Settings</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3136/files#diff-b4185be97a505e25badcdefe31ea86fa9d69f72264c4bb35eae17fba936a3d47">+11/-3</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>metrics.tsx</strong><dd><code>Refactor Metrics Settings page
loading</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3136/files#diff-f1cb73960dad3c0714aa08f92457282533feaa9b97b1c4f8cac572244a9e070c">+11/-15</a>&nbsp;
</td>

</tr>

</table></details></td></tr><tr><td><strong>Formatting</strong></td><td><details><summary>1
files</summary><table>
<tr>
<td><strong>LogsHeader.tsx</strong><dd><code>Minor formatting changes in
Logs Header</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3136/files#diff-ebb3285aa776c9c5ea8b72672c4aafd55994c6c694998bbf56ca9c56d1e77664">+10/-10</a>&nbsp;
</td>

</tr>

</table></details></td></tr><tr><td><strong>Documentation</strong></td><td><details><summary>1
files</summary><table>
<tr>
<td><strong>angry-zoos-learn.md</strong><dd><code>Add changeset for
dashboard stability improvements</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3136/files#diff-5ada479d7003769072ae842fdcc5555f7b336466da44dfce5f841b2698382cbc">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

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

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2025-01-15 18:02:09 -05:00
David BM
30daa4146e fix (dashboard): update links to docs in overview (#3135)
### **PR Type**
Bug fix, Documentation


___

### **Description**
- Update documentation links in project overview page

- Change 'platform' to 'product' in doc URLs

- Modify links for Database, GraphQL API, Authentication, Storage

- Add changeset for version tracking


___



### **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>features.tsx</strong><dd><code>Update documentation
links in features array</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/features/orgs/projects/overview/features/features.tsx

<li>Updated documentation links for Database, GraphQL API,
Authentication, <br>and Storage features<br> <li> Changed URL path from
'platform' to 'product' in all links


</details>


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

</tr>
</table></td></tr><tr><td><strong>Configuration
changes</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>early-lobsters-grow.md</strong><dd><code>Add changeset
for documentation link updates</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/early-lobsters-grow.md

<li>Added new changeset file for version tracking<br> <li> Specified
minor version bump for '@nhost/dashboard'<br> <li> Included description
of the fix


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3135/files#diff-06d3f7109478411e023920db9ff7831442d2ee56c29a2b6c3f43bb08bcc41790">+5/-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
2025-01-13 11:08:08 -05:00
David BM
7537237465 feat (dashboard): image preview toggle in storage (#3122)
### **User description**
Resolves #2814


___

### **PR Type**
Enhancement


___

### **Description**
- Add image preview toggle in storage

- Implement preview header with switch control

- Refactor data grid components and imports

- Update file preview functionality


___



### **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>21
files</summary><table>
<tr>
<td><strong>DataBrowserGridControls.tsx</strong><dd><code>Update import
paths for data grid components</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3122/files#diff-640ce3e15c8d5f35d8bbe74792c59493afe5bc69873d2a40f81233da2b02661c">+6/-6</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>DataGrid.tsx</strong><dd><code>Refactor import paths for
data grid components</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3122/files#diff-3bc6476aed14d8e4f26134fa452d22c41b6d3ecb0989871a8a99230a82496474">+8/-8</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>DataGridBody.tsx</strong><dd><code>Update import paths for
data grid components</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3122/files#diff-e5cdb81b2c99dbd7b9a669a63ed503f6964e9c0bc91ca2c0e61df5334eaa7a1b">+4/-4</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>DataGridBooleanCell.tsx</strong><dd><code>Update import
paths for data grid components</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3122/files#diff-b700eacab9c73b147e248ce58d47a208c1e499124a20444efd73db7ecb68505f">+3/-3</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>DataGridCell.tsx</strong><dd><code>Update import path for
DataBrowserGridCell type</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3122/files#diff-0049e6acaddf9f9b60fe43a1fbb2657564bd019e690d0361aae39f44a03adaa2">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>DataGridConfigContext.ts</strong><dd><code>Update import
path for UseDataGridReturn type</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3122/files#diff-597368f6e75d76d9a5956f816eaa8c82177b49e1e0d20c027fd85bef81347786">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>DataGridConfigProvider.tsx</strong><dd><code>Update import
path for UseDataGridReturn type</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3122/files#diff-ec52aa04de1bfb16370e811e294efdb3389ee929c2f75f90981933e89ea26a5d">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>useDataGridConfig.ts</strong><dd><code>Update import path
for UseDataGridReturn type</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3122/files#diff-dee956f638a871543fef38fc6b35f2f5e0e7dfcc449b61377d2c5613f24f13d4">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>DataGridDateCell.tsx</strong><dd><code>Update import paths
for data grid components</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3122/files#diff-95e5b8780946ddb0c020be73a0646e7627c90ea7cc63a408346a434d1f12938e">+2/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>DataGridDecimalCell.tsx</strong><dd><code>Update import
paths for data grid components</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3122/files#diff-9ad38d4c8a67f8daf6020b9782cb1d7a4933e2901b4937a597a2c19c2367d7d0">+2/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>DataGridFrame.tsx</strong><dd><code>Update import path for
useDataGridConfig hook</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3122/files#diff-d4b27ea795d9008758b8eb7d54d4f4f982cf19818a8bde118afe1c46e12088bc">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>DataGridHeader.tsx</strong><dd><code>Refactor DataGridHeader
component and add DataGridHeaderButton</code></dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3122/files#diff-3f5f16ea95a730255a07806c96b55fd4946c92eebcb869cdf83ad92bfe034b4c">+10/-51</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>DataGridHeaderButton.tsx</strong><dd><code>Add new
DataGridHeaderButton component</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3122/files#diff-4e9624559165361950af94e0775337d6937c300e4184106f08975e9b324c3010">+77/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>index.ts</strong><dd><code>Add index file for
DataGridHeaderButton component</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3122/files#diff-1afcdd509c37753c21ff73cc4d1c63d2f8ed30a7e629a676b48d60c6c2fe0fb8">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>DataGridIntegerCell.tsx</strong><dd><code>Update import
paths for data grid components</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3122/files#diff-9db68b16a44a34c57b847023c1dd2f74e486b0a028f84fcc0cc1f29e0ff38f0d">+4/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>DataGridPreviewCell.tsx</strong><dd><code>Implement preview
toggle functionality in DataGridPreviewCell</code></dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3122/files#diff-d7bffe5896d2c9bac505fa9675790c59549d4fb35a2ad0cce903cc0aa31a8321">+15/-4</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>DataGridTextCell.tsx</strong><dd><code>Update import paths
for data grid components</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3122/files#diff-d1ed74fe8eb7a61053dfe908966311e13915ad2127ee107b62f725d6c5282492">+4/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>FilesDataGrid.tsx</strong><dd><code>Add PreviewHeader
component to FilesDataGrid</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3122/files#diff-18c8df727e1a4fc6a94d03bd4a3a7a8cb3ad44d754803c4c7988c1c00a4b7caf">+5/-3</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>FilesDataGridControls.tsx</strong><dd><code>Update import
paths for data grid components</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3122/files#diff-b85b40168e9c149331a68cb1a0cbec570c75233fa34385945e094b8f4c032974">+3/-3</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>PreviewHeader.tsx</strong><dd><code>Add new PreviewHeader
component with toggle switch</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3122/files#diff-a435cd33ed8c3cd8cfde506860a5e4d2f84605548292bc0d92b63b55d664ddca">+23/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
<td><strong>index.ts</strong><dd><code>Add index file for PreviewHeader
component</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3122/files#diff-18b97d53e328ea33285d6a209f6d535a93d3fde2bcae2c21c59014807f7d0e7a">+1/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

</table></details></td></tr><tr><td><strong>Documentation</strong></td><td><details><summary>1
files</summary><table>
<tr>
<td><strong>twelve-llamas-tap.md</strong><dd><code>Add changeset for
image preview toggle feature</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3122/files#diff-275dd9152aa4b1730808a63caaf49742e808c7b53b67b5505b828e6210c83c52">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

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

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2025-01-13 07:11:39 -05:00
github-actions[bot]
76e77da5de chore: update versions (#3124)
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.10.0

### Minor Changes

-   04d2ce1: feat: add signin security key with user handle

### Patch Changes

-   44c1e17: chore: update `msw` to v1.3.5 to fix vulnerabilities

## @nhost/react@3.9.0

### Minor Changes

-   04d2ce1: feat: add signin security key with user handle

### Patch Changes

-   @nhost/nhost-js@3.2.3

## @nhost/vue@2.9.0

### Minor Changes

-   04d2ce1: feat: add signin security key with user handle

### Patch Changes

-   @nhost/nhost-js@3.2.3

## @nhost/apollo@8.0.3

### Patch Changes

-   @nhost/nhost-js@3.2.3

## @nhost/react-apollo@16.0.0

### Patch Changes

-   Updated dependencies [04d2ce1]
    -   @nhost/react@3.9.0
    -   @nhost/apollo@8.0.3

## @nhost/react-urql@13.0.0

### Patch Changes

-   Updated dependencies [04d2ce1]
    -   @nhost/react@3.9.0

## @nhost/nextjs@2.2.1

### Patch Changes

-   Updated dependencies [04d2ce1]
    -   @nhost/react@3.9.0

## @nhost/nhost-js@3.2.3

### Patch Changes

-   Updated dependencies [44c1e17]
-   Updated dependencies [04d2ce1]
    -   @nhost/hasura-auth-js@2.10.0

## @nhost/dashboard@2.14.0

### Minor Changes

- d43931e: fix: invalid organization slug/project subdomain doesn't open
404 page
- 5df6fa2: feat: add unencrypted disk warning in storage capacity
settings

### Patch Changes

-   44c1e17: chore: update `msw` to v1.3.5 to fix vulnerabilities
    -   @nhost/react-apollo@16.0.0
    -   @nhost/nextjs@2.2.1

## @nhost/docs@2.26.0

### Minor Changes

-   04d2ce1: feat: add reference documentation for signin security key

### Patch Changes

-   1fa6cc4: chore: added docs for pg_jsonschema

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

### Minor Changes

- 04d2ce1: feat: update signin components to use `useSignInSecuritykey`
with user handle

### Patch Changes

-   Updated dependencies [04d2ce1]
    -   @nhost/react@3.9.0
    -   @nhost/react-apollo@16.0.0

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

### Minor Changes

- 04d2ce1: feat: update signin components to use `useSignInSecuritykey`
with user handle

### Patch Changes

-   Updated dependencies [04d2ce1]
    -   @nhost/vue@2.9.0
    -   @nhost/nhost-js@3.2.3
    -   @nhost/apollo@8.0.3

## @nhost-examples/cli@0.3.16

### Patch Changes

-   @nhost/nhost-js@3.2.3

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

### Patch Changes

-   Updated dependencies [04d2ce1]
    -   @nhost/react@3.9.0
    -   @nhost/react-apollo@16.0.0

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

### Patch Changes

-   Updated dependencies [04d2ce1]
    -   @nhost/react@3.9.0

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

### Patch Changes

-   Updated dependencies [04d2ce1]
    -   @nhost/react@3.9.0
    -   @nhost/react-urql@13.0.0

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

### Patch Changes

-   @nhost/nhost-js@3.2.3

## @nhost-examples/nextjs@0.4.1

### Patch Changes

-   Updated dependencies [04d2ce1]
    -   @nhost/react@3.9.0
    -   @nhost/react-apollo@16.0.0
    -   @nhost/nextjs@2.2.1

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

### Patch Changes

-   @nhost/nhost-js@3.2.3

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

### Patch Changes

-   @nhost/nhost-js@3.2.3

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

### Patch Changes

-   Updated dependencies [04d2ce1]
    -   @nhost/react@3.9.0

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

### Patch Changes

-   Updated dependencies [04d2ce1]
    -   @nhost/react@3.9.0
    -   @nhost/react-apollo@16.0.0

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

### Patch Changes

-   Updated dependencies [04d2ce1]
    -   @nhost/vue@2.9.0
    -   @nhost/apollo@8.0.3

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-10 17:48:29 +01:00
Hassan Ben Jobrane
04d2ce110a feat: add support for webauthn modern flow (#3097)
### **User description**
resolves https://github.com/nhost/nhost/issues/3031


___

### **PR Type**
Enhancement


___

### **Description**
- Implemented WebAuthn-based security key authentication flow
- Added new `useSignInSecurityKey` hook for React applications
- Updated authentication machine to support security key sign-in
- Simplified security key sign-in process by removing email input
requirement
- Added `useSignInEmailOTP` hook for email OTP authentication
- Updated auth version in example configuration



___



### **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>8
files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>sign-in-security-key.tsx</strong><dd><code>Simplify
security key sign-in process</code>&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/src/components/routes/auth/sign-in/sign-in-security-key.tsx

<li>Removed email input and form-related imports and components<br> <li>
Replaced <code>useSignInEmailSecurityKey</code> with
<code>useSignInSecurityKey</code><br> <li> Simplified sign-in process to
use <code>signInSecurityKey</code> without email<br>


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>events.ts</strong><dd><code>Add new security key
sign-in event</code>&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>

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

- Added new event type `SIGNIN_SECURITY_KEY`



</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>machine.ts</strong><dd><code>Implement WebAuthn-based
security key authentication</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

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

<li>Added <code>signInSecurityKey</code> service<br> <li> Implemented
new state for security key authentication<br> <li> Added WebAuthn-based
authentication logic<br>


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>index.ts</strong><dd><code>Export new security key
sign-in module</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

packages/hasura-auth-js/src/promises/index.ts

- Exported `signInSecurityKey` module



</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>signInSecurityKey.ts</strong><dd><code>Add security key
sign-in promise and types</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

packages/hasura-auth-js/src/promises/signInSecurityKey.ts

<li>Implemented <code>signInSecurityKeyPromise</code> function<br> <li>
Added types and interfaces for security key sign-in<br>


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>index.ts</strong><dd><code>Export new security key
sign-in hook</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>

packages/react/src/index.ts

- Exported `useSignInSecurityKey` hook



</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>useSignInEmailOTP.ts</strong><dd><code>Add email OTP
sign-in hook</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; </dd></summary>
<hr>

packages/react/src/useSignInEmailOTP.ts

- Implemented `useSignInEmailOTP` hook for email OTP authentication



</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>useSignInSecurityKey.ts</strong><dd><code>Implement
security key sign-in hook</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>

packages/react/src/useSignInSecurityKey.ts

<li>Implemented <code>useSignInSecurityKey</code> hook for
WebAuthn-based <br>authentication<br> <li> Added types and interfaces
for the hook<br>


</details>


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

</tr>
</table></details></td></tr><tr><td><strong>Configuration
changes</strong></td><td><details><summary>1 files</summary><table>
<tr>
  <td>
    <details>
<summary><strong>nhost.toml</strong><dd><code>Update auth version in
configuration</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>

examples/react-apollo/nhost/nhost.toml

- Updated auth version from '0.32.1' to '0.36.1'



</details>


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

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

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2025-01-10 16:59:29 +01:00
David Barroso
b2755045c9 chore: added overlay for nhost cli (#3126)
### **PR Type**
Enhancement


___

### **Description**
- Add Nhost CLI package to Nix configuration

- Define Nhost CLI version and platform-specific distributions

- Implement Nix derivation for Nhost CLI installation

- Update overlay to include Nhost CLI package


___



### **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>nhost-cli.nix</strong><dd><code>Implement Nix package
for Nhost CLI</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>

nix/nhost-cli.nix

<li>Define Nhost CLI version and platform-specific distributions<br>
<li> Implement Nix derivation for Nhost CLI installation<br> <li> Set up
build and installation process<br> <li> Define package metadata and
maintainer information


</details>


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

</tr>
</table></td></tr><tr><td><strong>Configuration
changes</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>overlay.nix</strong><dd><code>Update overlay to include
Nhost CLI package</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

nix/overlay.nix

- Add Nhost CLI package to the Nix overlay


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3126/files#diff-0f31c68216d617b465827b69d5b5cd4c2c0c4489008cd14ee0e88b9887d15295">+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
2025-01-09 15:36:17 +01:00
David BM
d43931e761 fix (dashboard): invalid organization slug/project subdomain opens 404 page (#3125)
### **User description**
Fixes #3119


___

### **PR Type**
Bug fix


___

### **Description**
- Fix 404 redirect for invalid org slug/project subdomain

- Improve conditional checks in useNotFoundRedirect hook

- Add project and org loading states

- Update URL parameter handling and comparisons


___



### **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>useNotFoundRedirect.ts</strong><dd><code>Enhance
useNotFoundRedirect hook for better 404 handling</code>&nbsp;
</dd></summary>
<hr>


dashboard/src/features/projects/common/hooks/useNotFoundRedirect/useNotFoundRedirect.ts

<li>Added imports for useCurrentOrg and useProject hooks<br> <li>
Updated URL parameter handling and comparisons<br> <li> Introduced
project and org loading states<br> <li> Improved conditional checks for
404 redirect


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3125/files#diff-837279cf43199053bca09913f62c4af019063a2e8dc7bfb7643ec54b7cecd29d">+31/-9</a>&nbsp;
&nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>light-dryers-invite.md</strong><dd><code>Add changeset
for 404 page 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;
</dd></summary>
<hr>

.changeset/light-dryers-invite.md

<li>Added changeset file for version bump<br> <li> Described fix for
invalid org slug/project subdomain 404 issue


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3125/files#diff-e0305e7650a5062b889ab6efdd71872be23e6e4c8b7792190478ef6702c1abd3">+5/-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
2025-01-09 14:17:31 +01:00
Hassan Ben Jobrane
44c1e17fd5 chore: update dependencies with vulnerabilities (#3105)
### **User description**
resolves https://github.com/nhost/nhost/issues/3031


___

### **PR Type**
Enhancement


___

### **Description**
- Updated `audit-ci.jsonc` configuration:
- Removed 'micromatch' and 'path-to-regexp' from the allowlist,
potentially increasing security checks
- Modified `package.json`:
  - Added 'path-to-regexp' dependency (version ^8.2.0)
- These changes aim to address vulnerabilities and update dependencies



___



### **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 allowlist in
audit-ci configuration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

audit-ci.jsonc

- Removed 'micromatch' and 'path-to-regexp' from the allowlist


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3105/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 path-to-regexp
dependency</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>

package.json

- Added 'path-to-regexp' dependency with version '^8.2.0'


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3105/files#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519">+2/-1</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
2025-01-09 00:21:00 +01:00
David BM
5df6fa2d0b feat (dashboard): unencrypted disk warning (#3116)
### **User description**
Resolves #3050


___

### **PR Type**
Enhancement


___

### **Description**
- Add unencrypted disk warning in storage capacity settings

- Implement query for persistent volumes encryption status

- Display alert for enabling disk encryption

- Update GraphQL types and queries


___



### **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>DatabaseStorageCapacity.tsx</strong><dd><code>Add
unencrypted disk warning and encryption info</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/database/settings/components/DatabaseStorageCapacity/DatabaseStorageCapacity.tsx

<li>Import new UI components (Alert, Link, Text)<br> <li> Add query for
persistent volumes encryption status<br> <li> Implement conditional
rendering of encryption warning<br> <li> Display alert with instructions
to enable encryption


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3116/files#diff-097a59d13b44816051386182a444eadfe2dcacd69b88c121af6733d7eca3ee43">+34/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>graphql.ts</strong><dd><code>Update GraphQL types and
add encryption query</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/utils/__generated__/graphql.ts

<li>Add GetPersistentVolumesEncryptedQuery type and related
functions<br> <li> Update Organization_Member_Invites type


</details>


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

</tr>

<tr>
  <td>
    <details>

<summary><strong>getPersistentVolumesEncrypted.gql</strong><dd><code>Add
GraphQL query for persistent volumes encryption</code>&nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/database/settings/gql/getPersistentVolumesEncrypted.gql

- Add new GraphQL query for persistent volumes encryption status


</details>


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

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>sour-bats-repair.md</strong><dd><code>Add changeset for
unencrypted disk warning feature</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/sour-bats-repair.md

<li>Add changeset for minor version bump<br> <li> Describe new feature:
unencrypted disk warning


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3116/files#diff-257a5bb608ef376c916b9c93ca8b72e260560f9bfff0c07f954862feb25d5ea7">+5/-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
2025-01-08 17:35:18 +01:00
David Barroso
1fa6cc47ec chore (docs): added pg_jsonschema docs (#3123)
Fixes https://github.com/nhost/nhost/issues/3101
### **PR Type**
Enhancement, Documentation


___

### **Description**
- Added pg_jsonschema extension to available extensions list

- Included documentation for pg_jsonschema extension usage

- Updated extensions table with pg_jsonschema details

- Minor formatting changes in the document


___



### **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>loud-years-know.md</strong><dd><code>Add changeset for
pg_jsonschema documentation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/loud-years-know.md

<li>Added changeset file for @nhost/docs patch<br> <li> Described change
as adding docs for pg_jsonschema


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>extensions.mdx</strong><dd><code>Add pg_jsonschema
extension documentation</code>&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 pg_jsonschema to extensions table<br> <li> Included new
section with installation and usage instructions<br> <li> Provided
GitHub resource link for pg_jsonschema<br> <li> Minor formatting
adjustments


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3123/files#diff-7a41fa45d84db83a8c01a76ddb42ad614022ad94a4c3a6aa321f5b9a5300da8c">+25/-1</a>&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
2025-01-08 15:04:17 +01:00
github-actions[bot]
4854df4559 chore: update versions (#3099)
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.9.0

### Minor Changes

- b944d05: feat: introduce `initWithSession` to initialize auth client
with an existing session

## @nhost/hasura-storage-js@2.6.0

### Minor Changes

- 4148964: fix: stack overflow on storage client getHeaders method call

## @nhost/nextjs@2.2.0

### Minor Changes

- 46fc520: chore: add support to next.js 15, update quickstart template
commands in docs
-   29d27e1: chore: update `next` to v14.2.22 to fix vulnerabilities

### Patch Changes

-   @nhost/react@3.8.1

## @nhost/apollo@8.0.2

### Patch Changes

-   @nhost/nhost-js@3.2.2

## @nhost/react-apollo@15.0.1

### Patch Changes

-   @nhost/apollo@8.0.2
-   @nhost/react@3.8.1

## @nhost/react-urql@12.0.1

### Patch Changes

-   @nhost/react@3.8.1

## @nhost/nhost-js@3.2.2

### Patch Changes

-   Updated dependencies [b944d05]
-   Updated dependencies [4148964]
    -   @nhost/hasura-auth-js@2.9.0
    -   @nhost/hasura-storage-js@2.6.0

## @nhost/react@3.8.1

### Patch Changes

-   @nhost/nhost-js@3.2.2

## @nhost/vue@2.8.1

### Patch Changes

-   @nhost/nhost-js@3.2.2

## @nhost/dashboard@2.13.0

### Minor Changes

- 21e90da: chore: remove restrictions on SMTP sender so My Name
[name@acme.com](mailto:name@acme.com) can be added
- 865dd93: fix: duplicate Run placeholders when there is an error in the
backend
- 6902a36: fix: can remove resources if postgres capacity is higher than
10
-   a535aa3: fix: fetch user roles locally in auth section
-   0c50816: fix: allow decimal numbers in database row insert
- aea6d18: chore: add warning when pausing a project about losing Run
services persistent volume data
- d3b4fc3: feat: allow to change postgres settings if project is paused
-   29d27e1: chore: update `next` to v14.2.22 to fix vulnerabilities
-   c9dca09: feat: add reset password form
-   b3bcacb: fix: paused project banner cannot read null project name

### Patch Changes

-   Updated dependencies [46fc520]
-   Updated dependencies [29d27e1]
    -   @nhost/nextjs@2.2.0
    -   @nhost/react-apollo@15.0.1

## @nhost/docs@2.25.0

### Minor Changes

- 46fc520: chore: add support to next.js 15, update quickstart template
commands in docs
-   cdf6776: fix: update links to create new project in dashboard

## @nhost-examples/nextjs@0.4.0

### Minor Changes

-   29d27e1: chore: update `next` to v14.2.22 to fix vulnerabilities

### Patch Changes

-   Updated dependencies [46fc520]
-   Updated dependencies [29d27e1]
    -   @nhost/nextjs@2.2.0
    -   @nhost/react@3.8.1
    -   @nhost/react-apollo@15.0.1

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

### Minor Changes

- b944d05: chore: simplify Nhost client initialization with session and
remove xstate dependency
-   29d27e1: chore: update `next` to v14.2.22 to fix vulnerabilities

### Patch Changes

-   @nhost/nhost-js@3.2.2

## @nhost-examples/cli@0.3.15

### Patch Changes

-   @nhost/nhost-js@3.2.2

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

### Patch Changes

-   @nhost/react@3.8.1
-   @nhost/react-apollo@15.0.1

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

### Patch Changes

-   @nhost/react@3.8.1

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

### Patch Changes

-   @nhost/react@3.8.1
-   @nhost/react-urql@12.0.1

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

### Patch Changes

-   @nhost/nhost-js@3.2.2

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

### Patch Changes

-   @nhost/nhost-js@3.2.2

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

### Patch Changes

-   @nhost/react@3.8.1
-   @nhost/react-apollo@15.0.1

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

### Patch Changes

-   @nhost/react@3.8.1

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

### Patch Changes

-   @nhost/react@3.8.1
-   @nhost/react-apollo@15.0.1

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

### Patch Changes

-   @nhost/nhost-js@3.2.2
-   @nhost/apollo@8.0.2
-   @nhost/vue@2.8.1

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

### Patch Changes

-   @nhost/apollo@8.0.2
-   @nhost/vue@2.8.1

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-08 13:47:10 +01:00
David BM
865dd93fbe fix (dashboard): duplicate Run placeholders when there is a backend error (#3114)
### **User description**
Resolves #2842


___

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


___

### **Description**
- Refactor Run service creation process

- Use single mutation for service config insertion

- Remove duplicate placeholder creation

- Update GraphQL schema and related types


___



### **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>ServiceForm.tsx</strong><dd><code>Refactor service
creation process in ServiceForm component</code></dd></summary>
<hr>


dashboard/src/features/orgs/projects/services/components/ServiceForm/ServiceForm.tsx

<li>Replace separate insertRunService and insertRunServiceConfig
mutations <br>with single insertRunServiceConfig<br> <li> Remove UUID
generation for new services<br> <li> Update error handling and form
submission logic<br> <li> Adjust image handling for private registries


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3114/files#diff-a02746694d45a84390d09b49a1b3eec85c25a8bd9a70b4834ee5af1ba82cb88e">+14/-34</a>&nbsp;
</td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>ServiceFormTypes.ts</strong><dd><code>Enhance image
field validation in ServiceFormTypes</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/services/components/ServiceForm/ServiceFormTypes.ts

<li>Add trim() to image validation<br> <li> Enforce minimum length of 1
character for image field


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>graphql.ts</strong><dd><code>Update GraphQL schema
types for Run service changes</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

dashboard/src/utils/__generated__/graphql.ts

<li>Add InsertRunServiceConfigResponse type<br> <li> Update
Mutation_Root and related types<br> <li> Remove creatorUserId and
creator fields from Run_Service type<br> <li> Update Users type to
remove runServices field


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3114/files#diff-fbd5db84b560b1c91675004448c6c7fa0dcbfb28b9eb05d53b03e6cb7b83ebac">+16/-57</a>&nbsp;
</td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>insertRunService.graphql</strong><dd><code>Rename
insertRunService GraphQL mutation</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/gql/services/insertRunService.graphql

- Rename mutation from insertRunService to InsertRunService


</details>


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

</tr>

<tr>
  <td>
    <details>

<summary><strong>insertRunServiceConfig.graphql</strong><dd><code>Refactor
insertRunServiceConfig GraphQL mutation</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/gql/services/insertRunServiceConfig.graphql

<li>Rename mutation to InsertRunServiceConfig<br> <li> Remove serviceID
parameter<br> <li> Update return type to include serviceID and config


</details>


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

</tr>
</table></td></tr><tr><td><strong>Formatting</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>ReplicasFormSection.tsx</strong><dd><code>Minor styling
updates in ReplicasFormSection component</code>&nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>


dashboard/src/features/orgs/projects/services/components/ServiceForm/components/ReplicasFormSection/ReplicasFormSection.tsx

- Minor CSS class order adjustments
- Update InfoOutlinedIcon styling


</details>


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

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>curly-hotels-hang.md</strong><dd><code>Add changeset
for Run placeholders 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/curly-hotels-hang.md

- Add changeset for fixing duplicate Run placeholders issue


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3114/files#diff-f2d1135a0f9ad0a75d2b50a42fb2c0ce1f17749af238f4acd06e5699f303d668">+5/-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
2025-01-08 13:31:56 +01:00
David BM
0c50816717 fix (dashboard): allow decimal row insert (#3110)
### **User description**
Fixes #2923


___

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


___

### **Description**
- Allow decimal numbers in database row insert

- Separate integer and decimal cell components

- Update PostgreSQL type constants

- Refactor DataBrowserGrid component imports


___



### **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>7
files</summary><table>
<tr>
<td><strong>DataBrowserGrid.tsx</strong><dd><code>Refactor imports and
add decimal cell support</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3110/files#diff-5910fd8730fbe65c60aa5f54031989a7868e944d5958f69535e5684b72ca1396">+22/-11</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>postgresqlConstants.ts</strong><dd><code>Separate integer
and decimal PostgreSQL types</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3110/files#diff-b497da90feca5bff94b0d38b69e519d171d43acc292098054d672a73a89b4717">+8/-5</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>DataGridDecimalCell.tsx</strong><dd><code>Add new
DataGridDecimalCell component</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3110/files#diff-9ad38d4c8a67f8daf6020b9782cb1d7a4933e2901b4937a597a2c19c2367d7d0">+108/-0</a>&nbsp;
</td>

</tr>

<tr>
<td><strong>index.ts</strong><dd><code>Add index file for
DataGridDecimalCell</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3110/files#diff-796c86f4c7526c140e70830072876324b6809204eb0e59da9931f048bb00c3ed">+2/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>DataGridIntegerCell.tsx</strong><dd><code>Rename
DataGridNumericCell to DataGridIntegerCell</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3110/files#diff-9db68b16a44a34c57b847023c1dd2f74e486b0a028f84fcc0cc1f29e0ff38f0d">+3/-3</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>index.ts</strong><dd><code>Add index file for
DataGridIntegerCell</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3110/files#diff-2354d98927d0c0bf7165211cbe9f478727bb889793716cfe39083c200d625c40">+2/-0</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
<td><strong>index.ts</strong><dd><code>Remove DataGridNumericCell index
file</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3110/files#diff-0b88c218b31ef402892e055abae0b5a05b96ec1550881d69f0fd73bad93e159e">+0/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></details></td></tr><tr><td><strong>Bug
fix</strong></td><td><details><summary>1 files</summary><table>
<tr>
<td><strong>DatabaseRecordInputGroup.tsx</strong><dd><code>Remove step
property from input</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;
</dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3110/files#diff-52b5499e9afc3c5e4929046b487de649d421dda3250a4131462ec710575abc12">+0/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

</table></details></td></tr><tr><td><strong>Documentation</strong></td><td><details><summary>1
files</summary><table>
<tr>
<td><strong>nice-mangos-act.md</strong><dd><code>Add changeset for
decimal number fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></td>
<td><a
href="https://github.com/nhost/nhost/pull/3110/files#diff-7207f060172dcdd7fd5c3c4078140fb57564714e1f95c96d428a22d9e7a3e670">+5/-0</a>&nbsp;
&nbsp; &nbsp; </td>

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

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information
2025-01-07 22:18:04 +01:00
David BM
d3b4fc358e feat (dashboard): allow to change postgres settings if project is paused (#3106) 2025-01-07 18:09:12 +01:00
David BM
b3bcacb300 fix (dashboard): paused application cannot read null project name (#3117)
### **User description**
Fixes the occasional error `Cannot read properties of null (reading
'name') when you open a page inside a paused project

![image](https://github.com/user-attachments/assets/bfd57bf6-5679-48d3-ac8f-b69f6d72ee3d)


___

### **PR Type**
Bug fix


___

### **Description**
- Fix error when reading project name in paused application

- Update ApplicationPaused component to handle null project

- Add optional chaining to prevent null reference errors

- Improve error handling for paused projects in dashboard


___



### **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>ApplicationPaused.tsx</strong><dd><code>Add null checks
for project name in ApplicationPaused component</code></dd></summary>
<hr>


dashboard/src/features/orgs/projects/common/components/ApplicationPaused/ApplicationPaused.tsx

<li>Added optional chaining (<code>?.</code>) to
<code>project.name</code> references<br> <li> Updated modal title and
description to handle potential null project


</details>


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

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>wise-chefs-drum.md</strong><dd><code>Add changeset for
paused project banner fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

.changeset/wise-chefs-drum.md

<li>Added changeset file for version bump and change description<br>
<li> Described fix for paused project banner issue


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3117/files#diff-dddc3d3dd31a1cb69106c6acad798c2201016cffb11cb8447e0696563838300c">+5/-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
2025-01-07 15:51:47 +01:00
David Barroso
aa7ecdb38f chore: update pr-agent (#3121)
### **PR Type**
Enhancement


___

### **Description**
- Update PR Agent action to version 0.26


___



### **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>gen_ai_review.yaml</strong><dd><code>Upgrade PR Agent
action version</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;
</dd></summary>
<hr>

.github/workflows/gen_ai_review.yaml

- Updated PR Agent action from version 0.24 to 0.26


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3121/files#diff-d1e4c772e0acb5ce4891df2dd94ba58ffaf6393e8f75493ec7e10cbce1c4992c">+1/-1</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
2025-01-07 15:00:13 +01:00
David Barroso
20672c7a9b chore: update actions/cache to v4 (#3120)
### **PR Type**
Enhancement


___

### **Description**
- Update actions/cache from v3 to v4


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Dependencies</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>action.yaml</strong><dd><code>Update actions/cache
version</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>

.github/actions/install-dependencies/action.yaml

- Upgraded actions/cache from v3 to v4


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3120/files#diff-342d59190b4737ee45e2062eb625ada477bcea5b4a843b25900ad55d7982f200">+1/-1</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
2025-01-07 14:59:00 +01:00
David BM
29d27e19b4 chore: update dependencies with vulnerabilities, fix ci (#3118)
### **PR Type**
Enhancement, Bug fix


___

### **Description**
- Update Next.js to v14.2.22 for vulnerability fixes

- Upgrade @nhost/react and @nhost/react-apollo packages

- Update dependencies across multiple projects

- Add changeset for version bumps


___



### **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>smart-penguins-love.md</strong><dd><code>Add changeset
for Next.js and Nhost package updates</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

.changeset/smart-penguins-love.md

<li>Add new changeset file for version bumps<br> <li> Specify minor
version updates for multiple packages<br> <li> Note Next.js update to
v14.2.22 for vulnerability fixes


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3118/files#diff-e7c2756594d0b5180066d581205200665f8f3f6104259550e080c3916dca065a">+8/-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>Upgrade Next.js to
v14.2.22 in dashboard</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

dashboard/package.json

- Update `next` dependency from ^14.2.10 to ^14.2.22


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Upgrade Next.js to
v14.2.22 in NextJS example</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/nextjs/package.json

- Update `next` dependency from ^14.2.10 to ^14.2.22


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Upgrade Next.js to
v14.2.22 in server components example</code>&nbsp; </dd></summary>
<hr>

examples/quickstarts/nextjs-server-components/package.json

- Update `next` dependency from ^14.2.10 to ^14.2.22


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Upgrade Next.js dev
dependency in NextJS package</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

packages/nextjs/package.json

- Update `next` devDependency from ^14.2.10 to ^14.2.22


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Upgrade Nhost React and
Apollo packages in CRA template</code>&nbsp; &nbsp; </dd></summary>
<hr>

templates/cra-template-nhost-react-apollo-template/template/package.json

<li>Update @nhost/react from ^3.5.4 to ^3.8.0<br> <li> Update
@nhost/react-apollo from ^12.0.4 to ^12.0.5


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3118/files#diff-27f71682a447c654ff4a94d33944ebb70e10d07a4279107c230bd8ec7dce7391">+2/-2</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
2025-01-07 14:17:32 +01:00
David BM
46fc520707 chore (nextjs): fix nextjs tutorial quickstart commands in docs, add support to next.js 15 (#3109)
### **User description**
Resolves #3103, resolves #3102


___

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


___

### **Description**
- Update Next.js quickstart and tutorial for version 14

- Add support for Next.js 15 and React 19

- Fix typos in documentation

- Update Nhost package versions in template


___



### **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>nextjs.mdx</strong><dd><code>Update Next.js quickstart
guide for version 14</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

docs/guides/quickstarts/nextjs.mdx

<li>Updated command to create Next.js 14 app instead of latest<br> <li>
Fixed typo: 'Navidate' to 'Navigate'


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>vue.mdx</strong><dd><code>Fix typo in Vue quickstart
guide</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>

docs/guides/quickstarts/vue.mdx

- Fixed typo: 'Navidate' to 'Navigate'


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>nextjs.mdx</strong><dd><code>Update Next.js tutorial
for version 14</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/tutorials/nextjs.mdx

- Updated command to create Next.js 14 app instead of latest


</details>


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

</tr>
</table></td></tr><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Update Next.js and React
version support</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

packages/nextjs/package.json

<li>Added support for Next.js 15 and React 19 in peerDependencies<br>
<li> Updated devDependencies to Next.js 15 and React 19


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3109/files#diff-e5237f683dda3354b835c7c7c94b9759db2c743d4ba94d47d7f8b8e0b2bfb442">+7/-7</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>Update Nhost package
versions in template</code>&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/package.json

- Updated @nhost/react and @nhost/react-apollo versions


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3109/files#diff-27f71682a447c654ff4a94d33944ebb70e10d07a4279107c230bd8ec7dce7391">+2/-2</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
2025-01-02 17:24:01 +01:00
David BM
21e90da476 chore (dashboard): remove restrictions on SMTP sender so My Name <name@acme.com> can be added (#3111) 2025-01-02 11:38:48 +01:00
Hassan Ben Jobrane
b944d053d0 feat(hasura-auth-js): feat: addinitWithSession to initialize auth client with existing session (#3108)
### **User description**
resolves https://github.com/nhost/nhost/issues/2319


___

### **PR Type**
Enhancement


___

### **Description**
- Added new `initWithSession()` method to `@nhost/hasura-auth-js`
package, allowing initialization of auth client with an existing session
- Simplified Nhost client initialization in Next.js server components
example by using the new `initWithSession()` method
- Removed xstate dependency from Next.js server components example
- Updated changesets to document the new feature and changes in the
Next.js example
- Improved code organization and reduced complexity in the Next.js
example



___



### **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>nhost.ts</strong><dd><code>Simplify Nhost client
initialization in Next.js</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/quickstarts/nextjs-server-components/src/utils/nhost.ts

<li>Removed xstate dependency imports<br> <li> Replaced
<code>nhost.auth.client.start()</code> and <code>waitFor()</code> with
new <br><code>nhost.auth.initWithSession()</code> method<br> <li>
Simplified initialization of Nhost client with existing session


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>hasura-auth-client.ts</strong><dd><code>Add
initWithSession method to HasuraAuthClient</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

packages/hasura-auth-js/src/hasura-auth-client.ts

<li>Added <code>NhostSession</code> import<br> <li> Implemented new
<code>initWithSession()</code> method in <code>HasuraAuthClient</code>
class<br> <li> Added JSDoc comments for the new method


</details>


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

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>flat-apes-shake.md</strong><dd><code>Add changeset for
Next.js example updates</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

.changeset/flat-apes-shake.md

<li>Added changeset for Next.js server components example<br> <li>
Describes simplification of Nhost client initialization and removal of
<br>xstate dependency


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>long-guests-sparkle.md</strong><dd><code>Add changeset
for new initWithSession feature</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/long-guests-sparkle.md

<li>Added changeset for @nhost/hasura-auth-js package<br> <li> Describes
the addition of <code>initWithSession</code> method


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3108/files#diff-e7c748ca311b610d130750f4d24256383ef064c1ee2f1a9981060fd274335a1c">+5/-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>Remove xstate dependency
from Next.js example</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/quickstarts/nextjs-server-components/package.json

- Removed "xstate" dependency from the project


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3108/files#diff-04889f3402d5191034459febd340282af1c718175c3b0b14ff03fb2ab46cf9b3">+1/-2</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-12-31 12:58:10 +01:00
David BM
6902a36512 fix (dashboard): remove compute resources erases postgres capacity (#3107)
### **User description**
Fixes #3075


___

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


___

### **Description**
- Fixed a bug that prevented removing compute resources when PostgreSQL
capacity was higher than 10
- Refactored resource configuration handling to preserve non-compute
settings when disabling resources
- Updated GraphQL types and queries to include additional fields for
networking and storage configurations
- Implemented UI tweaks for better alignment and dark theme
compatibility in the resource settings form
- Added a changeset file to document the bug fix and minor version bump



___



### **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>ResourcesForm.tsx</strong><dd><code>Refactor resource
configuration handling</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/resources/settings/components/ResourcesForm/ResourcesForm.tsx

<li>Added <code>getFormattedConfig</code> function to handle resource
configuration<br> <li> Modified <code>handleSubmit</code> to use
<code>getFormattedConfig</code><br> <li> Updated initial resource
retrieval to include <code>rest</code> properties<br> <li> Implemented
logic to preserve non-compute resource settings when <br>disabling
resources


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3107/files#diff-0a7e99e6ee09c17eec103656a9aa088b379c7927a182098538b793488a1f9337">+118/-59</a></td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>graphql.ts</strong><dd><code>Update GraphQL types for
extended resource configurations</code></dd></summary>
<hr>

dashboard/src/utils/__generated__/graphql.ts

<li>Updated GraphQL types to include additional fields for resources<br>
<li> Added networking and storage-related fields to various resource
<br>configurations


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>getResources.gql</strong><dd><code>Extend GraphQL query
for resource configurations</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/resources/settings/gql/getResources.gql

<li>Updated GraphQL query to include additional fields for resources<br>
<li> Added networking and storage-related fields to the query


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3107/files#diff-45c2f030236a2836bd4ba61e46a20bc0b40f2ab08874c056c49b285a9c2c80eb">+14/-0</a>&nbsp;
&nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Formatting</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>ServiceResourcesFormFragment.tsx</strong><dd><code>UI
tweaks for resource settings form</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>


dashboard/src/features/orgs/projects/resources/settings/components/ServiceResourcesFormFragment/ServiceResourcesFormFragment.tsx

<li>Minor CSS class adjustments for better alignment<br> <li> Updated
icon color handling for dark theme compatibility


</details>


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

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>eighty-candles-tell.md</strong><dd><code>Add changeset
for bug 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; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/eighty-candles-tell.md

- Added a changeset file to document the bug fix


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3107/files#diff-872c88455eb33ca58fddfd06a1cb56559dddfbdc4fd4aea7b44c2984ef9785a1">+5/-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-12-31 12:21:20 +01:00
David BM
aea6d186c2 chore (dashboard): warn run volumes losing data on project pause (#3104)
### **User description**
Resolves #3048


___

### **PR Type**
Enhancement


___

### **Description**
- Added a warning message when pausing a project with Run services that
have persistent volume data.
- The warning is displayed in an Alert component within the pause
confirmation dialog.
- Implemented logic to determine when to show the warning based on the
project's plan and Run services configuration.
- Updated the pause confirmation dialog to include more detailed
information and styling.
- Added a changeset file to document the minor version bump for this
feature.



___



### **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>index.tsx</strong><dd><code>Add warning for Run service
data loss on project pause</code>&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/pages/orgs/[orgSlug]/projects/[appSubdomain]/settings/index.tsx

<li>Added imports for new UI components (Alert, Link, Text)<br> <li>
Implemented <code>useRunServices</code> hook and
<code>showWarning</code> logic<br> <li> Enhanced the pause project
dialog with a warning about data loss for <br>Run services<br> <li>
Added conditional rendering of the warning alert


</details>


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

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>purple-trains-itch.md</strong><dd><code>Add changeset
for Run services warning feature</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/purple-trains-itch.md

<li>Added a changeset file to document the minor version bump<br> <li>
Described the new feature of warning about data loss when pausing a
<br>project


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3104/files#diff-e810895b65f5a519690acf26c92d7f534ddab07a3baf619556674561d965b026">+5/-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-12-30 16:19:41 +01:00
David BM
a535aa3834 fix (dashboard): fetch user roles locally in auth section (#3096)
### **User description**
Fixes #2472


___

### **PR Type**
Bug fix


___

### **Description**
- Fixed issue with user roles not appearing locally in the Nhost
dashboard
- Implemented support for fetching user roles and locales using a local
Mimir client when not running on the Nhost platform
- Modified queries in EditUserForm and UsersBody components to use the
local client when appropriate
- Corrected image source paths for provider logos in both components
- Added a changeset file to document the minor version bump and fix
description
- Improved code formatting and consistency



___



### **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>EditUserForm.tsx</strong><dd><code>Add local client
support for user roles and locales</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/authentication/users/components/EditUserForm/EditUserForm.tsx

<li>Added <code>useIsPlatform</code> and
<code>useLocalMimirClient</code> hooks<br> <li> Modified
<code>useGetRolesPermissionsQuery</code> and
<code>useGetProjectLocalesQuery</code> to <br>use local Mimir client
when not on platform<br> <li> Fixed image source paths for provider
logos<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3096/files#diff-6867937d55b269352d4e146ff21b36ca939f6a838ee70b1b29efa9eabad88c2e">+10/-4</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>UsersBody.tsx</strong><dd><code>Implement local client
for user roles in UsersBody</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/authentication/users/components/UsersBody/UsersBody.tsx

<li>Added <code>useIsPlatform</code> and
<code>useLocalMimirClient</code> hooks<br> <li> Modified
<code>useGetRolesPermissionsQuery</code> to use local Mimir client when
<br>not on platform<br> <li> Fixed formatting for date display and image
source paths<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3096/files#diff-33b33017f46d5cb8e4652c183619f3dc86c5377125ed3a612888739e0da22484">+12/-7</a>&nbsp;
&nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>forty-knives-check.md</strong><dd><code>Add changeset
for user roles 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;
</dd></summary>
<hr>

.changeset/forty-knives-check.md

<li>Added a changeset file to document the minor version bump and fix
<br>description<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3096/files#diff-1eb27af3bf6c2c3562da23f6e52944ae10861d75351b5df022fdf0c50b1ca245">+5/-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-12-30 14:31:23 +01:00
Hassan Ben Jobrane
c9dca09478 feat(dashboard): add change password form (#3089)
### **User description**
resolves https://github.com/nhost/nhost/issues/3058


___

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


___

### **Description**
- Renamed the existing reset password page to `NewPasswordPage` and
updated its form handling.
- Added a new `ResetPasswordPage` with a form to change the password,
including validation and success/error handling.
- Updated the "Forgot password?" link in the sign-in page to direct
users to the new password page.



___



### **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>new.tsx</strong><dd><code>Rename and update reset
password page</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/pages/password/new.tsx

<li>Renamed <code>ResetPasswordPage</code> to
<code>NewPasswordPage</code>.<br> <li> Updated form type from
<code>ResetPasswordFormValues</code> to
<br><code>NewPasswordFormValues</code>.<br> <li> Changed layout title to
"Request Password Reset".<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3089/files#diff-153045bbcb44ce952fbd9ee585c63109891973ab4d1ecc1e1b5edf8f981b1259">+8/-6</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>reset.tsx</strong><dd><code>Add new reset password page
with form</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/pages/password/reset.tsx

<li>Introduced a new <code>ResetPasswordPage</code> component.<br> <li>
Implemented form for changing password with validation.<br> <li> Added
navigation to sign-in page upon successful password change.<br>


</details>


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

</tr>
</table></td></tr><tr><td><strong>Bug fix</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>email.tsx</strong><dd><code>Update forgot password link
and formatting</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/pages/signin/email.tsx

<li>Updated "Forgot password?" link to point to the new password
page.<br> <li> Minor formatting adjustments in the component.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3089/files#diff-b5d7db4460066bc114cb766771612d6f908bd6e440f40de98e4ac311a26b50cd">+4/-4</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-12-30 12:51:38 +01:00
David BM
414896491f fix (hasura-storage-js): stack overflow on storage client getHeaders (#3100)
### **User description**
Fixes #2964


___

### **PR Type**
Bug fix


___

### **Description**
- Fixed a critical bug in the `HasuraStorageClient` class where the
`getHeaders` method was causing a stack overflow due to recursive
self-calling.
- Updated the `getHeaders` method to correctly call
`this.api.getHeaders()` instead of `this.getHeaders()`.
- Added a changeset file to document the bug fix and specify a minor
version bump for the '@nhost/hasura-storage-js' package.
- This fix resolves the issue reported in ticket #2964, where calling
`nhostClient.storage.getHeaders()` was leading to a stack overflow
error.



___



### **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>hasura-storage-client.ts</strong><dd><code>Fix stack
overflow in storage client getHeaders method</code>&nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

packages/hasura-storage-js/src/hasura-storage-client.ts

<li>Fixed a recursive call in the <code>getHeaders</code> method that
was causing a <br>stack overflow<br> <li> Changed
<code>this.getHeaders()</code> to <code>this.api.getHeaders()</code><br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3100/files#diff-f994829d5b30e7a7d47629651e1a013110a71ed2c8cddced340fb3ac05603956">+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-wombats-begin.md</strong><dd><code>Add changeset
for storage client bug fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

.changeset/smooth-wombats-begin.md

<li>Added a changeset file to document the bug fix<br> <li> Specified a
minor version bump for '@nhost/hasura-storage-js' package<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3100/files#diff-9174b11cea1aad4b7308eac7c7fc2d78a44e81b7040b38e542974549ef15b047">+5/-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-12-27 16:46:00 +01:00
David BM
cdf6776523 fix (docs): broken links to create new project in dashboard (#3098)
### **PR Type**
Bug fix, Documentation


___

### **Description**
- Fixed broken links to create a new project in the Nhost Dashboard
across multiple documentation files
- Updated the URL from `https://app.nhost.io/new` to
`https://app.nhost.io` in all affected files
- Modified quickstart guides for Next.js, React Native, React, and Vue
- Updated tutorial guides for Next.js, React, and Vue
- Added a changeset file to document the fix and specify the version
change for `@nhost/docs`



___



### **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>wicked-colts-fetch.md</strong><dd><code>Add changeset
for documentation update</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.changeset/wicked-colts-fetch.md

<li>Added a new changeset file for documenting the fix<br> <li>
Specified the package and version change<br> <li> Brief description of
the fix<br>


</details>


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

</tr>
</table></td></tr><tr><td><strong>Bug fix</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>nextjs.mdx</strong><dd><code>Update Nhost Dashboard
link in Next.js quickstart</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

docs/guides/quickstarts/nextjs.mdx

- Updated the link to create a new project in the Nhost Dashboard



</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>react-native.mdx</strong><dd><code>Update Nhost
Dashboard link in React Native quickstart</code>&nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

docs/guides/quickstarts/react-native.mdx

- Updated the link to create a new project in the Nhost Dashboard



</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>react.mdx</strong><dd><code>Update Nhost Dashboard link
in React quickstart</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

docs/guides/quickstarts/react.mdx

- Updated the link to create a new project in the Nhost Dashboard



</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>vue.mdx</strong><dd><code>Update Nhost Dashboard link
in Vue quickstart</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

docs/guides/quickstarts/vue.mdx

- Updated the link to create a new project in the Nhost Dashboard



</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>nextjs.mdx</strong><dd><code>Update Nhost Dashboard
link in Next.js tutorial</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

docs/guides/tutorials/nextjs.mdx

- Updated the link to create a new project in the Nhost Dashboard



</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>react.mdx</strong><dd><code>Update Nhost Dashboard link
in React tutorial</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

docs/guides/tutorials/react.mdx

- Updated the link to create a new project in the Nhost Dashboard



</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>vue.mdx</strong><dd><code>Update Nhost Dashboard link
in Vue tutorial</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

docs/guides/tutorials/vue.mdx

- Updated the link to create a new project in the Nhost Dashboard



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3098/files#diff-861872b2c9bf786c73e9ded726a1d4b27f57918cc351dc06ba2bfaf51fda6d09">+1/-1</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-12-27 13:55:53 +01:00
github-actions[bot]
1b40e99530 chore: update versions (#3091)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@2.12.0

### Minor Changes

- eb95562: fix: show all available permission variables in permission
dropdown select

### Patch Changes

- 8b5c4a0: chore: cleanup layout and add disable duplicate atom key
checking in development mode

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-25 14:41:34 +01:00
Hassan Ben Jobrane
8b5c4a0951 chore(dashboard): cleanup layout and add disable duplicate atom key checking in development mode (#3093)
### **PR Type**
enhancement, bug fix


___

### **Description**
- Removed unused `contentContainerProps` from various layout components
to simplify the code.
- Refactored class names across multiple files for better readability
and consistency.
- Disabled duplicate atom key checking in development mode to improve
performance.
- Added a changeset for the layout cleanup and configuration 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>AuthenticatedLayout.tsx</strong><dd><code>Remove unused
props and clean imports in AuthenticatedLayout</code></dd></summary>
<hr>


dashboard/src/components/layout/AuthenticatedLayout/AuthenticatedLayout.tsx

<li>Removed unused <code>contentContainerProps</code> from
<code>AuthenticatedLayoutProps</code>.<br> <li> Cleaned up imports by
removing unnecessary type imports.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3093/files#diff-2d69ccffd267658f76d77a864cdece93fc222e08f6025955795fc6f4697f60e7">+3/-16</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>index.tsx</strong><dd><code>Simplify ProjectLayout
usage in UsersPage</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

dashboard/src/pages/[workspaceSlug]/[appSlug]/users/index.tsx

<li>Removed <code>contentContainerProps</code> from
<code>ProjectLayout</code> in <code>UsersPage</code>.<br>


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>index.tsx</strong><dd><code>Simplify
AuthenticatedLayout usage in IndexPage</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/pages/index.tsx

<li>Removed <code>contentContainerProps</code> from
<code>AuthenticatedLayout</code> in <code>IndexPage</code>.<br>


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>users.tsx</strong><dd><code>Refactor class names for
consistency in UsersPage</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/pages/orgs/[orgSlug]/projects/[appSubdomain]/users.tsx

- Adjusted class names for better readability and consistency.



</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>ticket.tsx</strong><dd><code>Refactor class names and
simplify layout in TicketPage</code>&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/pages/support/ticket.tsx

<li>Adjusted class names for better readability and consistency.<br>
<li> Removed <code>contentContainerProps</code> from
<code>AuthenticatedLayout</code> in <code>TicketPage</code>.<br>


</details>


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

</tr>
</table></td></tr><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>silver-goats-stare.md</strong><dd><code>Add changeset
for layout cleanup and atom key check</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; </dd></summary>
<hr>

.changeset/silver-goats-stare.md

<li>Added a changeset for layout cleanup and disabling duplicate atom
key <br>checking.<br>


</details>


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

</tr>
</table></td></tr><tr><td><strong>Bug fix</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Disable duplicate atom
key checking in dev mode</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/package.json

- Disabled duplicate atom key checking in development mode.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3093/files#diff-2d8d55c799cd71f1b35e831f075f8178ed1734c4820a2ad548b4dd24d6938d7c">+1/-1</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-12-25 14:18:53 +01:00
Hassan Ben Jobrane
f5594ef991 fix(ci): use node20 in Dockerfile and upgrade turbo from 1.11.3 to 2.3.3 (#3092)
### **PR Type**
enhancement, configuration changes


___

### **Description**
- Upgraded Node.js version from 18 to 20 in the Dockerfile to ensure
compatibility with newer features and improvements.
- Updated Turbo version from 1.11.3 to 2.3.3 in both Dockerfile and
package.json to leverage new features and optimizations.
- Adjusted environment variable syntax in Dockerfile for consistency.
- Modified build and test scripts in package.json to remove
`--include-dependencies` for streamlined operations.
- Changed `pipeline` key to `tasks` in turbo.json to align with updated
configuration standards.



___



### **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>Dockerfile</strong><dd><code>Upgrade Node.js and Turbo
versions in Dockerfile</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/Dockerfile

<li>Upgraded Node.js version from 18 to 20.<br> <li> Updated Turbo
version from 1.11.3 to 2.2.3.<br> <li> Adjusted environment variable
syntax for consistency.<br>


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>turbo.json</strong><dd><code>Modify Turbo configuration
structure</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>

turbo.json

- Changed `pipeline` key to `tasks`.



</details>


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

</tr>
</table></td></tr><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Update Turbo version and
modify scripts in package.json</code>&nbsp; &nbsp; </dd></summary>
<hr>

package.json

<li>Updated Turbo version from 1.11.3 to 2.3.3.<br> <li> Modified build
and test scripts to remove <code>--include-dependencies</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3092/files#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519">+4/-4</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-12-25 13:57:37 +01:00
David BM
eb9556280c fix (dashboard): retrieve all permission variables in permission dropdown select (#3012)
### **User description**
Fixes #2387


___

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


___

### **Description**
- Replaced `useCurrentWorkspaceAndProject` with `useProject` in
`RuleValueInput` component to streamline project data retrieval.
- Updated GraphQL query variables in `RuleValueInput` to use
`project?.id` for better consistency.
- Added a `convertOperator` function to handle `_in_hasura` and
`_nin_hasura` operators, ensuring they are converted to valid Hasura
operators.
- Modified `createNestedObjectFromRule` to utilize the new
`convertOperator` function for accurate rule conversion.



___



### **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>RuleValueInput.tsx</strong><dd><code>Update project
hook and GraphQL query variables</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/database/dataGrid/components/RuleGroupEditor/RuleValueInput.tsx

<li>Replaced <code>useCurrentWorkspaceAndProject</code> with
<code>useProject</code>.<br> <li> Updated GraphQL query variables to use
<code>project?.id</code> instead of
<br><code>currentProject?.id</code>.<br>


</details>


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

</tr>
</table></td></tr><tr><td><strong>Bug fix</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>convertToHasuraPermissions.ts</strong><dd><code>Add
operator conversion for Hasura permissions</code>&nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/database/dataGrid/utils/convertToHasuraPermissions/convertToHasuraPermissions.ts

<li>Added <code>convertOperator</code> function to handle
<code>_in_hasura</code> and <code>_nin_hasura</code>.<br> <li> Updated
<code>createNestedObjectFromRule</code> to use
<code>convertOperator</code>.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3012/files#diff-046bb93fc9fd9abd712719cd01982ebe633596af1e3ca488403d22a32c2c067e">+26/-4</a>&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-12-23 08:33:37 -05:00
github-actions[bot]
c87736eeeb chore: update versions (#3088)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@2.11.3

### Patch Changes

- 714dffa: fix: improve project polling logic and unify usage across
components

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-19 20:10:54 +01:00
Hassan Ben Jobrane
714dffa5ec feat: split get project query to improve performance while polling for the project state (#3086)
### **PR Type**
Enhancement, Other


___

### **Description**
- Introduced a new hook `useProjectWithState` to improve project state
polling, replacing the previous `useProject` hook.
- Updated components and hooks to use `useProjectWithState` for better
performance and state management.
- Enhanced GraphQL schema with new queries and types, including virus
management capabilities.
- Removed deprecated fields and functions from the GraphQL schema.



___



### **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>ProjectLayout.tsx</strong><dd><code>Update project hook
to improve state polling</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/features/orgs/layout/ProjectLayout/ProjectLayout.tsx

<li>Replaced <code>useProject</code> with
<code>useProjectWithState</code> to improve project state
<br>polling.<br>


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>useAppState.ts</strong><dd><code>Use updated project
hook for app state</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/common/hooks/useAppState/useAppState.ts

<li>Replaced <code>useProject</code> with
<code>useProjectWithState</code> for application state
<br>retrieval.<br>


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>useProject.ts</strong><dd><code>Simplify useProject
hook by removing polling</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dashboard/src/features/orgs/projects/hooks/useProject/useProject.ts

<li>Removed polling options from <code>useProject</code>.<br> <li>
Simplified the hook to not include polling logic.<br>


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>index.ts</strong><dd><code>Export useProjectWithState
hook</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; </dd></summary>
<hr>

dashboard/src/features/orgs/projects/hooks/useProjectWithState/index.ts

- Added export for `useProjectWithState`.



</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>useProjectWithState.ts</strong><dd><code>Implement
useProjectWithState hook with polling</code>&nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>


dashboard/src/features/orgs/projects/hooks/useProjectWithState/useProjectWithState.ts

<li>Implemented <code>useProjectWithState</code> hook with polling
logic.<br> <li> Utilizes <code>useQuery</code> for fetching project
state.<br>


</details>


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

</tr>

<tr>
  <td>
    <details>
<summary><strong>graphql.ts</strong><dd><code>Update GraphQL types and
queries for project state and virus
</code><br><code>management</code></dd></summary>
<hr>

dashboard/src/utils/__generated__/graphql.ts

<li>Added <code>GetProjectStateQuery</code> and related types.<br> <li>
Removed unused fields and functions.<br> <li> Added new fields and types
for virus management.<br>


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3086/files#diff-fbd5db84b560b1c91675004448c6c7fa0dcbfb28b9eb05d53b03e6cb7b83ebac">+501/-39</a></td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>getProjectState.gql</strong><dd><code>Add GraphQL query
for project state</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>

dashboard/src/gql/organizations/getProjectState.gql

- Added new GraphQL query for fetching project state by subdomain.



</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3086/files#diff-88f84673d467d0b44d14b789a6beed90050c7898bb3fb95847ad892b116a3b6d">+16/-0</a>&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-12-19 19:54:00 +01:00
208 changed files with 4897 additions and 2450 deletions

View File

@@ -20,7 +20,7 @@ runs:
id: pnpm-cache-dir
shell: bash
run: echo "dir=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
- uses: actions/cache@v4
id: pnpm-cache
with:
path: ${{ steps.pnpm-cache-dir.outputs.dir }}

View File

@@ -163,7 +163,7 @@ jobs:
# * Run this step only if the previous step failed, and Playwright generated a report
- name: Upload Playwright Report
if: ${{ failure() && hashFiles(format('{0}/playwright-report/**', matrix.package.path)) != ''}}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: playwright-${{ steps.file-name.outputs.fileName }}
path: ${{format('{0}/playwright-report/**', matrix.package.path)}}

View File

@@ -16,7 +16,7 @@ jobs:
steps:
- name: PR Agent action step
id: pragent
uses: Codium-ai/pr-agent@v0.24
uses: Codium-ai/pr-agent@v0.26
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OPENAI_KEY: ${{ secrets.OPENAI_API_KEY }}

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": ["vue-template-compiler", "micromatch", "path-to-regexp"]
"allowlist": ["vue-template-compiler"]
}

View File

@@ -1,5 +1,78 @@
# @nhost/dashboard
## 2.16.0
### Minor Changes
- f8e6b61: fix: can add rule groups in table permissions
- 9e404c8: fix: not redirect to 404 page if using local Nhost backend
- ac4aa01: fix: can delete column in database page
- 4385524: fix: update url to check service health in local dashboard
### Patch Changes
- @nhost/react-apollo@16.0.1
- @nhost/nextjs@2.2.2
## 2.15.0
### Minor Changes
- f1052a8: fix: improve stability of the dashboard when pausing projects
- 30daa41: fix: update links to docs in overview page
- 7537237: feat: add image preview toggle in storage
## 2.14.0
### Minor Changes
- d43931e: fix: invalid organization slug/project subdomain doesn't open 404 page
- 5df6fa2: feat: add unencrypted disk warning in storage capacity settings
### Patch Changes
- 44c1e17: chore: update `msw` to v1.3.5 to fix vulnerabilities
- @nhost/react-apollo@16.0.0
- @nhost/nextjs@2.2.1
## 2.13.0
### Minor Changes
- 21e90da: chore: remove restrictions on SMTP sender so My Name <name@acme.com> can be added
- 865dd93: fix: duplicate Run placeholders when there is an error in the backend
- 6902a36: fix: can remove resources if postgres capacity is higher than 10
- a535aa3: fix: fetch user roles locally in auth section
- 0c50816: fix: allow decimal numbers in database row insert
- aea6d18: chore: add warning when pausing a project about losing Run services persistent volume data
- d3b4fc3: feat: allow to change postgres settings if project is paused
- 29d27e1: chore: update `next` to v14.2.22 to fix vulnerabilities
- c9dca09: feat: add reset password form
- b3bcacb: fix: paused project banner cannot read null project name
### Patch Changes
- Updated dependencies [46fc520]
- Updated dependencies [29d27e1]
- @nhost/nextjs@2.2.0
- @nhost/react-apollo@15.0.1
## 2.12.0
### Minor Changes
- eb95562: fix: show all available permission variables in permission dropdown select
### Patch Changes
- 8b5c4a0: chore: cleanup layout and add disable duplicate atom key checking in development mode
## 2.11.3
### Patch Changes
- 714dffa: fix: improve project polling logic and unify usage across components
## 2.11.2
### Patch Changes

View File

@@ -1,13 +1,13 @@
FROM node:18-alpine AS pruner
FROM node:20-alpine AS pruner
RUN apk add --no-cache libc6-compat
RUN apk update
WORKDIR /app
RUN yarn global add turbo@1.11.3
RUN yarn global add turbo@2.2.3
COPY . .
RUN turbo prune --scope="@nhost/dashboard" --docker
FROM node:18-alpine AS builder
FROM node:20-alpine AS builder
ARG TURBO_TOKEN
ARG TURBO_TEAM
@@ -15,20 +15,20 @@ RUN apk add --no-cache libc6-compat python3 make g++
RUN apk update
WORKDIR /app
ENV NEXT_TELEMETRY_DISABLED 1
ENV NEXT_PUBLIC_ENV dev
ENV NEXT_PUBLIC_NHOST_PLATFORM false
ENV NEXT_TELEMETRY_DISABLED=1
ENV NEXT_PUBLIC_ENV=dev
ENV NEXT_PUBLIC_NHOST_PLATFORM=false
# placeholders for URLs, will be replaced on runtime by entrypoint script
ENV NEXT_PUBLIC_NHOST_ADMIN_SECRET __NEXT_PUBLIC_NHOST_ADMIN_SECRET__
ENV NEXT_PUBLIC_NHOST_AUTH_URL __NEXT_PUBLIC_NHOST_AUTH_URL__
ENV NEXT_PUBLIC_NHOST_FUNCTIONS_URL __NEXT_PUBLIC_NHOST_FUNCTIONS_URL__
ENV NEXT_PUBLIC_NHOST_GRAPHQL_URL __NEXT_PUBLIC_NHOST_GRAPHQL_URL__
ENV NEXT_PUBLIC_NHOST_STORAGE_URL __NEXT_PUBLIC_NHOST_STORAGE_URL__
ENV NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL __NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL__
ENV NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL __NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__
ENV NEXT_PUBLIC_NHOST_HASURA_API_URL __NEXT_PUBLIC_NHOST_HASURA_API_URL__
ENV NEXT_PUBLIC_NHOST_CONFIGSERVER_URL __NEXT_PUBLIC_NHOST_CONFIGSERVER_URL__
ENV NEXT_PUBLIC_NHOST_ADMIN_SECRET=__NEXT_PUBLIC_NHOST_ADMIN_SECRET__
ENV NEXT_PUBLIC_NHOST_AUTH_URL=__NEXT_PUBLIC_NHOST_AUTH_URL__
ENV NEXT_PUBLIC_NHOST_FUNCTIONS_URL=__NEXT_PUBLIC_NHOST_FUNCTIONS_URL__
ENV NEXT_PUBLIC_NHOST_GRAPHQL_URL=__NEXT_PUBLIC_NHOST_GRAPHQL_URL__
ENV NEXT_PUBLIC_NHOST_STORAGE_URL=__NEXT_PUBLIC_NHOST_STORAGE_URL__
ENV NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL=__NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL__
ENV NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL=__NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__
ENV NEXT_PUBLIC_NHOST_HASURA_API_URL=__NEXT_PUBLIC_NHOST_HASURA_API_URL__
ENV NEXT_PUBLIC_NHOST_CONFIGSERVER_URL=__NEXT_PUBLIC_NHOST_CONFIGSERVER_URL__
RUN yarn global add pnpm@9.15.0
COPY .gitignore .gitignore
@@ -41,7 +41,7 @@ COPY turbo.json turbo.json
COPY config/ config/
RUN pnpm build:dashboard
FROM node:18-alpine AS runner
FROM node:20-alpine AS runner
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs
@@ -58,4 +58,4 @@ COPY --from=builder --chown=nextjs:nodejs /app/dashboard/.next/standalone/app ./
COPY --from=builder --chown=nextjs:nodejs /app/dashboard/.next/static ./dashboard/.next/static
ENTRYPOINT ["./docker-entrypoint.sh"]
CMD ["node", "dashboard/server.js"]
CMD ["node", "dashboard/server.js"]

View File

@@ -1,10 +1,10 @@
{
"name": "@nhost/dashboard",
"version": "2.11.2",
"version": "2.16.0",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
"dev": "next dev",
"dev": "RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false next dev",
"build": "next build --no-lint",
"analyze": "ANALYZE=true pnpm build --no-lint",
"start": "next start",
@@ -84,7 +84,7 @@
"just-kebab-case": "^4.2.0",
"lodash.debounce": "^4.0.8",
"lucide-react": "^0.416.0",
"next": "^14.2.10",
"next": "^14.2.22",
"next-nprogress-bar": "^2.3.13",
"next-seo": "^6.5.0",
"next-themes": "^0.3.0",
@@ -177,7 +177,7 @@
"eslint-plugin-react-hooks": "^4.6.0",
"jsdom": "^22.1.0",
"lint-staged": "^15.2.2",
"msw": "^1.3.3",
"msw": "^1.3.5",
"msw-storybook-addon": "^1.10.0",
"node-fetch": "^3.3.2",
"postcss": "^8.4.38",

View File

@@ -21,22 +21,9 @@ import { useNotFoundRedirect } from '@/features/projects/common/hooks/useNotFoun
import { cn } from '@/lib/utils';
import Image from 'next/image';
import { useRouter } from 'next/router';
import {
useEffect,
useState,
type DetailedHTMLProps,
type HTMLProps,
} from 'react';
import { useEffect, useState } from 'react';
export interface AuthenticatedLayoutProps extends BaseLayoutProps {
/**
* Props passed to the internal content container.
*/
contentContainerProps?: DetailedHTMLProps<
HTMLProps<HTMLDivElement>,
HTMLDivElement
>;
}
export interface AuthenticatedLayoutProps extends BaseLayoutProps {}
export default function AuthenticatedLayout({
children,

View File

@@ -21,23 +21,22 @@ export default function UnauthenticatedLayout({
const router = useRouter();
const isPlatform = useIsPlatform();
const { isAuthenticated, isLoading } = useAuthenticationStatus();
const isOnResetPassword = router.route === '/password/reset';
useEffect(() => {
if (!isPlatform || (!isLoading && isAuthenticated)) {
router.push('/');
// we do not want to redirect if the user tries to reset their password
if (!isOnResetPassword) {
router.push('/');
}
}
}, [isLoading, isAuthenticated, router, isPlatform]);
}, [isLoading, isAuthenticated, router, isPlatform, isOnResetPassword]);
if (!isPlatform || isLoading || isAuthenticated) {
if ((!isPlatform || isLoading || isAuthenticated) && !isOnResetPassword) {
return (
<BaseLayout {...props}>
<LoadingScreen
sx={{ backgroundColor: (theme) => theme.palette.background.default }}
slotProps={{
activityIndicator: {
className: 'text-white',
},
}}
/>
</BaseLayout>
);
@@ -59,19 +58,19 @@ export default function UnauthenticatedLayout({
<RetryableErrorBoundary>
<Box
className="flex items-center min-h-screen"
className="flex min-h-screen items-center"
sx={{ backgroundColor: (theme) => theme.palette.common.black }}
>
<Container
rootClassName="bg-transparent h-full"
className="grid items-center w-full h-full gap-12 pt-8 pb-12 bg-transparent justify-items-center lg:grid-cols-2 lg:gap-4 lg:pb-0 lg:pt-0"
className="grid h-full w-full items-center justify-items-center gap-12 bg-transparent pb-12 pt-8 lg:grid-cols-2 lg:gap-4 lg:pb-0 lg:pt-0"
>
<div className="relative z-10 order-2 grid w-full max-w-[544px] grid-flow-row gap-12 lg:order-1">
{children}
</div>
<div className="relative z-0 order-1 flex h-full w-full items-center justify-center md:min-h-[150px] lg:order-2 lg:min-h-[none]">
<div className="absolute top-0 bottom-0 left-0 right-0 flex items-center justify-center w-full h-full max-w-xl mx-auto overflow-hidden opacity-70">
<div className="absolute bottom-0 left-0 right-0 top-0 mx-auto flex h-full w-full max-w-xl items-center justify-center overflow-hidden opacity-70">
<Image
priority
src="/assets/line-grid.svg"

View File

@@ -1,6 +1,6 @@
import { type DialogProps } from '@radix-ui/react-dialog';
import { Command as CommandPrimitive } from 'cmdk';
import { Search } from 'lucide-react';
import { Command as CommandPrimitive, useCommandState } from 'cmdk';
import { PlusIcon, Search } from 'lucide-react';
import * as React from 'react';
import { Dialog, DialogContent } from '@/components/ui/v3/dialog';
@@ -26,7 +26,7 @@ interface CommandDialogProps extends DialogProps {}
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (
<Dialog {...props}>
<DialogContent className="p-0 overflow-hidden shadow-lg">
<DialogContent className="overflow-hidden p-0 shadow-lg">
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
@@ -37,14 +37,22 @@ const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
<div className="flex items-center px-3 border-b" cmdk-input-wrapper="">
<Search className="w-4 h-4 mr-2 opacity-50 shrink-0" />
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input> & {
prefix?: React.ReactNode;
}
>(({ className, prefix, ...props }, ref) => (
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
{prefix && (
<span className="pointer-events-none flex items-center text-muted-foreground">
{prefix}
</span>
)}
<CommandPrimitive.Input
ref={ref}
className={cn(
'flex h-11 w-full rounded-md border-none bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground focus:ring-0 disabled:cursor-not-allowed disabled:opacity-50',
prefix && 'pl-0',
className,
)}
{...props}
@@ -73,7 +81,7 @@ const CommandEmpty = React.forwardRef<
>((props, ref) => (
<CommandPrimitive.Empty
ref={ref}
className="py-6 text-sm text-center"
className="py-6 text-center text-sm"
{...props}
/>
));
@@ -140,6 +148,25 @@ const CommandShortcut = ({
};
CommandShortcut.displayName = 'CommandShortcut';
const CommandCreateItem = ({
onCreate,
}: {
onCreate: (value: string) => void;
}) => {
const query = useCommandState((state) => state.search);
if (!query || !onCreate) {
return null;
}
return (
<CommandItem forceMount value="create" onSelect={() => onCreate(query)}>
<PlusIcon className="mr-2" /> {query}
</CommandItem>
);
};
CommandCreateItem.displayName = 'CommandCreateItem';
export {
Command,
CommandDialog,
@@ -150,4 +177,5 @@ export {
CommandItem,
CommandShortcut,
CommandSeparator,
CommandCreateItem,
};

View File

@@ -0,0 +1,183 @@
'use client';
import { X } from 'lucide-react';
import { Badge } from '@/components/ui/v3/badge';
import {
Command,
CommandGroup,
CommandItem,
CommandList,
} from '@/components/ui/v3/command';
import { cn } from '@/lib/utils';
import { Command as CommandPrimitive } from 'cmdk';
import {
useCallback,
useMemo,
useRef,
useState,
type KeyboardEvent,
} from 'react';
type Option = Record<'value' | 'label', string>;
interface FancyMultiSelectProps {
defaultValue?: Option[];
options?: Option[];
creatable?: boolean;
className?: string;
onChange?: (selected: Option[]) => void;
}
export function FancyMultiSelect({
defaultValue = [],
options = [],
creatable = false,
className,
onChange,
}: FancyMultiSelectProps) {
const inputRef = useRef<HTMLInputElement>(null);
const [open, setOpen] = useState(false);
const [selected, setSelected] = useState<Option[]>(defaultValue);
const [inputValue, setInputValue] = useState('');
const handleUnselect = useCallback((option: Option) => {
setSelected((prev) => prev.filter((s) => s.value !== option.value));
}, []);
const handleKeyDown = useCallback((e: KeyboardEvent<HTMLDivElement>) => {
const input = inputRef.current;
if (input) {
if (e.key === 'Delete' || e.key === 'Backspace') {
if (input.value === '') {
setSelected((prev) => {
const newSelected = [...prev];
newSelected.pop();
return newSelected;
});
}
}
// This is not a default behaviour of the <input /> field
if (e.key === 'Escape') {
input.blur();
}
}
}, []);
const handleSelect = useCallback(
(option: Option) => {
setInputValue('');
setSelected((prev) => {
const newSelected = [...prev, option];
onChange?.(newSelected);
return newSelected;
});
},
[onChange],
);
const selectables = useMemo(() => {
const filtered = options.filter(
(option) =>
!selected.map((s) => s.value).includes(option.value) &&
option.label.toLowerCase().includes(inputValue.toLowerCase()),
);
if (creatable && inputValue) {
return [
...filtered,
{
value: inputValue.toLowerCase(),
label: inputValue,
},
];
}
return filtered;
}, [options, selected, inputValue, creatable]);
return (
<Command
onKeyDown={handleKeyDown}
className="relative overflow-visible bg-transparent"
>
<div
className={cn(
'group flex min-h-10 flex-1 rounded-md border bg-background px-4 py-0 text-sm ring-offset-background hover:bg-accent',
className,
)}
>
<div className="flex flex-1 flex-wrap items-center gap-1 overflow-x-hidden py-1">
{selected.map((option) => {
return (
<Badge
className="h-7 overflow-x-hidden text-[12px] font-normal"
key={option.value}
variant="outline"
>
<span className="overflow-x-hidden text-ellipsis whitespace-nowrap break-words font-medium">
{option.label}
</span>
<button
type="button"
aria-label={`Remove ${option.label}`}
className="ml-1 rounded-full outline-none"
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleUnselect(option);
}
}}
onMouseDown={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onClick={() => handleUnselect(option)}
>
<X className="h-3 w-3 text-muted-foreground hover:text-foreground" />
</button>
</Badge>
);
})}
{/* Avoid having the "Search" Icon */}
<CommandPrimitive.Input
ref={inputRef}
value={inputValue}
onValueChange={setInputValue}
onBlur={() => setOpen(false)}
onFocus={() => setOpen(true)}
placeholder="Select options..."
className="flex flex-1 border-none bg-transparent px-0 py-1 text-sm font-medium outline-none !ring-0 !ring-offset-0 placeholder:text-sm placeholder:text-muted-foreground group-hover:text-accent-foreground"
/>
</div>
</div>
<div className="relative">
<CommandList>
{open && selectables.length > 0 ? (
<div className="absolute top-2 z-10 w-full rounded-md border bg-popover text-popover-foreground shadow-md outline-none animate-in">
<CommandGroup className="h-full overflow-auto">
{selectables.map((option) => {
return (
<CommandItem
key={option.value}
onMouseDown={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onSelect={() => handleSelect(option)}
className="cursor-pointer"
>
{creatable &&
!options.find((opt) => opt.value === option.value)
? `Create "${option.label}"`
: option.label}
</CommandItem>
);
})}
</CommandGroup>
</div>
) : null}
</CommandList>
</div>
</Command>
);
}

View File

@@ -8,6 +8,7 @@ import {
DialogTitle,
} from '@/components/ui/v3/dialog';
import { LoadingScreen } from '@/components/presentational/LoadingScreen';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import {
Form,
@@ -53,8 +54,8 @@ export default function TransferProjectDialog({
}: TransferProjectDialogProps) {
const { push } = useRouter();
const currentUserId = useUserId();
const { project } = useProject();
const { orgs, currentOrg } = useOrgs();
const { project, loading: projectLoading } = useProject();
const { orgs, currentOrg, loading: orgsLoading } = useOrgs();
const [transferProject] = useBillingTransferAppMutation();
const form = useForm<z.infer<typeof transferProjectFormSchema>>({
@@ -96,6 +97,10 @@ export default function TransferProjectDialog({
member.user.id === userId,
);
if (projectLoading || orgsLoading) {
return <LoadingScreen />;
}
return (
<Dialog
open={open}

View File

@@ -12,7 +12,7 @@ import { ApplicationUnknown } from '@/features/orgs/projects/common/components/A
import { ApplicationUnpausing } from '@/features/orgs/projects/common/components/ApplicationUnpausing';
import { useAppState } from '@/features/orgs/projects/common/hooks/useAppState';
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { useProjectWithState } from '@/features/orgs/projects/hooks/useProjectWithState';
import { ApplicationStatus } from '@/types/application';
import { NextSeo } from 'next-seo';
import { useRouter } from 'next/router';
@@ -37,7 +37,7 @@ function ProjectLayoutContent({
const { state } = useAppState();
const isPlatform = useIsPlatform();
const { project, loading, error } = useProject({ poll: true });
const { project, loading, error } = useProjectWithState();
const isOnOverviewPage = route === '/orgs/[orgSlug]/projects/[appSubdomain]';

View File

@@ -61,7 +61,7 @@ export default function AISettings() {
const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { project } = useProject();
const { project, loading: loadingProject } = useProject();
const [aiServiceEnabled, setAIServiceEnabled] = useState(true);
@@ -73,9 +73,10 @@ export default function AISettings() {
error: errorGettingAiSettings,
} = useGetAiSettingsQuery({
variables: {
appId: project.id,
appId: project?.id,
},
...(!isPlatform ? { client: localMimirClient } : {}),
skip: !project?.id,
});
const { data: graphiteVersionsData, loading: loadingGraphiteVersionsData } =
@@ -192,11 +193,11 @@ export default function AISettings() {
}
};
if (loadingAiSettings || loadingGraphiteVersionsData) {
if (loadingProject || loadingAiSettings || loadingGraphiteVersionsData) {
return (
<ActivityIndicator
delay={1000}
label="Loading Postgres version..."
label="Loading AI settings..."
className="justify-center"
/>
);
@@ -269,7 +270,7 @@ export default function AISettings() {
return (
<Box className="space-y-4" sx={{ backgroundColor: 'background.default' }}>
<Box className="flex flex-row items-center justify-between p-4 rounded-lg border-1">
<Box className="flex flex-row items-center justify-between rounded-lg border-1 p-4">
<Text className="text-lg font-semibold">Enable AI service</Text>
<Switch
checked={aiServiceEnabled}
@@ -298,7 +299,7 @@ export default function AISettings() {
<Tooltip title="Version of the service to use.">
<InfoIcon
aria-label="Info"
className="w-4 h-4"
className="h-4 w-4"
color="primary"
/>
</Tooltip>
@@ -353,7 +354,7 @@ export default function AISettings() {
<Tooltip title="Used to validate requests between postgres and the AI service. The AI service will also include the header X-Graphite-Webhook-Secret with this value set when calling external webhooks so the source of the request can be validated.">
<InfoIcon
aria-label="Info"
className="w-4 h-4"
className="h-4 w-4"
color="primary"
/>
</Tooltip>
@@ -377,7 +378,7 @@ export default function AISettings() {
<Tooltip title="Dedicated resources allocated for the service.">
<InfoIcon
aria-label="Info"
className="w-4 h-4"
className="h-4 w-4"
color="primary"
/>
</Tooltip>
@@ -417,7 +418,7 @@ export default function AISettings() {
<Tooltip title="Key to use for authenticating API requests to OpenAI">
<InfoIcon
aria-label="Info"
className="w-4 h-4"
className="h-4 w-4"
color="primary"
/>
</Tooltip>
@@ -440,7 +441,7 @@ export default function AISettings() {
<Tooltip title="Optional. OpenAI organization to use.">
<InfoIcon
aria-label="Info"
className="w-4 h-4"
className="h-4 w-4"
color="primary"
/>
</Tooltip>
@@ -468,7 +469,7 @@ export default function AISettings() {
<Tooltip title="How often to run the job that keeps embeddings up to date.">
<InfoIcon
aria-label="Info"
className="w-4 h-4"
className="h-4 w-4"
color="primary"
/>
</Tooltip>

View File

@@ -19,7 +19,7 @@ import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWith
const validationSchema = yup
.object({
sender: yup.string().label('SMTP Sender').email().required(),
sender: yup.string().label('SMTP Sender').required(),
password: yup.string().label('Password').required(),
})
.required();

View File

@@ -30,7 +30,7 @@ const smtpValidationSchema = yup
user: yup.string().label('Username').required(),
password: yup.string().label('Password'),
method: yup.string().required(),
sender: yup.string().label('SMTP Sender').email().required(),
sender: yup.string().label('SMTP Sender').required(),
})
.required();

View File

@@ -16,18 +16,20 @@ import { Text } from '@/components/ui/v2/Text';
import { useRemoteApplicationGQLClient } from '@/features/orgs/hooks/useRemoteApplicationGQLClient';
import { EditUserPasswordForm } from '@/features/orgs/projects/authentication/users/components/EditUserPasswordForm';
import { getReadableProviderName } from '@/features/orgs/projects/authentication/users/utils/getReadableProviderName';
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/features/orgs/projects/hooks/useLocalMimirClient';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
import { type RemoteAppUser } from '@/pages/orgs/[orgSlug]/projects/[appSubdomain]/users';
import type { DialogFormProps } from '@/types/common';
import { copy } from '@/utils/copy';
import {
RemoteAppGetUsersDocument,
useGetProjectLocalesQuery,
useGetRolesPermissionsQuery,
useUpdateRemoteAppUserMutation,
} from '@/utils/__generated__/graphql';
import { copy } from '@/utils/copy';
import { yupResolver } from '@hookform/resolvers/yup';
import { useTheme } from '@mui/material';
import { format } from 'date-fns';
@@ -106,6 +108,8 @@ export default function EditUserForm({
onDeleteUser,
roles,
}: EditUserFormProps) {
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const theme = useTheme();
const { onDirtyStateChange, openDialog } = useDialog();
const { project } = useProject();
@@ -196,6 +200,7 @@ export default function EditUserForm({
const { data: dataRoles } = useGetRolesPermissionsQuery({
variables: { appId: project?.id },
...(!isPlatform ? { client: localMimirClient } : {}),
});
const allAvailableProjectRoles = getUserRoles(
@@ -206,6 +211,7 @@ export default function EditUserForm({
variables: {
appId: project?.id,
},
...(!isPlatform ? { client: localMimirClient } : {}),
});
const allowedLocales = data?.config?.auth?.user?.locale?.allowed || [];

View File

@@ -15,6 +15,8 @@ import { Text } from '@/components/ui/v2/Text';
import { useRemoteApplicationGQLClient } from '@/features/orgs/hooks/useRemoteApplicationGQLClient';
import type { EditUserFormValues } from '@/features/orgs/projects/authentication/users/components/EditUserForm';
import { getReadableProviderName } from '@/features/orgs/projects/authentication/users/utils/getReadableProviderName';
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/features/orgs/projects/hooks/useLocalMimirClient';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
@@ -61,6 +63,8 @@ export interface UsersBodyProps {
export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
const theme = useTheme();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { openAlertDialog, openDrawer, closeDrawer } = useDialog();
const { project } = useProject();
const remoteProjectGQLClient = useRemoteApplicationGQLClient();
@@ -88,6 +92,7 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
*/
const { data: dataRoles } = useGetRolesPermissionsQuery({
variables: { appId: project?.id },
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { allowed: allowedRoles } = dataRoles?.config?.auth?.user?.roles || {};

View File

@@ -1,4 +1,5 @@
import { ContactUs } from '@/components/common/ContactUs';
import { LoadingScreen } from '@/components/presentational/LoadingScreen';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Button } from '@/components/ui/v2/Button';
import { Dropdown } from '@/components/ui/v2/Dropdown';
@@ -6,7 +7,7 @@ import { Text } from '@/components/ui/v2/Text';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { useInterval } from '@/hooks/useInterval';
import { getRelativeDateByApplicationState } from '@/utils/helpers';
import { useState } from 'react';
import { useEffect, useState } from 'react';
export interface AppLoaderProps {
/**
@@ -33,25 +34,31 @@ export default function AppLoader({
date,
restoring,
}: AppLoaderProps) {
const { project } = useProject();
const { project, loading } = useProject();
const [timeElapsed, setTimeElapsed] = useState<number>(0);
let timeElapsedSinceEventCreation: number;
useEffect(() => {
if (!project || loading) {
return;
}
if (date) {
timeElapsedSinceEventCreation = getRelativeDateByApplicationState(date);
} else if (unpause) {
timeElapsedSinceEventCreation = getRelativeDateByApplicationState(
project.appStates[0].createdAt,
);
} else {
timeElapsedSinceEventCreation = getRelativeDateByApplicationState(
project.createdAt,
);
}
let timeElapsedSinceEventCreation: number;
const [timeElapsed, setTimeElapsed] = useState(timeElapsedSinceEventCreation);
if (date) {
timeElapsedSinceEventCreation = getRelativeDateByApplicationState(date);
} else if (unpause) {
timeElapsedSinceEventCreation = getRelativeDateByApplicationState(
project.appStates[0].createdAt,
);
} else {
timeElapsedSinceEventCreation = getRelativeDateByApplicationState(
project.createdAt,
);
}
setTimeElapsed(timeElapsedSinceEventCreation);
}, [project, date, unpause, loading]);
// Would be also valuable to check the appCreatedTime so this doesn't ever appear if not created under a time limit. @GC
useInterval(
() => {
setTimeElapsed(timeElapsed + 1);
@@ -59,6 +66,10 @@ export default function AppLoader({
startLoader ? 1000 : null,
);
if (loading) {
return <LoadingScreen />;
}
return (
<div className="grid grid-flow-row gap-2">
<div className="grid grid-flow-row gap-1">
@@ -86,9 +97,9 @@ export default function AppLoader({
<ActivityIndicator className="mx-auto" />
{timeElapsed > 180 && (
<Dropdown.Root className="flex flex-col mx-auto">
<Dropdown.Root className="mx-auto flex flex-col">
<Dropdown.Trigger
className="flex mx-auto font-medium"
className="mx-auto flex font-medium"
hideChevron
asChild
>

View File

@@ -36,8 +36,8 @@ export default function ApplicationPaused() {
>
<RemoveApplicationModal
close={() => setShowDeletingModal(false)}
title={`Remove project ${project.name}?`}
description={`The project ${project.name} will be removed. All data will be lost and there will be no way to
title={`Remove project ${project?.name}?`}
description={`The project ${project?.name} will be removed. All data will be lost and there will be no way to
recover the app once it has been deleted.`}
className="z-50"
/>

View File

@@ -1,4 +1,4 @@
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { useProjectWithState } from '@/features/orgs/projects/hooks/useProjectWithState';
import { ApplicationStatus } from '@/types/application';
/**
@@ -9,7 +9,7 @@ export default function useAppState(): {
state: ApplicationStatus;
message?: string;
} {
const { project } = useProject({ poll: true });
const { project } = useProjectWithState();
const noApplication = !project;
if (noApplication) {

View File

@@ -13,12 +13,12 @@ export default function useIsHealthy() {
const appUrl = generateAppServiceUrl(
project?.subdomain,
project?.region,
'auth',
'hasura',
);
const { failureCount, status } = useQuery(
['/healthz'],
() => fetch(`${appUrl}/healthz`),
['/v1/version'],
() => fetch(`${appUrl}/v1/version`),
{
enabled: !isPlatform && !!project,
retry: true,

View File

@@ -24,7 +24,7 @@ export default function useProjectRedirectWhenReady(
const { data, client, startPolling, ...rest } = useGetApplicationStateQuery({
...options,
variables: { ...options.variables, appId: project?.id },
skip: !project.id,
skip: !project?.id,
});
useEffect(() => {

View File

@@ -1,3 +1,4 @@
import type { ProjectFragment } from '@/utils/__generated__/graphql';
import {
getAuthServiceUrl,
getDatabaseServiceUrl,
@@ -7,7 +8,6 @@ import {
getStorageServiceUrl,
isPlatform,
} from '@/utils/env';
import type { ProjectFragment } from '@/utils/__generated__/graphql';
export type NhostService =
| 'auth'
@@ -18,21 +18,6 @@ export type NhostService =
| 'hasura'
| 'grafana';
/**
* The default slugs that are used when running the dashboard locally. These
* values are used both in local mode and when running the dashboard locally
* against the remote (either staging or production) backend.
*/
export const defaultLocalBackendSlugs: Record<NhostService, string> = {
auth: '/v1/auth',
db: '',
graphql: '/v1/graphql',
functions: '/v1/functions',
storage: '/v1/files',
hasura: '',
grafana: '',
};
/**
* The default slugs that are used when running the dashboard against the
* remote (either staging or production) backend in a cloud environment.

View File

@@ -34,7 +34,11 @@ export default function AuthDomain() {
const localMimirClient = useLocalMimirClient();
const [isVerified, setIsVerified] = useState(false);
const { project, refetch: refetchProject } = useProject();
const {
project,
refetch: refetchProject,
loading: loadingProject,
} = useProject();
const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
@@ -48,7 +52,7 @@ export default function AuthDomain() {
const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: {
appId: project.id,
appId: project?.id,
},
...(!isPlatform ? { client: localMimirClient } : {}),
});
@@ -62,7 +66,7 @@ export default function AuthDomain() {
}
}, [data, loading, form, initialValue]);
if (loading) {
if (loadingProject || loading) {
return (
<ActivityIndicator
delay={1000}
@@ -147,7 +151,7 @@ export default function AuthDomain() {
loading: formState.isSubmitting,
},
}}
className="grid grid-flow-row px-4 gap-x-4 gap-y-4 lg:grid-cols-5"
className="grid grid-flow-row gap-x-4 gap-y-4 px-4 lg:grid-cols-5"
>
<Input
{...register('auth_fqdn')}

View File

@@ -14,10 +14,14 @@ const validationSchema = Yup.object({
export type DatabaseDomainFormValues = Yup.InferType<typeof validationSchema>;
export default function DatabaseDomain() {
const { project } = useProject();
const { project, loading } = useProject();
const [dbFQDN, setDbFQDN] = useState('');
if (loading) {
return null;
}
const postgresHost = generateAppServiceUrl(
project.subdomain,
project.region,
@@ -36,7 +40,7 @@ export default function DatabaseDomain() {
className: 'hidden',
},
}}
className="grid grid-flow-row px-4 gap-x-4 gap-y-4 lg:grid-cols-5"
className="grid grid-flow-row gap-x-4 gap-y-4 px-4 lg:grid-cols-5"
>
<Input
id="database_fqdn"

View File

@@ -33,7 +33,11 @@ export default function HasuraDomain() {
const localMimirClient = useLocalMimirClient();
const [isVerified, setIsVerified] = useState(false);
const { project, refetch: refetchProject } = useProject();
const {
project,
refetch: refetchProject,
loading: loadingProject,
} = useProject();
const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
@@ -47,7 +51,7 @@ export default function HasuraDomain() {
const { data, loading, error } = useGetHasuraSettingsQuery({
variables: {
appId: project.id,
appId: project?.id,
},
...(!isPlatform ? { client: localMimirClient } : {}),
});
@@ -61,7 +65,7 @@ export default function HasuraDomain() {
}
}, [data, loading, form, initialValue]);
if (loading) {
if (loadingProject || loading) {
return (
<ActivityIndicator
delay={0}
@@ -148,7 +152,7 @@ export default function HasuraDomain() {
loading: formState.isSubmitting,
},
}}
className="grid grid-flow-row px-4 gap-x-4 gap-y-4 lg:grid-cols-5"
className="grid grid-flow-row gap-x-4 gap-y-4 px-4 lg:grid-cols-5"
>
<Input
{...register('hasura_fqdn')}

View File

@@ -35,7 +35,11 @@ export default function ServerlessFunctionsDomain() {
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const [isVerified, setIsVerified] = useState(false);
const { project, refetch: refetchProject } = useProject();
const {
project,
refetch: refetchProject,
loading: loadingProject,
} = useProject();
const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
@@ -49,7 +53,7 @@ export default function ServerlessFunctionsDomain() {
const { data, loading, error } = useGetServerlessFunctionsSettingsQuery({
variables: {
appId: project.id,
appId: project?.id,
},
...(!isPlatform ? { client: localMimirClient } : {}),
});
@@ -63,7 +67,7 @@ export default function ServerlessFunctionsDomain() {
}
}, [data, loading, form, initialValue]);
if (loading) {
if (loadingProject || loading) {
return (
<ActivityIndicator
delay={1000}
@@ -151,7 +155,7 @@ export default function ServerlessFunctionsDomain() {
loading: formState.isSubmitting,
},
}}
className="grid grid-flow-row px-4 gap-x-4 gap-y-4 lg:grid-cols-5"
className="grid grid-flow-row gap-x-4 gap-y-4 px-4 lg:grid-cols-5"
>
<Input
{...register('functions_fqdn')}

View File

@@ -65,8 +65,7 @@ const Template: ComponentStory<typeof ColumnAutocomplete> = function Template(
<ColumnAutocomplete
{...args}
name="firstReference"
label="First Reference"
onChange={(_event, newValue) =>
onChange={(newValue) =>
form.setValue('firstReference', newValue.value, {
shouldDirty: true,
})
@@ -80,8 +79,7 @@ const Template: ComponentStory<typeof ColumnAutocomplete> = function Template(
<ColumnAutocomplete
{...args}
name="secondReference"
label="Second Reference"
onChange={(_event, newValue) =>
onChange={(newValue) =>
form.setValue('secondReference', newValue.value, {
shouldDirty: true,
})

View File

@@ -7,6 +7,10 @@ import { setupServer } from 'msw/node';
import { afterAll, afterEach, beforeAll, test, vi } from 'vitest';
import ColumnAutocomplete from './ColumnAutocomplete';
vi.mock('@/lib/utils', () => ({
cn: (...classes: (string | undefined)[]) => classes.filter(Boolean).join(' '),
}));
const server = setupServer(
tableQuery,
hasuraMetadataQuery,
@@ -21,17 +25,9 @@ afterAll(() => {
});
test('should render a combobox', () => {
render(
<ColumnAutocomplete
schema="public"
table="books"
label="Column Autocomplete"
/>,
);
render(<ColumnAutocomplete schema="public" table="books" />);
expect(
screen.getByRole('combobox', { name: /column autocomplete/i }),
).toBeInTheDocument();
expect(screen.getByRole('combobox')).toBeInTheDocument();
});
// Note: Network requests don't go through in tests, so we can't test the

View File

@@ -1,39 +1,33 @@
import { InlineCode } from '@/components/presentational/InlineCode';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import type { AutocompleteOption } from '@/components/ui/v2/Autocomplete';
import { AutocompletePopper } from '@/components/ui/v2/Autocomplete';
import { Box } from '@/components/ui/v2/Box';
import { IconButton } from '@/components/ui/v2/IconButton';
import { ArrowLeftIcon } from '@/components/ui/v2/icons/ArrowLeftIcon';
import type { InputProps } from '@/components/ui/v2/Input';
import { Input, inputClasses } from '@/components/ui/v2/Input';
import { List } from '@/components/ui/v2/List';
import { OptionBase } from '@/components/ui/v2/Option';
import { OptionGroupBase } from '@/components/ui/v2/OptionGroup';
import { Text } from '@/components/ui/v2/Text';
import { Button, type ButtonProps } from '@/components/ui/v3/button';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/components/ui/v3/command';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/v3/popover';
import { useMetadataQuery } from '@/features/orgs/projects/database/dataGrid/hooks/useMetadataQuery';
import { useTableQuery } from '@/features/orgs/projects/database/dataGrid/hooks/useTableQuery';
import { getTruncatedText } from '@/utils/getTruncatedText';
import type { AutocompleteGroupedOption } from '@mui/base/useAutocomplete';
import { useAutocomplete } from '@mui/base/useAutocomplete';
import type { AutocompleteRenderGroupParams } from '@mui/material/Autocomplete';
import { autocompleteClasses } from '@mui/material/Autocomplete';
import type {
ChangeEvent,
ForwardedRef,
HTMLAttributes,
PropsWithoutRef,
SyntheticEvent,
} from 'react';
import { cn } from '@/lib/utils';
import { Check, ChevronLeft, ChevronsUpDown } from 'lucide-react';
import useRuleGroupEditor from '@/features/orgs/projects/database/dataGrid/components/RuleGroupEditor/useRuleGroupEditor';
import { CommandLoading } from 'cmdk';
import type { ForwardedRef } from 'react';
import { forwardRef, useEffect, useState } from 'react';
import { twMerge } from 'tailwind-merge';
import type { UseAsyncValueOptions } from './useAsyncValue';
import useAsyncValue from './useAsyncValue';
import type { UseColumnGroupsOptions } from './useColumnGroups';
import useColumnGroups from './useColumnGroups';
export interface ColumnAutocompleteProps
extends Omit<PropsWithoutRef<InputProps>, 'onChange'> {
export interface ColumnAutocompleteProps extends Omit<ButtonProps, 'onChange'> {
value?: string;
/**
* Schema where the `table` is located.
*/
@@ -45,70 +39,39 @@ export interface ColumnAutocompleteProps
/**
* Function to be called when the value changes.
*/
onChange?: (
event: SyntheticEvent,
value: {
value: string;
columnMetadata?: Record<string, any>;
disableReset?: boolean;
},
) => void;
onChange?: (value: {
value: string;
columnMetadata?: Record<string, any>;
disableReset?: boolean;
}) => void;
/**
* Function to be called when the input is asynchronously initialized.
*/
onInitialized?: UseAsyncValueOptions['onInitialized'];
/**
* Class name to be applied to the root element.
*/
rootClassName?: string;
/**
* Determines if the autocomplete should allow relationships.
*/
disableRelationships?: UseColumnGroupsOptions['disableRelationships'];
}
function renderGroup(params: AutocompleteRenderGroupParams) {
return (
<li key={params.key}>
<OptionGroupBase>{params.group}</OptionGroupBase>
<List>{params.children}</List>
</li>
);
}
function renderOption(
option: AutocompleteOption<string>,
optionProps: HTMLAttributes<HTMLLIElement>,
) {
return (
<OptionBase
{...optionProps}
className="grid grid-flow-col items-baseline justify-start justify-items-start gap-1.5"
>
<Text component="span">{option.label}</Text>
{option.group === 'columns' && (
<InlineCode>{option.metadata?.udt_name || option.value}</InlineCode>
)}
</OptionBase>
);
}
function ColumnAutocomplete(
{
rootClassName,
schema: defaultSchema,
table: defaultTable,
value: externalValue,
disableRelationships,
onChange,
onInitialized,
...props
}: ColumnAutocompleteProps,
ref: ForwardedRef<HTMLInputElement>,
ref: ForwardedRef<HTMLButtonElement>,
) {
const [open, setOpen] = useState(false);
const [value, setValue] = useState('');
const { disabled } = useRuleGroupEditor();
const [search, setSearch] = useState('');
const [activeRelationship, setActiveRelationship] = useState<{
schema: string;
table: string;
@@ -120,7 +83,6 @@ function ColumnAutocomplete(
const {
data: tableData,
status: tableStatus,
error: tableError,
isFetching: isTableFetching,
} = useTableQuery([`default.${selectedSchema}.${selectedTable}`], {
schema: selectedSchema,
@@ -132,7 +94,6 @@ function ColumnAutocomplete(
const {
data: metadata,
status: metadataStatus,
error: metadataError,
isFetching: isMetadataFetching,
} = useMetadataQuery([`default.metadata`], {
queryOptions: { refetchOnWindowFocus: false },
@@ -140,8 +101,6 @@ function ColumnAutocomplete(
const {
initialized,
inputValue,
setInputValue,
selectedColumn,
setSelectedColumn,
selectedRelationships,
@@ -159,57 +118,20 @@ function ColumnAutocomplete(
onInitialized,
});
const [pages, setPages] = useState<string[]>([]);
useEffect(() => {
setPages(
relationshipDotNotation ? [relationshipDotNotation?.split('.')[0]] : [],
);
}, [relationshipDotNotation]);
const activePage = pages[pages.length - 1];
useEffect(() => {
setActiveRelationship(asyncActiveRelationship);
}, [asyncActiveRelationship]);
function isOptionEqualToValue(
option: AutocompleteOption,
value: NonNullable<string | AutocompleteOption>,
) {
if (!value) {
return false;
}
if (typeof value === 'string') {
return option.value === value;
}
return option.value === value.value && option.custom === value.custom;
}
function handleChange(
event: SyntheticEvent,
value: NonNullable<string | AutocompleteOption>,
) {
if (typeof value === 'string' || Array.isArray(value) || !value) {
return;
}
if ('group' in value && value.group === 'columns') {
setSelectedColumn(value);
setOpen(false);
setInputValue(value.value);
onChange?.(event, {
value:
selectedRelationships.length > 0
? [relationshipDotNotation, value.value].join('.')
: value.value,
columnMetadata: value.metadata,
});
return;
}
setInputValue('');
setSelectedColumn(null);
setSelectedRelationships((currentRelationships) => [
...currentRelationships,
value.metadata?.target,
]);
}
const options = useColumnGroups({
selectedSchema,
selectedTable,
@@ -218,246 +140,214 @@ function ColumnAutocomplete(
disableRelationships,
});
const {
popupOpen,
anchorEl,
setAnchorEl,
getRootProps,
getInputLabelProps,
getInputProps,
getListboxProps,
getOptionProps,
groupedOptions,
} = useAutocomplete({
open,
inputValue,
options,
id: props?.name,
openOnFocus: !props.disabled,
disableCloseOnSelect: true,
value: selectedColumn,
onClose: () => setOpen(false),
groupBy: (option) => option.group,
isOptionEqualToValue,
onChange: handleChange,
});
const handleChange = (newValue: string) => {
const selectedOption = options.find((option) => option.value === newValue);
function handleInputValueChange(
event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) {
const { value } = event.target;
setInputValue(value);
if (!selectedOption) {
return;
}
setSelectedColumn({
value,
label: value,
metadata: selectedColumn?.metadata || {
table_schema: selectedSchema,
table_name: selectedTable,
},
});
setSelectedColumn(selectedOption);
setOpen(false);
setValue(newValue === value ? '' : newValue);
onChange?.(event, {
const valueObj = {
value:
selectedRelationships.length > 0
? [relationshipDotNotation, value].join('.')
: value,
columnMetadata: {
table_schema: selectedSchema,
table_name: selectedTable,
},
});
}
? [relationshipDotNotation, newValue].join('.')
: newValue,
columnMetadata: selectedOption.metadata,
};
onChange?.(valueObj);
};
const handleRelationshipChange = (newValue: string) => {
const selectedOption = options.find((option) => option.value === newValue);
if (!selectedOption) {
return;
}
setPages((p) => [...p, newValue]);
setSelectedColumn(null);
setSearch('');
setSelectedRelationships((currentRelationships) => [
...currentRelationships,
selectedOption.metadata?.target,
]);
};
const columns = options.filter((option) => option.group === 'columns');
const relationships = options.filter(
(option) => option.group === 'relationships',
);
const handleBackRelationship = () => {
setPages((p) => p.slice(0, -1));
setSelectedColumn(null);
setSelectedRelationships((activeRelationships) =>
activeRelationships.slice(0, -1),
);
setSearch('');
};
const buttonPrefix = relationshipDotNotation
? `${selectedTable}.${relationshipDotNotation}`
: '';
return (
<>
<div {...getRootProps()} className={rootClassName}>
<Input
{...props}
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
ref={ref}
fullWidth
slotProps={{
...(props.slotProps || {}),
label: getInputLabelProps(),
input: {
...(props.slotProps?.input || {}),
ref: setAnchorEl,
sx: [
...(Array.isArray(props.slotProps?.input?.sx)
? props.slotProps.input.sx
: [props.slotProps?.input?.sx || {}]),
{
[`& .${inputClasses.input}`]: {
backgroundColor: 'transparent',
},
},
],
},
inputRoot: {
...getInputProps(),
className: twMerge(
Boolean(selectedColumn) || Boolean(relationshipDotNotation)
? '!pl-0'
: null,
props.slotProps?.inputRoot?.className,
),
},
}}
onFocus={() => {
if (props.disabled) {
return;
}
setOpen(true);
}}
onClick={() => {
if (props.disabled) {
return;
}
setOpen(true);
}}
error={Boolean(tableError || metadataError) || props.error}
helperText={
String(tableError || metadataError || '') || props.helperText
}
onChange={handleInputValueChange}
value={inputValue}
startAdornment={
selectedColumn || relationshipDotNotation ? (
<Text
component="span"
sx={{
color: props.disabled ? 'text.disabled' : 'text.primary',
}}
className="!ml-2 flex-shrink-0 truncate lg:max-w-[200px]"
>
<Text component="span" color="disabled">
{selectedTable}
</Text>
.
{relationshipDotNotation && (
<>
<span className="hidden lg:inline">
{getTruncatedText(relationshipDotNotation, 15, 'end')}.
</span>
<span className="inline lg:hidden">
{getTruncatedText(relationshipDotNotation, 35, 'end')}.
</span>
</>
)}
</Text>
) : null
}
endAdornment={
tableStatus === 'loading' ||
metadataStatus === 'loading' ||
!initialized ? (
<ActivityIndicator className="mr-2" delay={500} />
) : null
}
/>
</div>
<AutocompletePopper
onMouseDown={(event) => event.preventDefault()}
modifiers={[{ name: 'offset', options: { offset: [0, 10] } }]}
placement="bottom-start"
open={popupOpen}
anchorEl={anchorEl}
style={{ width: anchorEl?.parentElement?.clientWidth }}
disabled={disabled}
variant="outline"
role="combobox"
aria-expanded={open}
className="justify-between"
>
{buttonPrefix ? (
<div className="flex flex-shrink-0 gap-0 truncate">
<span className="flex-shrink-0 truncate text-sm text-muted-foreground lg:max-w-[200px]">
{buttonPrefix}.
</span>
{selectedColumn?.label}
</div>
) : (
selectedColumn?.label || 'Select a column'
)}
<ChevronsUpDown className="ml-2 h-5 w-5 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent
side="bottom"
align="start"
className="max-h-[var(--radix-popover-content-available-height)] w-[var(--radix-popover-trigger-width)] p-0"
>
<Box
className={autocompleteClasses.paper}
sx={{
borderWidth: (theme) => (theme.palette.mode === 'dark' ? 1 : 0),
borderColor: (theme) =>
theme.palette.mode === 'dark' ? 'grey.400' : 'none',
<Command
onKeyDown={(e) => {
if (e.key === 'Escape' || (e.key === 'Backspace' && !search)) {
e.preventDefault();
setPages((p) => p.slice(0, -1));
setSelectedColumn(null);
setSelectedRelationships((activeRelationships) =>
activeRelationships.slice(0, -1),
);
}
}}
>
<Box
className="grid grid-flow-col items-center justify-start gap-2 border-b-1 px-3 py-2.5"
sx={{ backgroundColor: 'transparent' }}
>
{selectedRelationships.length > 0 && (
<IconButton
variant="borderless"
color="secondary"
onClick={(event) => {
event.stopPropagation();
setInputValue('');
setSelectedColumn(null);
setSelectedRelationships((activeRelationships) =>
activeRelationships.slice(0, -1),
);
}}
<CommandInput
value={search}
onValueChange={setSearch}
autoFocus
placeholder=""
prefix={
relationshipDotNotation
? `
${selectedTable}.${relationshipDotNotation}.`
: ``
}
/>
{pages?.length > 0 ? (
<div className="flex flex-row items-center gap-2 px-2 py-1.5">
<Button
variant="outline"
size="icon"
className="h-8 w-8"
onClick={handleBackRelationship}
>
<ArrowLeftIcon className="h-4 w-4" />
</IconButton>
)}
<Text className="direction-rtl truncate text-left">
<Text component="span" color="disabled">
{defaultTable}
</Text>
{relationshipDotNotation && (
<>
<span className="hidden lg:inline">
.{getTruncatedText(relationshipDotNotation, 20, 'start')}
</span>
<span className="inline lg:hidden">
.{relationshipDotNotation}
</span>
</>
)}
</Text>
</Box>
{(tableStatus === 'loading' ||
metadataStatus === 'loading' ||
!initialized) && (
<div className="p-2">
<ActivityIndicator label="Loading..." />
<ChevronLeft className="h-5 w-5" />
</Button>
<span className="py-1.5 text-sm text-muted-foreground">
{defaultTable}.{pages.join('.')}
</span>
</div>
)}
{groupedOptions.length > 0 && (
<List
{...getListboxProps()}
className={autocompleteClasses.listbox}
>
{(
groupedOptions as AutocompleteGroupedOption<
(typeof options)[number]
>[]
).map((optionGroup) =>
renderGroup({
key: `${optionGroup.key}`,
group: optionGroup.group,
children: optionGroup.options.map((option, index) =>
renderOption(
option,
getOptionProps({
option,
index: optionGroup.index + index,
}),
),
),
}),
)}
</List>
)}
{groupedOptions.length === 0 && Boolean(anchorEl) && (
<Text className={autocompleteClasses.noOptions}>No options</Text>
)}
</Box>
</AutocompletePopper>
</>
) : null}
<CommandList>
{!activePage && (
<>
<CommandEmpty>No options found.</CommandEmpty>
{tableStatus === 'loading' ||
metadataStatus === 'loading' ||
(!initialized && <CommandLoading>Loading...</CommandLoading>)}
<CommandGroup heading="columns">
{columns.map((option) => (
<CommandItem
key={option.value}
value={option.value}
onSelect={handleChange}
className="overflow-x-hidden"
>
<Check
className={cn(
'mr-2 h-4 w-4',
value === option.value ? 'opacity-100' : 'opacity-0',
)}
/>
<div className="flex gap-3">
<span className="line-clamp-2 break-all">
{option.label}
</span>
<div className="flex items-center">
<code className="relative rounded bg-primary px-[0.2rem] font-mono text-white">
{option.metadata?.udt_name || option.value}
</code>
</div>
</div>
</CommandItem>
))}
</CommandGroup>
{relationships.length > 0 && !disableRelationships && (
<CommandGroup heading="relationships">
{relationships.map((option) => (
<CommandItem
key={option.value}
value={option.value}
onSelect={handleRelationshipChange}
>
{option.label}
</CommandItem>
))}
</CommandGroup>
)}
</>
)}
{activePage && (
<>
<CommandEmpty>No options found.</CommandEmpty>
<CommandGroup heading="columns">
{columns.map((option) => (
<CommandItem
key={option.value}
value={option.value}
onSelect={handleChange}
>
<Check
className={cn(
'mr-2 h-4 w-4',
value === option.value ? 'opacity-100' : 'opacity-0',
)}
/>
<div className="flex gap-3">
<span className="line-clamp-2 break-all">
{option.label}
</span>
<div className="flex items-center">
<code className="relative rounded bg-primary px-[0.2rem] font-mono text-white">
{option.metadata?.udt_name || option.value}
</code>
</div>
</div>
</CommandItem>
))}
</CommandGroup>
</>
)}
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}

View File

@@ -53,7 +53,6 @@ export default function useAsyncValue({
onInitialized,
}: UseAsyncValueOptions) {
const currentTablePath = `${selectedSchema}.${selectedTable}`;
const [inputValue, setInputValue] = useState('');
const [initialized, setInitialized] = useState(false);
// We might not going to have the most up-to-date table data because the
// relationship is loaded asynchronously, so we need to make sure we don't
@@ -131,7 +130,6 @@ export default function useAsyncValue({
),
});
setRemainingColumnPath((columnPath) => columnPath.slice(1));
setInputValue(activeColumn);
}, [
remainingColumnPath,
isTableLoading,
@@ -287,8 +285,6 @@ export default function useAsyncValue({
return {
initialized,
inputValue,
setInputValue,
activeRelationship,
selectedRelationships: initialized ? selectedRelationships : [],
selectedColumn: initialized ? selectedColumn : null,

View File

@@ -1,10 +1,4 @@
import { useDialog } from '@/components/common/DialogProvider';
import type { DataGridProps } from '@/components/dataGrid/DataGrid';
import { DataGrid } from '@/components/dataGrid/DataGrid';
import { DataGridBooleanCell } from '@/components/dataGrid/DataGridBooleanCell';
import { DataGridDateCell } from '@/components/dataGrid/DataGridDateCell';
import { DataGridNumericCell } from '@/components/dataGrid/DataGridNumericCell';
import { DataGridTextCell } from '@/components/dataGrid/DataGridTextCell';
import { FormActivityIndicator } from '@/components/form/FormActivityIndicator';
import { InlineCode } from '@/components/presentational/InlineCode';
import { KeyIcon } from '@/components/ui/v2/icons/KeyIcon';
@@ -23,11 +17,19 @@ import { normalizeDefaultValue } from '@/features/orgs/projects/database/dataGri
import {
POSTGRESQL_CHARACTER_TYPES,
POSTGRESQL_DATE_TIME_TYPES,
POSTGRESQL_DECIMAL_TYPES,
POSTGRESQL_INTEGER_TYPES,
POSTGRESQL_JSON_TYPES,
POSTGRESQL_NUMERIC_TYPES,
} from '@/features/orgs/projects/database/dataGrid/utils/postgresqlConstants';
import { isSchemaLocked } from '@/features/orgs/projects/database/dataGrid/utils/schemaHelpers';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import type { DataGridProps } from '@/features/orgs/projects/storage/dataGrid/components/DataGrid';
import { DataGrid } from '@/features/orgs/projects/storage/dataGrid/components/DataGrid';
import { DataGridBooleanCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridBooleanCell';
import { DataGridDateCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridDateCell';
import { DataGridDecimalCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridDecimalCell';
import { DataGridIntegerCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridIntegerCell';
import { DataGridTextCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridTextCell';
import { useQueryClient } from '@tanstack/react-query';
import dynamic from 'next/dynamic';
import { useRouter } from 'next/router';
@@ -68,10 +70,10 @@ export function createDataGridColumn(
const defaultColumnConfiguration = {
Header: () => (
<div className="grid items-center justify-start grid-flow-col gap-1 font-normal">
<div className="grid grid-flow-col items-center justify-start gap-1 font-normal">
{column.is_primary && <KeyIcon className="text-sm" />}
<span className="font-bold truncate" title={column.column_name}>
<span className="truncate font-bold" title={column.column_name}>
{column.column_name}
</span>
@@ -104,12 +106,21 @@ export function createDataGridColumn(
foreignKeyRelation: column.foreign_key_relation,
};
if (POSTGRESQL_NUMERIC_TYPES.includes(column.data_type)) {
if (POSTGRESQL_INTEGER_TYPES.includes(column.data_type)) {
return {
...defaultColumnConfiguration,
type: 'number',
width: 200,
Cell: DataGridNumericCell,
Cell: DataGridIntegerCell,
};
}
if (POSTGRESQL_DECIMAL_TYPES.includes(column.data_type)) {
return {
...defaultColumnConfiguration,
type: 'text',
width: 200,
Cell: DataGridDecimalCell,
};
}

View File

@@ -1,7 +1,4 @@
import { useDialog } from '@/components/common/DialogProvider';
import { useDataGridConfig } from '@/components/dataGrid/DataGridConfigProvider';
import type { DataGridPaginationProps } from '@/components/dataGrid/DataGridPagination';
import { DataGridPagination } from '@/components/dataGrid/DataGridPagination';
import type { BoxProps } from '@/components/ui/v2/Box';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
@@ -13,6 +10,9 @@ import { RowIcon } from '@/components/ui/v2/icons/RowIcon';
import { useDeleteRecordMutation } from '@/features/orgs/projects/database/dataGrid/hooks/useDeleteRecordMutation';
import type { DataBrowserGridColumn } from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { useDataGridConfig } from '@/features/orgs/projects/storage/dataGrid/components/DataGridConfigProvider';
import type { DataGridPaginationProps } from '@/features/orgs/projects/storage/dataGrid/components/DataGridPagination';
import { DataGridPagination } from '@/features/orgs/projects/storage/dataGrid/components/DataGridPagination';
import { triggerToast } from '@/utils/toast';
import { useQueryClient } from '@tanstack/react-query';
import { useState } from 'react';
@@ -120,7 +120,7 @@ export default function DataBrowserGridControls({
)}
>
{numberOfSelectedRows > 0 && (
<div className="grid items-center grid-flow-col gap-2 place-content-start">
<div className="grid grid-flow-col place-content-start items-center gap-2">
<Chip
size="small"
color="info"
@@ -161,7 +161,7 @@ export default function DataBrowserGridControls({
)}
{numberOfSelectedRows === 0 && (
<div className="grid items-center grid-flow-col col-span-6 gap-2">
<div className="col-span-6 grid grid-flow-col items-center gap-2">
{columns.length > 0 && (
<DataGridPagination
className={twMerge(
@@ -177,7 +177,7 @@ export default function DataBrowserGridControls({
<Dropdown.Root>
<Dropdown.Trigger asChild hideChevron>
<Button
startIcon={<PlusIcon className="w-4 h-4" />}
startIcon={<PlusIcon className="h-4 w-4" />}
size="small"
>
Insert

View File

@@ -209,7 +209,6 @@ export default function DatabaseRecordInputGroup({
autoFocus={index === 0 && autoFocusFirstInput}
slotProps={{
label: commonLabelProps,
inputRoot: { step: 1 },
}}
/>
);

View File

@@ -326,10 +326,10 @@ export default function RolePermissionEditorForm({
return (
<FormProvider {...form}>
{error && error instanceof Error && (
<div className="px-6 mb-4 -mt-3">
<div className="-mt-3 mb-4 px-6">
<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> {error.message}
@@ -349,13 +349,13 @@ export default function RolePermissionEditorForm({
<Form
onSubmit={handleSubmit}
className="flex flex-col content-between flex-auto overflow-hidden border-t-1"
className="flex flex-auto flex-col content-between overflow-hidden border-t-1"
sx={{ backgroundColor: 'background.default' }}
>
<div className="grid content-start flex-auto grid-flow-row gap-6 py-4 overflow-auto">
<div className="grid flex-auto grid-flow-row content-start gap-6 overflow-auto py-4">
<PermissionSettingsSection
title="Selected role & action"
className="justify-between grid-flow-col"
className="grid-flow-col justify-between"
>
<div className="grid grid-flow-col gap-4">
<Text>
@@ -408,7 +408,7 @@ export default function RolePermissionEditorForm({
{action !== 'select' && <BackendOnlySection disabled={disabled} />}
</div>
<Box className="grid flex-shrink-0 gap-2 p-2 border-t-1 sm:grid-flow-col sm:justify-between">
<Box className="grid flex-shrink-0 gap-2 border-t-1 p-2 sm:grid-flow-col sm:justify-between">
<Button
variant="borderless"
color="secondary"

View File

@@ -18,21 +18,7 @@ const ruleSchema = Yup.object().shape({
});
const ruleGroupSchema = Yup.object().shape({
operator: Yup.string().test(
'operator',
'Please select an operator.',
(selectedOperator, ctx) => {
// `from` is part of the Yup API, but it's not typed.
// @ts-ignore
const [, { value }] = ctx.from;
if (Object.keys(value.filter).length > 0 && !selectedOperator) {
return false;
}
return true;
},
),
operator: Yup.string().required('Please select an operator.'),
rules: Yup.array().of(ruleSchema),
groups: Yup.array().of(Yup.lazy(() => ruleGroupSchema) as any),
});

View File

@@ -0,0 +1,135 @@
import { Check, ChevronsUpDown } from 'lucide-react';
import { Button } from '@/components/ui/v3/button';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/components/ui/v3/command';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/v3/popover';
import type { HasuraOperator } from '@/features/database/dataGrid/types/dataBrowser';
import { cn } from '@/lib/utils';
import { useState } from 'react';
import { useFormContext } from 'react-hook-form';
const commonOperators: {
value: HasuraOperator;
label?: string;
helperText?: string;
}[] = [
{ value: '_eq', helperText: 'equal' },
{ value: '_neq', helperText: 'not equal' },
{ value: '_in', helperText: 'in (array)' },
{ value: '_nin', helperText: 'not in (array)' },
{ value: '_gt', helperText: 'greater than' },
{ value: '_lt', helperText: 'lower than' },
{ value: '_gte', helperText: 'greater than or equal' },
{ value: '_lte', helperText: 'lower than or equal' },
{ value: '_ceq', helperText: 'equal to column' },
{ value: '_cne', helperText: 'not equal to column' },
{ value: '_cgt', helperText: 'greater than column' },
{ value: '_clt', helperText: 'lower than column' },
{ value: '_cgte', helperText: 'greater than or equal to column' },
{ value: '_clte', helperText: 'lower than or equal to column' },
{ value: '_is_null', helperText: 'null' },
];
const textSpecificOperators: typeof commonOperators = [
{ value: '_like', helperText: 'like' },
{ value: '_nlike', helperText: 'not like' },
{ value: '_ilike', helperText: 'like (case-insensitive)' },
{ value: '_nilike', helperText: 'not like (case-insensitive)' },
{ value: '_similar', helperText: 'similar' },
{ value: '_nsimilar', helperText: 'not similar' },
{ value: '_regex', helperText: 'matches regex' },
{ value: '_nregex', helperText: `doesn't match regex` },
{ value: '_iregex', helperText: 'matches case-insensitive regex' },
{ value: '_niregex', helperText: `doesn't match case-insensitive regex` },
];
interface OperatorComboBoxProps {
name: string;
disabled?: boolean;
selectedColumnType?: string;
}
export default function OperatorComboBox({
name,
disabled,
selectedColumnType,
}: OperatorComboBoxProps) {
const [open, setOpen] = useState(false);
const { watch, setValue } = useFormContext();
const operator = watch(`${name}.operator`);
const availableOperators = [
...commonOperators,
...(selectedColumnType === 'text' ? textSpecificOperators : []),
];
const handleSelect = (value: string) => {
if (['_in', '_nin'].includes(value)) {
setValue(`${name}.value`, [], { shouldDirty: true });
}
setValue(`${name}.operator`, value, { shouldDirty: true });
setOpen(false);
};
return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
disabled={disabled}
variant="outline"
role="combobox"
aria-expanded={open}
className="justify-between"
>
{operator ?? 'Select operator...'}
<ChevronsUpDown className="h-5 w-5 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent side="bottom" align="start" className="p-0">
<Command>
<CommandInput placeholder="Search operator..." />
<CommandList>
<CommandEmpty>No operator found.</CommandEmpty>
<CommandGroup>
{availableOperators.map((op) => (
<CommandItem
key={op.value}
keywords={[op.helperText]}
value={op.value}
onSelect={handleSelect}
className="flex flex-row justify-between"
>
<div className="flex flex-row gap-2">
<span className="min-w-[9ch]">{op.value}</span>
<span className="text-muted-foreground">
{op.helperText}
</span>
</div>
<Check
className={cn(
'ml-auto',
op.value === operator ? 'opacity-100' : 'opacity-0',
)}
/>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}

View File

@@ -1,12 +1,9 @@
import { ControlledSelect } from '@/components/form/ControlledSelect';
import { Option } from '@/components/ui/v2/Option';
import { Text } from '@/components/ui/v2/Text';
import { ColumnAutocomplete } from '@/features/orgs/projects/database/dataGrid/components/ColumnAutocomplete';
import type { HasuraOperator } from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
import type { DetailedHTMLProps, HTMLProps } from 'react';
import { useState } from 'react';
import { useController, useFormContext } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
import OperatorComboBox from './OperatorComboBox';
import RuleRemoveButton from './RuleRemoveButton';
import RuleValueInput from './RuleValueInput';
import useRuleGroupEditor from './useRuleGroupEditor';
@@ -25,69 +22,6 @@ export interface RuleEditorRowProps
* Function to be called when the remove button is clicked.
*/
onRemove?: VoidFunction;
/**
* List of operators to be disabled for the rule editor.
*
* @default []
*/
disabledOperators?: HasuraOperator[];
}
const commonOperators: {
value: HasuraOperator;
label?: string;
helperText?: string;
}[] = [
{ value: '_eq', helperText: 'equal' },
{ value: '_neq', helperText: 'not equal' },
{ value: '_in_hasura', label: '_in', helperText: 'in (X-Hasura-)' },
{ value: '_in', helperText: 'in (array)' },
{ value: '_nin_hasura', label: '_nin', helperText: 'not in (X-Hasura-)' },
{ value: '_nin', helperText: 'not in (array)' },
{ value: '_gt', helperText: 'greater than' },
{ value: '_lt', helperText: 'lower than' },
{ value: '_gte', helperText: 'greater than or equal' },
{ value: '_lte', helperText: 'lower than or equal' },
{ value: '_ceq', helperText: 'equal to column' },
{ value: '_cne', helperText: 'not equal to column' },
{ value: '_cgt', helperText: 'greater than column' },
{ value: '_clt', helperText: 'lower than column' },
{ value: '_cgte', helperText: 'greater than or equal to column' },
{ value: '_clte', helperText: 'lower than or equal to column' },
{ value: '_is_null', helperText: 'null' },
];
const textSpecificOperators: typeof commonOperators = [
{ value: '_like', helperText: 'like' },
{ value: '_nlike', helperText: 'not like' },
{ value: '_ilike', helperText: 'like (case-insensitive)' },
{ value: '_nilike', helperText: 'not like (case-insensitive)' },
{ value: '_similar', helperText: 'similar' },
{ value: '_nsimilar', helperText: 'not similar' },
{ value: '_regex', helperText: 'matches regex' },
{ value: '_nregex', helperText: `doesn't match regex` },
{ value: '_iregex', helperText: 'matches case-insensitive regex' },
{ value: '_niregex', helperText: `doesn't match case-insensitive regex` },
];
function renderOption({
value,
label,
helperText,
}: (typeof commonOperators)[number]) {
return (
<Option key={value} value={value} className="grid grid-flow-col gap-2">
<Text component="span" className="inline-block w-16">
{label || value}
</Text>
{helperText && (
<Text component="span" color="disabled">
{helperText}
</Text>
)}
</Option>
);
}
export default function RuleEditorRow({
@@ -95,17 +29,12 @@ export default function RuleEditorRow({
index,
onRemove,
className,
disabledOperators = [],
...props
}: RuleEditorRowProps) {
const { schema, table, disabled } = useRuleGroupEditor();
const { control, setValue, getFieldState } = useFormContext();
const { schema, table } = useRuleGroupEditor();
const { control, setValue } = useFormContext();
const rowName = `${name}.rules.${index}`;
const columnState = getFieldState(`${rowName}.column`);
const operatorState = getFieldState(`${rowName}.operator`);
const valueState = getFieldState(`${rowName}.value`);
const [selectedTablePath, setSelectedTablePath] = useState<string>('');
const [selectedColumnType, setSelectedColumnType] = useState<string>('');
const { field: autocompleteField } = useController({
@@ -113,48 +42,19 @@ export default function RuleEditorRow({
control,
});
const disabledOperatorMap = disabledOperators.reduce(
(map, currentOperator) => map.set(currentOperator, true),
new Map<string, boolean>(),
);
const availableOperators = [
...commonOperators.filter(({ value }) => !disabledOperatorMap.has(value)),
...(selectedColumnType === 'text'
? textSpecificOperators.filter(
({ value }) => !disabledOperatorMap.get(value),
)
: []),
];
return (
<div
className={twMerge(
'grid grid-flow-row space-y-1 lg:max-h-10 lg:grid-cols-[320px_140px_minmax(100px,_1fr)_40px] lg:space-y-0',
'flex flex-col gap-1 space-y-1 overflow-x-hidden pb-4 xl:grid xl:grid-flow-row xl:grid-cols-[320px_140px_minmax(100px,_1fr)_40px] xl:space-y-0 xl:overflow-x-visible',
className,
)}
{...props}
>
<ColumnAutocomplete
{...autocompleteField}
disabled={disabled}
schema={schema}
table={table}
rootClassName="h-10"
slotProps={{
input: {
className: 'lg:!rounded-r-none',
sx: !disabled
? {
backgroundColor: (theme) =>
theme.palette.mode === 'dark' ? 'grey.300' : 'common.white',
}
: undefined,
},
}}
fullWidth
error={Boolean(columnState?.error?.message)}
onChange={(_event, { value, columnMetadata, disableReset }) => {
onChange={({ value, columnMetadata, disableReset }) => {
setSelectedTablePath(
`${columnMetadata.table_schema}.${columnMetadata.table_name}`,
);
@@ -182,69 +82,21 @@ export default function RuleEditorRow({
});
}}
/>
<ControlledSelect
disabled={disabled}
name={`${rowName}.operator`}
className="h-10"
slotProps={{
root: {
className: 'lg:!rounded-none',
sx: !disabled
? {
backgroundColor: (theme) =>
theme.palette.mode === 'dark'
? `${theme.palette.grey[300]} !important`
: `${theme.palette.common.white} !important`,
}
: {},
},
listbox: { className: 'max-h-[300px]' },
popper: { disablePortal: false, className: 'z-[10000]' },
}}
fullWidth
error={Boolean(operatorState?.error?.message)}
onChange={(_event, value: HasuraOperator) => {
if (!['_in', '_nin', '_in_hasura', '_nin_hasura'].includes(value)) {
return;
}
if (value === '_in_hasura' || value === '_nin_hasura') {
setValue(`${rowName}.value`, null, {
shouldDirty: true,
});
return;
}
setValue(`${rowName}.value`, [], { shouldDirty: true });
}}
renderValue={(option) => {
if (!option?.value) {
return <span />;
}
if (option.value === '_in_hasura') {
return <span>_in</span>;
}
if (option.value === '_nin_hasura') {
return <span>_nin</span>;
}
return <span>{option.value}</span>;
}}
>
{availableOperators.map(renderOption)}
</ControlledSelect>
<OperatorComboBox
name={rowName}
selectedColumnType={selectedColumnType}
/>
<RuleValueInput
selectedTablePath={selectedTablePath}
name={rowName}
error={Boolean(valueState?.error?.message)}
className="min-h-10"
/>
<RuleRemoveButton onRemove={onRemove} name={name} disabled={disabled} />
<RuleRemoveButton
className="w-full xl:w-auto"
onRemove={onRemove}
name={name}
/>
</div>
);
}

View File

@@ -1,9 +1,14 @@
import { ControlledSelect } from '@/components/form/ControlledSelect';
import { Option } from '@/components/ui/v2/Option';
import { Text } from '@/components/ui/v2/Text';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/v3/select';
import type { RuleGroup } from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
import type { DetailedHTMLProps, HTMLProps } from 'react';
import { useWatch } from 'react-hook-form';
import { useFormContext, useWatch } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
import useRuleGroupEditor from './useRuleGroupEditor';
@@ -32,9 +37,11 @@ export default function RuleGroupControls({
...props
}: RuleGroupControlsProps) {
const { disabled } = useRuleGroupEditor();
const inputName = `${name}.operator`;
const currentOperator: RuleGroup['operator'] = useWatch({
name: `${name}.operator`,
name: inputName,
});
const { setValue } = useFormContext();
return (
<div
@@ -42,24 +49,26 @@ export default function RuleGroupControls({
{...props}
>
{showSelect ? (
<ControlledSelect
<Select
disabled={disabled}
name={`${name}.operator`}
slotProps={{
root: {
sx: {
backgroundColor: (theme) =>
theme.palette.mode === 'dark'
? `${theme.palette.grey[300]} !important`
: `${theme.palette.common.white} !important`,
},
},
name={inputName}
onValueChange={(newValue: string) => {
setValue(inputName, newValue, { shouldDirty: true });
}}
fullWidth
defaultValue={currentOperator}
>
<Option value="_and">and</Option>
<Option value="_or">or</Option>
</ControlledSelect>
<SelectTrigger className="border hover:bg-accent hover:text-accent-foreground focus:ring-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="_and">
<span className="font-medium">and</span>
</SelectItem>
<SelectItem value="_or">
<span className="font-medium">or</span>
</SelectItem>
</SelectContent>
</Select>
) : (
<Text className="p-2 !font-medium">
{operatorDictionary[currentOperator]}

View File

@@ -89,9 +89,3 @@ const Template: ComponentStory<typeof RuleGroupEditor> = function Template(
export const Default = Template.bind({});
Default.args = {};
Default.parameters = defaultParameters;
export const DisabledOperators = Template.bind({});
DisabledOperators.args = {
disabledOperators: ['_in_hasura', '_nin_hasura', '_is_null'],
};
DisabledOperators.parameters = defaultParameters;

View File

@@ -5,23 +5,20 @@ import { Button } from '@/components/ui/v2/Button';
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import { Link } from '@/components/ui/v2/Link';
import { Text } from '@/components/ui/v2/Text';
import { generateAppServiceUrl } from '@/features/orgs/projects/common/utils/generateAppServiceUrl';
import type {
Rule,
RuleGroup,
} from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { useMemo } from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
import type { RuleEditorRowProps } from './RuleEditorRow';
import RuleEditorRow from './RuleEditorRow';
import RuleGroupControls from './RuleGroupControls';
import { RuleGroupEditorContext } from './useRuleGroupEditor';
export interface RuleGroupEditorProps
extends BoxProps,
Pick<RuleEditorRowProps, 'disabledOperators'> {
export interface RuleGroupEditorProps extends BoxProps {
/**
* Determines whether or not the rule group editor is disabled.
*/
@@ -63,7 +60,6 @@ export default function RuleGroupEditor({
name,
className,
disableRemove,
disabledOperators = [],
depth = 0,
maxDepth,
schema,
@@ -72,7 +68,7 @@ export default function RuleGroupEditor({
sx,
...props
}: RuleGroupEditorProps) {
const { currentProject } = useCurrentWorkspaceAndProject();
const { project } = useProject();
const form = useFormContext();
const { control, getValues } = form;
@@ -115,7 +111,7 @@ export default function RuleGroupEditor({
<Box
{...props}
className={twMerge(
'rounded-lg border border-r-8 border-transparent pl-2',
'flex min-h-44 flex-col justify-between rounded-lg border border-r-8 border-transparent pl-2',
className,
)}
sx={[
@@ -147,7 +143,6 @@ export default function RuleGroupEditor({
name={name}
index={ruleIndex}
onRemove={() => removeRule(ruleIndex)}
disabledOperators={disabledOperators}
/>
</div>
))}
@@ -177,7 +172,6 @@ export default function RuleGroupEditor({
table={table}
onRemove={() => removeGroup(ruleGroupIndex)}
disableRemove={rules.length === 0 && groups.length === 1}
disabledOperators={disabledOperators}
name={`${name}.groups.${ruleGroupIndex}`}
depth={depth + 1}
disabled={disabled}
@@ -191,13 +185,13 @@ export default function RuleGroupEditor({
<Text>
This rule group contains one or more objects (e.g: _exists) that
are not supported by our dashboard yet.{' '}
{currentProject && (
{project && (
<span>
Please{' '}
<Link
href={`${generateAppServiceUrl(
currentProject.subdomain,
currentProject.region,
project.subdomain,
project.region,
'hasura',
)}/console/data/default/schema/${schema}/tables/${table}/permissions`}
underline="hover"
@@ -247,7 +241,7 @@ export default function RuleGroupEditor({
{onRemove && (
<Button
variant="borderless"
color="secondary"
color="error"
onClick={onRemove}
disabled={disableRemove}
>

View File

@@ -1,10 +1,9 @@
import type { ButtonProps } from '@/components/ui/v2/Button';
import { Button } from '@/components/ui/v2/Button';
import { XIcon } from '@/components/ui/v2/icons/XIcon';
import { Button, type ButtonProps } from '@/components/ui/v3/button';
import type {
Rule,
RuleGroup,
} from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
import { X } from 'lucide-react';
import { useWatch } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
@@ -34,9 +33,9 @@ function RuleRemoveButton({
return (
<Button
variant="outlined"
color="secondary"
className={twMerge('h-10 !min-w-0 lg:!rounded-l-none', className)}
variant="outline"
size="icon"
className={twMerge('h-10 !min-w-0', className)}
disabled={
disabled ||
(rules.length === 1 && !groups?.length && !unsupported?.length)
@@ -44,18 +43,8 @@ function RuleRemoveButton({
{...props}
aria-label="Remove Rule"
onClick={onRemove}
sx={
!disabled
? {
backgroundColor: (theme) =>
theme.palette.mode === 'dark'
? `${theme.palette.grey[300]} !important`
: `${theme.palette.common.white} !important`,
}
: undefined
}
>
<XIcon className="!h-4 !w-4" />
<X className="h-4 w-4" />
</Button>
);
}

View File

@@ -1,17 +1,41 @@
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
import { ControlledSelect } from '@/components/form/ControlledSelect';
import { ReadOnlyToggle } from '@/components/presentational/ReadOnlyToggle';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import type { AutocompleteOption } from '@/components/ui/v2/Autocomplete';
import type { InputProps } from '@/components/ui/v2/Input';
import { inputClasses } from '@/components/ui/v2/Input';
import { Option } from '@/components/ui/v2/Option';
import type { ColumnAutocompleteProps } from '@/features/orgs/projects/database/dataGrid/components/ColumnAutocomplete';
import { ColumnAutocomplete } from '@/features/orgs/projects/database/dataGrid/components/ColumnAutocomplete';
import { Check, ChevronsUpDown } from 'lucide-react';
import { Button } from '@/components/ui/v3/button';
import {
Command,
CommandCreateItem,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/components/ui/v3/command';
import { FancyMultiSelect } from '@/components/ui/v3/fancy-multi-select';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/v3/popover';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/v3/select';
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
import {
ColumnAutocomplete,
type ColumnAutocompleteProps,
} from '@/features/orgs/projects/database/dataGrid/components/ColumnAutocomplete';
import type { HasuraOperator } from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useLocalMimirClient } from '@/features/orgs/projects/hooks/useLocalMimirClient';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { getAllPermissionVariables } from '@/features/projects/permissions/settings/utils/getAllPermissionVariables';
import { cn } from '@/lib/utils';
import { useGetRolesPermissionsQuery } from '@/utils/__generated__/graphql';
import { CommandLoading } from 'cmdk';
import { useState } from 'react';
import { useController, useFormContext, useWatch } from 'react-hook-form';
import useRuleGroupEditor from './useRuleGroupEditor';
@@ -41,23 +65,7 @@ function ColumnSelectorInput({
schema={schema}
table={table}
disableRelationships
slotProps={{
input: {
className: 'lg:!rounded-none !z-10',
sx: !disabled
? {
backgroundColor: (theme) =>
theme.palette.mode === 'dark'
? theme.palette.grey[300]
: theme.palette.common.white,
[`& .${inputClasses.input}`]: {
backgroundColor: 'transparent',
},
}
: undefined,
},
}}
onChange={(_event, { value }) => {
onChange={({ value }) => {
if (selectedTablePath === `${schema}.${table}`) {
setValue(name, [value], { shouldDirty: true });
return;
@@ -75,113 +83,92 @@ export interface RuleValueInputProps {
* Name of the parent group editor.
*/
name: string;
/**
* Class name to apply to the input wrapper.
*/
className?: string;
/**
* Path of the table selected through the column input.
*/
selectedTablePath?: string;
/**
* Whether the input should be marked as invalid.
*/
error?: InputProps['error'];
/**
* Helper text to display below the input.
*/
helperText?: InputProps['helperText'];
}
export default function RuleValueInput({
name,
selectedTablePath,
error,
helperText,
className,
}: RuleValueInputProps) {
const { schema, table, disabled } = useRuleGroupEditor();
const { currentProject } = useCurrentWorkspaceAndProject();
const { setValue } = useFormContext();
const { project } = useProject();
const { setValue, control } = useFormContext();
const inputName = `${name}.value`;
const operator: HasuraOperator = useWatch({ name: `${name}.operator` });
const isHasuraInput = operator === '_in_hasura' || operator === '_nin_hasura';
const sharedInputSx: InputProps['sx'] = !disabled
? {
backgroundColor: (theme) =>
theme.palette.mode === 'dark'
? theme.palette.grey[300]
: theme.palette.common.white,
[`& .${inputClasses.input}`]: {
backgroundColor: 'transparent',
},
}
: undefined;
const { field } = useController({
name: inputName,
control,
});
const {
data,
loading,
error: customClaimsError,
} = useGetRolesPermissionsQuery({
variables: { appId: currentProject?.id },
skip: !isHasuraInput || !currentProject?.id,
const [open, setOpen] = useState(false);
const comboboxValue = useWatch({ name: inputName });
const operator: HasuraOperator = useWatch({ name: `${name}.operator` });
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { data, loading } = useGetRolesPermissionsQuery({
variables: { appId: project?.id },
skip: !project?.id,
...(!isPlatform ? { client: localMimirClient } : {}),
});
if (operator === '_is_null') {
const defaultValue = !Array.isArray(comboboxValue) ? comboboxValue : null;
return (
<ControlledSelect
<Select
disabled={disabled}
name={inputName}
fullWidth
slotProps={{
root: {
className: 'lg:!rounded-none h-10',
sx: !disabled
? {
backgroundColor: (theme) =>
theme.palette.mode === 'dark'
? `${theme.palette.grey[300]} !important`
: `${theme.palette.common.white} !important`,
}
: null,
},
popper: { disablePortal: false, className: 'z-[10000]' },
onValueChange={(newValue: string) => {
setValue(inputName, newValue, { shouldDirty: true });
}}
error={error}
helperText={helperText}
defaultValue={defaultValue}
>
<Option value="true">
<ReadOnlyToggle
checked
slotProps={{ label: { className: '!text-sm' } }}
/>
</Option>
<Option value="false">
<ReadOnlyToggle
checked={false}
slotProps={{ label: { className: '!text-sm' } }}
/>
</Option>
</ControlledSelect>
<SelectTrigger className="border hover:bg-accent hover:text-accent-foreground focus:ring-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2">
<SelectValue placeholder="Is null?" />
</SelectTrigger>
<SelectContent>
<SelectItem value="true">
<span className="font-medium">true</span>
</SelectItem>
<SelectItem value="false">
<span className="font-medium">false</span>
</SelectItem>
</SelectContent>
</Select>
);
}
const availableHasuraPermissionVariables = getAllPermissionVariables(
data?.config?.auth?.session?.accessToken?.customClaims,
).map(({ key }) => ({
value: `X-Hasura-${key}`,
label: `X-Hasura-${key}`,
group: 'Frequently used',
}));
if (operator === '_in' || operator === '_nin') {
const defaultValue = Array.isArray(field.value) ? field.value : [];
return (
<ControlledAutocomplete
disabled={disabled}
name={inputName}
multiple
freeSolo
limitTags={3}
slotProps={{
input: {
className: 'lg:!rounded-none !z-10',
sx: sharedInputSx,
},
paper: { className: 'hidden' },
<FancyMultiSelect
className={className}
options={availableHasuraPermissionVariables}
creatable
defaultValue={defaultValue.map((v) => ({ value: v, label: v }))}
onChange={(value) => {
setValue(
inputName,
value.map((v) => v.value),
{ shouldDirty: true },
);
}}
options={[]}
fullWidth
filterSelectedOptions
error={error}
helperText={helperText}
/>
);
}
@@ -194,71 +181,70 @@ export default function RuleValueInput({
schema={schema}
table={table}
name={inputName}
error={error}
helperText={helperText}
/>
);
}
const availableHasuraPermissionVariables = getAllPermissionVariables(
data?.config?.auth?.session?.accessToken?.customClaims,
).map(({ key }) => ({
value: `X-Hasura-${key}`,
label: `X-Hasura-${key}`,
group: 'Frequently used',
}));
const selectedVariable = availableHasuraPermissionVariables.find(
(variable) => variable.value === comboboxValue,
);
const comboboxLabel =
selectedVariable?.label || comboboxValue || 'Select variable...';
return (
<ControlledAutocomplete
disabled={disabled}
freeSolo={!isHasuraInput}
autoHighlight={isHasuraInput}
isOptionEqualToValue={(
option,
value: string | number | AutocompleteOption<string>,
) => {
if (typeof value !== 'object') {
return option.value.toLowerCase() === value?.toString().toLowerCase();
}
return option.value.toLowerCase() === value.value.toLowerCase();
}}
name={inputName}
groupBy={(option) => option.group}
slotProps={{
input: {
className: 'lg:!rounded-none',
sx: sharedInputSx,
},
formControl: { className: '!bg-transparent' },
paper: { className: 'empty:border-transparent' },
}}
fullWidth
loading={loading}
loadingText={<ActivityIndicator label="Loading..." />}
error={Boolean(customClaimsError) || error}
helperText={customClaimsError?.message || helperText}
options={
isHasuraInput
? availableHasuraPermissionVariables
: [
{
value: 'X-Hasura-User-Id',
label: 'X-Hasura-User-Id',
group: 'Frequently used',
},
]
}
onChange={(_event, _value, reason, details) => {
if (
reason !== 'selectOption' &&
details.option.value !== 'X-Hasura-User-Id'
) {
return;
}
setValue(inputName, details.option.value, { shouldDirty: true });
}}
/>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="justify-between"
>
<span className="truncate">{comboboxLabel}</span>
<ChevronsUpDown className="h-5 min-h-5 w-5 min-w-5 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent
side="bottom"
align="start"
className="max-h-[var(--radix-popover-content-available-height)] w-[var(--radix-popover-trigger-width)] p-0"
>
<Command>
<CommandInput placeholder="Choose variable..." />
<CommandList>
<CommandEmpty>No variable found.</CommandEmpty>
{loading && <CommandLoading>Loading...</CommandLoading>}
<CommandGroup>
{availableHasuraPermissionVariables.map((variable) => (
<CommandItem
key={variable.value}
value={variable.value}
onSelect={(currentValue) => {
setValue(inputName, currentValue, { shouldDirty: true });
setOpen(false);
}}
>
{variable.label}
<Check
className={cn(
'ml-auto',
comboboxValue === variable.value
? 'opacity-100'
: 'opacity-0',
)}
/>
</CommandItem>
))}
</CommandGroup>
<CommandCreateItem
onCreate={(currentValue) => {
setValue(inputName, currentValue, { shouldDirty: true });
setOpen(false);
}}
/>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}

View File

@@ -1,5 +1,5 @@
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { getHasuraAdminSecret } from '@/utils/env';
import type { MutationOptions } from '@tanstack/react-query';
@@ -39,10 +39,10 @@ export default function useDeleteColumnMutation({
const {
query: { dataSourceSlug, schemaSlug, tableSlug },
} = useRouter();
const { currentProject } = useCurrentWorkspaceAndProject();
const { project } = useProject();
const appUrl = generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region,
project?.subdomain,
project?.region,
'hasura',
);
const mutationFn = isPlatform ? deleteColumn : deleteColumnMigration;
@@ -55,7 +55,7 @@ export default function useDeleteColumnMutation({
adminSecret:
process.env.NEXT_PUBLIC_ENV === 'dev'
? getHasuraAdminSecret()
: customAdminSecret || currentProject?.config?.hasura.adminSecret,
: customAdminSecret || project?.config?.hasura.adminSecret,
dataSource: customDataSource || (dataSourceSlug as string),
schema: customSchema || (schemaSlug as string),
table: customTable || (tableSlug as string),

View File

@@ -544,9 +544,7 @@ export type HasuraOperator =
| '_eq'
| '_neq'
| '_in'
| '_in_hasura'
| '_nin'
| '_nin_hasura'
| '_gt'
| '_lt'
| '_gte'

View File

@@ -202,36 +202,6 @@ test('should convert a complex permission to a rule group', () => {
});
});
test(`should convert an _in or _nin value that do not have an array as value to _in_hasura or _nin_hasura`, () => {
expect(
convertToRuleGroup({ title: { _in: ['X-Hasura-Allowed-Ids'] } }),
).toMatchObject({
operator: '_and',
rules: [
{
column: 'title',
operator: '_in',
value: ['X-Hasura-Allowed-Ids'],
},
],
groups: [],
});
expect(
convertToRuleGroup({ title: { _in: 'X-Hasura-Allowed-Ids' } }),
).toMatchObject({
operator: '_and',
rules: [
{
column: 'title',
operator: '_in_hasura',
value: 'X-Hasura-Allowed-Ids',
},
],
groups: [],
});
});
test('should transform operators and relations if the _not operator is being used', () => {
expect(
convertToRuleGroup({ _not: { title: { _eq: 'test' } } }),

View File

@@ -52,8 +52,6 @@ const negatedValueOperatorPairs: Record<HasuraOperator, HasuraOperator> = {
_cgte: '_clt',
_clte: '_cgt',
_is_null: '_is_null',
_in_hasura: '_nin_hasura',
_nin_hasura: '_in_hasura',
};
export default function convertToRuleGroup(
@@ -151,16 +149,14 @@ export default function convertToRuleGroup(
(currentKey === '_in' || currentKey === '_nin') &&
typeof value === 'string'
) {
const operator = currentKey === '_in' ? '_in_hasura' : '_nin_hasura';
return {
operator: '_and',
rules: [
{
column: previousKey,
operator: shouldNegate
? negatedValueOperatorPairs[operator]
: operator,
? negatedValueOperatorPairs[currentKey]
: currentKey,
value,
},
],

View File

@@ -19,20 +19,23 @@ export const POSTGRESQL_ERROR_CODES = {
*
* @docs https://www.postgresql.org/docs/current/datatype-numeric.html
*/
export const POSTGRESQL_NUMERIC_TYPES = [
export const POSTGRESQL_INTEGER_TYPES = [
'smallint',
'integer',
'bigint',
'decimal',
'numeric',
'real',
'double precision',
'smallserial',
'serial',
'bigserial',
'oid',
];
export const POSTGRESQL_DECIMAL_TYPES = [
'decimal',
'numeric',
'real',
'double precision',
];
/**
* Character data types in PostgreSQL.
*

View File

@@ -118,8 +118,8 @@ export default function DatabaseConnectionInfo() {
}
const postgresHost = generateAppServiceUrl(
project.subdomain,
project.region,
project?.subdomain,
project?.region,
'db',
).replace('https://', '');

View File

@@ -43,7 +43,8 @@ const validationSchema = Yup.object({
value: Yup.string().required('Major version is a required field'),
})
.label('Postgres major version')
.required(),
.required()
.test('not-zero', 'Invalid major version', (value) => value?.value !== '0'),
minorVersion: Yup.object({
label: Yup.string().required(),
value: Yup.string().required('Minor version is a required field'),
@@ -186,18 +187,29 @@ export default function DatabaseServiceVersionSettings() {
shouldPoll: true,
});
const showMigrateWarning =
Number(selectedMajor) > Number(currentPostgresMajor);
const { state } = useAppState();
const applicationUpdating =
state === ApplicationStatus.Updating ||
state === ApplicationStatus.Migrating;
const applicationLive = state === ApplicationStatus.Live;
const applicationPaused = state === ApplicationStatus.Paused;
const applicationPausing = state === ApplicationStatus.Pausing;
const showMigrateWarning =
!applicationPaused &&
!applicationPausing &&
Number(selectedMajor) > Number(currentPostgresMajor);
const applicationUnhealthy =
state !== ApplicationStatus.Live && !applicationUpdating;
!applicationLive &&
!applicationPaused &&
!applicationPausing &&
!applicationUpdating;
const isMajorVersionDirty = formState?.dirtyFields?.majorVersion;
const isMinorVersionDirty = formState?.dirtyFields?.minorVersion;
const isDirty = isMajorVersionDirty || isMinorVersionDirty;
const versionFieldsDisabled =
applicationUpdating || applicationUnhealthy || maintenanceActive;
const saveDisabled = versionFieldsDisabled || !isDirty;
@@ -208,7 +220,7 @@ export default function DatabaseServiceVersionSettings() {
const newVersion = `${formValues.majorVersion.value}.${formValues.minorVersion.value}`;
// Major version change
if (isMajorVersionDirty) {
if (isMajorVersionDirty && applicationLive) {
openDialog({
title: 'Update Postgres MAJOR version',
component: (
@@ -228,7 +240,7 @@ export default function DatabaseServiceVersionSettings() {
return;
}
// Minor version change
// Only minor version change or project is paused/pausing
const updateConfigPromise = updateConfig({
variables: {
appId: project.id,
@@ -338,7 +350,6 @@ export default function DatabaseServiceVersionSettings() {
return option.value;
}}
showCustomOption="auto"
isOptionEqualToValue={() => false}
filterOptions={(options, { inputValue }) => {
const inputValueLower = inputValue.toLowerCase();
const matched = [];
@@ -383,12 +394,13 @@ export default function DatabaseServiceVersionSettings() {
form.setValue('majorVersion', value);
}
}}
clearOnBlur
fullWidth
className="lg:col-span-1"
label="MAJOR"
options={availableMajorVersions}
error={!!formState.errors?.majorVersion?.value?.message}
helperText={formState.errors?.majorVersion?.value?.message}
error={!!formState.errors?.majorVersion?.message}
helperText={formState.errors?.majorVersion?.message}
customOptionLabel={(value) => `Use custom value: "${value}"`}
/>
<ControlledAutocomplete
@@ -424,12 +436,13 @@ export default function DatabaseServiceVersionSettings() {
return result;
}}
clearOnBlur
fullWidth
className="lg:col-span-2"
label="MINOR"
options={availableMinorVersions}
error={!!formState.errors?.minorVersion?.value?.message}
helperText={formState.errors?.minorVersion?.value?.message}
error={!!formState.errors?.minorVersion?.message}
helperText={formState.errors?.minorVersion?.message}
showCustomOption="auto"
customOptionLabel={(value) => `Use custom value: "${value}"`}
/>

View File

@@ -5,28 +5,41 @@ import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Alert } from '@/components/ui/v2/Alert';
import { Box } from '@/components/ui/v2/Box';
import { Input } from '@/components/ui/v2/Input';
import { InputAdornment } from '@/components/ui/v2/InputAdornment';
import { Link } from '@/components/ui/v2/Link';
import { Text } from '@/components/ui/v2/Text';
import { UpgradeNotification } from '@/features/orgs/projects/common/components/UpgradeNotification';
import { useAppState } from '@/features/orgs/projects/common/hooks/useAppState';
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
import { DatabaseStorageCapacityWarning } from '@/features/orgs/projects/database/settings/components/DatabaseStorageCapacityWarning';
import { useCurrentOrg } from '@/features/orgs/projects/hooks/useCurrentOrg';
import { useLocalMimirClient } from '@/features/orgs/projects/hooks/useLocalMimirClient';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
import {
useGetPersistentVolumesEncryptedQuery,
useGetPostgresSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { ApplicationStatus } from '@/types/application';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { useEffect, useMemo } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
const validationSchema = Yup.object({
capacity: Yup.number().required().min(10),
capacity: Yup.number()
.integer('Capacity must be an integer')
.typeError('You must specify a number')
.min(1, 'Capacity must be greater than 0')
.required('Capacity is required'),
});
export type AuthDomainFormValues = Yup.InferType<typeof validationSchema>;
export type DatabaseStorageCapacityFormValues = Yup.InferType<
typeof validationSchema
>;
export default function AuthDomain() {
export default function DatabaseStorageCapacity() {
const isPlatform = useIsPlatform();
const { org } = useCurrentOrg();
const { maintenanceActive } = useUI();
@@ -48,6 +61,15 @@ export default function AuthDomain() {
org?.plan?.featureMaxDbSize) ||
0;
const { data: encryptedVolumesData } = useGetPersistentVolumesEncryptedQuery({
variables: { appId: project?.id },
skip: !isPlatform,
});
const showEncryptionWarning = encryptedVolumesData
? !encryptedVolumesData?.systemConfig?.persistentVolumesEncrypted
: false;
const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
@@ -58,8 +80,32 @@ export default function AuthDomain() {
resolver: yupResolver(validationSchema),
});
const { formState, register, reset } = form;
const { state } = useAppState();
const applicationPause =
state === ApplicationStatus.Paused || state === ApplicationStatus.Pausing;
const { formState, register, reset, watch } = form;
const isDirty = Object.keys(formState.dirtyFields).length > 0;
const newCapacity = watch('capacity');
const decreasingSize = newCapacity < capacity;
const submitDisabled = useMemo(() => {
if (!isDirty) {
return true;
}
if (maintenanceActive) {
return true;
}
if (decreasingSize && !applicationPause) {
return true;
}
return false;
}, [isDirty, maintenanceActive, decreasingSize, applicationPause]);
useEffect(() => {
if (data && !loading) {
@@ -81,7 +127,7 @@ export default function AuthDomain() {
throw error;
}
async function handleSubmit(formValues: AuthDomainFormValues) {
async function handleSubmit(formValues: DatabaseStorageCapacityFormValues) {
await execPromiseWithErrorToast(
async () => {
await updateConfig({
@@ -120,7 +166,7 @@ export default function AuthDomain() {
description="Specify the storage capacity for your PostgreSQL database."
slotProps={{
submitButton: {
disabled: !isDirty || maintenanceActive,
disabled: submitDisabled,
loading: formState.isSubmitting,
},
}}
@@ -134,26 +180,48 @@ export default function AuthDomain() {
{...register('capacity')}
id="capacity"
name="capacity"
type="number"
type="text"
endAdornment={
<InputAdornment className="absolute right-2" position="end">
GB
</InputAdornment>
}
fullWidth
disabled={project.legacyPlan?.isFree}
className="lg:col-span-2"
error={Boolean(formState.errors.capacity?.message)}
helperText={formState.errors.capacity?.message}
slotProps={{
inputRoot: {
min: capacity,
},
}}
/>
</Box>
{!project.legacyPlan?.isFree && (
<Alert severity="info" className="col-span-6 text-left">
Note that volumes can only be increased (not decreased). Also, due
to an AWS limitation, the same volume can only be increased once
every 6 hours.
</Alert>
<DatabaseStorageCapacityWarning
state={state}
decreasingSize={decreasingSize}
isDirty={isDirty}
/>
)}
{showEncryptionWarning ? (
<Alert severity="warning" className="flex flex-col gap-3 text-left">
<div className="flex flex-col gap-2 lg:flex-row lg:justify-between">
<Text className="flex items-start gap-1 font-semibold">
Disk encryption is now available!
</Text>
</div>
<div>
<Text>
To enable encryption in this project all you have to do is
pause & unpause it in{' '}
<Link
href={`/orgs/${org?.slug}/projects/${project?.subdomain}/settings`}
underline="hover"
>
General Settings
</Link>
.
</Text>
</div>
</Alert>
) : null}
</SettingsContainer>
</Form>
</FormProvider>

View File

@@ -0,0 +1,73 @@
import { Alert } from '@/components/ui/v2/Alert';
import { Text } from '@/components/ui/v2/Text';
import { ApplicationStatus } from '@/types/application';
interface DatabaseStorageCapacityWarningProps {
state: ApplicationStatus;
decreasingSize: boolean;
isDirty: boolean;
}
export default function DatabaseStorageCapacityWarning({
state,
decreasingSize,
isDirty,
}: DatabaseStorageCapacityWarningProps) {
const applicationPause =
state === ApplicationStatus.Paused || state === ApplicationStatus.Pausing;
if (!isDirty) {
return null;
}
if (state === ApplicationStatus.Live && !decreasingSize) {
return (
<Alert severity="warning" className="flex flex-col gap-3 text-left">
<div className="flex flex-col gap-2 lg:flex-row lg:justify-between">
<Text className="flex items-start gap-1 font-semibold">
<span></span> Warning: Increasing disk size
</Text>
</div>
<div>
<Text>
Due to AWS limitations, disk size can only be modified once every 6
hours. Please ensure you increase capacity sufficiently to cover
your needs during this period.
</Text>
</div>
</Alert>
);
}
if (state === ApplicationStatus.Live && decreasingSize) {
return (
<Alert severity="warning" className="flex flex-col gap-3 text-left">
<div className="flex flex-col gap-2 lg:flex-row lg:justify-between">
<Text className="flex items-start gap-1 font-semibold">
<span></span> Warning: Decreasing disk size requires project to be
paused first.
</Text>
</div>
</Alert>
);
}
if (applicationPause && decreasingSize) {
return (
<Alert severity="warning" className="flex flex-col gap-3 text-left">
<div className="flex flex-col gap-2 lg:flex-row lg:justify-between">
<Text className="flex items-start gap-1 font-semibold">
<span></span> Warning: Ensure enough space before downsizing.
</Text>
</div>
<div>
<Text>
Before downsizing, ensure enough space for your database, WAL files,
and other supporting data to prevent issues when unpausing your
project.
</Text>
</div>
</Alert>
);
}
return null;
}

View File

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

View File

@@ -0,0 +1,5 @@
query GetPersistentVolumesEncrypted($appId: uuid!) {
systemConfig(appID: $appId) {
persistentVolumesEncrypted
}
}

View File

@@ -12,10 +12,6 @@ import { useMemo } from 'react';
type Project = GetProjectQuery['apps'][0];
interface UseProjectOptions {
poll?: boolean;
}
export interface UseProjectReturnType {
project: Project;
loading?: boolean;
@@ -23,9 +19,7 @@ export interface UseProjectReturnType {
refetch: (variables?: any) => Promise<any>;
}
export default function useProject({
poll = false,
}: UseProjectOptions = {}): UseProjectReturnType {
export default function useProject(): UseProjectReturnType {
const {
query: { appSubdomain },
isReady: isRouterReady,
@@ -46,7 +40,7 @@ export default function useProject({
);
const { data, isLoading, refetch, error } = useQuery(
['currentProject', appSubdomain as string],
['project', appSubdomain as string],
async () => {
const response = await client.graphql.request<{
apps: ProjectFragment[];
@@ -57,11 +51,6 @@ export default function useProject({
},
{
enabled: shouldFetchProject,
keepPreviousData: true,
refetchInterval: poll ? 10000 : false,
refetchOnWindowFocus: true,
staleTime: 1000 * 60 * 5, // 1 minutes
cacheTime: 1000 * 60 * 6, //
},
);

View File

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

View File

@@ -0,0 +1,77 @@
import { localApplication } from '@/features/orgs/utils/local-dashboard';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
GetProjectStateDocument,
type GetProjectQuery,
type ProjectFragment,
} from '@/utils/__generated__/graphql';
import { useAuthenticationStatus, useNhostClient } from '@nhost/nextjs';
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import { useMemo } from 'react';
type Project = GetProjectQuery['apps'][0];
export interface UseProjectWithStateReturnType {
project: Project;
loading?: boolean;
error?: Error;
refetch: (variables?: any) => Promise<any>;
}
export default function useProjectWithState(): UseProjectWithStateReturnType {
const {
query: { appSubdomain },
isReady: isRouterReady,
} = useRouter();
const client = useNhostClient();
const isPlatform = useIsPlatform();
const { isAuthenticated, isLoading: isAuthLoading } =
useAuthenticationStatus();
const shouldFetchProject = useMemo(
() =>
isPlatform &&
isAuthenticated &&
!isAuthLoading &&
!!appSubdomain &&
isRouterReady,
[isPlatform, isAuthenticated, isAuthLoading, appSubdomain, isRouterReady],
);
const { data, isLoading, refetch, error } = useQuery(
['projectWithState', appSubdomain as string],
async () => {
const response = await client.graphql.request<{
apps: ProjectFragment[];
}>(GetProjectStateDocument, {
subdomain: (appSubdomain as string) || '',
});
return response;
},
{
enabled: shouldFetchProject,
keepPreviousData: true,
refetchOnWindowFocus: true,
refetchInterval: 10000, // poll every 10s
staleTime: 1000 * 60 * 5, // 1 minutes
cacheTime: 1000 * 60 * 6, //
},
);
if (isPlatform) {
return {
project: data?.data?.apps?.[0] || null,
loading: isLoading && shouldFetchProject,
error: Array.isArray(error || {}) ? error[0] : error,
refetch,
};
}
return {
project: localApplication,
loading: false,
error: null,
refetch: () => Promise.resolve(),
};
}

View File

@@ -13,8 +13,8 @@ import { Tooltip } from '@/components/ui/v2/Tooltip';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { LogsRangeSelector } from '@/features/orgs/projects/logs/components/LogsRangeSelector';
import { AvailableLogsService } from '@/features/orgs/projects/logs/utils/constants/services';
import { MINUTES_TO_DECREASE_FROM_CURRENT_DATE } from '@/utils/constants/common';
import { useGetServiceLabelValuesQuery } from '@/utils/__generated__/graphql';
import { MINUTES_TO_DECREASE_FROM_CURRENT_DATE } from '@/utils/constants/common';
import { yupResolver } from '@hookform/resolvers/yup';
import { subMinutes } from 'date-fns';
import { useEffect, useState } from 'react';
@@ -55,7 +55,7 @@ export default function LogsHeader({
const { data, loading: loadingServiceLabelValues } =
useGetServiceLabelValuesQuery({
variables: { appID: project.id },
variables: { appID: project?.id },
});
useEffect(() => {
@@ -121,7 +121,7 @@ export default function LogsHeader({
<Box className="flex flex-row space-x-2">
<ControlledSelect
{...register('service')}
className="w-full text-sm font-normal min-w-fit"
className="w-full min-w-fit text-sm font-normal"
placeholder="All Services"
aria-label="Select service"
hideEmptyHelperText
@@ -165,12 +165,12 @@ export default function LogsHeader({
},
}}
title={
<div className="p-2 space-y-4">
<div className="space-y-4 p-2">
<h2>Here are some useful regular expressions:</h2>
<ul className="pl-3 space-y-2 list-disc">
<ul className="list-disc space-y-2 pl-3">
<li>
use
<code className="px-1 py-px mx-1 rounded-md bg-slate-500 text-slate-100">
<code className="mx-1 rounded-md bg-slate-500 px-1 py-px text-slate-100">
(?i)error
</code>
to search for lines with the word <b>error</b> (case
@@ -178,7 +178,7 @@ export default function LogsHeader({
</li>
<li>
use
<code className="px-1 py-px mx-1 rounded-md bg-slate-500 text-slate-100">
<code className="mx-1 rounded-md bg-slate-500 px-1 py-px text-slate-100">
error
</code>
to search for lines with the word <b>error</b> (case
@@ -186,7 +186,7 @@ export default function LogsHeader({
</li>
<li>
use
<code className="px-1 py-px mx-1 rounded-md bg-slate-500 text-slate-100">
<code className="mx-1 rounded-md bg-slate-500 px-1 py-px text-slate-100">
/metadata.*error
</code>
to search for errors in hasura&apos;s metadata endpoint
@@ -208,10 +208,10 @@ export default function LogsHeader({
</div>
}
>
<Box className="ml-2 rounded-full cursor-pointer">
<Box className="ml-2 cursor-pointer rounded-full">
<InfoIcon
aria-label="Info"
className="w-5 h-5"
className="h-5 w-5"
color="info"
/>
</Box>
@@ -224,7 +224,7 @@ export default function LogsHeader({
className="h-10"
startIcon={
loading ? (
<ActivityIndicator className="w-4 h-4" />
<ActivityIndicator className="h-4 w-4" />
) : (
<SearchIcon />
)

View File

@@ -28,7 +28,7 @@ const smtpValidationSchema = yup
.required(),
user: yup.string().label('Username').required(),
password: yup.string().label('Password'),
sender: yup.string().label('SMTP Sender').email().required(),
sender: yup.string().label('SMTP Sender').required(),
})
.required();

View File

@@ -33,15 +33,24 @@ export default function MetricsSettings() {
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { project, refetch: refetchProject } = useProject();
const {
project,
refetch: refetchProject,
loading: loadingProject,
} = useProject();
const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetObservabilitySettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { data, loading, error } = useGetObservabilitySettingsQuery({
const {
data,
loading: loadingObservabilitySettings,
error,
} = useGetObservabilitySettingsQuery({
variables: { appId: project?.id },
...(!isPlatform ? { client: localMimirClient } : {}),
skip: !project?.id,
});
const { enabled: alertingEnabled } =
@@ -59,14 +68,14 @@ export default function MetricsSettings() {
const alerting = watch('enabled');
useEffect(() => {
if (!loading) {
if (!loadingObservabilitySettings) {
alertingForm.reset({
enabled: alertingEnabled,
});
}
}, [loading, alertingEnabled, alertingForm]);
}, [loadingObservabilitySettings, alertingEnabled, alertingForm]);
if (loading) {
if (loadingProject || loadingObservabilitySettings) {
return (
<ActivityIndicator
delay={1000}
@@ -124,7 +133,7 @@ export default function MetricsSettings() {
}
return (
<div className="grid max-w-5xl grid-flow-row bg-transparent gap-y-6">
<div className="grid max-w-5xl grid-flow-row gap-y-6 bg-transparent">
<FormProvider {...alertingForm}>
<Form onSubmit={handleSubmit}>
<SettingsContainer

View File

@@ -10,28 +10,28 @@ const features: CardProps[] = [
description: 'Learn how to use Postgres with Nhost',
icon: <DatabaseIcon className="h-8 w-8" sx={{ color: 'text.secondary' }} />,
disableIconBackground: true,
link: 'https://docs.nhost.io/platform/database',
link: 'https://docs.nhost.io/product/database',
},
{
title: 'GraphQL API',
description: 'Learn how to interact with the GraphQL API',
icon: <GraphQLIcon className="h-8 w-8" sx={{ color: 'text.secondary' }} />,
disableIconBackground: true,
link: 'https://docs.nhost.io/platform/graphql',
link: 'https://docs.nhost.io/product/graphql',
},
{
title: 'Authentication',
description: 'Learn how to authenticate users with Nhost',
icon: <UserIcon className="h-8 w-8" sx={{ color: 'text.secondary' }} />,
disableIconBackground: true,
link: 'https://docs.nhost.io/platform/authentication',
link: 'https://docs.nhost.io/product/authentication',
},
{
title: 'Storage',
description: 'Learn how to use Storage with Nhost',
icon: <StorageIcon className="h-8 w-8" sx={{ color: 'text.secondary' }} />,
disableIconBackground: true,
link: 'https://docs.nhost.io/platform/storage',
link: 'https://docs.nhost.io/product/storage',
},
];

View File

@@ -18,15 +18,19 @@ import { calculateBillableResources } from '@/features/orgs/projects/resources/s
import type { ResourceSettingsFormValues } from '@/features/orgs/projects/resources/settings/utils/resourceSettingsValidationSchema';
import { resourceSettingsValidationSchema } from '@/features/orgs/projects/resources/settings/utils/resourceSettingsValidationSchema';
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
import {
RESOURCE_VCPU_MULTIPLIER,
RESOURCE_VCPU_PRICE,
} from '@/utils/constants/common';
import type { GetResourcesQuery } from '@/utils/__generated__/graphql';
import type {
ConfigConfigUpdateInput,
GetResourcesQuery,
} from '@/utils/__generated__/graphql';
import {
useGetResourcesQuery,
useUpdateConfigMutation,
} from '@/utils/__generated__/graphql';
import {
RESOURCE_VCPU_MULTIPLIER,
RESOURCE_VCPU_PRICE,
} from '@/utils/constants/common';
import { removeTypename } from '@/utils/helpers';
import { yupResolver } from '@hookform/resolvers/yup';
import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge';
@@ -36,7 +40,7 @@ function getInitialServiceResources(
data: GetResourcesQuery,
service: Exclude<keyof GetResourcesQuery['config'], '__typename'>,
) {
const { compute, replicas, autoscaler } =
const { compute, replicas, autoscaler, ...rest } =
data?.config?.[service]?.resources || {};
return {
@@ -44,6 +48,7 @@ function getInitialServiceResources(
vcpu: compute?.cpu || 0,
memory: compute?.memory || 0,
autoscale: autoscaler || null,
rest,
};
}
@@ -176,76 +181,130 @@ export default function ResourcesForm() {
? (billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) * RESOURCE_VCPU_PRICE
: 0;
const getFormattedConfig = (
values: ResourceSettingsFormValues,
): ConfigConfigUpdateInput => {
const sanitizedValues = removeTypename(
values,
) as ResourceSettingsFormValues;
const sanitizedInitialDatabaseResources = removeTypename(
initialDatabaseResources,
);
const sanitizedInitialHasuraResources = removeTypename(
initialHasuraResources,
);
const sanitizedInitialAuthResources = removeTypename(initialAuthResources);
const sanitizedInitialStorageResources = removeTypename(
initialStorageResources,
);
if (sanitizedValues.enabled) {
return {
postgres: {
resources: {
compute: {
cpu: sanitizedValues.database.vcpu,
memory: sanitizedValues.database.memory,
},
replicas: sanitizedValues.database.replicas,
autoscaler: sanitizedValues.database.autoscale
? {
maxReplicas: sanitizedValues.database.maxReplicas,
}
: null,
...sanitizedInitialDatabaseResources.rest,
},
},
hasura: {
resources: {
compute: {
cpu: sanitizedValues.hasura.vcpu,
memory: sanitizedValues.hasura.memory,
},
replicas: sanitizedValues.hasura.replicas,
autoscaler: sanitizedValues.hasura.autoscale
? {
maxReplicas: sanitizedValues.hasura.maxReplicas,
}
: null,
...sanitizedInitialHasuraResources.rest,
},
},
auth: {
resources: {
compute: {
cpu: sanitizedValues.auth.vcpu,
memory: sanitizedValues.auth.memory,
},
replicas: sanitizedValues.auth.replicas,
autoscaler: sanitizedValues.auth.autoscale
? {
maxReplicas: sanitizedValues.auth.maxReplicas,
}
: null,
...sanitizedInitialAuthResources.rest,
},
},
storage: {
resources: {
compute: {
cpu: sanitizedValues.storage.vcpu,
memory: sanitizedValues.storage.memory,
},
replicas: sanitizedValues.storage.replicas,
autoscaler: sanitizedValues.storage.autoscale
? {
maxReplicas: sanitizedValues.storage.maxReplicas,
}
: null,
...sanitizedInitialStorageResources.rest,
},
},
};
}
return {
postgres: {
resources: {
compute: null,
replicas: null,
autoscaler: null,
...sanitizedInitialDatabaseResources.rest,
},
},
hasura: {
resources: {
compute: null,
replicas: null,
autoscaler: null,
...sanitizedInitialHasuraResources.rest,
},
},
auth: {
resources: {
compute: null,
replicas: null,
autoscaler: null,
...sanitizedInitialAuthResources.rest,
},
},
storage: {
resources: {
compute: null,
replicas: null,
autoscaler: null,
...sanitizedInitialStorageResources.rest,
},
},
};
};
async function handleSubmit(formValues: ResourceSettingsFormValues) {
const updateConfigPromise = updateConfig({
variables: {
appId: project?.id,
config: {
postgres: {
resources: formValues.enabled
? {
compute: {
cpu: formValues.database.vcpu,
memory: formValues.database.memory,
},
replicas: formValues.database.replicas,
autoscaler: formValues.database.autoscale
? {
maxReplicas: formValues.database.maxReplicas,
}
: null,
}
: null,
},
hasura: {
resources: formValues.enabled
? {
compute: {
cpu: formValues.hasura.vcpu,
memory: formValues.hasura.memory,
},
replicas: formValues.hasura.replicas,
autoscaler: formValues.hasura.autoscale
? {
maxReplicas: formValues.hasura.maxReplicas,
}
: null,
}
: null,
},
auth: {
resources: formValues.enabled
? {
compute: {
cpu: formValues.auth.vcpu,
memory: formValues.auth.memory,
},
replicas: formValues.auth.replicas,
autoscaler: formValues.auth.autoscale
? {
maxReplicas: formValues.auth.maxReplicas,
}
: null,
}
: null,
},
storage: {
resources: formValues.enabled
? {
compute: {
cpu: formValues.storage.vcpu,
memory: formValues.storage.memory,
},
replicas: formValues.storage.replicas,
autoscaler: formValues.storage.autoscale
? {
maxReplicas: formValues.storage.maxReplicas,
}
: null,
}
: null,
},
},
config: getFormattedConfig(formValues),
},
});

View File

@@ -171,7 +171,7 @@ export default function ServiceResourcesFormFragment({
</Box>
<Box className="grid grid-flow-row gap-2">
<Box className="grid items-center justify-between grid-flow-col gap-2">
<Box className="grid grid-flow-col items-center justify-between gap-2">
<Text>
Allocated vCPUs:{' '}
<span className="font-medium">
@@ -201,7 +201,7 @@ export default function ServiceResourcesFormFragment({
</Box>
<Box className="grid grid-flow-row gap-2">
<Box className="grid items-center justify-between grid-flow-col gap-2">
<Box className="grid grid-flow-col items-center justify-between gap-2">
<Text>
Allocated Memory:{' '}
<span className="font-medium">
@@ -246,7 +246,7 @@ export default function ServiceResourcesFormFragment({
>
<ExclamationIcon
color="error"
className="w-4 h-4"
className="h-4 w-4"
aria-hidden="false"
/>
</Tooltip>
@@ -274,7 +274,7 @@ export default function ServiceResourcesFormFragment({
>
<ExclamationIcon
color="error"
className="w-4 h-4"
className="h-4 w-4"
aria-hidden="false"
/>
</Tooltip>
@@ -306,7 +306,7 @@ export default function ServiceResourcesFormFragment({
<Tooltip
title={`Enable autoscaler to automatically provision extra ${title} replicas when needed.`}
>
<InfoOutlinedIcon className="w-4 h-4 text-black" />
<InfoOutlinedIcon className="h-4 w-4" />
</Tooltip>
</Box>
</Box>
@@ -323,7 +323,7 @@ export default function ServiceResourcesFormFragment({
className="font-medium"
>
Service Replicas
<ArrowSquareOutIcon className="w-4 h-4 ml-1" />
<ArrowSquareOutIcon className="ml-1 h-4 w-4" />
</Link>
</Text>
)}

View File

@@ -9,6 +9,11 @@ fragment ServiceResources on ConfigConfig {
autoscaler {
maxReplicas
}
networking {
ingresses {
fqdn
}
}
}
}
hasura {
@@ -21,10 +26,19 @@ fragment ServiceResources on ConfigConfig {
autoscaler {
maxReplicas
}
networking {
ingresses {
fqdn
}
}
}
}
postgres {
resources {
storage {
capacity
}
enablePublicAccess
compute {
cpu
memory

View File

@@ -19,7 +19,6 @@ import { StorageFormSection } from '@/features/orgs/projects/services/components
import { useHostName } from '@/features/projects/common/hooks/useHostName';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import { v4 as uuidv4 } from 'uuid';
import {
validationSchema,
@@ -29,16 +28,15 @@ import {
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import {
useInsertRunServiceConfigMutation,
useReplaceRunServiceConfigMutation,
type ConfigRunServiceConfigInsertInput,
} from '@/utils/__generated__/graphql';
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { removeTypename } from '@/utils/helpers';
import {
useInsertRunServiceConfigMutation,
useInsertRunServiceMutation,
useReplaceRunServiceConfigMutation,
type ConfigRunServiceConfigInsertInput,
} from '@/utils/__generated__/graphql';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect, useMemo, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
@@ -58,9 +56,10 @@ export default function ServiceForm({
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { onDirtyStateChange, openDialog, closeDialog } = useDialog();
const [insertRunService] = useInsertRunServiceMutation();
const { project } = useProject();
const [insertRunServiceConfig] = useInsertRunServiceConfigMutation();
const [insertRunServiceConfig] = useInsertRunServiceConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const [replaceRunServiceConfig] = useReplaceRunServiceConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
@@ -96,14 +95,14 @@ export default function ServiceForm({
if (serviceID) {
return serviceID;
}
return uuidv4();
return '<uuid-to-be-generated-on-creation>';
}, [serviceID]);
const privateRegistryImage = `registry.${project?.region.name}.${project?.region.domain}/${newServiceID}`;
let initialImageType: 'public' | 'private' | 'nhost' = 'public';
if (initialData?.image?.startsWith(privateRegistryImage)) {
if (initialData?.image?.startsWith(privateRegistryImage.split('/')[0])) {
initialImageType = 'nhost';
}
@@ -225,33 +224,14 @@ export default function ServiceForm({
});
}
} else {
// Insert service config
const {
data: {
insertRunService: { id },
},
} = await insertRunService({
variables: {
object: {
appID: project.id,
id: newServiceID,
},
},
});
// Create service
await insertRunServiceConfig({
variables: {
appID: project.id,
serviceID: id,
config: {
...config,
image: {
// If the image field left empty then we auto-populate following this format
// registry.<region>.<nhost_domain>/<service_id>
image:
values.image.length > 0
? values.image
: `registry.${project.region.name}.${project.region.domain}/${newServiceID}`,
image: values.image,
pullCredentials:
values.pullCredentials?.length > 0
? values.pullCredentials
@@ -335,7 +315,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>
@@ -359,7 +339,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>
@@ -414,7 +394,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

@@ -12,7 +12,11 @@ import {
export const validationSchema = Yup.object({
name: Yup.string().required('The name is required.'),
image: Yup.string().label('Image to run').required('The image is required.'),
image: Yup.string()
.trim()
.label('Image to run')
.required('The image is required.')
.min(1, 'Image must be at least 1 character long'),
pullCredentials: Yup.string().label('Pull credentials').nullable(),
command: Yup.string(),
environment: Yup.array().of(

View File

@@ -45,7 +45,7 @@ export default function ReplicasFormSection() {
};
return (
<Box className="p-4 space-y-4 rounded border-1">
<Box className="space-y-4 rounded border-1 p-4">
<Box className="flex flex-row items-center space-x-2">
<Text variant="h4" className="font-semibold">
Replicas ({replicas})
@@ -65,7 +65,7 @@ export default function ReplicasFormSection() {
</Text>
}
>
<InfoIcon aria-label="Info" className="w-4 h-4" color="primary" />
<InfoIcon aria-label="Info" className="h-4 w-4" color="primary" />
</Tooltip>
</Box>
@@ -121,7 +121,7 @@ export default function ReplicasFormSection() {
/>
<Text>Autoscaler</Text>
<Tooltip title="Enable autoscaler to automatically provision extra run service replicas when needed.">
<InfoOutlinedIcon className="w-4 h-4 text-black" />
<InfoOutlinedIcon className="h-4 w-4" />
</Tooltip>
</Box>
</Box>

View File

@@ -46,7 +46,7 @@ export default function useRunServices() {
refetch: refetchPlatformServices,
} = useGetRunServicesQuery({
variables: {
appID: project.id,
appID: project?.id,
resolve: false,
limit: limit.current,
offset,
@@ -59,7 +59,7 @@ export default function useRunServices() {
data: localServicesData,
refetch: refetchLocalServices,
} = useGetLocalRunServiceConfigsQuery({
variables: { appID: project.id as any, resolve: false },
variables: { appID: project?.id as any, resolve: false },
skip: isPlatform,
client: localMimirClient,
});

View File

@@ -1,13 +1,13 @@
import type { UseDataGridOptions } from '@/components/dataGrid/DataGrid/useDataGrid';
import { DataGridBody } from '@/components/dataGrid/DataGridBody';
import { DataGridConfigProvider } from '@/components/dataGrid/DataGridConfigProvider';
import { DataGridFrame } from '@/components/dataGrid/DataGridFrame';
import type { DataGridHeaderProps } from '@/components/dataGrid/DataGridHeader';
import { DataGridHeader } from '@/components/dataGrid/DataGridHeader';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Box } from '@/components/ui/v2/Box';
import { DataBrowserEmptyState } from '@/features/database/dataGrid/components/DataBrowserEmptyState';
import type { DataBrowserGridColumn } from '@/features/database/dataGrid/types/dataBrowser';
import { DataBrowserEmptyState } from '@/features/orgs/projects/database/dataGrid/components/DataBrowserEmptyState';
import type { DataBrowserGridColumn } from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
import type { UseDataGridOptions } from '@/features/orgs/projects/storage/dataGrid/components/DataGrid/useDataGrid';
import { DataGridBody } from '@/features/orgs/projects/storage/dataGrid/components/DataGridBody';
import { DataGridConfigProvider } from '@/features/orgs/projects/storage/dataGrid/components/DataGridConfigProvider';
import { DataGridFrame } from '@/features/orgs/projects/storage/dataGrid/components/DataGridFrame';
import type { DataGridHeaderProps } from '@/features/orgs/projects/storage/dataGrid/components/DataGridHeader';
import { DataGridHeader } from '@/features/orgs/projects/storage/dataGrid/components/DataGridHeader';
import type { ForwardedRef } from 'react';
import { forwardRef, useEffect, useRef } from 'react';
import mergeRefs from 'react-merge-refs';

View File

@@ -1,11 +1,11 @@
import type { DataGridProps } from '@/components/dataGrid/DataGrid';
import { DataGridCell } from '@/components/dataGrid/DataGridCell';
import { useDataGridConfig } from '@/components/dataGrid/DataGridConfigProvider';
import type { BoxProps } from '@/components/ui/v2/Box';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import type { DataBrowserGridColumn } from '@/features/database/dataGrid/types/dataBrowser';
import type { DataBrowserGridColumn } from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
import type { DataGridProps } from '@/features/orgs/projects/storage/dataGrid/components/DataGrid/DataGrid';
import { DataGridCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridCell';
import { useDataGridConfig } from '@/features/orgs/projects/storage/dataGrid/components/DataGridConfigProvider';
import type { DetailedHTMLProps, HTMLProps, KeyboardEvent } from 'react';
import { Fragment, useMemo, useRef } from 'react';
import type { Row } from 'react-table';

View File

@@ -1,8 +1,8 @@
import type { CommonDataGridCellProps } from '@/components/dataGrid/DataGridCell';
import { useDataGridCell } from '@/components/dataGrid/DataGridCell';
import { ReadOnlyToggle } from '@/components/presentational/ReadOnlyToggle';
import { Dropdown } from '@/components/ui/v2/Dropdown';
import type { KeyboardEvent as ReactKeyboardEvent, MouseEvent } from 'react';
import type { CommonDataGridCellProps } from '@/features/orgs/projects/storage/dataGrid/components/DataGridCell';
import { useDataGridCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridCell';
import type { MouseEvent, KeyboardEvent as ReactKeyboardEvent } from 'react';
import { twMerge } from 'tailwind-merge';
export type DataGridBooleanCellProps<TData extends object> =

View File

@@ -6,7 +6,7 @@ import type {
ColumnType,
DataBrowserGridCell,
DataBrowserGridCellProps,
} from '@/features/database/dataGrid/types/dataBrowser';
} from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
import { triggerToast } from '@/utils/toast';
import type {
FocusEvent,

View File

@@ -1,4 +1,4 @@
import type { UseDataGridReturn } from '@/components/dataGrid/DataGrid';
import type { UseDataGridReturn } from '@/features/orgs/projects/storage/dataGrid/components/DataGrid/useDataGrid';
import { createContext } from 'react';
const DataGridConfigContext = createContext<Partial<UseDataGridReturn>>(null);

View File

@@ -1,4 +1,4 @@
import type { UseDataGridReturn } from '@/components/dataGrid/DataGrid';
import type { UseDataGridReturn } from '@/features/orgs/projects/storage/dataGrid/components/DataGrid/useDataGrid';
import type { PropsWithChildren } from 'react';
import DataGridConfigContext from './DataGridConfigContext';

View File

@@ -1,4 +1,4 @@
import type { UseDataGridReturn } from '@/components/dataGrid/DataGrid';
import type { UseDataGridReturn } from '@/features/orgs/projects/storage/dataGrid/components/DataGrid';
import { useContext } from 'react';
import DataGridConfigContext from './DataGridConfigContext';

View File

@@ -1,8 +1,8 @@
import type { CommonDataGridCellProps } from '@/components/dataGrid/DataGridCell';
import { useDataGridCell } from '@/components/dataGrid/DataGridCell';
import { Input, inputClasses } from '@/components/ui/v2/Input';
import type { TextProps } from '@/components/ui/v2/Text';
import { Text } from '@/components/ui/v2/Text';
import type { CommonDataGridCellProps } from '@/features/orgs/projects/storage/dataGrid/components/DataGridCell';
import { useDataGridCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridCell';
import { getDateComponents } from '@/utils/getDateComponents';
import type { ChangeEvent, KeyboardEvent } from 'react';
import { twMerge } from 'tailwind-merge';

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
import { useDataGridConfig } from '@/components/dataGrid/DataGridConfigProvider';
import { useDataGridConfig } from '@/features/orgs/projects/storage/dataGrid/components/DataGridConfigProvider';
import clsx from 'clsx';
import type { DetailedHTMLProps, HTMLProps } from 'react';

View File

@@ -1,5 +1,3 @@
import type { DataGridProps } from '@/components/dataGrid/DataGrid';
import { useDataGridConfig } from '@/components/dataGrid/DataGridConfigProvider';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { Divider } from '@/components/ui/v2/Divider';
@@ -9,7 +7,10 @@ import { ArrowUpIcon } from '@/components/ui/v2/icons/ArrowUpIcon';
import { PencilIcon } from '@/components/ui/v2/icons/PencilIcon';
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import { TrashIcon } from '@/components/ui/v2/icons/TrashIcon';
import type { DataBrowserGridColumn } from '@/features/database/dataGrid/types/dataBrowser';
import type { DataBrowserGridColumn } from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
import type { DataGridProps } from '@/features/orgs/projects/storage/dataGrid/components/DataGrid';
import { useDataGridConfig } from '@/features/orgs/projects/storage/dataGrid/components/DataGridConfigProvider';
import { DataGridHeaderButton } from '@/features/orgs/projects/storage/dataGrid/components/DataGridHeaderButton';
import type { DetailedHTMLProps, HTMLProps } from 'react';
import { twMerge } from 'tailwind-merge';
@@ -54,7 +55,7 @@ export default function DataGridHeader<T extends object>({
componentsProps,
...props
}: DataGridHeaderProps<T>) {
const { flatHeaders, allowSort, allowResize } = useDataGridConfig<T>();
const { flatHeaders } = useDataGridConfig<T>();
return (
<div
@@ -96,53 +97,11 @@ export default function DataGridHeader<T extends object>({
}}
key={column.id}
>
{column.id === 'selection' ? (
<span
{...headerProps}
className="relative grid w-full grid-flow-col items-center justify-between p-2"
>
{column.render('Header')}
</span>
) : (
<Dropdown.Trigger
className={twMerge(
'focus:outline-none motion-safe:transition-colors',
)}
disabled={
column.isDisabled || (column.disableSortBy && !onRemoveColumn)
}
hideChevron
>
<span
{...headerProps}
className="relative grid w-full grid-flow-col items-center justify-between p-2"
>
{column.render('Header')}
{allowSort && (
<Box component="span" sx={{ color: 'text.primary' }}>
{column.isSorted && !column.isSortedDesc && (
<ArrowUpIcon className="h-3 w-3" />
)}
{column.isSorted && column.isSortedDesc && (
<ArrowDownIcon className="h-3 w-3" />
)}
</Box>
)}
</span>
{allowResize && !column.disableResizing && (
<span
{...column.getResizerProps({
onClick: (event: Event) => event.stopPropagation(),
})}
className="absolute -right-0.5 bottom-0 top-0 z-10 h-full w-1.5 group-hover:bg-slate-900 group-hover:bg-opacity-20 group-active:bg-slate-900 group-active:bg-opacity-20 motion-safe:transition-colors"
/>
)}
</Dropdown.Trigger>
)}
<DataGridHeaderButton
column={column}
headerProps={headerProps}
onRemoveColumn={onRemoveColumn}
/>
<Dropdown.Content
menu
PaperProps={{ className: 'w-52 mt-1' }}

View File

@@ -0,0 +1,77 @@
import { Box } from '@/components/ui/v2/Box';
import { Dropdown } from '@/components/ui/v2/Dropdown';
import { ArrowDownIcon } from '@/components/ui/v2/icons/ArrowDownIcon';
import { ArrowUpIcon } from '@/components/ui/v2/icons/ArrowUpIcon';
import type { DataBrowserGridColumn } from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
import { useDataGridConfig } from '@/features/orgs/projects/storage/dataGrid/components/DataGridConfigProvider';
import type { TableHeaderProps } from 'react-table';
import { twMerge } from 'tailwind-merge';
interface DataGridHeaderButtonProps<T extends object> {
column: DataBrowserGridColumn<T>;
headerProps: TableHeaderProps;
onRemoveColumn: (column: DataBrowserGridColumn<T>) => void;
}
export default function DataGridHeaderButton<T extends object>({
column,
headerProps,
onRemoveColumn,
}: DataGridHeaderButtonProps<T>) {
const { allowSort, allowResize } = useDataGridConfig();
if (column.id === 'selection') {
return (
<span
{...headerProps}
className="relative grid w-full grid-flow-col items-center justify-between p-2"
>
{column.render('Header')}
</span>
);
}
if (column.id === 'preview') {
return (
<div className="focus:outline-none motion-safe:transition-colors">
{column.render('Header')}
</div>
);
}
return (
<Dropdown.Trigger
className={twMerge('focus:outline-none motion-safe:transition-colors')}
disabled={column.isDisabled || (column.disableSortBy && !onRemoveColumn)}
hideChevron
>
<span
{...headerProps}
className="relative grid w-full grid-flow-col items-center justify-between p-2"
>
{column.render('Header')}
{allowSort && (
<Box component="span" sx={{ color: 'text.primary' }}>
{column.isSorted && !column.isSortedDesc && (
<ArrowUpIcon className="h-3 w-3" />
)}
{column.isSorted && column.isSortedDesc && (
<ArrowDownIcon className="h-3 w-3" />
)}
</Box>
)}
</span>
{allowResize && !column.disableResizing && (
<span
{...column.getResizerProps({
onClick: (event: Event) => event.stopPropagation(),
})}
className="absolute -right-0.5 bottom-0 top-0 z-10 h-full w-1.5 group-hover:bg-slate-900 group-hover:bg-opacity-20 group-active:bg-slate-900 group-active:bg-opacity-20 motion-safe:transition-colors"
/>
)}
</Dropdown.Trigger>
);
}

View File

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

View File

@@ -1,18 +1,20 @@
import type { CommonDataGridCellProps } from '@/components/dataGrid/DataGridCell';
import { useDataGridCell } from '@/components/dataGrid/DataGridCell';
import { Input, inputClasses } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text';
import {
useDataGridCell,
type CommonDataGridCellProps,
} from '@/features/orgs/projects/storage/dataGrid/components/DataGridCell';
import type { ChangeEvent, KeyboardEvent } from 'react';
export type DataGridNumericCellProps<TData extends object> =
export type DataGridIntegerCellProps<TData extends object> =
CommonDataGridCellProps<TData, number>;
export default function DataGridNumericCell<TData extends object>({
export default function DataGridIntegerCell<TData extends object>({
onSave,
optimisticValue,
temporaryValue,
onTemporaryValueChange,
}: DataGridNumericCellProps<TData>) {
}: DataGridIntegerCellProps<TData>) {
const { inputRef, focusCell, isEditing, cancelEditCell } =
useDataGridCell<HTMLInputElement>();

View File

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

View File

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

View File

@@ -9,6 +9,8 @@ import { VideoPreviewIcon } from '@/components/ui/v2/icons/VideoPreviewIcon';
import { XIcon } from '@/components/ui/v2/icons/XIcon';
import { useAppClient } from '@/features/orgs/projects/hooks/useAppClient';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { usePreviewToggle } from '@/features/orgs/projects/storage/dataGrid/hooks/usePreviewToggle';
import { useSSRLocalStorage } from '@/hooks/useSSRLocalStorage';
import clsx from 'clsx';
import type { ReactNode } from 'react';
import { useEffect, useReducer, useState } from 'react';
@@ -46,11 +48,16 @@ function useBlob({
const [objectUrl, setObjectUrl] = useState<string>();
const [error, setError] = useState<Error>();
const [loading, setLoading] = useState<boolean>(false);
const [preview] = useSSRLocalStorage('preview', true);
// This side-effect fetches the blob of the file from the server and sets the
// relevant `objectUrl` state. Abort controller is reponsible for cancelling
// the fetch if the component is unmounted.
useEffect(() => {
if (!preview) {
return undefined;
}
const abortController = new AbortController();
async function generateOptimizedObjectUrl() {
@@ -104,7 +111,7 @@ function useBlob({
generateObjectUrl();
return () => abortController.abort();
}, [blob, fetchBlob, objectUrl, mimeType]);
}, [blob, fetchBlob, objectUrl, mimeType, preview]);
return { objectUrl, error, loading };
}
@@ -168,8 +175,13 @@ export default function DataGridPreviewCell<TData extends object>({
}: DataGridPreviewCellProps<TData>) {
const { project } = useProject();
const appClient = useAppClient();
const { objectUrl, loading, error } = useBlob({ fetchBlob, blob, mimeType });
const { objectUrl, loading, error } = useBlob({
fetchBlob,
blob,
mimeType,
});
const [showModal, setShowModal] = useState(false);
const { previewEnabled } = usePreviewToggle();
const [
{ loading: previewLoading, error: previewError, data: previewUrl },
@@ -365,7 +377,7 @@ export default function DataGridPreviewCell<TData extends object>({
</Modal>
<div className="flex h-full w-full justify-center">
{previewableImages.includes(mimeType) && objectUrl && (
{previewEnabled && previewableImages.includes(mimeType) && objectUrl ? (
<button
type="button"
aria-label={alt}
@@ -381,9 +393,11 @@ export default function DataGridPreviewCell<TData extends object>({
/>
</picture>
</button>
)}
) : null}
{(!previewableImages.includes(mimeType) || !objectUrl) && (
{(!previewableImages.includes(mimeType) ||
!objectUrl ||
!previewEnabled) && (
<button
type="button"
onClick={handleOpenPreview}

View File

@@ -1,9 +1,11 @@
import type { CommonDataGridCellProps } from '@/components/dataGrid/DataGridCell';
import { useDataGridCell } from '@/components/dataGrid/DataGridCell';
import { Button } from '@/components/ui/v2/Button';
import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
import { Input, inputClasses } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text';
import {
useDataGridCell,
type CommonDataGridCellProps,
} from '@/features/orgs/projects/storage/dataGrid/components/DataGridCell';
import { copy } from '@/utils/copy';
import type { ChangeEvent, KeyboardEvent, Ref } from 'react';
import { useEffect } from 'react';

View File

@@ -10,6 +10,7 @@ import { FilePreviewIcon } from '@/components/ui/v2/icons/FilePreviewIcon';
import { useAppClient } from '@/features/orgs/projects/hooks/useAppClient';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { FilesDataGridControls } from '@/features/orgs/projects/storage/dataGrid/components/FilesDataGridControls';
import { PreviewHeader } from '@/features/orgs/projects/storage/dataGrid/components/PreviewHeader';
import { useBuckets } from '@/features/orgs/projects/storage/dataGrid/hooks/useBuckets';
import { useFiles } from '@/features/orgs/projects/storage/dataGrid/hooks/useFiles';
import { useFilesAggregate } from '@/features/orgs/projects/storage/dataGrid/hooks/useFilesAggregate';
@@ -112,7 +113,8 @@ export default function FilesDataGrid(props: FilesDataGridProps) {
const memoizedColumns: Column<StoredFile>[] = useMemo(
() => [
{
Header: 'Preview',
id: 'preview',
Header: PreviewHeader,
accessor: 'preview',
Cell: (cellProps) =>
DataGridPreviewCell({
@@ -121,8 +123,8 @@ export default function FilesDataGrid(props: FilesDataGridProps) {
<FilePreviewIcon className="h-5 w-5 fill-current" />
),
}),
minWidth: 80,
width: 80,
minWidth: 120,
width: 120,
disableSortBy: true,
disableResizing: true,
},

View File

@@ -1,7 +1,4 @@
import { useDialog } from '@/components/common/DialogProvider';
import { useDataGridConfig } from '@/components/dataGrid/DataGridConfigProvider';
import type { DataGridPaginationProps } from '@/components/dataGrid/DataGridPagination';
import { DataGridPagination } from '@/components/dataGrid/DataGridPagination';
import type { BoxProps } from '@/components/ui/v2/Box';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
@@ -10,6 +7,9 @@ import type { InputProps } from '@/components/ui/v2/Input';
import { Input } from '@/components/ui/v2/Input';
import { useAppClient } from '@/features/orgs/projects/hooks/useAppClient';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { useDataGridConfig } from '@/features/orgs/projects/storage/dataGrid/components/DataGridConfigProvider';
import type { DataGridPaginationProps } from '@/features/orgs/projects/storage/dataGrid/components/DataGridPagination';
import { DataGridPagination } from '@/features/orgs/projects/storage/dataGrid/components/DataGridPagination';
import type { FileUploadButtonProps } from '@/features/orgs/projects/storage/dataGrid/components/FileUploadButton';
import { FileUploadButton } from '@/features/orgs/projects/storage/dataGrid/components/FileUploadButton';
import type { Files } from '@/utils/__generated__/graphql';

View File

@@ -0,0 +1,22 @@
import { Switch } from '@/components/ui/v2/Switch';
import { usePreviewToggle } from '@/features/orgs/projects/storage/dataGrid/hooks/usePreviewToggle';
import { type ChangeEvent } from 'react';
export default function PreviewHeader() {
const { previewEnabled, setPreviewEnabled } = usePreviewToggle();
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
setPreviewEnabled(e.target.checked);
};
return (
<div className="flex flex-row items-center gap-2 p-2">
Preview
<Switch
className="self-center"
checked={previewEnabled}
onChange={handleChange}
/>
</div>
);
}

View File

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

View File

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

View File

@@ -0,0 +1,29 @@
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { useSSRLocalStorage } from '@/hooks/useSSRLocalStorage';
// Store {projectId: previewEnabled, projectId2: previewEnabled, ...}
type PreviewLocalStorage = {
[key: string]: boolean | undefined;
};
export default function usePreviewToggle() {
const [preview, setPreview] = useSSRLocalStorage<PreviewLocalStorage>(
'preview',
{},
);
const { project } = useProject();
// Default to previewEnabled true if not set
const previewEnabled = preview[project?.id] ?? true;
const setPreviewEnabled = (value: boolean) => {
const newPreview = { ...preview };
newPreview[project?.id] = value;
setPreview(newPreview);
};
return {
previewEnabled,
setPreviewEnabled,
};
}

View File

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

View File

@@ -1,31 +0,0 @@
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { useQuery } from '@tanstack/react-query';
/**
* Returns whether or not the app is healthy.
*/
export default function useIsHealthy() {
const isPlatform = useIsPlatform();
const { currentProject } = useCurrentWorkspaceAndProject();
const appUrl = generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region,
'auth',
);
const { failureCount, status } = useQuery(
['/healthz'],
() => fetch(`${appUrl}/healthz`),
{
enabled: !isPlatform && !!currentProject,
retry: true,
retryDelay: 5000,
cacheTime: 0,
},
);
return isPlatform || (status === 'success' && failureCount === 0);
}

View File

@@ -1,17 +1,35 @@
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
import { useCurrentOrg } from '@/features/orgs/projects/hooks/useCurrentOrg';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
/**
* Redirects to 404 page if either currentWorkspace/currentProject resolves to undefined.
* Redirects to 404 page if either currentWorkspace/currentProject resolves to undefined
* or if the current pathname is not a valid organization/project.
* Not applicable if running dashboard with local Nhost backend.
*/
export default function useNotFoundRedirect() {
const router = useRouter();
const {
query: { orgSlug, workspaceSlug, appSubdomain, updating, appSlug },
query: {
orgSlug: urlOrgSlug,
workspaceSlug: urlWorkspaceSlug,
appSubdomain: urlAppSubdomain,
updating,
appSlug: urlAppSlug,
},
isReady,
} = router;
const { project, loading: projectLoading } = useProject();
const isPlatform = useIsPlatform();
const { org, loading: orgLoading } = useCurrentOrg();
const { subdomain: projectSubdomain } = project || {};
const { slug: currentOrgSlug } = org || {};
const { currentProject, currentWorkspace, loading } =
useCurrentWorkspaceAndProject();
@@ -23,6 +41,10 @@ export default function useNotFoundRedirect() {
!isReady ||
// If the current workspace and project are not loaded, we don't want to redirect to 404
loading ||
// If the project is loading, we don't want to redirect to 404
projectLoading ||
// If the org is loading, we don't want to redirect to 404
orgLoading ||
// If we're already on the 404 page, we don't want to redirect to 404
router.pathname === '/404' ||
router.pathname === '/' ||
@@ -31,12 +53,14 @@ export default function useNotFoundRedirect() {
router.pathname === '/run-one-click-install' ||
router.pathname.includes('/orgs/_') ||
router.pathname.includes('/orgs/_/projects/_') ||
orgSlug ||
(orgSlug && appSubdomain) ||
(!isPlatform &&
router.pathname.includes('/orgs/[orgSlug]/projects/[appSubdomain]')) ||
(urlOrgSlug === currentOrgSlug && !urlAppSubdomain) ||
(urlOrgSlug === currentOrgSlug && urlAppSubdomain === projectSubdomain) ||
// If we are on a valid workspace and project, we don't want to redirect to 404
(workspaceSlug && currentWorkspace && appSlug && currentProject) ||
(urlWorkspaceSlug && currentWorkspace && urlAppSlug && currentProject) ||
// If we are on a valid workspace and no project is selected, we don't want to redirect to 404
(workspaceSlug && currentWorkspace && !appSlug && !currentProject)
(urlWorkspaceSlug && currentWorkspace && !urlAppSlug && !currentProject)
) {
return;
}
@@ -47,11 +71,16 @@ export default function useNotFoundRedirect() {
currentWorkspace,
isReady,
loading,
appSubdomain,
appSlug,
urlAppSubdomain,
urlAppSlug,
router,
updating,
workspaceSlug,
orgSlug,
projectLoading,
orgLoading,
currentOrgSlug,
projectSubdomain,
urlWorkspaceSlug,
urlOrgSlug,
isPlatform,
]);
}

View File

@@ -28,16 +28,15 @@ import {
type ServiceFormValues,
} from '@/features/services/components/ServiceForm/ServiceFormTypes';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import {
useInsertRunServiceConfigMutation,
useReplaceRunServiceConfigMutation,
type ConfigRunServiceConfigInsertInput,
} from '@/utils/__generated__/graphql';
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { removeTypename } from '@/utils/helpers';
import {
useInsertRunServiceConfigMutation,
useInsertRunServiceMutation,
useReplaceRunServiceConfigMutation,
type ConfigRunServiceConfigInsertInput,
} from '@/utils/__generated__/graphql';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
@@ -57,7 +56,6 @@ export default function ServiceForm({
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { onDirtyStateChange, openDialog, closeDialog } = useDialog();
const [insertRunService] = useInsertRunServiceMutation();
const { currentProject } = useCurrentWorkspaceAndProject();
const [insertRunServiceConfig] = useInsertRunServiceConfigMutation();
const [replaceRunServiceConfig] = useReplaceRunServiceConfigMutation({
@@ -187,20 +185,11 @@ export default function ServiceForm({
// Insert service config
const {
data: {
insertRunService: { id: newServiceID, subdomain },
insertRunServiceConfig: { serviceID: newServiceID },
},
} = await insertRunService({
variables: {
object: {
appID: currentProject.id,
},
},
});
await insertRunServiceConfig({
} = await insertRunServiceConfig({
variables: {
appID: currentProject.id,
serviceID: newServiceID,
config: {
...config,
image: {
@@ -209,14 +198,14 @@ export default function ServiceForm({
image:
values.image.length > 0
? values.image
: `registry.${currentProject.region.name}.${currentProject.region.domain}/${newServiceID}`,
: `registry.${currentProject.region.name}.${currentProject.region.domain}/<uuid-to-be-generated-on-creation>`,
},
},
},
});
setDetailsServiceId(newServiceID);
setDetailsServiceSubdomain(subdomain);
setDetailsServiceSubdomain('');
}
};
@@ -322,7 +311,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>
@@ -362,7 +351,7 @@ export default function ServiceForm({
>
<InfoIcon
aria-label="Info"
className="w-4 h-4"
className="h-4 w-4"
color="primary"
/>
</Tooltip>
@@ -393,7 +382,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>
@@ -441,7 +430,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

@@ -0,0 +1,23 @@
query getProjectState($subdomain: String!) {
apps(where: { subdomain: { _eq: $subdomain } }) {
id
name
subdomain
region {
id
countryCode
name
domain
city
}
createdAt
desiredState
appStates(order_by: { createdAt: desc }, limit: 1) {
id
appId
message
stateId
createdAt
}
}
}

View File

@@ -1,6 +0,0 @@
mutation insertRunService($object: run_service_insert_input!) {
insertRunService(object: $object) {
id
subdomain
}
}

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