Compare commits
32 Commits
@nhost/das
...
@nhost/das
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11f9ed7507 | ||
|
|
ac6d1b6e01 | ||
|
|
77fba27d12 | ||
|
|
7163854767 | ||
|
|
66bd4504d7 | ||
|
|
a03fb2cf82 | ||
|
|
87a37cfc08 | ||
|
|
6fb0cc27aa | ||
|
|
2c33051f83 | ||
|
|
a9413af6e0 | ||
|
|
f4f0353f2e | ||
|
|
defffd8bc4 | ||
|
|
614c20cbbf | ||
|
|
aef4a0a4fc | ||
|
|
d0c9f4cd17 | ||
|
|
e2646cab55 | ||
|
|
d5077c7ca4 | ||
|
|
c6d5c5cc8c | ||
|
|
f1d9b472d1 | ||
|
|
c6dc7f44df | ||
|
|
3cea460c36 | ||
|
|
4c351714f5 | ||
|
|
3143d66a8e | ||
|
|
8512a7f181 | ||
|
|
e503b8fe8b | ||
|
|
304065ae22 | ||
|
|
68e0622eb0 | ||
|
|
70c6834636 | ||
|
|
a7bde37bba | ||
|
|
1bc615beca | ||
|
|
a58c5cfc96 | ||
|
|
c61228e45d |
@@ -1,5 +1,67 @@
|
||||
# @nhost/dashboard
|
||||
|
||||
## 1.17.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 77fba27: fix: postgres version validation when activating ai in ai settings page
|
||||
- ac6d1b6: feat: use name instead of awsName
|
||||
|
||||
## 1.16.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 87a37cf: fix: remove unnecessary isPlatform check from verify button disable logic on custom domains
|
||||
- @nhost/react-apollo@12.0.2
|
||||
- @nhost/nextjs@2.1.16
|
||||
|
||||
## 1.16.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a9413af: fix: update `GetAllWorkspacesAndProjects` query polling to use exponential backoff
|
||||
- @nhost/react-apollo@12.0.1
|
||||
- @nhost/nextjs@2.1.15
|
||||
|
||||
## 1.16.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react-apollo@12.0.0
|
||||
- @nhost/nextjs@2.1.14
|
||||
|
||||
## 1.16.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- c6d5c5c: feat: add toggle switch to enable/disable public access in the database settings
|
||||
|
||||
## 1.15.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react-apollo@11.0.4
|
||||
- @nhost/nextjs@2.1.13
|
||||
|
||||
## 1.15.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react-apollo@11.0.3
|
||||
- @nhost/nextjs@2.1.12
|
||||
|
||||
## 1.15.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- a7bde37: feat: send metadata in the edit form
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1bc615b: feat: improve error message handling in `ErrorToast` component
|
||||
- @nhost/react-apollo@11.0.2
|
||||
- @nhost/nextjs@2.1.11
|
||||
|
||||
## 1.14.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
2
dashboard/e2e/e2e-tests-project/.gitignore
vendored
Normal file
2
dashboard/e2e/e2e-tests-project/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.secrets
|
||||
.nhost
|
||||
1
dashboard/e2e/e2e-tests-project/nhost/config.yaml
Normal file
1
dashboard/e2e/e2e-tests-project/nhost/config.yaml
Normal file
@@ -0,0 +1 @@
|
||||
version: 3
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Потвърдете смяната на вашия имейл</h2>
|
||||
<p>Използвайте посочения линк, за да повърдите смяната на имейл:</p>
|
||||
<p>
|
||||
<a href="${link}">
|
||||
Смени имейл
|
||||
</a>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
Потвърждение за смяна на имейл
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Потвърдете вашия имейл</h2>
|
||||
<p>Използвайте посочения линк, за да потвърдите вашия имейл:</p>
|
||||
<p>
|
||||
<a href="${link}">
|
||||
Потвърдете имейл
|
||||
</a>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
Потвърждаване на имейл
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Смяна на парола</h2>
|
||||
<p>Използвайте посочения линк, за да смените вашата парола:</p>
|
||||
<p>
|
||||
<a href="${link}">
|
||||
Смяна на парола
|
||||
</a>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
Смяна на парола
|
||||
@@ -0,0 +1 @@
|
||||
Вашият код е ${code}.
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Магически линк за вход</h2>
|
||||
<p>Използвайте посочения линк за защитен и бърз вход:</p>
|
||||
<p>
|
||||
<a href="${link}">
|
||||
Вход
|
||||
</a>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
Магически линк за вход
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Potvrzení změny emailové adresy</h2>
|
||||
<p>Použijte tento odkaz k potvrzení změny emailové adresy:</p>
|
||||
<p>
|
||||
<a href="${link}">
|
||||
Změnit email
|
||||
</a>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
Změna vaší emailové adresy
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Ověření emailové adresy</h2>
|
||||
<p>Použijte tento odkaz k ověření vaší emailové adresy:</p>
|
||||
<p>
|
||||
<a href="${link}">
|
||||
Ověřit emailovou adresu
|
||||
</a>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
Ověření vaší emailové adresy
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Obnova hesla</h2>
|
||||
<p>Použijte tento odkaz k obnovení vašeho hesla:</p>
|
||||
<p>
|
||||
<a href="${link}">
|
||||
Obnova hesla
|
||||
</a>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
Obnova hesla
|
||||
@@ -0,0 +1 @@
|
||||
Váš kód je ${code}.
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Magický odkaz</h2>
|
||||
<p>Použijte tento odkaz k bezpečnému přihlášení:</p>
|
||||
<p>
|
||||
<a href="${link}">
|
||||
Přihlášení
|
||||
</a>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
Bezpečný odkaz k přihlášení
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Confirm Email Change</h2>
|
||||
<p>Use this link to confirm changing email:</p>
|
||||
<p>
|
||||
<a href="${link}">
|
||||
Change email
|
||||
</a>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
Change your email address
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Verify Email</h2>
|
||||
<p>Use this link to verify your email:</p>
|
||||
<p>
|
||||
<a href="${link}">
|
||||
Verify Email
|
||||
</a>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
Verify your email
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Reset Password</h2>
|
||||
<p>Use this link to reset your password:</p>
|
||||
<p>
|
||||
<a href="${link}">
|
||||
Reset password
|
||||
</a>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
Reset your password
|
||||
@@ -0,0 +1 @@
|
||||
Your code is ${code}.
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Magic Link</h2>
|
||||
<p>Use this link to securely sign in:</p>
|
||||
<p>
|
||||
<a href="${link}">
|
||||
Sign In
|
||||
</a>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
Secure sign-in link
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Confirmar cambio de correo electrónico</h2>
|
||||
<p>Utiliza el siguiente enlace para confirmar el cambio de correo:</p>
|
||||
<p>
|
||||
<a href="${link}">
|
||||
Cambiar correo electrónico
|
||||
</a>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
Cambiar dirección de correo electrónico
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Verificar correo electrónico</h2>
|
||||
<p>Utilza el siguiente enlace para verificar tu correo:</p>
|
||||
<p>
|
||||
<a href="${link}">
|
||||
Verificar correo electrónico
|
||||
</a>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
Verifica tu correo electrónico
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Recuperar contraseña</h2>
|
||||
<p>Utiliza el siguiente enlace para recuperar tu contraseña:</p>
|
||||
<p>
|
||||
<a href="${link}">
|
||||
Recuperar contraseña
|
||||
</a>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
Recuperar contraseña
|
||||
@@ -0,0 +1 @@
|
||||
Tu código es ${code}.
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Enlace mágico</h2>
|
||||
<p>Utiliza este enlace para iniciar sesión de forma segura:</p>
|
||||
<p>
|
||||
<a href="${link}">
|
||||
Iniciar sesión
|
||||
</a>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
Enlace de acceso seguro
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Confirmer changement de courriel</h2>
|
||||
<p>Utilisez ce lien pour confirmer le changement de courriel :</p>
|
||||
<p>
|
||||
<a href="${link}">
|
||||
Changer courriel
|
||||
</a>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
Changez votre adresse courriel
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Vérifiez votre courriel</h2>
|
||||
<p>Utilisez ce lien pour vérifier votre courriel :</p>
|
||||
<p>
|
||||
<a href="${link}">
|
||||
Vérifier courriel
|
||||
</a>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
Vérifier votre courriel
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Réinitialiser votre mot de passe</h2>
|
||||
<p>Utilisez ce lien pour réinitialiser votre mot de passe :</p>
|
||||
<p>
|
||||
<a href="${link}">
|
||||
Réinitialiser mot de passe
|
||||
</a>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
Réinitialiser votre mot de passe
|
||||
@@ -0,0 +1 @@
|
||||
Votre code est ${code}.
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Lien magique</h2>
|
||||
<p>Utilisez ce lien pour vous connecter de façon sécurisée :</p>
|
||||
<p>
|
||||
<a href="${link}">
|
||||
Connexion
|
||||
</a>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
Lien de connexion sécurisé
|
||||
@@ -0,0 +1,8 @@
|
||||
${link},
|
||||
${displayName},
|
||||
${email},
|
||||
${ticket},
|
||||
${redirectTo},
|
||||
${serverUrl},
|
||||
${clientUrl},
|
||||
${locale},
|
||||
@@ -0,0 +1 @@
|
||||
${link}, ${displayName}, ${email}, ${ticket}, ${redirectTo}, ${serverUrl}, ${clientUrl}, ${locale}
|
||||
151
dashboard/e2e/e2e-tests-project/nhost/nhost.toml
Normal file
151
dashboard/e2e/e2e-tests-project/nhost/nhost.toml
Normal file
@@ -0,0 +1,151 @@
|
||||
[global]
|
||||
|
||||
[hasura]
|
||||
version = 'v2.33.4-ce'
|
||||
adminSecret = '{{ secrets.HASURA_GRAPHQL_ADMIN_SECRET }}'
|
||||
webhookSecret = '{{ secrets.NHOST_WEBHOOK_SECRET }}'
|
||||
|
||||
[[hasura.jwtSecrets]]
|
||||
type = 'HS256'
|
||||
key = '{{ secrets.HASURA_GRAPHQL_JWT_SECRET }}'
|
||||
|
||||
[hasura.settings]
|
||||
corsDomain = ['*']
|
||||
devMode = true
|
||||
enableAllowList = false
|
||||
enableConsole = true
|
||||
enableRemoteSchemaPermissions = false
|
||||
enabledAPIs = ['metadata', 'graphql', 'pgdump', 'config']
|
||||
liveQueriesMultiplexedRefetchInterval = 1000
|
||||
stringifyNumericTypes = false
|
||||
|
||||
[hasura.logs]
|
||||
level = 'warn'
|
||||
|
||||
[hasura.events]
|
||||
httpPoolSize = 100
|
||||
|
||||
[functions]
|
||||
[functions.node]
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.24.1'
|
||||
|
||||
[auth.elevatedPrivileges]
|
||||
mode = 'disabled'
|
||||
|
||||
[auth.redirections]
|
||||
clientUrl = 'http://localhost:3000'
|
||||
|
||||
[auth.signUp]
|
||||
enabled = true
|
||||
disableNewUsers = false
|
||||
|
||||
[auth.user]
|
||||
[auth.user.roles]
|
||||
default = 'user'
|
||||
allowed = ['user', 'me']
|
||||
|
||||
[auth.user.locale]
|
||||
default = 'en'
|
||||
allowed = ['en']
|
||||
|
||||
[auth.user.gravatar]
|
||||
enabled = true
|
||||
default = 'blank'
|
||||
rating = 'g'
|
||||
|
||||
[auth.user.email]
|
||||
|
||||
[auth.user.emailDomains]
|
||||
|
||||
[auth.session]
|
||||
[auth.session.accessToken]
|
||||
expiresIn = 900
|
||||
|
||||
[auth.session.refreshToken]
|
||||
expiresIn = 2592000
|
||||
|
||||
[auth.method]
|
||||
[auth.method.anonymous]
|
||||
enabled = false
|
||||
|
||||
[auth.method.emailPasswordless]
|
||||
enabled = false
|
||||
|
||||
[auth.method.emailPassword]
|
||||
hibpEnabled = false
|
||||
emailVerificationRequired = true
|
||||
passwordMinLength = 9
|
||||
|
||||
[auth.method.smsPasswordless]
|
||||
enabled = false
|
||||
|
||||
[auth.method.oauth]
|
||||
[auth.method.oauth.apple]
|
||||
enabled = false
|
||||
|
||||
[auth.method.oauth.azuread]
|
||||
tenant = 'common'
|
||||
enabled = false
|
||||
|
||||
[auth.method.oauth.bitbucket]
|
||||
enabled = false
|
||||
|
||||
[auth.method.oauth.discord]
|
||||
enabled = false
|
||||
|
||||
[auth.method.oauth.facebook]
|
||||
enabled = false
|
||||
|
||||
[auth.method.oauth.github]
|
||||
enabled = false
|
||||
|
||||
[auth.method.oauth.gitlab]
|
||||
enabled = false
|
||||
|
||||
[auth.method.oauth.google]
|
||||
enabled = false
|
||||
|
||||
[auth.method.oauth.linkedin]
|
||||
enabled = false
|
||||
|
||||
[auth.method.oauth.spotify]
|
||||
enabled = false
|
||||
|
||||
[auth.method.oauth.strava]
|
||||
enabled = false
|
||||
|
||||
[auth.method.oauth.twitch]
|
||||
enabled = false
|
||||
|
||||
[auth.method.oauth.twitter]
|
||||
enabled = false
|
||||
|
||||
[auth.method.oauth.windowslive]
|
||||
enabled = false
|
||||
|
||||
[auth.method.oauth.workos]
|
||||
enabled = false
|
||||
|
||||
[auth.method.webauthn]
|
||||
enabled = false
|
||||
|
||||
[auth.method.webauthn.attestation]
|
||||
timeout = 60000
|
||||
|
||||
[auth.totp]
|
||||
enabled = false
|
||||
|
||||
[postgres]
|
||||
version = '14.6-20240129-1'
|
||||
|
||||
[provider]
|
||||
|
||||
[storage]
|
||||
version = '0.6.0'
|
||||
|
||||
[observability]
|
||||
[observability.grafana]
|
||||
adminPassword = '{{ secrets.GRAFANA_ADMIN_PASSWORD }}'
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/dashboard",
|
||||
"version": "1.14.0",
|
||||
"version": "1.17.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
|
||||
@@ -29,10 +29,10 @@ const getInternalErrorMessage = (
|
||||
|
||||
if (error.name === 'ApolloError') {
|
||||
// @ts-ignore
|
||||
const internalError = error.graphQLErrors?.[0]?.extensions?.internal as {
|
||||
error: { message: string };
|
||||
};
|
||||
return internalError?.error?.message || null;
|
||||
const graphqlError = error.graphQLErrors?.[0];
|
||||
const graphqlExtensionsError = graphqlError?.extensions?.internal
|
||||
?.error as { message: string };
|
||||
return graphqlError.message || graphqlExtensionsError?.message || null;
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
|
||||
@@ -32,8 +32,7 @@ import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import * as Yup from 'yup';
|
||||
import { DisableAIServiceConfirmationDialog } from './DisableAIServiceConfirmationDialog';
|
||||
|
||||
const MIN_POSTGRES_VERSION_SUPPORTING_AI = '14.6-20231018-1';
|
||||
import { isPostgresVersionValidForAI } from '@/features/ai/settings/utils/isPostgresVersionValidForAI';
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
version: Yup.object({
|
||||
@@ -165,7 +164,7 @@ export default function AISettings() {
|
||||
]);
|
||||
|
||||
const toggleAIService = async (enabled: boolean) => {
|
||||
if (postgresVersion < MIN_POSTGRES_VERSION_SUPPORTING_AI) {
|
||||
if (!isPostgresVersionValidForAI(postgresVersion)) {
|
||||
toast.error(
|
||||
'In order to enable the AI service you need to update your database version to 14.6-20231018-1 or newer.',
|
||||
{
|
||||
@@ -495,3 +494,4 @@ export default function AISettings() {
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default as isPostgresVersionValidForAI } from './isPostgresVersionValidForAI';
|
||||
@@ -0,0 +1,22 @@
|
||||
import { test, vi } from 'vitest';
|
||||
|
||||
import isPostgresVersionValidForAI from './isPostgresVersionValidForAI';
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
test('greater than minimum version, minor version with two digits, should be valid', () => {
|
||||
const postgresVersion = '14.11-20240515-1';
|
||||
expect(isPostgresVersionValidForAI(postgresVersion)).toBe(true);
|
||||
});
|
||||
|
||||
test('less than minimum version, should be invalid', () => {
|
||||
const postgresVersion = '14.6-20221110-1';
|
||||
expect(isPostgresVersionValidForAI(postgresVersion)).toBe(false);
|
||||
});
|
||||
|
||||
test('equal to minimum version, should be valid', () => {
|
||||
const postgresVersion = '14.6-20231018-1';
|
||||
expect(isPostgresVersionValidForAI(postgresVersion)).toBe(true);
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Check if the given postgres version is valid for enabling AI in the project
|
||||
*
|
||||
* @param postgresVersion - Postgres version used in the project.
|
||||
* @returns Whether is valid for enabling AI.
|
||||
*/
|
||||
export default function isPostgresVersionValidForAI(
|
||||
postgresVersion: string,
|
||||
): boolean {
|
||||
const MIN_POSTGRES_VERSION_SUPPORTING_AI = '14.6-20231018-1';
|
||||
|
||||
if (/^14\.6-/.test(postgresVersion)) {
|
||||
return postgresVersion >= MIN_POSTGRES_VERSION_SUPPORTING_AI;
|
||||
}
|
||||
|
||||
// Note: No need to account for versions less than 14.6
|
||||
return true;
|
||||
}
|
||||
@@ -31,9 +31,10 @@ import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useTheme } from '@mui/material';
|
||||
import { format } from 'date-fns';
|
||||
import kebabCase from 'just-kebab-case';
|
||||
import debounce from 'lodash.debounce';
|
||||
import Image from 'next/image';
|
||||
import type { RemoteAppUser } from 'pages/[workspaceSlug]/[appSlug]/users';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -76,6 +77,21 @@ export const EditUserFormValidationSchema = Yup.object({
|
||||
locale: Yup.string(),
|
||||
defaultRole: Yup.string(),
|
||||
roles: Yup.array().of(Yup.boolean()),
|
||||
metadata: Yup.string().test(
|
||||
'is-valid-json',
|
||||
'Metadata must be valid JSON or empty',
|
||||
(value) => {
|
||||
if (value === '') {
|
||||
return true;
|
||||
} // Allow empty string as valid input
|
||||
try {
|
||||
JSON.parse(value);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
),
|
||||
});
|
||||
|
||||
export type EditUserFormValues = Yup.InferType<
|
||||
@@ -116,14 +132,55 @@ export default function EditUserForm({
|
||||
locale: user.locale,
|
||||
defaultRole: user.defaultRole,
|
||||
roles: roles.map((role) => Object.values(role)[0]),
|
||||
metadata: user?.metadata ? JSON.stringify(user.metadata, null, 2) : '',
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
register,
|
||||
setError,
|
||||
clearErrors,
|
||||
formState: { errors, dirtyFields, isSubmitting, isValidating },
|
||||
} = form;
|
||||
|
||||
const handleMetadataError = useMemo(() => {
|
||||
const debouncedSetError = debounce((value) => {
|
||||
try {
|
||||
JSON.parse(value);
|
||||
// Only set an error if JSON parsing fails
|
||||
} catch (error) {
|
||||
setError('metadata', {
|
||||
type: 'manual',
|
||||
message: 'Invalid JSON format',
|
||||
});
|
||||
}
|
||||
}, 500);
|
||||
|
||||
return {
|
||||
call: debouncedSetError,
|
||||
cancel: debouncedSetError.cancel, // lodash debounce provides a cancel method to stop the delayed function
|
||||
};
|
||||
}, [setError]);
|
||||
|
||||
const handleMetadataChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { value } = event.target;
|
||||
if (value === '') {
|
||||
clearErrors('metadata'); // Clear errors when the input is explicitly cleared
|
||||
handleMetadataError.cancel(); // Cancel any debounced error checks
|
||||
} else {
|
||||
try {
|
||||
JSON.parse(value);
|
||||
clearErrors('metadata'); // Clear errors when valid JSON is entered
|
||||
handleMetadataError.cancel(); // Cancel pending debounced error checks
|
||||
} catch (error) {
|
||||
handleMetadataError.call(value); // Call the debounced error setter
|
||||
}
|
||||
}
|
||||
},
|
||||
[clearErrors, handleMetadataError],
|
||||
);
|
||||
|
||||
const isDirty = Object.keys(dirtyFields).length > 0;
|
||||
|
||||
useEffect(() => {
|
||||
@@ -467,6 +524,28 @@ export default function EditUserForm({
|
||||
</div>
|
||||
</Box>
|
||||
)}
|
||||
<Box component="section" className="grid grid-flow-row gap-8 p-6">
|
||||
<Input
|
||||
{...register('metadata', { onChange: handleMetadataChange })}
|
||||
id="metadata"
|
||||
label="Metadata"
|
||||
variant="inline"
|
||||
hideEmptyHelperText
|
||||
error={!!errors.metadata}
|
||||
fullWidth
|
||||
multiline
|
||||
inputProps={{
|
||||
className: 'resize-y min-h-[130px]',
|
||||
}}
|
||||
helperText={
|
||||
errors.metadata
|
||||
? errors.metadata.message
|
||||
: 'Enter valid JSON. This can be a number, boolean, array, or object.'
|
||||
}
|
||||
maxRows={100}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box className="grid w-full flex-shrink-0 snap-end grid-flow-col justify-between gap-3 place-self-end border-t-1 p-2">
|
||||
|
||||
@@ -113,6 +113,9 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
|
||||
phoneNumber: values.phoneNumber,
|
||||
phoneNumberVerified: values.phoneNumberVerified,
|
||||
locale: values.locale,
|
||||
...(values?.metadata !== undefined && values.metadata !== ''
|
||||
? { metadata: JSON.parse(values.metadata) }
|
||||
: { metadata: null }),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { Alert } from '@/components/ui/v2/Alert';
|
||||
@@ -7,11 +11,32 @@ import type { InputProps } from '@/components/ui/v2/Input';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { InputAdornment } from '@/components/ui/v2/InputAdornment';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { useGetPostgresSettingsQuery } from '@/utils/__generated__/graphql';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
useGetPostgresSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const databasePublicAccessValidationSchema = Yup.object({
|
||||
enablePublicAccess: Yup.bool(),
|
||||
});
|
||||
|
||||
type DatabasePublicAccessFormValues = Yup.InferType<
|
||||
typeof databasePublicAccessValidationSchema
|
||||
>;
|
||||
|
||||
export default function DatabaseConnectionInfo() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
|
||||
const { data, loading, error } = useGetPostgresSettingsQuery({
|
||||
@@ -19,6 +44,61 @@ export default function DatabaseConnectionInfo() {
|
||||
fetchPolicy: 'cache-only',
|
||||
});
|
||||
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const enablePublicAccess =
|
||||
!!data?.config?.postgres?.resources?.enablePublicAccess;
|
||||
|
||||
const form = useForm<DatabasePublicAccessFormValues>({
|
||||
reValidateMode: 'onSubmit',
|
||||
defaultValues: {
|
||||
enablePublicAccess,
|
||||
},
|
||||
resolver: yupResolver(databasePublicAccessValidationSchema),
|
||||
});
|
||||
|
||||
async function handleSubmit(formValues: DatabasePublicAccessFormValues) {
|
||||
const updateConfigPromise = updateConfig({
|
||||
variables: {
|
||||
appId: currentProject.id,
|
||||
config: {
|
||||
postgres: {
|
||||
resources: {
|
||||
enablePublicAccess: formValues.enablePublicAccess,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Database settings are being updated...',
|
||||
successMessage: 'Database settings have been updated successfully.',
|
||||
errorMessage:
|
||||
"An error occurred while trying to update the project's database settings.",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -76,49 +156,72 @@ export default function DatabaseConnectionInfo() {
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<SettingsContainer
|
||||
title="Connection Info"
|
||||
description="Connect directly to the Postgres database with this information."
|
||||
slotProps={{ footer: { className: 'hidden' } }}
|
||||
className="grid grid-cols-6 gap-4 pb-2"
|
||||
>
|
||||
{settingsDatabaseCustomInputs.map(
|
||||
({ name, label, className, value: inputValue }) => (
|
||||
<Input
|
||||
key={name}
|
||||
label={label}
|
||||
required
|
||||
disabled
|
||||
value={inputValue}
|
||||
className={className}
|
||||
slotProps={{ inputRoot: { className: '!pr-8 truncate' } }}
|
||||
fullWidth
|
||||
hideEmptyHelperText
|
||||
endAdornment={
|
||||
<InputAdornment position="end" className="absolute right-2">
|
||||
<Button
|
||||
sx={{ minWidth: 0, padding: 0 }}
|
||||
color="secondary"
|
||||
variant="borderless"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
copy(inputValue as string, `${label}`);
|
||||
}}
|
||||
>
|
||||
<CopyIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</InputAdornment>
|
||||
}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
const { formState } = form;
|
||||
|
||||
<Alert severity="info" className="col-span-6 text-left">
|
||||
To connect to the Postgres database directly, generate a new password,
|
||||
securely save it, and then modify your connection string with the newly
|
||||
created password.
|
||||
</Alert>
|
||||
</SettingsContainer>
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<SettingsContainer
|
||||
title="Public access"
|
||||
description={
|
||||
enablePublicAccess
|
||||
? 'Connect directly to the Postgres database with this information.'
|
||||
: 'Enable public access to your Postgres database.'
|
||||
}
|
||||
slotProps={{
|
||||
submitButton: {
|
||||
disabled: !formState.isDirty || maintenanceActive,
|
||||
loading: formState.isSubmitting,
|
||||
},
|
||||
}}
|
||||
className="grid grid-cols-6 gap-4 pb-2"
|
||||
switchId="enablePublicAccess"
|
||||
showSwitch
|
||||
>
|
||||
{enablePublicAccess && (
|
||||
<>
|
||||
{settingsDatabaseCustomInputs.map(
|
||||
({ name, label, className, value: inputValue }) => (
|
||||
<Input
|
||||
key={name}
|
||||
label={label}
|
||||
required
|
||||
disabled
|
||||
value={inputValue}
|
||||
className={className}
|
||||
slotProps={{ inputRoot: { className: '!pr-8 truncate' } }}
|
||||
fullWidth
|
||||
hideEmptyHelperText
|
||||
endAdornment={
|
||||
<InputAdornment
|
||||
position="end"
|
||||
className="absolute right-2"
|
||||
>
|
||||
<Button
|
||||
sx={{ minWidth: 0, padding: 0 }}
|
||||
color="secondary"
|
||||
variant="borderless"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
copy(inputValue as string, `${label}`);
|
||||
}}
|
||||
>
|
||||
<CopyIcon className="w-4 h-4" />
|
||||
</Button>
|
||||
</InputAdornment>
|
||||
}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
<Alert severity="info" className="col-span-6 text-left">
|
||||
To connect to the Postgres database directly, generate a new
|
||||
password, securely save it, and then modify your connection
|
||||
string with the newly created password.
|
||||
</Alert>
|
||||
</>
|
||||
)}
|
||||
</SettingsContainer>
|
||||
</Form>
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ query GetPostgresSettings($appId: uuid!) {
|
||||
storage {
|
||||
capacity
|
||||
}
|
||||
enablePublicAccess
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ import { useGetAnnouncementsQuery } from '@/utils/__generated__/graphql';
|
||||
import formatDistance from 'date-fns/formatDistance';
|
||||
|
||||
export default function Announcements() {
|
||||
const { data, loading, error } = useGetAnnouncementsQuery();
|
||||
const { data, loading, error } = useGetAnnouncementsQuery({
|
||||
fetchPolicy: 'cache-first',
|
||||
});
|
||||
|
||||
const announcements = data?.announcements || [];
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ export default function useCurrentWorkspaceAndProject(): UseCurrentWorkspaceAndP
|
||||
id: null,
|
||||
countryCode: null,
|
||||
city: null,
|
||||
awsName: null,
|
||||
name: null,
|
||||
domain: null,
|
||||
},
|
||||
createdAt: new Date().toISOString(),
|
||||
|
||||
@@ -31,7 +31,7 @@ afterEach(() => {
|
||||
|
||||
const region: ProjectFragment['region'] = {
|
||||
id: '1',
|
||||
awsName: 'eu-west-1',
|
||||
name: 'eu-west-1',
|
||||
domain: 'nhost.run',
|
||||
city: 'Dublin',
|
||||
countryCode: 'IE',
|
||||
|
||||
@@ -89,7 +89,7 @@ export default function generateAppServiceUrl(
|
||||
const constructedDomain = [
|
||||
subdomain,
|
||||
service,
|
||||
region?.awsName,
|
||||
region?.name,
|
||||
region?.domain || 'nhost.run',
|
||||
]
|
||||
.filter(Boolean)
|
||||
|
||||
@@ -165,7 +165,7 @@ export default function AuthDomain() {
|
||||
<VerifyDomain
|
||||
recordType="CNAME"
|
||||
hostname={auth_fqdn}
|
||||
value={`lb.${currentProject.region.awsName}.${currentProject.region.domain}.`}
|
||||
value={`lb.${currentProject.region.name}.${currentProject.region.domain}.`}
|
||||
onHostNameVerified={() => setIsVerified(true)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -167,7 +167,7 @@ export default function HasuraDomain() {
|
||||
<VerifyDomain
|
||||
recordType="CNAME"
|
||||
hostname={hasura_fqdn}
|
||||
value={`lb.${currentProject.region.awsName}.${currentProject.region.domain}.`}
|
||||
value={`lb.${currentProject.region.name}.${currentProject.region.domain}.`}
|
||||
onHostNameVerified={() => setIsVerified(true)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -162,7 +162,7 @@ export default function RunServicePortDomain({
|
||||
<VerifyDomain
|
||||
recordType="CNAME"
|
||||
hostname={runServicePortFQDN}
|
||||
value={`lb.${currentProject.region.awsName}.${currentProject.region.domain}.`}
|
||||
value={`lb.${currentProject.region.name}.${currentProject.region.domain}.`}
|
||||
onHostNameVerified={() => setIsVerified(true)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -169,7 +169,7 @@ export default function ServerlessFunctionsDomain() {
|
||||
<VerifyDomain
|
||||
recordType="CNAME"
|
||||
hostname={functions_fqdn}
|
||||
value={`lb.${currentProject.region.awsName}.${currentProject.region.domain}.`}
|
||||
value={`lb.${currentProject.region.name}.${currentProject.region.domain}.`}
|
||||
onHostNameVerified={() => setIsVerified(true)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -131,7 +131,7 @@ export default function VerifyDomain({
|
||||
</div>
|
||||
{isPlatform ? (
|
||||
<Button
|
||||
disabled={loading || !hostname || isPlatform}
|
||||
disabled={loading || !hostname}
|
||||
onClick={handleVerifyDomain}
|
||||
className="mt-4 sm:absolute sm:bottom-0 sm:right-0 sm:mt-0"
|
||||
>
|
||||
|
||||
@@ -101,7 +101,7 @@ export default function SystemEnvironmentVariableSettings() {
|
||||
|
||||
const systemEnvironmentVariables = [
|
||||
{ key: 'NHOST_SUBDOMAIN', value: currentProject.subdomain },
|
||||
{ key: 'NHOST_REGION', value: currentProject.region.awsName },
|
||||
{ key: 'NHOST_REGION', value: currentProject.region.name },
|
||||
{
|
||||
key: 'NHOST_HASURA_URL',
|
||||
value:
|
||||
|
||||
@@ -7,7 +7,7 @@ export default function OverviewProjectInfo() {
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const { region, subdomain } = currentProject || {};
|
||||
const isRegionAvailable =
|
||||
region?.awsName && region?.countryCode && region?.city;
|
||||
region?.name && region?.countryCode && region?.city;
|
||||
|
||||
return (
|
||||
<div className="grid grid-flow-row content-start gap-6">
|
||||
@@ -17,7 +17,7 @@ export default function OverviewProjectInfo() {
|
||||
<div className="grid grid-flow-row gap-3">
|
||||
<InfoCard
|
||||
title="Region"
|
||||
value={region?.awsName}
|
||||
value={region?.name}
|
||||
customValue={
|
||||
region?.countryCode &&
|
||||
region?.city && (
|
||||
@@ -30,7 +30,7 @@ export default function OverviewProjectInfo() {
|
||||
/>
|
||||
|
||||
<Text className="truncate text-sm font-medium">
|
||||
{region.city} ({region.awsName})
|
||||
{region.city} ({region.name})
|
||||
</Text>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -190,7 +190,7 @@ export default function ServiceForm({
|
||||
image:
|
||||
values.image.length > 0
|
||||
? values.image
|
||||
: `registry.${currentProject.region.awsName}.${currentProject.region.domain}/${newServiceID}`,
|
||||
: `registry.${currentProject.region.name}.${currentProject.region.domain}/${newServiceID}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -361,7 +361,7 @@ export default function ServiceForm({
|
||||
{isPlatform && serviceID && serviceImage && (
|
||||
<InfoCard
|
||||
title="Private registry"
|
||||
value={`registry.${currentProject.region.awsName}.${currentProject.region.domain}/${serviceID}`}
|
||||
value={`registry.${currentProject.region.name}.${currentProject.region.domain}/${serviceID}`}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ export default function PortsFormSection() {
|
||||
const getPortURL = (_port: string | number, subdomain: string) => {
|
||||
const port = Number(_port) > 0 ? Number(_port) : '[port]';
|
||||
|
||||
return `https://${subdomain}-${port}.svc.${currentProject?.region.awsName}.${currentProject?.region.domain}`;
|
||||
return `https://${subdomain}-${port}.svc.${currentProject?.region.name}.${currentProject?.region.domain}`;
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function ServiceDetailsDialog({
|
||||
const getPortURL = (_port: string | number) => {
|
||||
const port = Number(_port) > 0 ? Number(_port) : '[port]';
|
||||
|
||||
return `https://${subdomain}-${port}.svc.${currentProject?.region.awsName}.${currentProject?.region.domain}`;
|
||||
return `https://${subdomain}-${port}.svc.${currentProject?.region.name}.${currentProject?.region.domain}`;
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -44,7 +44,7 @@ export default function ServiceDetailsDialog({
|
||||
<Text color="secondary">Private registry</Text>
|
||||
<InfoCard
|
||||
title=""
|
||||
value={`registry.${currentProject.region.awsName}.${currentProject.region.domain}/${serviceID}`}
|
||||
value={`registry.${currentProject.region.name}.${currentProject.region.domain}/${serviceID}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ mutation UpdateConfig($appId: uuid!, $config: ConfigConfigUpdateInput!) {
|
||||
storage {
|
||||
capacity
|
||||
}
|
||||
enablePublicAccess
|
||||
}
|
||||
}
|
||||
ai {
|
||||
|
||||
@@ -39,7 +39,7 @@ fragment Project on apps {
|
||||
region {
|
||||
id
|
||||
countryCode
|
||||
awsName
|
||||
name
|
||||
domain
|
||||
city
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ fragment RemoteAppGetUsers on users {
|
||||
defaultRole
|
||||
lastSeen
|
||||
locale
|
||||
metadata
|
||||
roles {
|
||||
id
|
||||
role
|
||||
|
||||
@@ -7,41 +7,55 @@ import { Button } from '@/components/ui/v2/Button';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { WorkspaceAndProjectList } from '@/features/projects/common/components/WorkspaceAndProjectList';
|
||||
import { WorkspaceSidebar } from '@/features/projects/common/components/WorkspaceSidebar';
|
||||
import { useGetAllWorkspacesAndProjectsQuery } from '@/utils/__generated__/graphql';
|
||||
import {
|
||||
useGetAllWorkspacesAndProjectsQuery,
|
||||
type GetAllWorkspacesAndProjectsQuery,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import { NetworkStatus } from '@apollo/client';
|
||||
import { darken } from '@mui/system';
|
||||
import { useUserData } from '@nhost/nextjs';
|
||||
import NavLink from 'next/link';
|
||||
import type { ReactElement } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useState, type ReactElement } from 'react';
|
||||
|
||||
export default function IndexPage() {
|
||||
const user = useUserData();
|
||||
const { data, loading, startPolling, stopPolling } =
|
||||
const [, setPollInterval] = useState(1_000);
|
||||
|
||||
// keep polling for workspaces until there is a workspace available.
|
||||
// We do this because when a user signs up a workspace is created automatically
|
||||
// and the serverless function can take some time to complete.
|
||||
const { data, startPolling, stopPolling, networkStatus } =
|
||||
useGetAllWorkspacesAndProjectsQuery({
|
||||
skip: !user,
|
||||
notifyOnNetworkStatusChange: true,
|
||||
onError: () => {
|
||||
// When there's an error (graphql, network error) apply an exponential backoff strategy
|
||||
setPollInterval((prevInterval) => {
|
||||
const newInterval = Math.min(60_000, prevInterval * 2);
|
||||
startPolling(newInterval);
|
||||
return newInterval;
|
||||
});
|
||||
},
|
||||
onCompleted: (queryData: GetAllWorkspacesAndProjectsQuery) => {
|
||||
if (!queryData?.workspaces.length) {
|
||||
setPollInterval(1000);
|
||||
startPolling(1000);
|
||||
} else {
|
||||
setPollInterval(0);
|
||||
stopPolling();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// keep showing loading indicator while polling
|
||||
const loading = networkStatus === NetworkStatus.loading;
|
||||
|
||||
const numberOfProjects = data?.workspaces.reduce(
|
||||
(projectCount, currentWorkspace) =>
|
||||
projectCount + currentWorkspace.projects.length,
|
||||
0,
|
||||
);
|
||||
|
||||
// keep polling for workspaces until there is a workspace available.
|
||||
// We do this because when a user signs up a workspace is created automatically
|
||||
// and the serverless function can take some time to complete.
|
||||
useEffect(() => {
|
||||
startPolling(1000);
|
||||
}, [startPolling]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!data?.workspaces.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
stopPolling();
|
||||
}, [data?.workspaces, stopPolling]);
|
||||
|
||||
if ((!data && loading) || !user) {
|
||||
return <LoadingScreen />;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ export const mockApplication: Project = {
|
||||
appStates: [],
|
||||
subdomain: '',
|
||||
region: {
|
||||
awsName: 'us-east-1',
|
||||
name: 'us-east-1',
|
||||
city: 'New York',
|
||||
countryCode: 'US',
|
||||
id: '1',
|
||||
|
||||
154
dashboard/src/utils/__generated__/graphql.ts
generated
154
dashboard/src/utils/__generated__/graphql.ts
generated
@@ -1804,6 +1804,7 @@ export type ConfigPostgresInsertInput = {
|
||||
export type ConfigPostgresResources = {
|
||||
__typename?: 'ConfigPostgresResources';
|
||||
compute?: Maybe<ConfigResourcesCompute>;
|
||||
enablePublicAccess?: Maybe<Scalars['Boolean']>;
|
||||
networking?: Maybe<ConfigNetworking>;
|
||||
/** Number of replicas for a service */
|
||||
replicas?: Maybe<Scalars['ConfigUint8']>;
|
||||
@@ -1815,6 +1816,7 @@ export type ConfigPostgresResourcesComparisonExp = {
|
||||
_not?: InputMaybe<ConfigPostgresResourcesComparisonExp>;
|
||||
_or?: InputMaybe<Array<ConfigPostgresResourcesComparisonExp>>;
|
||||
compute?: InputMaybe<ConfigResourcesComputeComparisonExp>;
|
||||
enablePublicAccess?: InputMaybe<ConfigBooleanComparisonExp>;
|
||||
networking?: InputMaybe<ConfigNetworkingComparisonExp>;
|
||||
replicas?: InputMaybe<ConfigUint8ComparisonExp>;
|
||||
storage?: InputMaybe<ConfigPostgresStorageComparisonExp>;
|
||||
@@ -1822,6 +1824,7 @@ export type ConfigPostgresResourcesComparisonExp = {
|
||||
|
||||
export type ConfigPostgresResourcesInsertInput = {
|
||||
compute?: InputMaybe<ConfigResourcesComputeInsertInput>;
|
||||
enablePublicAccess?: InputMaybe<Scalars['Boolean']>;
|
||||
networking?: InputMaybe<ConfigNetworkingInsertInput>;
|
||||
replicas?: InputMaybe<Scalars['ConfigUint8']>;
|
||||
storage?: InputMaybe<ConfigPostgresStorageInsertInput>;
|
||||
@@ -1829,6 +1832,7 @@ export type ConfigPostgresResourcesInsertInput = {
|
||||
|
||||
export type ConfigPostgresResourcesUpdateInput = {
|
||||
compute?: InputMaybe<ConfigResourcesComputeUpdateInput>;
|
||||
enablePublicAccess?: InputMaybe<Scalars['Boolean']>;
|
||||
networking?: InputMaybe<ConfigNetworkingUpdateInput>;
|
||||
replicas?: InputMaybe<Scalars['ConfigUint8']>;
|
||||
storage?: InputMaybe<ConfigPostgresStorageUpdateInput>;
|
||||
@@ -2524,7 +2528,9 @@ export type ConfigSystemConfigPostgres = {
|
||||
__typename?: 'ConfigSystemConfigPostgres';
|
||||
connectionString: ConfigSystemConfigPostgresConnectionString;
|
||||
database: Scalars['String'];
|
||||
disk?: Maybe<ConfigSystemConfigPostgresDisk>;
|
||||
enabled?: Maybe<Scalars['Boolean']>;
|
||||
majorVersion?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ConfigSystemConfigPostgresComparisonExp = {
|
||||
@@ -2533,7 +2539,9 @@ export type ConfigSystemConfigPostgresComparisonExp = {
|
||||
_or?: InputMaybe<Array<ConfigSystemConfigPostgresComparisonExp>>;
|
||||
connectionString?: InputMaybe<ConfigSystemConfigPostgresConnectionStringComparisonExp>;
|
||||
database?: InputMaybe<ConfigStringComparisonExp>;
|
||||
disk?: InputMaybe<ConfigSystemConfigPostgresDiskComparisonExp>;
|
||||
enabled?: InputMaybe<ConfigBooleanComparisonExp>;
|
||||
majorVersion?: InputMaybe<ConfigStringComparisonExp>;
|
||||
};
|
||||
|
||||
export type ConfigSystemConfigPostgresConnectionString = {
|
||||
@@ -2568,16 +2576,44 @@ export type ConfigSystemConfigPostgresConnectionStringUpdateInput = {
|
||||
storage?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ConfigSystemConfigPostgresDisk = {
|
||||
__typename?: 'ConfigSystemConfigPostgresDisk';
|
||||
iops?: Maybe<Scalars['ConfigUint32']>;
|
||||
tput?: Maybe<Scalars['ConfigUint32']>;
|
||||
};
|
||||
|
||||
export type ConfigSystemConfigPostgresDiskComparisonExp = {
|
||||
_and?: InputMaybe<Array<ConfigSystemConfigPostgresDiskComparisonExp>>;
|
||||
_not?: InputMaybe<ConfigSystemConfigPostgresDiskComparisonExp>;
|
||||
_or?: InputMaybe<Array<ConfigSystemConfigPostgresDiskComparisonExp>>;
|
||||
iops?: InputMaybe<ConfigUint32ComparisonExp>;
|
||||
tput?: InputMaybe<ConfigUint32ComparisonExp>;
|
||||
};
|
||||
|
||||
export type ConfigSystemConfigPostgresDiskInsertInput = {
|
||||
iops?: InputMaybe<Scalars['ConfigUint32']>;
|
||||
tput?: InputMaybe<Scalars['ConfigUint32']>;
|
||||
};
|
||||
|
||||
export type ConfigSystemConfigPostgresDiskUpdateInput = {
|
||||
iops?: InputMaybe<Scalars['ConfigUint32']>;
|
||||
tput?: InputMaybe<Scalars['ConfigUint32']>;
|
||||
};
|
||||
|
||||
export type ConfigSystemConfigPostgresInsertInput = {
|
||||
connectionString: ConfigSystemConfigPostgresConnectionStringInsertInput;
|
||||
database: Scalars['String'];
|
||||
disk?: InputMaybe<ConfigSystemConfigPostgresDiskInsertInput>;
|
||||
enabled?: InputMaybe<Scalars['Boolean']>;
|
||||
majorVersion?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ConfigSystemConfigPostgresUpdateInput = {
|
||||
connectionString?: InputMaybe<ConfigSystemConfigPostgresConnectionStringUpdateInput>;
|
||||
database?: InputMaybe<Scalars['String']>;
|
||||
disk?: InputMaybe<ConfigSystemConfigPostgresDiskUpdateInput>;
|
||||
enabled?: InputMaybe<Scalars['Boolean']>;
|
||||
majorVersion?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ConfigSystemConfigUpdateInput = {
|
||||
@@ -2621,6 +2657,12 @@ export type ConfigUserRoleComparisonExp = {
|
||||
_nin?: InputMaybe<Array<Scalars['ConfigUserRole']>>;
|
||||
};
|
||||
|
||||
export type ContainerError = {
|
||||
__typename?: 'ContainerError';
|
||||
lastError: LastError;
|
||||
name: Scalars['String'];
|
||||
};
|
||||
|
||||
/** Boolean expression to compare columns of type "Int". All fields are combined with logical 'AND'. */
|
||||
export type Int_Comparison_Exp = {
|
||||
_eq?: InputMaybe<Scalars['Int']>;
|
||||
@@ -2647,6 +2689,13 @@ export type InvoiceSummary = {
|
||||
items: Array<InvoiceItem>;
|
||||
};
|
||||
|
||||
export type LastError = {
|
||||
__typename?: 'LastError';
|
||||
exitCode: Scalars['Int'];
|
||||
message: Scalars['String'];
|
||||
reason: Scalars['String'];
|
||||
};
|
||||
|
||||
export type Log = {
|
||||
__typename?: 'Log';
|
||||
log: Scalars['String'];
|
||||
@@ -2659,12 +2708,31 @@ export type Metrics = {
|
||||
value: Scalars['float64'];
|
||||
};
|
||||
|
||||
export type StatsDailyLiveFreeApps = {
|
||||
__typename?: 'StatsDailyLiveFreeApps';
|
||||
avg: Scalars['Int'];
|
||||
max: Scalars['Int'];
|
||||
min: Scalars['Int'];
|
||||
raw: Array<Scalars['Int']>;
|
||||
export type ProjectStatusResponse = {
|
||||
__typename?: 'ProjectStatusResponse';
|
||||
services: Array<ServiceStatus>;
|
||||
};
|
||||
|
||||
export type ReplicaStatus = {
|
||||
__typename?: 'ReplicaStatus';
|
||||
date: Scalars['Timestamp'];
|
||||
errors: Array<ContainerError>;
|
||||
ready: Scalars['Boolean'];
|
||||
};
|
||||
|
||||
export enum ServiceState {
|
||||
Error = 'Error',
|
||||
None = 'None',
|
||||
Running = 'Running',
|
||||
UpdateError = 'UpdateError',
|
||||
Updating = 'Updating'
|
||||
}
|
||||
|
||||
export type ServiceStatus = {
|
||||
__typename?: 'ServiceStatus';
|
||||
name: Scalars['String'];
|
||||
replicas: Array<ReplicaStatus>;
|
||||
state: ServiceState;
|
||||
};
|
||||
|
||||
export type StatsLiveApps = {
|
||||
@@ -11798,6 +11866,7 @@ export type Mutation_Root = {
|
||||
billingUpdatePersistentVolume: Scalars['Boolean'];
|
||||
billingUpdateReports: Scalars['Boolean'];
|
||||
billingUploadReports: Scalars['Boolean'];
|
||||
changeDatabaseVersion: Scalars['Boolean'];
|
||||
/** delete single row from the table: "apps" */
|
||||
deleteApp?: Maybe<Apps>;
|
||||
/** delete single row from the table: "app_states" */
|
||||
@@ -12483,6 +12552,14 @@ export type Mutation_RootBillingUpdateReportsArgs = {
|
||||
};
|
||||
|
||||
|
||||
/** mutation root */
|
||||
export type Mutation_RootChangeDatabaseVersionArgs = {
|
||||
appID: Scalars['uuid'];
|
||||
force?: InputMaybe<Scalars['Boolean']>;
|
||||
version: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
/** mutation root */
|
||||
export type Mutation_RootDeleteAppArgs = {
|
||||
id: Scalars['uuid'];
|
||||
@@ -15911,6 +15988,7 @@ export type Query_Root = {
|
||||
getLogsVolume: Metrics;
|
||||
getPostgresVolumeCapacity: Metrics;
|
||||
getPostgresVolumeUsage: Metrics;
|
||||
getProjectStatus: ProjectStatusResponse;
|
||||
/**
|
||||
* Returns list of label values for a given label within a range of time.
|
||||
*
|
||||
@@ -15988,12 +16066,6 @@ export type Query_Root = {
|
||||
softwareVersions: Array<Software_Versions>;
|
||||
/** fetch aggregated fields from the table: "software_versions" */
|
||||
softwareVersionsAggregate: Software_Versions_Aggregate;
|
||||
/**
|
||||
* Returns the per-day number of free live apps in the given time range, as well as the min, max and avg.
|
||||
*
|
||||
* Requests that returned a 4xx or 5xx status code are not counted as live traffic.
|
||||
*/
|
||||
statsDailyLiveFreeApps: StatsDailyLiveFreeApps;
|
||||
/**
|
||||
* Returns lists of apps that have some live traffic in the give time range.
|
||||
* From defaults to 24 hours ago and to defaults to now.
|
||||
@@ -16003,6 +16075,8 @@ export type Query_Root = {
|
||||
statsLiveApps: StatsLiveApps;
|
||||
systemConfig?: Maybe<ConfigSystemConfig>;
|
||||
systemConfigs: Array<ConfigAppSystemConfig>;
|
||||
/** Returns system logs for a given application */
|
||||
systemLogs: Array<Log>;
|
||||
/** fetch data from the table: "auth.users" using primary key columns */
|
||||
user?: Maybe<Users>;
|
||||
/** fetch data from the table: "auth.users" */
|
||||
@@ -16768,6 +16842,11 @@ export type Query_RootGetPostgresVolumeUsageArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type Query_RootGetProjectStatusArgs = {
|
||||
appID: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type Query_RootGetServiceLabelValuesArgs = {
|
||||
appID: Scalars['String'];
|
||||
};
|
||||
@@ -17051,12 +17130,6 @@ export type Query_RootSoftwareVersionsAggregateArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type Query_RootStatsDailyLiveFreeAppsArgs = {
|
||||
from?: InputMaybe<Scalars['Timestamp']>;
|
||||
to?: InputMaybe<Scalars['Timestamp']>;
|
||||
};
|
||||
|
||||
|
||||
export type Query_RootStatsLiveAppsArgs = {
|
||||
from?: InputMaybe<Scalars['Timestamp']>;
|
||||
to?: InputMaybe<Scalars['Timestamp']>;
|
||||
@@ -17073,6 +17146,14 @@ export type Query_RootSystemConfigsArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type Query_RootSystemLogsArgs = {
|
||||
action: Scalars['String'];
|
||||
appID: Scalars['String'];
|
||||
from?: InputMaybe<Scalars['Timestamp']>;
|
||||
to?: InputMaybe<Scalars['Timestamp']>;
|
||||
};
|
||||
|
||||
|
||||
export type Query_RootUserArgs = {
|
||||
id: Scalars['uuid'];
|
||||
};
|
||||
@@ -17392,6 +17473,7 @@ export type Regions = {
|
||||
domain: Scalars['String'];
|
||||
id: Scalars['uuid'];
|
||||
isGdprCompliant: Scalars['Boolean'];
|
||||
name: Scalars['String'];
|
||||
/** An object relationship */
|
||||
region_type: Region_Type;
|
||||
/** An array relationship */
|
||||
@@ -17769,6 +17851,7 @@ export type Regions_Bool_Exp = {
|
||||
domain?: InputMaybe<String_Comparison_Exp>;
|
||||
id?: InputMaybe<Uuid_Comparison_Exp>;
|
||||
isGdprCompliant?: InputMaybe<Boolean_Comparison_Exp>;
|
||||
name?: InputMaybe<String_Comparison_Exp>;
|
||||
region_type?: InputMaybe<Region_Type_Bool_Exp>;
|
||||
regions_allowed_workspaces?: InputMaybe<Regions_Allowed_Workspace_Bool_Exp>;
|
||||
regions_allowed_workspaces_aggregate?: InputMaybe<Regions_Allowed_Workspace_Aggregate_Bool_Exp>;
|
||||
@@ -17796,6 +17879,7 @@ export type Regions_Insert_Input = {
|
||||
domain?: InputMaybe<Scalars['String']>;
|
||||
id?: InputMaybe<Scalars['uuid']>;
|
||||
isGdprCompliant?: InputMaybe<Scalars['Boolean']>;
|
||||
name?: InputMaybe<Scalars['String']>;
|
||||
region_type?: InputMaybe<Region_Type_Obj_Rel_Insert_Input>;
|
||||
regions_allowed_workspaces?: InputMaybe<Regions_Allowed_Workspace_Arr_Rel_Insert_Input>;
|
||||
type?: InputMaybe<Region_Type_Enum>;
|
||||
@@ -17812,6 +17896,7 @@ export type Regions_Max_Fields = {
|
||||
description?: Maybe<Scalars['String']>;
|
||||
domain?: Maybe<Scalars['String']>;
|
||||
id?: Maybe<Scalars['uuid']>;
|
||||
name?: Maybe<Scalars['String']>;
|
||||
updatedAt?: Maybe<Scalars['timestamptz']>;
|
||||
};
|
||||
|
||||
@@ -17824,6 +17909,7 @@ export type Regions_Max_Order_By = {
|
||||
description?: InputMaybe<Order_By>;
|
||||
domain?: InputMaybe<Order_By>;
|
||||
id?: InputMaybe<Order_By>;
|
||||
name?: InputMaybe<Order_By>;
|
||||
updatedAt?: InputMaybe<Order_By>;
|
||||
};
|
||||
|
||||
@@ -17837,6 +17923,7 @@ export type Regions_Min_Fields = {
|
||||
description?: Maybe<Scalars['String']>;
|
||||
domain?: Maybe<Scalars['String']>;
|
||||
id?: Maybe<Scalars['uuid']>;
|
||||
name?: Maybe<Scalars['String']>;
|
||||
updatedAt?: Maybe<Scalars['timestamptz']>;
|
||||
};
|
||||
|
||||
@@ -17849,6 +17936,7 @@ export type Regions_Min_Order_By = {
|
||||
description?: InputMaybe<Order_By>;
|
||||
domain?: InputMaybe<Order_By>;
|
||||
id?: InputMaybe<Order_By>;
|
||||
name?: InputMaybe<Order_By>;
|
||||
updatedAt?: InputMaybe<Order_By>;
|
||||
};
|
||||
|
||||
@@ -17889,6 +17977,7 @@ export type Regions_Order_By = {
|
||||
domain?: InputMaybe<Order_By>;
|
||||
id?: InputMaybe<Order_By>;
|
||||
isGdprCompliant?: InputMaybe<Order_By>;
|
||||
name?: InputMaybe<Order_By>;
|
||||
region_type?: InputMaybe<Region_Type_Order_By>;
|
||||
regions_allowed_workspaces_aggregate?: InputMaybe<Regions_Allowed_Workspace_Aggregate_Order_By>;
|
||||
type?: InputMaybe<Order_By>;
|
||||
@@ -17921,6 +18010,8 @@ export enum Regions_Select_Column {
|
||||
/** column name */
|
||||
IsGdprCompliant = 'isGdprCompliant',
|
||||
/** column name */
|
||||
Name = 'name',
|
||||
/** column name */
|
||||
Type = 'type',
|
||||
/** column name */
|
||||
UpdatedAt = 'updatedAt'
|
||||
@@ -17953,6 +18044,7 @@ export type Regions_Set_Input = {
|
||||
domain?: InputMaybe<Scalars['String']>;
|
||||
id?: InputMaybe<Scalars['uuid']>;
|
||||
isGdprCompliant?: InputMaybe<Scalars['Boolean']>;
|
||||
name?: InputMaybe<Scalars['String']>;
|
||||
type?: InputMaybe<Region_Type_Enum>;
|
||||
updatedAt?: InputMaybe<Scalars['timestamptz']>;
|
||||
};
|
||||
@@ -17976,6 +18068,7 @@ export type Regions_Stream_Cursor_Value_Input = {
|
||||
domain?: InputMaybe<Scalars['String']>;
|
||||
id?: InputMaybe<Scalars['uuid']>;
|
||||
isGdprCompliant?: InputMaybe<Scalars['Boolean']>;
|
||||
name?: InputMaybe<Scalars['String']>;
|
||||
type?: InputMaybe<Region_Type_Enum>;
|
||||
updatedAt?: InputMaybe<Scalars['timestamptz']>;
|
||||
};
|
||||
@@ -18001,6 +18094,8 @@ export enum Regions_Update_Column {
|
||||
/** column name */
|
||||
IsGdprCompliant = 'isGdprCompliant',
|
||||
/** column name */
|
||||
Name = 'name',
|
||||
/** column name */
|
||||
Type = 'type',
|
||||
/** column name */
|
||||
UpdatedAt = 'updatedAt'
|
||||
@@ -22662,7 +22757,7 @@ export type GetPostgresSettingsQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type GetPostgresSettingsQuery = { __typename?: 'query_root', systemConfig?: { __typename?: 'ConfigSystemConfig', postgres: { __typename?: 'ConfigSystemConfigPostgres', database: string } } | null, config?: { __typename: 'ConfigConfig', id: 'ConfigConfig', postgres?: { __typename?: 'ConfigPostgres', version?: string | null, resources?: { __typename?: 'ConfigPostgresResources', storage?: { __typename?: 'ConfigPostgresStorage', capacity: any } | null } | null } | null } | null };
|
||||
export type GetPostgresSettingsQuery = { __typename?: 'query_root', systemConfig?: { __typename?: 'ConfigSystemConfig', postgres: { __typename?: 'ConfigSystemConfigPostgres', database: string } } | null, config?: { __typename: 'ConfigConfig', id: 'ConfigConfig', postgres?: { __typename?: 'ConfigPostgres', version?: string | null, resources?: { __typename?: 'ConfigPostgresResources', enablePublicAccess?: boolean | null, storage?: { __typename?: 'ConfigPostgresStorage', capacity: any } | null } | null } | null } | null };
|
||||
|
||||
export type ResetDatabasePasswordMutationVariables = Exact<{
|
||||
appId: Scalars['String'];
|
||||
@@ -22730,7 +22825,7 @@ export type DeleteApplicationMutation = { __typename?: 'mutation_root', deleteAp
|
||||
export type GetAllWorkspacesAndProjectsQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type GetAllWorkspacesAndProjectsQuery = { __typename?: 'query_root', workspaces: Array<{ __typename?: 'workspaces', id: any, name: string, slug: string, creatorUserId?: any | null, workspaceMembers: Array<{ __typename?: 'workspaceMembers', id: any, type: string, user: { __typename?: 'users', id: any, email?: any | null, displayName: string } }>, projects: Array<{ __typename?: 'apps', id: any, slug: string, name: string, repositoryProductionBranch: string, subdomain: string, createdAt: any, desiredState: number, nhostBaseFolder: string, config?: { __typename?: 'ConfigConfig', observability: { __typename?: 'ConfigObservability', grafana: { __typename?: 'ConfigGrafana', adminPassword: string } }, hasura: { __typename?: 'ConfigHasura', adminSecret: string, settings?: { __typename?: 'ConfigHasuraSettings', enableConsole?: boolean | null } | null }, ai?: { __typename?: 'ConfigAI', version?: string | null } | null } | null, featureFlags: Array<{ __typename?: 'featureFlags', description: string, id: any, name: string, value: string }>, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }>, region: { __typename?: 'regions', id: any, countryCode: string, awsName: string, domain: string, city: string }, plan: { __typename?: 'plans', id: any, name: string, price: number, isFree: boolean, featureMaxDbSize: number }, githubRepository?: { __typename?: 'githubRepositories', fullName: string } | null, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null }> }> };
|
||||
export type GetAllWorkspacesAndProjectsQuery = { __typename?: 'query_root', workspaces: Array<{ __typename?: 'workspaces', id: any, name: string, slug: string, creatorUserId?: any | null, workspaceMembers: Array<{ __typename?: 'workspaceMembers', id: any, type: string, user: { __typename?: 'users', id: any, email?: any | null, displayName: string } }>, projects: Array<{ __typename?: 'apps', id: any, slug: string, name: string, repositoryProductionBranch: string, subdomain: string, createdAt: any, desiredState: number, nhostBaseFolder: string, config?: { __typename?: 'ConfigConfig', observability: { __typename?: 'ConfigObservability', grafana: { __typename?: 'ConfigGrafana', adminPassword: string } }, hasura: { __typename?: 'ConfigHasura', adminSecret: string, settings?: { __typename?: 'ConfigHasuraSettings', enableConsole?: boolean | null } | null }, ai?: { __typename?: 'ConfigAI', version?: string | null } | null } | null, featureFlags: Array<{ __typename?: 'featureFlags', description: string, id: any, name: string, value: string }>, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }>, region: { __typename?: 'regions', id: any, countryCode: string, name: string, domain: string, city: string }, plan: { __typename?: 'plans', id: any, name: string, price: number, isFree: boolean, featureMaxDbSize: number }, githubRepository?: { __typename?: 'githubRepositories', fullName: string } | null, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null }> }> };
|
||||
|
||||
export type GetAppPlanAndGlobalPlansAppFragment = { __typename?: 'apps', id: any, subdomain: string, workspace: { __typename?: 'workspaces', id: any, paymentMethods: Array<{ __typename?: 'paymentMethods', id: any }> }, plan: { __typename?: 'plans', id: any, name: string } };
|
||||
|
||||
@@ -22787,7 +22882,7 @@ export type GetWorkspaceAndProjectQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type GetWorkspaceAndProjectQuery = { __typename?: 'query_root', workspaces: Array<{ __typename?: 'workspaces', id: any, name: string, slug: string, creatorUserId?: any | null, workspaceMembers: Array<{ __typename?: 'workspaceMembers', id: any, type: string, user: { __typename?: 'users', id: any, email?: any | null, displayName: string } }>, projects: Array<{ __typename?: 'apps', id: any, slug: string, name: string, repositoryProductionBranch: string, subdomain: string, createdAt: any, desiredState: number, nhostBaseFolder: string, config?: { __typename?: 'ConfigConfig', observability: { __typename?: 'ConfigObservability', grafana: { __typename?: 'ConfigGrafana', adminPassword: string } }, hasura: { __typename?: 'ConfigHasura', adminSecret: string, settings?: { __typename?: 'ConfigHasuraSettings', enableConsole?: boolean | null } | null }, ai?: { __typename?: 'ConfigAI', version?: string | null } | null } | null, featureFlags: Array<{ __typename?: 'featureFlags', description: string, id: any, name: string, value: string }>, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }>, region: { __typename?: 'regions', id: any, countryCode: string, awsName: string, domain: string, city: string }, plan: { __typename?: 'plans', id: any, name: string, price: number, isFree: boolean, featureMaxDbSize: number }, githubRepository?: { __typename?: 'githubRepositories', fullName: string } | null, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null }> }> };
|
||||
export type GetWorkspaceAndProjectQuery = { __typename?: 'query_root', workspaces: Array<{ __typename?: 'workspaces', id: any, name: string, slug: string, creatorUserId?: any | null, workspaceMembers: Array<{ __typename?: 'workspaceMembers', id: any, type: string, user: { __typename?: 'users', id: any, email?: any | null, displayName: string } }>, projects: Array<{ __typename?: 'apps', id: any, slug: string, name: string, repositoryProductionBranch: string, subdomain: string, createdAt: any, desiredState: number, nhostBaseFolder: string, config?: { __typename?: 'ConfigConfig', observability: { __typename?: 'ConfigObservability', grafana: { __typename?: 'ConfigGrafana', adminPassword: string } }, hasura: { __typename?: 'ConfigHasura', adminSecret: string, settings?: { __typename?: 'ConfigHasuraSettings', enableConsole?: boolean | null } | null }, ai?: { __typename?: 'ConfigAI', version?: string | null } | null } | null, featureFlags: Array<{ __typename?: 'featureFlags', description: string, id: any, name: string, value: string }>, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }>, region: { __typename?: 'regions', id: any, countryCode: string, name: string, domain: string, city: string }, plan: { __typename?: 'plans', id: any, name: string, price: number, isFree: boolean, featureMaxDbSize: number }, githubRepository?: { __typename?: 'githubRepositories', fullName: string } | null, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null }> }> };
|
||||
|
||||
export type InsertApplicationMutationVariables = Exact<{
|
||||
app: Apps_Insert_Input;
|
||||
@@ -22894,7 +22989,7 @@ export type UpdateConfigMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateConfigMutation = { __typename?: 'mutation_root', updateConfig: { __typename?: 'ConfigConfig', id: 'ConfigConfig', postgres?: { __typename?: 'ConfigPostgres', resources?: { __typename?: 'ConfigPostgresResources', storage?: { __typename?: 'ConfigPostgresStorage', capacity: any } | null } | null } | null, ai?: { __typename?: 'ConfigAI', version?: string | null, webhookSecret: string, autoEmbeddings?: { __typename?: 'ConfigAIAutoEmbeddings', synchPeriodMinutes?: any | null } | null, openai: { __typename?: 'ConfigAIOpenai', organization?: string | null, apiKey: string }, resources: { __typename?: 'ConfigAIResources', compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any } } } | null } };
|
||||
export type UpdateConfigMutation = { __typename?: 'mutation_root', updateConfig: { __typename?: 'ConfigConfig', id: 'ConfigConfig', postgres?: { __typename?: 'ConfigPostgres', resources?: { __typename?: 'ConfigPostgresResources', enablePublicAccess?: boolean | null, storage?: { __typename?: 'ConfigPostgresStorage', capacity: any } | null } | null } | null, ai?: { __typename?: 'ConfigAI', version?: string | null, webhookSecret: string, autoEmbeddings?: { __typename?: 'ConfigAIAutoEmbeddings', synchPeriodMinutes?: any | null } | null, openai: { __typename?: 'ConfigAIOpenai', organization?: string | null, apiKey: string }, resources: { __typename?: 'ConfigAIResources', compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any } } } | null } };
|
||||
|
||||
export type UnpauseApplicationMutationVariables = Exact<{
|
||||
appId: Scalars['uuid'];
|
||||
@@ -22979,9 +23074,9 @@ export type GetFilesAggregateQuery = { __typename?: 'query_root', filesAggregate
|
||||
|
||||
export type AppStateHistoryFragment = { __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any };
|
||||
|
||||
export type ProjectFragment = { __typename?: 'apps', id: any, slug: string, name: string, repositoryProductionBranch: string, subdomain: string, createdAt: any, desiredState: number, nhostBaseFolder: string, config?: { __typename?: 'ConfigConfig', observability: { __typename?: 'ConfigObservability', grafana: { __typename?: 'ConfigGrafana', adminPassword: string } }, hasura: { __typename?: 'ConfigHasura', adminSecret: string, settings?: { __typename?: 'ConfigHasuraSettings', enableConsole?: boolean | null } | null }, ai?: { __typename?: 'ConfigAI', version?: string | null } | null } | null, featureFlags: Array<{ __typename?: 'featureFlags', description: string, id: any, name: string, value: string }>, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }>, region: { __typename?: 'regions', id: any, countryCode: string, awsName: string, domain: string, city: string }, plan: { __typename?: 'plans', id: any, name: string, price: number, isFree: boolean, featureMaxDbSize: number }, githubRepository?: { __typename?: 'githubRepositories', fullName: string } | null, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null };
|
||||
export type ProjectFragment = { __typename?: 'apps', id: any, slug: string, name: string, repositoryProductionBranch: string, subdomain: string, createdAt: any, desiredState: number, nhostBaseFolder: string, config?: { __typename?: 'ConfigConfig', observability: { __typename?: 'ConfigObservability', grafana: { __typename?: 'ConfigGrafana', adminPassword: string } }, hasura: { __typename?: 'ConfigHasura', adminSecret: string, settings?: { __typename?: 'ConfigHasuraSettings', enableConsole?: boolean | null } | null }, ai?: { __typename?: 'ConfigAI', version?: string | null } | null } | null, featureFlags: Array<{ __typename?: 'featureFlags', description: string, id: any, name: string, value: string }>, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }>, region: { __typename?: 'regions', id: any, countryCode: string, name: string, domain: string, city: string }, plan: { __typename?: 'plans', id: any, name: string, price: number, isFree: boolean, featureMaxDbSize: number }, githubRepository?: { __typename?: 'githubRepositories', fullName: string } | null, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null };
|
||||
|
||||
export type WorkspaceFragment = { __typename?: 'workspaces', id: any, name: string, slug: string, creatorUserId?: any | null, workspaceMembers: Array<{ __typename?: 'workspaceMembers', id: any, type: string, user: { __typename?: 'users', id: any, email?: any | null, displayName: string } }>, projects: Array<{ __typename?: 'apps', id: any, slug: string, name: string, repositoryProductionBranch: string, subdomain: string, createdAt: any, desiredState: number, nhostBaseFolder: string, config?: { __typename?: 'ConfigConfig', observability: { __typename?: 'ConfigObservability', grafana: { __typename?: 'ConfigGrafana', adminPassword: string } }, hasura: { __typename?: 'ConfigHasura', adminSecret: string, settings?: { __typename?: 'ConfigHasuraSettings', enableConsole?: boolean | null } | null }, ai?: { __typename?: 'ConfigAI', version?: string | null } | null } | null, featureFlags: Array<{ __typename?: 'featureFlags', description: string, id: any, name: string, value: string }>, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }>, region: { __typename?: 'regions', id: any, countryCode: string, awsName: string, domain: string, city: string }, plan: { __typename?: 'plans', id: any, name: string, price: number, isFree: boolean, featureMaxDbSize: number }, githubRepository?: { __typename?: 'githubRepositories', fullName: string } | null, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null }> };
|
||||
export type WorkspaceFragment = { __typename?: 'workspaces', id: any, name: string, slug: string, creatorUserId?: any | null, workspaceMembers: Array<{ __typename?: 'workspaceMembers', id: any, type: string, user: { __typename?: 'users', id: any, email?: any | null, displayName: string } }>, projects: Array<{ __typename?: 'apps', id: any, slug: string, name: string, repositoryProductionBranch: string, subdomain: string, createdAt: any, desiredState: number, nhostBaseFolder: string, config?: { __typename?: 'ConfigConfig', observability: { __typename?: 'ConfigObservability', grafana: { __typename?: 'ConfigGrafana', adminPassword: string } }, hasura: { __typename?: 'ConfigHasura', adminSecret: string, settings?: { __typename?: 'ConfigHasuraSettings', enableConsole?: boolean | null } | null }, ai?: { __typename?: 'ConfigAI', version?: string | null } | null } | null, featureFlags: Array<{ __typename?: 'featureFlags', description: string, id: any, name: string, value: string }>, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }>, region: { __typename?: 'regions', id: any, countryCode: string, name: string, domain: string, city: string }, plan: { __typename?: 'plans', id: any, name: string, price: number, isFree: boolean, featureMaxDbSize: number }, githubRepository?: { __typename?: 'githubRepositories', fullName: string } | null, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null }> };
|
||||
|
||||
export type GithubRepositoryFragment = { __typename?: 'githubRepositories', id: any, name: string, fullName: string, private: boolean, githubAppInstallation: { __typename?: 'githubAppInstallations', id: any, accountLogin?: string | null, accountType?: string | null, accountAvatarUrl?: string | null } };
|
||||
|
||||
@@ -23098,7 +23193,7 @@ export type GetRemoteAppMetricsQueryVariables = Exact<{ [key: string]: never; }>
|
||||
|
||||
export type GetRemoteAppMetricsQuery = { __typename?: 'query_root', filesAggregate: { __typename?: 'files_aggregate', aggregate?: { __typename?: 'files_aggregate_fields', count: number, sum?: { __typename?: 'files_sum_fields', size?: number | null } | null } | null }, usersAggregate: { __typename?: 'users_aggregate', aggregate?: { __typename?: 'users_aggregate_fields', count: number } | null } };
|
||||
|
||||
export type RemoteAppGetUsersFragment = { __typename?: 'users', id: any, createdAt: any, displayName: string, avatarUrl: string, email?: any | null, emailVerified: boolean, phoneNumber?: string | null, phoneNumberVerified: boolean, disabled: boolean, defaultRole: string, lastSeen?: any | null, locale: string, roles: Array<{ __typename?: 'authUserRoles', id: any, role: string }>, userProviders: Array<{ __typename?: 'authUserProviders', id: any, providerId: string }> };
|
||||
export type RemoteAppGetUsersFragment = { __typename?: 'users', id: any, createdAt: any, displayName: string, avatarUrl: string, email?: any | null, emailVerified: boolean, phoneNumber?: string | null, phoneNumberVerified: boolean, disabled: boolean, defaultRole: string, lastSeen?: any | null, locale: string, metadata?: any | null, roles: Array<{ __typename?: 'authUserRoles', id: any, role: string }>, userProviders: Array<{ __typename?: 'authUserProviders', id: any, providerId: string }> };
|
||||
|
||||
export type RemoteAppGetUsersQueryVariables = Exact<{
|
||||
where: Users_Bool_Exp;
|
||||
@@ -23107,7 +23202,7 @@ export type RemoteAppGetUsersQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type RemoteAppGetUsersQuery = { __typename?: 'query_root', users: Array<{ __typename?: 'users', id: any, createdAt: any, displayName: string, avatarUrl: string, email?: any | null, emailVerified: boolean, phoneNumber?: string | null, phoneNumberVerified: boolean, disabled: boolean, defaultRole: string, lastSeen?: any | null, locale: string, roles: Array<{ __typename?: 'authUserRoles', id: any, role: string }>, userProviders: Array<{ __typename?: 'authUserProviders', id: any, providerId: string }> }>, filteredUsersAggreggate: { __typename?: 'users_aggregate', aggregate?: { __typename?: 'users_aggregate_fields', count: number } | null }, usersAggregate: { __typename?: 'users_aggregate', aggregate?: { __typename?: 'users_aggregate_fields', count: number } | null } };
|
||||
export type RemoteAppGetUsersQuery = { __typename?: 'query_root', users: Array<{ __typename?: 'users', id: any, createdAt: any, displayName: string, avatarUrl: string, email?: any | null, emailVerified: boolean, phoneNumber?: string | null, phoneNumberVerified: boolean, disabled: boolean, defaultRole: string, lastSeen?: any | null, locale: string, metadata?: any | null, roles: Array<{ __typename?: 'authUserRoles', id: any, role: string }>, userProviders: Array<{ __typename?: 'authUserProviders', id: any, providerId: string }> }>, filteredUsersAggreggate: { __typename?: 'users_aggregate', aggregate?: { __typename?: 'users_aggregate_fields', count: number } | null }, usersAggregate: { __typename?: 'users_aggregate', aggregate?: { __typename?: 'users_aggregate_fields', count: number } | null } };
|
||||
|
||||
export type RemoteAppGetUsersCustomQueryVariables = Exact<{
|
||||
where: Users_Bool_Exp;
|
||||
@@ -23124,7 +23219,7 @@ export type RemoteAppGetUsersWholeQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type RemoteAppGetUsersWholeQuery = { __typename?: 'query_root', users: Array<{ __typename?: 'users', id: any, createdAt: any, displayName: string, avatarUrl: string, email?: any | null, emailVerified: boolean, phoneNumber?: string | null, phoneNumberVerified: boolean, disabled: boolean, defaultRole: string, lastSeen?: any | null, locale: string, roles: Array<{ __typename?: 'authUserRoles', id: any, role: string }>, userProviders: Array<{ __typename?: 'authUserProviders', id: any, providerId: string }> }>, usersAggregate: { __typename?: 'users_aggregate', aggregate?: { __typename?: 'users_aggregate_fields', count: number } | null } };
|
||||
export type RemoteAppGetUsersWholeQuery = { __typename?: 'query_root', users: Array<{ __typename?: 'users', id: any, createdAt: any, displayName: string, avatarUrl: string, email?: any | null, emailVerified: boolean, phoneNumber?: string | null, phoneNumberVerified: boolean, disabled: boolean, defaultRole: string, lastSeen?: any | null, locale: string, metadata?: any | null, roles: Array<{ __typename?: 'authUserRoles', id: any, role: string }>, userProviders: Array<{ __typename?: 'authUserProviders', id: any, providerId: string }> }>, usersAggregate: { __typename?: 'users_aggregate', aggregate?: { __typename?: 'users_aggregate_fields', count: number } | null } };
|
||||
|
||||
export type TotalUsersQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
@@ -23524,7 +23619,7 @@ export const ProjectFragmentDoc = gql`
|
||||
region {
|
||||
id
|
||||
countryCode
|
||||
awsName
|
||||
name
|
||||
domain
|
||||
city
|
||||
}
|
||||
@@ -23623,6 +23718,7 @@ export const RemoteAppGetUsersFragmentDoc = gql`
|
||||
defaultRole
|
||||
lastSeen
|
||||
locale
|
||||
metadata
|
||||
roles {
|
||||
id
|
||||
role
|
||||
@@ -24036,6 +24132,7 @@ export const GetPostgresSettingsDocument = gql`
|
||||
storage {
|
||||
capacity
|
||||
}
|
||||
enablePublicAccess
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25399,6 +25496,7 @@ export const UpdateConfigDocument = gql`
|
||||
storage {
|
||||
capacity
|
||||
}
|
||||
enablePublicAccess
|
||||
}
|
||||
}
|
||||
ai {
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# @nhost/docs
|
||||
|
||||
## 2.13.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 6fb0cc2: fix: minor improvements to compute resources' docs
|
||||
- 66bd450: chore: various improvements
|
||||
|
||||
## 2.12.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- d5077c7: feat: added docs about how to connect to postgres
|
||||
|
||||
## 2.11.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- c6dc7f4: chore: docs: add Nhost client reference
|
||||
|
||||
## 2.10.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a58c5cf: fix: broken link
|
||||
|
||||
## 2.10.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
176
docs/guides/auth/custom-jwts.mdx
Normal file
176
docs/guides/auth/custom-jwts.mdx
Normal file
@@ -0,0 +1,176 @@
|
||||
---
|
||||
title: Custom JWTs
|
||||
description: Creating custom JWTs
|
||||
icon: ghost
|
||||
---
|
||||
|
||||
In some cases it is necessary to act on behalf of a user. While the Auth service doesn't allow that it is not difficult to implement such functionality as a serverless function. Below you can find an example of a function that can generate a valid access token for your application with customized values. For details read the docstring in the function itself.
|
||||
|
||||
Feel free to adapt to your needs.
|
||||
|
||||
### Dependencies
|
||||
|
||||
```
|
||||
npm install jsonwebtoken
|
||||
```
|
||||
|
||||
### Function
|
||||
|
||||
Create a file under `functions` (for instance `/functions/custom-jwts.ts`), with the following contents:
|
||||
|
||||
```js
|
||||
import { Request, Response } from 'express'
|
||||
import process from 'process'
|
||||
import jwt from 'jsonwebtoken'
|
||||
|
||||
// function to extract jwt from the request
|
||||
const getJwt = (req: Request): string | null => {
|
||||
const authHeader = req.headers.authorization
|
||||
if (!authHeader) {
|
||||
return null
|
||||
}
|
||||
|
||||
const parts = authHeader.split(' ')
|
||||
if (parts.length !== 2) {
|
||||
return null
|
||||
}
|
||||
|
||||
const [scheme, token] = parts
|
||||
if (!/^Bearer$/i.test(scheme)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
// validate jwt token is valid and caller is either an admin or an operator
|
||||
const jwtIsAuthorized = (req: Request, key: string): string => {
|
||||
const token = getJwt(req)
|
||||
if (!token) {
|
||||
return ""
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, key)
|
||||
|
||||
const claims = decoded['https://hasura.io/jwt/claims']
|
||||
if ( !claims || !claims['x-hasura-allowed-roles'] ) {
|
||||
return ""
|
||||
}
|
||||
|
||||
if (
|
||||
claims['x-hasura-allowed-roles'].includes('admin') ||
|
||||
claims['x-hasura-allowed-roles'].includes('operator')
|
||||
) {
|
||||
return decoded.sub
|
||||
}
|
||||
|
||||
return ""
|
||||
} catch (e) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
This is a sample function that generates a JWT token to impersonate users.
|
||||
|
||||
Authorization:
|
||||
|
||||
- send a valid JWT token with the request. The token should have the `admin` or `operator` role.
|
||||
- send the `x-hasura-admin-secret` header with the request
|
||||
|
||||
Body:
|
||||
|
||||
A json object with the following keys:
|
||||
|
||||
- `userId` (string): the user id for which the token is generated
|
||||
- `defaultRole` (string): the default role for the userId
|
||||
- `allowedRoles` (array of strings): the roles that the userId can assume
|
||||
|
||||
Returns:
|
||||
|
||||
A json object with the following keys:
|
||||
|
||||
- `accessToken` (string) - The generated access token
|
||||
|
||||
|
||||
In addition to the typical JWT claims generated by Nhost, the token generated by this function will have the following claims:
|
||||
|
||||
- `x-hasura-on-behalf-of`: the user id of the caller or `admin` if the caller used the `x-hasura-admin-secret` header
|
||||
|
||||
You are free to modify this function to suit your needs.
|
||||
*/
|
||||
export default (req: Request, res: Response) => {
|
||||
let authorizedCaller = ""
|
||||
if (req.headers['x-hasura-admin-secret'] === process.env.HASURA_GRAPHQL_ADMIN_SECRET) {
|
||||
authorizedCaller = "admin"
|
||||
}
|
||||
|
||||
const jwtSecret = JSON.parse(process.env.NHOST_JWT_SECRET)
|
||||
if (!authorizedCaller) {
|
||||
authorizedCaller = jwtIsAuthorized(req, jwtSecret.key)
|
||||
}
|
||||
|
||||
if (!authorizedCaller) {
|
||||
return res.status(401).json({ message: 'Unauthorized' })
|
||||
}
|
||||
|
||||
// extract from json in the body
|
||||
const {userId, defaultRole, allowedRoles} = req.body
|
||||
if (!userId || !defaultRole || !allowedRoles) {
|
||||
return res.status(400).json({ message: 'Bad request' })
|
||||
}
|
||||
|
||||
let token = jwt.sign({
|
||||
"exp": Math.floor(Date.now() / 1000) + 60 * 60, // 1 hour
|
||||
"https://hasura.io/jwt/claims": {
|
||||
"x-hasura-allowed-roles": allowedRoles,
|
||||
"x-hasura-default-role": defaultRole,
|
||||
"x-hasura-user-id": userId,
|
||||
"x-hasura-user-is-anonymous": "false",
|
||||
"x-hasura-on-behalf-of": authorizedCaller
|
||||
},
|
||||
"iat": Math.floor(Date.now() / 1000),
|
||||
"iss": "custom-lambda",
|
||||
"sub": userId,
|
||||
}, jwtSecret.key);
|
||||
|
||||
res.status(200).json(
|
||||
{
|
||||
accessToken: token,
|
||||
},
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Now you can call it like:
|
||||
|
||||
```
|
||||
curl -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "x-hasura-admin-secret: nhost-admin-secret" \
|
||||
-d '{"userId": "FFAB5354-C5EB-42C1-8BC3-AD21D2297883", "defaultRole": "user", "allowedRoles": ["user", "me"]}' \
|
||||
https://local.functions.nhost.run/v1/custom-jwt
|
||||
{"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTcxNDIyMTMsImh0dHBzOi8vaGFzdXJhLmlvL2p3dC9jbGFpbXMiOnsieC1oYXN1cmEtYWxsb3dlZC1yb2xlcyI6WyJ1c2VyIiwibWUiXSwieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoidXNlciIsIngtaGFzdXJhLXVzZXItaWQiOiJGRkFCNTM1NC1DNUVCLTQyQzEtOEJDMy1BRDIxRDIyOTc4ODMiLCJ4LWhhc3VyYS11c2VyLWlzLWFub255bW91cyI6ImZhbHNlIiwieC1oYXN1cmEtb24tYmVoYWxmLW9mIjoiYWRtaW4ifSwiaWF0IjoxNzE3MTM4NjEzLCJpc3MiOiJjdXN0b20tbGFtYmRhIiwic3ViIjoiRkZBQjUzNTQtQzVFQi00MkMxLThCQzMtQUQyMUQyMjk3ODgzIn0.bRhzJvXMdkQA8aXPH95uMT17WHED2rSRq3gE21Vp3Ak"}
|
||||
```
|
||||
|
||||
The new token should be a valid token for your application with the custom values requested:
|
||||
|
||||
```json
|
||||
{
|
||||
"exp": 1717141288,
|
||||
"https://hasura.io/jwt/claims": {
|
||||
"x-hasura-allowed-roles": [
|
||||
"a",
|
||||
"b"
|
||||
],
|
||||
"x-hasura-default-role": "user",
|
||||
"x-hasura-user-id": "FFAB5354-C5EB-42C1-8BC3-AD21D2297883",
|
||||
"x-hasura-user-is-anonymous": "false",
|
||||
"x-hasura-on-behalf-of": "admin"
|
||||
},
|
||||
"iat": 1717137688,
|
||||
"iss": "custom-lambda",
|
||||
"sub": "FFAB5354-C5EB-42C1-8BC3-AD21D2297883"
|
||||
}
|
||||
```
|
||||
@@ -93,3 +93,7 @@ nhost.auth.signIn({
|
||||
provider: 'apple'
|
||||
})
|
||||
```
|
||||
|
||||
<Note>
|
||||
To use your own domain for the callback URL refer to the [custom domains](/platform/custom-domains) documentation.
|
||||
</Note>
|
||||
|
||||
@@ -41,3 +41,7 @@ nhost.auth.signIn({
|
||||
provider: 'discord'
|
||||
})
|
||||
```
|
||||
|
||||
<Note>
|
||||
To use your own domain for the callback URL refer to the [custom domains](/platform/custom-domains) documentation.
|
||||
</Note>
|
||||
|
||||
@@ -64,3 +64,7 @@ nhost.auth.signIn({
|
||||
provider: 'facebook'
|
||||
})
|
||||
```
|
||||
|
||||
<Note>
|
||||
To use your own domain for the callback URL refer to the [custom domains](/platform/custom-domains) documentation.
|
||||
</Note>
|
||||
|
||||
@@ -49,3 +49,7 @@ nhost.auth.signIn({
|
||||
provider: "github",
|
||||
});
|
||||
```
|
||||
|
||||
<Note>
|
||||
To use your own domain for the callback URL refer to the [custom domains](/platform/custom-domains) documentation.
|
||||
</Note>
|
||||
|
||||
@@ -73,3 +73,7 @@ nhost.auth.signIn({
|
||||
provider: 'google'
|
||||
})
|
||||
```
|
||||
|
||||
<Note>
|
||||
To use your own domain for the callback URL refer to the [custom domains](/platform/custom-domains) documentation.
|
||||
</Note>
|
||||
|
||||
@@ -64,3 +64,7 @@ nhost.auth.signIn({
|
||||
provider: 'linkedin'
|
||||
})
|
||||
```
|
||||
|
||||
<Note>
|
||||
To use your own domain for the callback URL refer to the [custom domains](/platform/custom-domains) documentation.
|
||||
</Note>
|
||||
|
||||
@@ -49,3 +49,7 @@ nhost.auth.signIn({
|
||||
provider: 'spotify'
|
||||
})
|
||||
```
|
||||
|
||||
<Note>
|
||||
To use your own domain for the callback URL refer to the [custom domains](/platform/custom-domains) documentation.
|
||||
</Note>
|
||||
|
||||
@@ -43,3 +43,7 @@ nhost.auth.signIn({
|
||||
provider: 'twitch'
|
||||
})
|
||||
```
|
||||
|
||||
<Note>
|
||||
To use your own domain for the callback URL refer to the [custom domains](/platform/custom-domains) documentation.
|
||||
</Note>
|
||||
|
||||
@@ -63,3 +63,7 @@ nhost.auth.signIn({
|
||||
provider: 'workos'
|
||||
})
|
||||
```
|
||||
|
||||
<Note>
|
||||
To use your own domain for the callback URL refer to the [custom domains](/platform/custom-domains) documentation.
|
||||
</Note>
|
||||
|
||||
43
docs/guides/database/access.mdx
Normal file
43
docs/guides/database/access.mdx
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
title: Accessing the Database
|
||||
description: How to access the database directly using the connection string
|
||||
icon: key
|
||||
---
|
||||
|
||||
In most cases you will not need to access the database directly, choosing to interact with the data via the Graphql API, however, if you need direct access to postgres you can access it via the connection string.
|
||||
|
||||
|
||||
# Nhost Run
|
||||
|
||||
You can find details on how to connect to the database from an [Nhost Run](/product/run) service [here](/guides/run/networking#connecting-to-the-nhost-stack). If you don't know the password you can set a new password in the dashboard:
|
||||
|
||||
**Project Dashboard -> Settings -> Database**
|
||||
|
||||

|
||||
|
||||
# Public Access
|
||||
|
||||
For security reasons, by default your database won't be accessible online. If you need to access it directly from the Internet, first you will need to enable public access (enabling public access will also show the connection details):
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Dashboard">
|
||||
**Project Dashboard -> Settings -> Database**
|
||||
|
||||

|
||||
|
||||
</Tab>
|
||||
<Tab title="Config">
|
||||
```toml
|
||||
[postgres.resources]
|
||||
enablePublicAccess = true
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Note>
|
||||
Public access to your database utilizes [pgbouncer](http://www.pgbouncer.org). As this pooler is shared infrastructure the pooler will still be available even if your database has no public access configured. The pooler will simply not have access to your database.
|
||||
</Note>
|
||||
|
||||
# Functions
|
||||
|
||||
[Functions](/product/functions) run on a separate network, which means in order to access the database you will first need to [make it public](#public-access).
|
||||
@@ -16,7 +16,7 @@ In case your Postgres service is not meeting your performance expectations, you
|
||||
|
||||
4. Evaluate the usage of indexes in your database. Identify queries that could benefit from additional indexes and strategically add them to improve query performance.
|
||||
|
||||
5. Increase the disk size to increase [disk performance](/platform/compute-resources#disk-performance). Keep in mind increasing the disk size isn't reversible and increasing the memory of the service may yield better results. This is mostly useful when your data is very volatile and the postgres cache can't work effectively. Only attempt to increase disk for performance reasons if your reads and writes are very high and increasing memory isn't effective.
|
||||
5. If the problem is related to IOPS, consider increasing [disk performance](/platform/compute-resources#disk-performance).
|
||||
|
||||
By implementing these steps, you can effectively address performance concerns and enhance the overall performance of your Postgres service.
|
||||
|
||||
|
||||
BIN
docs/images/guides/database/access/public.png
Normal file
BIN
docs/images/guides/database/access/public.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 426 KiB |
BIN
docs/images/guides/database/access/reset.png
Normal file
BIN
docs/images/guides/database/access/reset.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 382 KiB |
BIN
docs/images/platform/compute-resources/guaranteed-resources.png
Normal file
BIN
docs/images/platform/compute-resources/guaranteed-resources.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 233 KiB |
BIN
docs/images/platform/compute-resources/resource-utilization.png
Normal file
BIN
docs/images/platform/compute-resources/resource-utilization.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 154 KiB |
|
Before Width: | Height: | Size: 241 KiB After Width: | Height: | Size: 241 KiB |
@@ -96,7 +96,7 @@
|
||||
},
|
||||
{
|
||||
"group": "Database",
|
||||
"pages": ["guides/database/configuring-postgres", "guides/database/extensions", "guides/database/performance"]
|
||||
"pages": ["guides/database/configuring-postgres", "guides/database/access", "guides/database/extensions", "guides/database/performance"]
|
||||
},
|
||||
{
|
||||
"group": "AI",
|
||||
@@ -134,7 +134,8 @@
|
||||
"guides/auth/sign-in-phone-number",
|
||||
"guides/auth/sign-in-webauthn",
|
||||
"guides/auth/elevated-permissions",
|
||||
"guides/auth/email-templates"
|
||||
"guides/auth/email-templates",
|
||||
"guides/auth/custom-jwts"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -298,6 +299,10 @@
|
||||
"group": "JavaScript",
|
||||
"icon": "js",
|
||||
"pages": [
|
||||
{
|
||||
"group": "nhost-js",
|
||||
"pages": ["reference/javascript/nhost-js/nhost-client", "reference/javascript/nhost-js/set-role", "reference/javascript/nhost-js/unset-role"]
|
||||
},
|
||||
{
|
||||
"group": "Auth",
|
||||
"pages": [
|
||||
@@ -338,7 +343,10 @@
|
||||
"reference/javascript/storage/get-public-url",
|
||||
"reference/javascript/storage/delete",
|
||||
"reference/javascript/storage/set-access-token",
|
||||
"reference/javascript/storage/set-admin-secret"
|
||||
"reference/javascript/storage/set-admin-secret",
|
||||
"reference/javascript/storage/set-headers",
|
||||
"reference/javascript/storage/unset-headers",
|
||||
"reference/javascript/storage/get-headers"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -347,7 +355,10 @@
|
||||
"reference/javascript/graphql/nhost-graphql-client",
|
||||
"reference/javascript/graphql/get-url",
|
||||
"reference/javascript/graphql/set-access-token",
|
||||
"reference/javascript/graphql/request"
|
||||
"reference/javascript/graphql/request",
|
||||
"reference/javascript/graphql/set-headers",
|
||||
"reference/javascript/graphql/unset-headers",
|
||||
"reference/javascript/graphql/get-headers"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user