Compare commits
24 Commits
@nhost/das
...
@nhost/rea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76e77da5de | ||
|
|
04d2ce110a | ||
|
|
b2755045c9 | ||
|
|
d43931e761 | ||
|
|
44c1e17fd5 | ||
|
|
5df6fa2d0b | ||
|
|
1fa6cc47ec | ||
|
|
4854df4559 | ||
|
|
865dd93fbe | ||
|
|
0c50816717 | ||
|
|
d3b4fc358e | ||
|
|
b3bcacb300 | ||
|
|
aa7ecdb38f | ||
|
|
20672c7a9b | ||
|
|
29d27e19b4 | ||
|
|
46fc520707 | ||
|
|
21e90da476 | ||
|
|
b944d053d0 | ||
|
|
6902a36512 | ||
|
|
aea6d186c2 | ||
|
|
a535aa3834 | ||
|
|
c9dca09478 | ||
|
|
414896491f | ||
|
|
cdf6776523 |
@@ -20,7 +20,7 @@ runs:
|
||||
id: pnpm-cache-dir
|
||||
shell: bash
|
||||
run: echo "dir=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
id: pnpm-cache
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache-dir.outputs.dir }}
|
||||
|
||||
2
.github/workflows/gen_ai_review.yaml
vendored
2
.github/workflows/gen_ai_review.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
steps:
|
||||
- name: PR Agent action step
|
||||
id: pragent
|
||||
uses: Codium-ai/pr-agent@v0.24
|
||||
uses: Codium-ai/pr-agent@v0.26
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
OPENAI_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
// $schema provides code completion hints to IDEs.
|
||||
"$schema": "https://github.com/IBM/audit-ci/raw/main/docs/schema.json",
|
||||
"moderate": true,
|
||||
"allowlist": ["vue-template-compiler", "micromatch", "path-to-regexp"]
|
||||
"allowlist": ["vue-template-compiler"]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,40 @@
|
||||
# @nhost/dashboard
|
||||
|
||||
## 2.14.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- d43931e: fix: invalid organization slug/project subdomain doesn't open 404 page
|
||||
- 5df6fa2: feat: add unencrypted disk warning in storage capacity settings
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 44c1e17: chore: update `msw` to v1.3.5 to fix vulnerabilities
|
||||
- @nhost/react-apollo@16.0.0
|
||||
- @nhost/nextjs@2.2.1
|
||||
|
||||
## 2.13.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 21e90da: chore: remove restrictions on SMTP sender so My Name <name@acme.com> can be added
|
||||
- 865dd93: fix: duplicate Run placeholders when there is an error in the backend
|
||||
- 6902a36: fix: can remove resources if postgres capacity is higher than 10
|
||||
- a535aa3: fix: fetch user roles locally in auth section
|
||||
- 0c50816: fix: allow decimal numbers in database row insert
|
||||
- aea6d18: chore: add warning when pausing a project about losing Run services persistent volume data
|
||||
- d3b4fc3: feat: allow to change postgres settings if project is paused
|
||||
- 29d27e1: chore: update `next` to v14.2.22 to fix vulnerabilities
|
||||
- c9dca09: feat: add reset password form
|
||||
- b3bcacb: fix: paused project banner cannot read null project name
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [46fc520]
|
||||
- Updated dependencies [29d27e1]
|
||||
- @nhost/nextjs@2.2.0
|
||||
- @nhost/react-apollo@15.0.1
|
||||
|
||||
## 2.12.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/dashboard",
|
||||
"version": "2.12.0",
|
||||
"version": "2.14.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
@@ -84,7 +84,7 @@
|
||||
"just-kebab-case": "^4.2.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lucide-react": "^0.416.0",
|
||||
"next": "^14.2.10",
|
||||
"next": "^14.2.22",
|
||||
"next-nprogress-bar": "^2.3.13",
|
||||
"next-seo": "^6.5.0",
|
||||
"next-themes": "^0.3.0",
|
||||
@@ -177,7 +177,7 @@
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"jsdom": "^22.1.0",
|
||||
"lint-staged": "^15.2.2",
|
||||
"msw": "^1.3.3",
|
||||
"msw": "^1.3.5",
|
||||
"msw-storybook-addon": "^1.10.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"postcss": "^8.4.38",
|
||||
|
||||
@@ -21,23 +21,22 @@ export default function UnauthenticatedLayout({
|
||||
const router = useRouter();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { isAuthenticated, isLoading } = useAuthenticationStatus();
|
||||
const isOnResetPassword = router.route === '/password/reset';
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPlatform || (!isLoading && isAuthenticated)) {
|
||||
router.push('/');
|
||||
// we do not want to redirect if the user tries to reset their password
|
||||
if (!isOnResetPassword) {
|
||||
router.push('/');
|
||||
}
|
||||
}
|
||||
}, [isLoading, isAuthenticated, router, isPlatform]);
|
||||
}, [isLoading, isAuthenticated, router, isPlatform, isOnResetPassword]);
|
||||
|
||||
if (!isPlatform || isLoading || isAuthenticated) {
|
||||
if ((!isPlatform || isLoading || isAuthenticated) && !isOnResetPassword) {
|
||||
return (
|
||||
<BaseLayout {...props}>
|
||||
<LoadingScreen
|
||||
sx={{ backgroundColor: (theme) => theme.palette.background.default }}
|
||||
slotProps={{
|
||||
activityIndicator: {
|
||||
className: 'text-white',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</BaseLayout>
|
||||
);
|
||||
@@ -59,19 +58,19 @@ export default function UnauthenticatedLayout({
|
||||
|
||||
<RetryableErrorBoundary>
|
||||
<Box
|
||||
className="flex items-center min-h-screen"
|
||||
className="flex min-h-screen items-center"
|
||||
sx={{ backgroundColor: (theme) => theme.palette.common.black }}
|
||||
>
|
||||
<Container
|
||||
rootClassName="bg-transparent h-full"
|
||||
className="grid items-center w-full h-full gap-12 pt-8 pb-12 bg-transparent justify-items-center lg:grid-cols-2 lg:gap-4 lg:pb-0 lg:pt-0"
|
||||
className="grid h-full w-full items-center justify-items-center gap-12 bg-transparent pb-12 pt-8 lg:grid-cols-2 lg:gap-4 lg:pb-0 lg:pt-0"
|
||||
>
|
||||
<div className="relative z-10 order-2 grid w-full max-w-[544px] grid-flow-row gap-12 lg:order-1">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
<div className="relative z-0 order-1 flex h-full w-full items-center justify-center md:min-h-[150px] lg:order-2 lg:min-h-[none]">
|
||||
<div className="absolute top-0 bottom-0 left-0 right-0 flex items-center justify-center w-full h-full max-w-xl mx-auto overflow-hidden opacity-70">
|
||||
<div className="absolute bottom-0 left-0 right-0 top-0 mx-auto flex h-full w-full max-w-xl items-center justify-center overflow-hidden opacity-70">
|
||||
<Image
|
||||
priority
|
||||
src="/assets/line-grid.svg"
|
||||
|
||||
@@ -19,7 +19,7 @@ import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWith
|
||||
|
||||
const validationSchema = yup
|
||||
.object({
|
||||
sender: yup.string().label('SMTP Sender').email().required(),
|
||||
sender: yup.string().label('SMTP Sender').required(),
|
||||
password: yup.string().label('Password').required(),
|
||||
})
|
||||
.required();
|
||||
|
||||
@@ -30,7 +30,7 @@ const smtpValidationSchema = yup
|
||||
user: yup.string().label('Username').required(),
|
||||
password: yup.string().label('Password'),
|
||||
method: yup.string().required(),
|
||||
sender: yup.string().label('SMTP Sender').email().required(),
|
||||
sender: yup.string().label('SMTP Sender').required(),
|
||||
})
|
||||
.required();
|
||||
|
||||
|
||||
@@ -16,18 +16,20 @@ import { Text } from '@/components/ui/v2/Text';
|
||||
import { useRemoteApplicationGQLClient } from '@/features/orgs/hooks/useRemoteApplicationGQLClient';
|
||||
import { EditUserPasswordForm } from '@/features/orgs/projects/authentication/users/components/EditUserPasswordForm';
|
||||
import { getReadableProviderName } from '@/features/orgs/projects/authentication/users/utils/getReadableProviderName';
|
||||
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
|
||||
import { useLocalMimirClient } from '@/features/orgs/projects/hooks/useLocalMimirClient';
|
||||
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
|
||||
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
|
||||
import { type RemoteAppUser } from '@/pages/orgs/[orgSlug]/projects/[appSubdomain]/users';
|
||||
import type { DialogFormProps } from '@/types/common';
|
||||
import { copy } from '@/utils/copy';
|
||||
import {
|
||||
RemoteAppGetUsersDocument,
|
||||
useGetProjectLocalesQuery,
|
||||
useGetRolesPermissionsQuery,
|
||||
useUpdateRemoteAppUserMutation,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useTheme } from '@mui/material';
|
||||
import { format } from 'date-fns';
|
||||
@@ -106,6 +108,8 @@ export default function EditUserForm({
|
||||
onDeleteUser,
|
||||
roles,
|
||||
}: EditUserFormProps) {
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const theme = useTheme();
|
||||
const { onDirtyStateChange, openDialog } = useDialog();
|
||||
const { project } = useProject();
|
||||
@@ -196,6 +200,7 @@ export default function EditUserForm({
|
||||
|
||||
const { data: dataRoles } = useGetRolesPermissionsQuery({
|
||||
variables: { appId: project?.id },
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const allAvailableProjectRoles = getUserRoles(
|
||||
@@ -206,6 +211,7 @@ export default function EditUserForm({
|
||||
variables: {
|
||||
appId: project?.id,
|
||||
},
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const allowedLocales = data?.config?.auth?.user?.locale?.allowed || [];
|
||||
|
||||
@@ -15,6 +15,8 @@ import { Text } from '@/components/ui/v2/Text';
|
||||
import { useRemoteApplicationGQLClient } from '@/features/orgs/hooks/useRemoteApplicationGQLClient';
|
||||
import type { EditUserFormValues } from '@/features/orgs/projects/authentication/users/components/EditUserForm';
|
||||
import { getReadableProviderName } from '@/features/orgs/projects/authentication/users/utils/getReadableProviderName';
|
||||
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
|
||||
import { useLocalMimirClient } from '@/features/orgs/projects/hooks/useLocalMimirClient';
|
||||
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
|
||||
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
|
||||
@@ -61,6 +63,8 @@ export interface UsersBodyProps {
|
||||
|
||||
export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
|
||||
const theme = useTheme();
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { openAlertDialog, openDrawer, closeDrawer } = useDialog();
|
||||
const { project } = useProject();
|
||||
const remoteProjectGQLClient = useRemoteApplicationGQLClient();
|
||||
@@ -88,6 +92,7 @@ export default function UsersBody({ users, onSubmit }: UsersBodyProps) {
|
||||
*/
|
||||
const { data: dataRoles } = useGetRolesPermissionsQuery({
|
||||
variables: { appId: project?.id },
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { allowed: allowedRoles } = dataRoles?.config?.auth?.user?.roles || {};
|
||||
|
||||
@@ -36,8 +36,8 @@ export default function ApplicationPaused() {
|
||||
>
|
||||
<RemoveApplicationModal
|
||||
close={() => setShowDeletingModal(false)}
|
||||
title={`Remove project ${project.name}?`}
|
||||
description={`The project ${project.name} will be removed. All data will be lost and there will be no way to
|
||||
title={`Remove project ${project?.name}?`}
|
||||
description={`The project ${project?.name} will be removed. All data will be lost and there will be no way to
|
||||
recover the app once it has been deleted.`}
|
||||
className="z-50"
|
||||
/>
|
||||
|
||||
@@ -24,7 +24,7 @@ export default function useProjectRedirectWhenReady(
|
||||
const { data, client, startPolling, ...rest } = useGetApplicationStateQuery({
|
||||
...options,
|
||||
variables: { ...options.variables, appId: project?.id },
|
||||
skip: !project.id,
|
||||
skip: !project?.id,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import type { DataGridProps } from '@/components/dataGrid/DataGrid';
|
||||
import { DataGrid } from '@/components/dataGrid/DataGrid';
|
||||
import { DataGridBooleanCell } from '@/components/dataGrid/DataGridBooleanCell';
|
||||
import { DataGridDateCell } from '@/components/dataGrid/DataGridDateCell';
|
||||
import { DataGridNumericCell } from '@/components/dataGrid/DataGridNumericCell';
|
||||
import { DataGridTextCell } from '@/components/dataGrid/DataGridTextCell';
|
||||
import { FormActivityIndicator } from '@/components/form/FormActivityIndicator';
|
||||
import { InlineCode } from '@/components/presentational/InlineCode';
|
||||
import { KeyIcon } from '@/components/ui/v2/icons/KeyIcon';
|
||||
@@ -23,11 +17,19 @@ import { normalizeDefaultValue } from '@/features/orgs/projects/database/dataGri
|
||||
import {
|
||||
POSTGRESQL_CHARACTER_TYPES,
|
||||
POSTGRESQL_DATE_TIME_TYPES,
|
||||
POSTGRESQL_DECIMAL_TYPES,
|
||||
POSTGRESQL_INTEGER_TYPES,
|
||||
POSTGRESQL_JSON_TYPES,
|
||||
POSTGRESQL_NUMERIC_TYPES,
|
||||
} from '@/features/orgs/projects/database/dataGrid/utils/postgresqlConstants';
|
||||
import { isSchemaLocked } from '@/features/orgs/projects/database/dataGrid/utils/schemaHelpers';
|
||||
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||
import type { DataGridProps } from '@/features/orgs/projects/storage/dataGrid/components/DataGrid';
|
||||
import { DataGrid } from '@/features/orgs/projects/storage/dataGrid/components/DataGrid';
|
||||
import { DataGridBooleanCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridBooleanCell';
|
||||
import { DataGridDateCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridDateCell';
|
||||
import { DataGridDecimalCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridDecimalCell';
|
||||
import { DataGridIntegerCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridIntegerCell';
|
||||
import { DataGridTextCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridTextCell';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useRouter } from 'next/router';
|
||||
@@ -68,10 +70,10 @@ export function createDataGridColumn(
|
||||
|
||||
const defaultColumnConfiguration = {
|
||||
Header: () => (
|
||||
<div className="grid items-center justify-start grid-flow-col gap-1 font-normal">
|
||||
<div className="grid grid-flow-col items-center justify-start gap-1 font-normal">
|
||||
{column.is_primary && <KeyIcon className="text-sm" />}
|
||||
|
||||
<span className="font-bold truncate" title={column.column_name}>
|
||||
<span className="truncate font-bold" title={column.column_name}>
|
||||
{column.column_name}
|
||||
</span>
|
||||
|
||||
@@ -104,12 +106,21 @@ export function createDataGridColumn(
|
||||
foreignKeyRelation: column.foreign_key_relation,
|
||||
};
|
||||
|
||||
if (POSTGRESQL_NUMERIC_TYPES.includes(column.data_type)) {
|
||||
if (POSTGRESQL_INTEGER_TYPES.includes(column.data_type)) {
|
||||
return {
|
||||
...defaultColumnConfiguration,
|
||||
type: 'number',
|
||||
width: 200,
|
||||
Cell: DataGridNumericCell,
|
||||
Cell: DataGridIntegerCell,
|
||||
};
|
||||
}
|
||||
|
||||
if (POSTGRESQL_DECIMAL_TYPES.includes(column.data_type)) {
|
||||
return {
|
||||
...defaultColumnConfiguration,
|
||||
type: 'text',
|
||||
width: 200,
|
||||
Cell: DataGridDecimalCell,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -209,7 +209,6 @@ export default function DatabaseRecordInputGroup({
|
||||
autoFocus={index === 0 && autoFocusFirstInput}
|
||||
slotProps={{
|
||||
label: commonLabelProps,
|
||||
inputRoot: { step: 1 },
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -19,20 +19,23 @@ export const POSTGRESQL_ERROR_CODES = {
|
||||
*
|
||||
* @docs https://www.postgresql.org/docs/current/datatype-numeric.html
|
||||
*/
|
||||
export const POSTGRESQL_NUMERIC_TYPES = [
|
||||
export const POSTGRESQL_INTEGER_TYPES = [
|
||||
'smallint',
|
||||
'integer',
|
||||
'bigint',
|
||||
'decimal',
|
||||
'numeric',
|
||||
'real',
|
||||
'double precision',
|
||||
'smallserial',
|
||||
'serial',
|
||||
'bigserial',
|
||||
'oid',
|
||||
];
|
||||
|
||||
export const POSTGRESQL_DECIMAL_TYPES = [
|
||||
'decimal',
|
||||
'numeric',
|
||||
'real',
|
||||
'double precision',
|
||||
];
|
||||
|
||||
/**
|
||||
* Character data types in PostgreSQL.
|
||||
*
|
||||
|
||||
@@ -118,8 +118,8 @@ export default function DatabaseConnectionInfo() {
|
||||
}
|
||||
|
||||
const postgresHost = generateAppServiceUrl(
|
||||
project.subdomain,
|
||||
project.region,
|
||||
project?.subdomain,
|
||||
project?.region,
|
||||
'db',
|
||||
).replace('https://', '');
|
||||
|
||||
|
||||
@@ -43,7 +43,8 @@ const validationSchema = Yup.object({
|
||||
value: Yup.string().required('Major version is a required field'),
|
||||
})
|
||||
.label('Postgres major version')
|
||||
.required(),
|
||||
.required()
|
||||
.test('not-zero', 'Invalid major version', (value) => value?.value !== '0'),
|
||||
minorVersion: Yup.object({
|
||||
label: Yup.string().required(),
|
||||
value: Yup.string().required('Minor version is a required field'),
|
||||
@@ -186,18 +187,29 @@ export default function DatabaseServiceVersionSettings() {
|
||||
shouldPoll: true,
|
||||
});
|
||||
|
||||
const showMigrateWarning =
|
||||
Number(selectedMajor) > Number(currentPostgresMajor);
|
||||
|
||||
const { state } = useAppState();
|
||||
const applicationUpdating =
|
||||
state === ApplicationStatus.Updating ||
|
||||
state === ApplicationStatus.Migrating;
|
||||
|
||||
const applicationLive = state === ApplicationStatus.Live;
|
||||
const applicationPaused = state === ApplicationStatus.Paused;
|
||||
const applicationPausing = state === ApplicationStatus.Pausing;
|
||||
|
||||
const showMigrateWarning =
|
||||
!applicationPaused &&
|
||||
!applicationPausing &&
|
||||
Number(selectedMajor) > Number(currentPostgresMajor);
|
||||
|
||||
const applicationUnhealthy =
|
||||
state !== ApplicationStatus.Live && !applicationUpdating;
|
||||
!applicationLive &&
|
||||
!applicationPaused &&
|
||||
!applicationPausing &&
|
||||
!applicationUpdating;
|
||||
const isMajorVersionDirty = formState?.dirtyFields?.majorVersion;
|
||||
const isMinorVersionDirty = formState?.dirtyFields?.minorVersion;
|
||||
const isDirty = isMajorVersionDirty || isMinorVersionDirty;
|
||||
|
||||
const versionFieldsDisabled =
|
||||
applicationUpdating || applicationUnhealthy || maintenanceActive;
|
||||
const saveDisabled = versionFieldsDisabled || !isDirty;
|
||||
@@ -208,7 +220,7 @@ export default function DatabaseServiceVersionSettings() {
|
||||
const newVersion = `${formValues.majorVersion.value}.${formValues.minorVersion.value}`;
|
||||
|
||||
// Major version change
|
||||
if (isMajorVersionDirty) {
|
||||
if (isMajorVersionDirty && applicationLive) {
|
||||
openDialog({
|
||||
title: 'Update Postgres MAJOR version',
|
||||
component: (
|
||||
@@ -228,7 +240,7 @@ export default function DatabaseServiceVersionSettings() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Minor version change
|
||||
// Only minor version change or project is paused/pausing
|
||||
const updateConfigPromise = updateConfig({
|
||||
variables: {
|
||||
appId: project.id,
|
||||
@@ -338,7 +350,6 @@ export default function DatabaseServiceVersionSettings() {
|
||||
return option.value;
|
||||
}}
|
||||
showCustomOption="auto"
|
||||
isOptionEqualToValue={() => false}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
const inputValueLower = inputValue.toLowerCase();
|
||||
const matched = [];
|
||||
@@ -383,12 +394,13 @@ export default function DatabaseServiceVersionSettings() {
|
||||
form.setValue('majorVersion', value);
|
||||
}
|
||||
}}
|
||||
clearOnBlur
|
||||
fullWidth
|
||||
className="lg:col-span-1"
|
||||
label="MAJOR"
|
||||
options={availableMajorVersions}
|
||||
error={!!formState.errors?.majorVersion?.value?.message}
|
||||
helperText={formState.errors?.majorVersion?.value?.message}
|
||||
error={!!formState.errors?.majorVersion?.message}
|
||||
helperText={formState.errors?.majorVersion?.message}
|
||||
customOptionLabel={(value) => `Use custom value: "${value}"`}
|
||||
/>
|
||||
<ControlledAutocomplete
|
||||
@@ -424,12 +436,13 @@ export default function DatabaseServiceVersionSettings() {
|
||||
|
||||
return result;
|
||||
}}
|
||||
clearOnBlur
|
||||
fullWidth
|
||||
className="lg:col-span-2"
|
||||
label="MINOR"
|
||||
options={availableMinorVersions}
|
||||
error={!!formState.errors?.minorVersion?.value?.message}
|
||||
helperText={formState.errors?.minorVersion?.value?.message}
|
||||
error={!!formState.errors?.minorVersion?.message}
|
||||
helperText={formState.errors?.minorVersion?.message}
|
||||
showCustomOption="auto"
|
||||
customOptionLabel={(value) => `Use custom value: "${value}"`}
|
||||
/>
|
||||
|
||||
@@ -5,28 +5,41 @@ import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { Alert } from '@/components/ui/v2/Alert';
|
||||
import { Box } from '@/components/ui/v2/Box';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { InputAdornment } from '@/components/ui/v2/InputAdornment';
|
||||
import { Link } from '@/components/ui/v2/Link';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { UpgradeNotification } from '@/features/orgs/projects/common/components/UpgradeNotification';
|
||||
import { useAppState } from '@/features/orgs/projects/common/hooks/useAppState';
|
||||
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
|
||||
import { DatabaseStorageCapacityWarning } from '@/features/orgs/projects/database/settings/components/DatabaseStorageCapacityWarning';
|
||||
import { useCurrentOrg } from '@/features/orgs/projects/hooks/useCurrentOrg';
|
||||
import { useLocalMimirClient } from '@/features/orgs/projects/hooks/useLocalMimirClient';
|
||||
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
useGetPersistentVolumesEncryptedQuery,
|
||||
useGetPostgresSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { ApplicationStatus } from '@/types/application';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
capacity: Yup.number().required().min(10),
|
||||
capacity: Yup.number()
|
||||
.integer('Capacity must be an integer')
|
||||
.typeError('You must specify a number')
|
||||
.min(1, 'Capacity must be greater than 0')
|
||||
.required('Capacity is required'),
|
||||
});
|
||||
|
||||
export type AuthDomainFormValues = Yup.InferType<typeof validationSchema>;
|
||||
export type DatabaseStorageCapacityFormValues = Yup.InferType<
|
||||
typeof validationSchema
|
||||
>;
|
||||
|
||||
export default function AuthDomain() {
|
||||
export default function DatabaseStorageCapacity() {
|
||||
const isPlatform = useIsPlatform();
|
||||
const { org } = useCurrentOrg();
|
||||
const { maintenanceActive } = useUI();
|
||||
@@ -48,6 +61,15 @@ export default function AuthDomain() {
|
||||
org?.plan?.featureMaxDbSize) ||
|
||||
0;
|
||||
|
||||
const { data: encryptedVolumesData } = useGetPersistentVolumesEncryptedQuery({
|
||||
variables: { appId: project?.id },
|
||||
skip: !isPlatform,
|
||||
});
|
||||
|
||||
const showEncryptionWarning = encryptedVolumesData
|
||||
? !encryptedVolumesData?.systemConfig?.persistentVolumesEncrypted
|
||||
: false;
|
||||
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
@@ -58,8 +80,32 @@ export default function AuthDomain() {
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
const { formState, register, reset } = form;
|
||||
const { state } = useAppState();
|
||||
|
||||
const applicationPause =
|
||||
state === ApplicationStatus.Paused || state === ApplicationStatus.Pausing;
|
||||
|
||||
const { formState, register, reset, watch } = form;
|
||||
const isDirty = Object.keys(formState.dirtyFields).length > 0;
|
||||
const newCapacity = watch('capacity');
|
||||
|
||||
const decreasingSize = newCapacity < capacity;
|
||||
|
||||
const submitDisabled = useMemo(() => {
|
||||
if (!isDirty) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (maintenanceActive) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (decreasingSize && !applicationPause) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}, [isDirty, maintenanceActive, decreasingSize, applicationPause]);
|
||||
|
||||
useEffect(() => {
|
||||
if (data && !loading) {
|
||||
@@ -81,7 +127,7 @@ export default function AuthDomain() {
|
||||
throw error;
|
||||
}
|
||||
|
||||
async function handleSubmit(formValues: AuthDomainFormValues) {
|
||||
async function handleSubmit(formValues: DatabaseStorageCapacityFormValues) {
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfig({
|
||||
@@ -120,7 +166,7 @@ export default function AuthDomain() {
|
||||
description="Specify the storage capacity for your PostgreSQL database."
|
||||
slotProps={{
|
||||
submitButton: {
|
||||
disabled: !isDirty || maintenanceActive,
|
||||
disabled: submitDisabled,
|
||||
loading: formState.isSubmitting,
|
||||
},
|
||||
}}
|
||||
@@ -134,26 +180,48 @@ export default function AuthDomain() {
|
||||
{...register('capacity')}
|
||||
id="capacity"
|
||||
name="capacity"
|
||||
type="number"
|
||||
type="text"
|
||||
endAdornment={
|
||||
<InputAdornment className="absolute right-2" position="end">
|
||||
GB
|
||||
</InputAdornment>
|
||||
}
|
||||
fullWidth
|
||||
disabled={project.legacyPlan?.isFree}
|
||||
className="lg:col-span-2"
|
||||
error={Boolean(formState.errors.capacity?.message)}
|
||||
helperText={formState.errors.capacity?.message}
|
||||
slotProps={{
|
||||
inputRoot: {
|
||||
min: capacity,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{!project.legacyPlan?.isFree && (
|
||||
<Alert severity="info" className="col-span-6 text-left">
|
||||
Note that volumes can only be increased (not decreased). Also, due
|
||||
to an AWS limitation, the same volume can only be increased once
|
||||
every 6 hours.
|
||||
</Alert>
|
||||
<DatabaseStorageCapacityWarning
|
||||
state={state}
|
||||
decreasingSize={decreasingSize}
|
||||
isDirty={isDirty}
|
||||
/>
|
||||
)}
|
||||
{showEncryptionWarning ? (
|
||||
<Alert severity="warning" className="flex flex-col gap-3 text-left">
|
||||
<div className="flex flex-col gap-2 lg:flex-row lg:justify-between">
|
||||
<Text className="flex items-start gap-1 font-semibold">
|
||||
Disk encryption is now available!
|
||||
</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text>
|
||||
To enable encryption in this project all you have to do is
|
||||
pause & unpause it in{' '}
|
||||
<Link
|
||||
href={`/orgs/${org?.slug}/projects/${project?.subdomain}/settings`}
|
||||
underline="hover"
|
||||
>
|
||||
General Settings
|
||||
</Link>
|
||||
.
|
||||
</Text>
|
||||
</div>
|
||||
</Alert>
|
||||
) : null}
|
||||
</SettingsContainer>
|
||||
</Form>
|
||||
</FormProvider>
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { Alert } from '@/components/ui/v2/Alert';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { ApplicationStatus } from '@/types/application';
|
||||
|
||||
interface DatabaseStorageCapacityWarningProps {
|
||||
state: ApplicationStatus;
|
||||
decreasingSize: boolean;
|
||||
isDirty: boolean;
|
||||
}
|
||||
|
||||
export default function DatabaseStorageCapacityWarning({
|
||||
state,
|
||||
decreasingSize,
|
||||
isDirty,
|
||||
}: DatabaseStorageCapacityWarningProps) {
|
||||
const applicationPause =
|
||||
state === ApplicationStatus.Paused || state === ApplicationStatus.Pausing;
|
||||
|
||||
if (!isDirty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (state === ApplicationStatus.Live && !decreasingSize) {
|
||||
return (
|
||||
<Alert severity="warning" className="flex flex-col gap-3 text-left">
|
||||
<div className="flex flex-col gap-2 lg:flex-row lg:justify-between">
|
||||
<Text className="flex items-start gap-1 font-semibold">
|
||||
<span>⚠</span> Warning: Increasing disk size
|
||||
</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text>
|
||||
Due to AWS limitations, disk size can only be modified once every 6
|
||||
hours. Please ensure you increase capacity sufficiently to cover
|
||||
your needs during this period.
|
||||
</Text>
|
||||
</div>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
if (state === ApplicationStatus.Live && decreasingSize) {
|
||||
return (
|
||||
<Alert severity="warning" className="flex flex-col gap-3 text-left">
|
||||
<div className="flex flex-col gap-2 lg:flex-row lg:justify-between">
|
||||
<Text className="flex items-start gap-1 font-semibold">
|
||||
<span>⚠</span> Warning: Decreasing disk size requires project to be
|
||||
paused first.
|
||||
</Text>
|
||||
</div>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
if (applicationPause && decreasingSize) {
|
||||
return (
|
||||
<Alert severity="warning" className="flex flex-col gap-3 text-left">
|
||||
<div className="flex flex-col gap-2 lg:flex-row lg:justify-between">
|
||||
<Text className="flex items-start gap-1 font-semibold">
|
||||
<span>⚠</span> Warning: Ensure enough space before downsizing.
|
||||
</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text>
|
||||
Before downsizing, ensure enough space for your database, WAL files,
|
||||
and other supporting data to prevent issues when unpausing your
|
||||
project.
|
||||
</Text>
|
||||
</div>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as DatabaseStorageCapacityWarning } from './DatabaseStorageCapacityWarning';
|
||||
@@ -0,0 +1,5 @@
|
||||
query GetPersistentVolumesEncrypted($appId: uuid!) {
|
||||
systemConfig(appID: $appId) {
|
||||
persistentVolumesEncrypted
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ const smtpValidationSchema = yup
|
||||
.required(),
|
||||
user: yup.string().label('Username').required(),
|
||||
password: yup.string().label('Password'),
|
||||
sender: yup.string().label('SMTP Sender').email().required(),
|
||||
sender: yup.string().label('SMTP Sender').required(),
|
||||
})
|
||||
.required();
|
||||
|
||||
|
||||
@@ -18,15 +18,19 @@ import { calculateBillableResources } from '@/features/orgs/projects/resources/s
|
||||
import type { ResourceSettingsFormValues } from '@/features/orgs/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
||||
import { resourceSettingsValidationSchema } from '@/features/orgs/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
||||
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
RESOURCE_VCPU_MULTIPLIER,
|
||||
RESOURCE_VCPU_PRICE,
|
||||
} from '@/utils/constants/common';
|
||||
import type { GetResourcesQuery } from '@/utils/__generated__/graphql';
|
||||
import type {
|
||||
ConfigConfigUpdateInput,
|
||||
GetResourcesQuery,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import {
|
||||
useGetResourcesQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import {
|
||||
RESOURCE_VCPU_MULTIPLIER,
|
||||
RESOURCE_VCPU_PRICE,
|
||||
} from '@/utils/constants/common';
|
||||
import { removeTypename } from '@/utils/helpers';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
@@ -36,7 +40,7 @@ function getInitialServiceResources(
|
||||
data: GetResourcesQuery,
|
||||
service: Exclude<keyof GetResourcesQuery['config'], '__typename'>,
|
||||
) {
|
||||
const { compute, replicas, autoscaler } =
|
||||
const { compute, replicas, autoscaler, ...rest } =
|
||||
data?.config?.[service]?.resources || {};
|
||||
|
||||
return {
|
||||
@@ -44,6 +48,7 @@ function getInitialServiceResources(
|
||||
vcpu: compute?.cpu || 0,
|
||||
memory: compute?.memory || 0,
|
||||
autoscale: autoscaler || null,
|
||||
rest,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -176,76 +181,130 @@ export default function ResourcesForm() {
|
||||
? (billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) * RESOURCE_VCPU_PRICE
|
||||
: 0;
|
||||
|
||||
const getFormattedConfig = (
|
||||
values: ResourceSettingsFormValues,
|
||||
): ConfigConfigUpdateInput => {
|
||||
const sanitizedValues = removeTypename(
|
||||
values,
|
||||
) as ResourceSettingsFormValues;
|
||||
|
||||
const sanitizedInitialDatabaseResources = removeTypename(
|
||||
initialDatabaseResources,
|
||||
);
|
||||
const sanitizedInitialHasuraResources = removeTypename(
|
||||
initialHasuraResources,
|
||||
);
|
||||
const sanitizedInitialAuthResources = removeTypename(initialAuthResources);
|
||||
const sanitizedInitialStorageResources = removeTypename(
|
||||
initialStorageResources,
|
||||
);
|
||||
|
||||
if (sanitizedValues.enabled) {
|
||||
return {
|
||||
postgres: {
|
||||
resources: {
|
||||
compute: {
|
||||
cpu: sanitizedValues.database.vcpu,
|
||||
memory: sanitizedValues.database.memory,
|
||||
},
|
||||
replicas: sanitizedValues.database.replicas,
|
||||
autoscaler: sanitizedValues.database.autoscale
|
||||
? {
|
||||
maxReplicas: sanitizedValues.database.maxReplicas,
|
||||
}
|
||||
: null,
|
||||
...sanitizedInitialDatabaseResources.rest,
|
||||
},
|
||||
},
|
||||
hasura: {
|
||||
resources: {
|
||||
compute: {
|
||||
cpu: sanitizedValues.hasura.vcpu,
|
||||
memory: sanitizedValues.hasura.memory,
|
||||
},
|
||||
replicas: sanitizedValues.hasura.replicas,
|
||||
autoscaler: sanitizedValues.hasura.autoscale
|
||||
? {
|
||||
maxReplicas: sanitizedValues.hasura.maxReplicas,
|
||||
}
|
||||
: null,
|
||||
...sanitizedInitialHasuraResources.rest,
|
||||
},
|
||||
},
|
||||
auth: {
|
||||
resources: {
|
||||
compute: {
|
||||
cpu: sanitizedValues.auth.vcpu,
|
||||
memory: sanitizedValues.auth.memory,
|
||||
},
|
||||
replicas: sanitizedValues.auth.replicas,
|
||||
autoscaler: sanitizedValues.auth.autoscale
|
||||
? {
|
||||
maxReplicas: sanitizedValues.auth.maxReplicas,
|
||||
}
|
||||
: null,
|
||||
...sanitizedInitialAuthResources.rest,
|
||||
},
|
||||
},
|
||||
storage: {
|
||||
resources: {
|
||||
compute: {
|
||||
cpu: sanitizedValues.storage.vcpu,
|
||||
memory: sanitizedValues.storage.memory,
|
||||
},
|
||||
replicas: sanitizedValues.storage.replicas,
|
||||
autoscaler: sanitizedValues.storage.autoscale
|
||||
? {
|
||||
maxReplicas: sanitizedValues.storage.maxReplicas,
|
||||
}
|
||||
: null,
|
||||
...sanitizedInitialStorageResources.rest,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
postgres: {
|
||||
resources: {
|
||||
compute: null,
|
||||
replicas: null,
|
||||
autoscaler: null,
|
||||
...sanitizedInitialDatabaseResources.rest,
|
||||
},
|
||||
},
|
||||
hasura: {
|
||||
resources: {
|
||||
compute: null,
|
||||
replicas: null,
|
||||
autoscaler: null,
|
||||
...sanitizedInitialHasuraResources.rest,
|
||||
},
|
||||
},
|
||||
auth: {
|
||||
resources: {
|
||||
compute: null,
|
||||
replicas: null,
|
||||
autoscaler: null,
|
||||
...sanitizedInitialAuthResources.rest,
|
||||
},
|
||||
},
|
||||
storage: {
|
||||
resources: {
|
||||
compute: null,
|
||||
replicas: null,
|
||||
autoscaler: null,
|
||||
...sanitizedInitialStorageResources.rest,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
async function handleSubmit(formValues: ResourceSettingsFormValues) {
|
||||
const updateConfigPromise = updateConfig({
|
||||
variables: {
|
||||
appId: project?.id,
|
||||
config: {
|
||||
postgres: {
|
||||
resources: formValues.enabled
|
||||
? {
|
||||
compute: {
|
||||
cpu: formValues.database.vcpu,
|
||||
memory: formValues.database.memory,
|
||||
},
|
||||
replicas: formValues.database.replicas,
|
||||
autoscaler: formValues.database.autoscale
|
||||
? {
|
||||
maxReplicas: formValues.database.maxReplicas,
|
||||
}
|
||||
: null,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
hasura: {
|
||||
resources: formValues.enabled
|
||||
? {
|
||||
compute: {
|
||||
cpu: formValues.hasura.vcpu,
|
||||
memory: formValues.hasura.memory,
|
||||
},
|
||||
replicas: formValues.hasura.replicas,
|
||||
autoscaler: formValues.hasura.autoscale
|
||||
? {
|
||||
maxReplicas: formValues.hasura.maxReplicas,
|
||||
}
|
||||
: null,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
auth: {
|
||||
resources: formValues.enabled
|
||||
? {
|
||||
compute: {
|
||||
cpu: formValues.auth.vcpu,
|
||||
memory: formValues.auth.memory,
|
||||
},
|
||||
replicas: formValues.auth.replicas,
|
||||
autoscaler: formValues.auth.autoscale
|
||||
? {
|
||||
maxReplicas: formValues.auth.maxReplicas,
|
||||
}
|
||||
: null,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
storage: {
|
||||
resources: formValues.enabled
|
||||
? {
|
||||
compute: {
|
||||
cpu: formValues.storage.vcpu,
|
||||
memory: formValues.storage.memory,
|
||||
},
|
||||
replicas: formValues.storage.replicas,
|
||||
autoscaler: formValues.storage.autoscale
|
||||
? {
|
||||
maxReplicas: formValues.storage.maxReplicas,
|
||||
}
|
||||
: null,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
},
|
||||
config: getFormattedConfig(formValues),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ export default function ServiceResourcesFormFragment({
|
||||
</Box>
|
||||
|
||||
<Box className="grid grid-flow-row gap-2">
|
||||
<Box className="grid items-center justify-between grid-flow-col gap-2">
|
||||
<Box className="grid grid-flow-col items-center justify-between gap-2">
|
||||
<Text>
|
||||
Allocated vCPUs:{' '}
|
||||
<span className="font-medium">
|
||||
@@ -201,7 +201,7 @@ export default function ServiceResourcesFormFragment({
|
||||
</Box>
|
||||
|
||||
<Box className="grid grid-flow-row gap-2">
|
||||
<Box className="grid items-center justify-between grid-flow-col gap-2">
|
||||
<Box className="grid grid-flow-col items-center justify-between gap-2">
|
||||
<Text>
|
||||
Allocated Memory:{' '}
|
||||
<span className="font-medium">
|
||||
@@ -246,7 +246,7 @@ export default function ServiceResourcesFormFragment({
|
||||
>
|
||||
<ExclamationIcon
|
||||
color="error"
|
||||
className="w-4 h-4"
|
||||
className="h-4 w-4"
|
||||
aria-hidden="false"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -274,7 +274,7 @@ export default function ServiceResourcesFormFragment({
|
||||
>
|
||||
<ExclamationIcon
|
||||
color="error"
|
||||
className="w-4 h-4"
|
||||
className="h-4 w-4"
|
||||
aria-hidden="false"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -306,7 +306,7 @@ export default function ServiceResourcesFormFragment({
|
||||
<Tooltip
|
||||
title={`Enable autoscaler to automatically provision extra ${title} replicas when needed.`}
|
||||
>
|
||||
<InfoOutlinedIcon className="w-4 h-4 text-black" />
|
||||
<InfoOutlinedIcon className="h-4 w-4" />
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -323,7 +323,7 @@ export default function ServiceResourcesFormFragment({
|
||||
className="font-medium"
|
||||
>
|
||||
Service Replicas
|
||||
<ArrowSquareOutIcon className="w-4 h-4 ml-1" />
|
||||
<ArrowSquareOutIcon className="ml-1 h-4 w-4" />
|
||||
</Link>
|
||||
</Text>
|
||||
)}
|
||||
|
||||
@@ -9,6 +9,11 @@ fragment ServiceResources on ConfigConfig {
|
||||
autoscaler {
|
||||
maxReplicas
|
||||
}
|
||||
networking {
|
||||
ingresses {
|
||||
fqdn
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
hasura {
|
||||
@@ -21,10 +26,19 @@ fragment ServiceResources on ConfigConfig {
|
||||
autoscaler {
|
||||
maxReplicas
|
||||
}
|
||||
networking {
|
||||
ingresses {
|
||||
fqdn
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
postgres {
|
||||
resources {
|
||||
storage {
|
||||
capacity
|
||||
}
|
||||
enablePublicAccess
|
||||
compute {
|
||||
cpu
|
||||
memory
|
||||
|
||||
@@ -19,7 +19,6 @@ import { StorageFormSection } from '@/features/orgs/projects/services/components
|
||||
import { useHostName } from '@/features/projects/common/hooks/useHostName';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import {
|
||||
validationSchema,
|
||||
@@ -29,16 +28,15 @@ import {
|
||||
|
||||
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import {
|
||||
useInsertRunServiceConfigMutation,
|
||||
useReplaceRunServiceConfigMutation,
|
||||
type ConfigRunServiceConfigInsertInput,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { removeTypename } from '@/utils/helpers';
|
||||
import {
|
||||
useInsertRunServiceConfigMutation,
|
||||
useInsertRunServiceMutation,
|
||||
useReplaceRunServiceConfigMutation,
|
||||
type ConfigRunServiceConfigInsertInput,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
@@ -58,9 +56,10 @@ export default function ServiceForm({
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { onDirtyStateChange, openDialog, closeDialog } = useDialog();
|
||||
const [insertRunService] = useInsertRunServiceMutation();
|
||||
const { project } = useProject();
|
||||
const [insertRunServiceConfig] = useInsertRunServiceConfigMutation();
|
||||
const [insertRunServiceConfig] = useInsertRunServiceConfigMutation({
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
const [replaceRunServiceConfig] = useReplaceRunServiceConfigMutation({
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
@@ -96,14 +95,14 @@ export default function ServiceForm({
|
||||
if (serviceID) {
|
||||
return serviceID;
|
||||
}
|
||||
return uuidv4();
|
||||
return '<uuid-to-be-generated-on-creation>';
|
||||
}, [serviceID]);
|
||||
|
||||
const privateRegistryImage = `registry.${project?.region.name}.${project?.region.domain}/${newServiceID}`;
|
||||
|
||||
let initialImageType: 'public' | 'private' | 'nhost' = 'public';
|
||||
|
||||
if (initialData?.image?.startsWith(privateRegistryImage)) {
|
||||
if (initialData?.image?.startsWith(privateRegistryImage.split('/')[0])) {
|
||||
initialImageType = 'nhost';
|
||||
}
|
||||
|
||||
@@ -225,33 +224,14 @@ export default function ServiceForm({
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Insert service config
|
||||
const {
|
||||
data: {
|
||||
insertRunService: { id },
|
||||
},
|
||||
} = await insertRunService({
|
||||
variables: {
|
||||
object: {
|
||||
appID: project.id,
|
||||
id: newServiceID,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Create service
|
||||
await insertRunServiceConfig({
|
||||
variables: {
|
||||
appID: project.id,
|
||||
serviceID: id,
|
||||
config: {
|
||||
...config,
|
||||
image: {
|
||||
// If the image field left empty then we auto-populate following this format
|
||||
// registry.<region>.<nhost_domain>/<service_id>
|
||||
image:
|
||||
values.image.length > 0
|
||||
? values.image
|
||||
: `registry.${project.region.name}.${project.region.domain}/${newServiceID}`,
|
||||
image: values.image,
|
||||
pullCredentials:
|
||||
values.pullCredentials?.length > 0
|
||||
? values.pullCredentials
|
||||
@@ -335,7 +315,7 @@ export default function ServiceForm({
|
||||
<Tooltip title="Name of the service, must be unique per project.">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="w-4 h-4"
|
||||
className="h-4 w-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -359,7 +339,7 @@ export default function ServiceForm({
|
||||
<Tooltip title="Command to run when to start the service. This is optional as the image may already have a baked-in command.">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="w-4 h-4"
|
||||
className="h-4 w-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -414,7 +394,7 @@ export default function ServiceForm({
|
||||
{createServiceFormError && (
|
||||
<Alert
|
||||
severity="error"
|
||||
className="grid items-center justify-between grid-flow-col px-4 py-3"
|
||||
className="grid grid-flow-col items-center justify-between px-4 py-3"
|
||||
>
|
||||
<span className="text-left">
|
||||
<strong>Error:</strong> {createServiceFormError.message}
|
||||
|
||||
@@ -12,7 +12,11 @@ import {
|
||||
|
||||
export const validationSchema = Yup.object({
|
||||
name: Yup.string().required('The name is required.'),
|
||||
image: Yup.string().label('Image to run').required('The image is required.'),
|
||||
image: Yup.string()
|
||||
.trim()
|
||||
.label('Image to run')
|
||||
.required('The image is required.')
|
||||
.min(1, 'Image must be at least 1 character long'),
|
||||
pullCredentials: Yup.string().label('Pull credentials').nullable(),
|
||||
command: Yup.string(),
|
||||
environment: Yup.array().of(
|
||||
|
||||
@@ -45,7 +45,7 @@ export default function ReplicasFormSection() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box className="p-4 space-y-4 rounded border-1">
|
||||
<Box className="space-y-4 rounded border-1 p-4">
|
||||
<Box className="flex flex-row items-center space-x-2">
|
||||
<Text variant="h4" className="font-semibold">
|
||||
Replicas ({replicas})
|
||||
@@ -65,7 +65,7 @@ export default function ReplicasFormSection() {
|
||||
</Text>
|
||||
}
|
||||
>
|
||||
<InfoIcon aria-label="Info" className="w-4 h-4" color="primary" />
|
||||
<InfoIcon aria-label="Info" className="h-4 w-4" color="primary" />
|
||||
</Tooltip>
|
||||
</Box>
|
||||
|
||||
@@ -121,7 +121,7 @@ export default function ReplicasFormSection() {
|
||||
/>
|
||||
<Text>Autoscaler</Text>
|
||||
<Tooltip title="Enable autoscaler to automatically provision extra run service replicas when needed.">
|
||||
<InfoOutlinedIcon className="w-4 h-4 text-black" />
|
||||
<InfoOutlinedIcon className="h-4 w-4" />
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import type { CommonDataGridCellProps } from '@/components/dataGrid/DataGridCell';
|
||||
import { useDataGridCell } from '@/components/dataGrid/DataGridCell';
|
||||
import { Input, inputClasses } from '@/components/ui/v2/Input';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import type { ChangeEvent, KeyboardEvent } from 'react';
|
||||
|
||||
export type DataGridDecimalCellProps<TData extends object> =
|
||||
CommonDataGridCellProps<TData, number | string>;
|
||||
|
||||
export default function DataGridDecimalCell<TData extends object>({
|
||||
onSave,
|
||||
optimisticValue,
|
||||
temporaryValue,
|
||||
onTemporaryValueChange,
|
||||
}: DataGridDecimalCellProps<TData>) {
|
||||
const { inputRef, focusCell, isEditing, cancelEditCell } =
|
||||
useDataGridCell<HTMLInputElement>();
|
||||
|
||||
async function handleSave() {
|
||||
if (onSave) {
|
||||
if (typeof temporaryValue === 'string') {
|
||||
await onSave(parseFloat(temporaryValue));
|
||||
} else if (typeof temporaryValue === 'number') {
|
||||
await onSave(temporaryValue);
|
||||
} else {
|
||||
await onSave(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleKeyDown(event: KeyboardEvent<HTMLInputElement>) {
|
||||
if (
|
||||
event.key === 'ArrowLeft' ||
|
||||
event.key === 'ArrowRight' ||
|
||||
event.key === 'ArrowUp' ||
|
||||
event.key === 'ArrowDown' ||
|
||||
event.key === 'Backspace'
|
||||
) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
if (event.key === 'Tab') {
|
||||
await handleSave();
|
||||
}
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
await handleSave();
|
||||
await focusCell();
|
||||
cancelEditCell();
|
||||
}
|
||||
}
|
||||
|
||||
function handleChange(event: ChangeEvent<HTMLInputElement>) {
|
||||
if (onTemporaryValueChange) {
|
||||
onTemporaryValueChange(event.target.value ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
if (isEditing) {
|
||||
return (
|
||||
<Input
|
||||
type="text"
|
||||
ref={inputRef}
|
||||
value={
|
||||
temporaryValue !== null && typeof temporaryValue !== 'undefined'
|
||||
? temporaryValue
|
||||
: ''
|
||||
}
|
||||
onKeyDown={handleKeyDown}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
className="absolute top-0 z-10 -mx-0.5 h-full place-content-stretch"
|
||||
sx={{
|
||||
[`&.${inputClasses.focused}`]: {
|
||||
boxShadow: `inset 0 0 0 1.5px rgba(0, 82, 205, 1)`,
|
||||
borderColor: 'transparent !important',
|
||||
borderRadius: 0,
|
||||
backgroundColor: (theme) =>
|
||||
theme.palette.mode === 'dark'
|
||||
? `${theme.palette.secondary[100]} !important`
|
||||
: `${theme.palette.common.white} !important`,
|
||||
},
|
||||
[`& .${inputClasses.input}`]: {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
}}
|
||||
slotProps={{
|
||||
inputWrapper: { className: 'h-full' },
|
||||
input: { className: 'h-full' },
|
||||
inputRoot: {
|
||||
className:
|
||||
'resize-none outline-none focus:outline-none !text-xs focus:ring-0',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (optimisticValue === null || typeof optimisticValue === 'undefined') {
|
||||
return (
|
||||
<Text className="truncate !text-xs" color="disabled">
|
||||
null
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return <Text className="truncate !text-xs">{optimisticValue}</Text>;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './DataGridDecimalCell';
|
||||
export { default as DataGridDecimalCell } from './DataGridDecimalCell';
|
||||
@@ -4,15 +4,15 @@ import { Input, inputClasses } from '@/components/ui/v2/Input';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import type { ChangeEvent, KeyboardEvent } from 'react';
|
||||
|
||||
export type DataGridNumericCellProps<TData extends object> =
|
||||
export type DataGridIntegerCellProps<TData extends object> =
|
||||
CommonDataGridCellProps<TData, number>;
|
||||
|
||||
export default function DataGridNumericCell<TData extends object>({
|
||||
export default function DataGridIntegerCell<TData extends object>({
|
||||
onSave,
|
||||
optimisticValue,
|
||||
temporaryValue,
|
||||
onTemporaryValueChange,
|
||||
}: DataGridNumericCellProps<TData>) {
|
||||
}: DataGridIntegerCellProps<TData>) {
|
||||
const { inputRef, focusCell, isEditing, cancelEditCell } =
|
||||
useDataGridCell<HTMLInputElement>();
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './DataGridIntegerCell';
|
||||
export { default as DataGridIntegerCell } from './DataGridIntegerCell';
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './DataGridNumericCell';
|
||||
export { default as DataGridNumericCell } from './DataGridNumericCell';
|
||||
@@ -1,3 +1,5 @@
|
||||
import { useCurrentOrg } from '@/features/orgs/projects/hooks/useCurrentOrg';
|
||||
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
@@ -8,10 +10,22 @@ import { useEffect } from 'react';
|
||||
export default function useNotFoundRedirect() {
|
||||
const router = useRouter();
|
||||
const {
|
||||
query: { orgSlug, workspaceSlug, appSubdomain, updating, appSlug },
|
||||
query: {
|
||||
orgSlug: urlOrgSlug,
|
||||
workspaceSlug: urlWorkspaceSlug,
|
||||
appSubdomain: urlAppSubdomain,
|
||||
updating,
|
||||
appSlug: urlAppSlug,
|
||||
},
|
||||
isReady,
|
||||
} = router;
|
||||
|
||||
const { project, loading: projectLoading } = useProject();
|
||||
const { org, loading: orgLoading } = useCurrentOrg();
|
||||
|
||||
const { subdomain: projectSubdomain } = project || {};
|
||||
const { slug: currentOrgSlug } = org || {};
|
||||
|
||||
const { currentProject, currentWorkspace, loading } =
|
||||
useCurrentWorkspaceAndProject();
|
||||
|
||||
@@ -23,6 +37,10 @@ export default function useNotFoundRedirect() {
|
||||
!isReady ||
|
||||
// If the current workspace and project are not loaded, we don't want to redirect to 404
|
||||
loading ||
|
||||
// If the project is loading, we don't want to redirect to 404
|
||||
projectLoading ||
|
||||
// If the org is loading, we don't want to redirect to 404
|
||||
orgLoading ||
|
||||
// If we're already on the 404 page, we don't want to redirect to 404
|
||||
router.pathname === '/404' ||
|
||||
router.pathname === '/' ||
|
||||
@@ -31,12 +49,12 @@ export default function useNotFoundRedirect() {
|
||||
router.pathname === '/run-one-click-install' ||
|
||||
router.pathname.includes('/orgs/_') ||
|
||||
router.pathname.includes('/orgs/_/projects/_') ||
|
||||
orgSlug ||
|
||||
(orgSlug && appSubdomain) ||
|
||||
(urlOrgSlug === currentOrgSlug && !urlAppSubdomain) ||
|
||||
(urlOrgSlug === currentOrgSlug && urlAppSubdomain === projectSubdomain) ||
|
||||
// If we are on a valid workspace and project, we don't want to redirect to 404
|
||||
(workspaceSlug && currentWorkspace && appSlug && currentProject) ||
|
||||
(urlWorkspaceSlug && currentWorkspace && urlAppSlug && currentProject) ||
|
||||
// If we are on a valid workspace and no project is selected, we don't want to redirect to 404
|
||||
(workspaceSlug && currentWorkspace && !appSlug && !currentProject)
|
||||
(urlWorkspaceSlug && currentWorkspace && !urlAppSlug && !currentProject)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -47,11 +65,15 @@ export default function useNotFoundRedirect() {
|
||||
currentWorkspace,
|
||||
isReady,
|
||||
loading,
|
||||
appSubdomain,
|
||||
appSlug,
|
||||
urlAppSubdomain,
|
||||
urlAppSlug,
|
||||
router,
|
||||
updating,
|
||||
workspaceSlug,
|
||||
orgSlug,
|
||||
projectLoading,
|
||||
orgLoading,
|
||||
currentOrgSlug,
|
||||
projectSubdomain,
|
||||
urlWorkspaceSlug,
|
||||
urlOrgSlug,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -28,16 +28,15 @@ import {
|
||||
type ServiceFormValues,
|
||||
} from '@/features/services/components/ServiceForm/ServiceFormTypes';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import {
|
||||
useInsertRunServiceConfigMutation,
|
||||
useReplaceRunServiceConfigMutation,
|
||||
type ConfigRunServiceConfigInsertInput,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { removeTypename } from '@/utils/helpers';
|
||||
import {
|
||||
useInsertRunServiceConfigMutation,
|
||||
useInsertRunServiceMutation,
|
||||
useReplaceRunServiceConfigMutation,
|
||||
type ConfigRunServiceConfigInsertInput,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
@@ -57,7 +56,6 @@ export default function ServiceForm({
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { onDirtyStateChange, openDialog, closeDialog } = useDialog();
|
||||
const [insertRunService] = useInsertRunServiceMutation();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [insertRunServiceConfig] = useInsertRunServiceConfigMutation();
|
||||
const [replaceRunServiceConfig] = useReplaceRunServiceConfigMutation({
|
||||
@@ -187,20 +185,11 @@ export default function ServiceForm({
|
||||
// Insert service config
|
||||
const {
|
||||
data: {
|
||||
insertRunService: { id: newServiceID, subdomain },
|
||||
insertRunServiceConfig: { serviceID: newServiceID },
|
||||
},
|
||||
} = await insertRunService({
|
||||
variables: {
|
||||
object: {
|
||||
appID: currentProject.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await insertRunServiceConfig({
|
||||
} = await insertRunServiceConfig({
|
||||
variables: {
|
||||
appID: currentProject.id,
|
||||
serviceID: newServiceID,
|
||||
config: {
|
||||
...config,
|
||||
image: {
|
||||
@@ -209,14 +198,14 @@ export default function ServiceForm({
|
||||
image:
|
||||
values.image.length > 0
|
||||
? values.image
|
||||
: `registry.${currentProject.region.name}.${currentProject.region.domain}/${newServiceID}`,
|
||||
: `registry.${currentProject.region.name}.${currentProject.region.domain}/<uuid-to-be-generated-on-creation>`,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
setDetailsServiceId(newServiceID);
|
||||
setDetailsServiceSubdomain(subdomain);
|
||||
setDetailsServiceSubdomain('');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -322,7 +311,7 @@ export default function ServiceForm({
|
||||
<Tooltip title="Name of the service, must be unique per project.">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="w-4 h-4"
|
||||
className="h-4 w-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -362,7 +351,7 @@ export default function ServiceForm({
|
||||
>
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="w-4 h-4"
|
||||
className="h-4 w-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -393,7 +382,7 @@ export default function ServiceForm({
|
||||
<Tooltip title="Command to run when to start the service. This is optional as the image may already have a baked-in command.">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="w-4 h-4"
|
||||
className="h-4 w-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -441,7 +430,7 @@ export default function ServiceForm({
|
||||
{createServiceFormError && (
|
||||
<Alert
|
||||
severity="error"
|
||||
className="grid items-center justify-between grid-flow-col px-4 py-3"
|
||||
className="grid grid-flow-col items-center justify-between px-4 py-3"
|
||||
>
|
||||
<span className="text-left">
|
||||
<strong>Error:</strong> {createServiceFormError.message}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
mutation insertRunService($object: run_service_insert_input!) {
|
||||
insertRunService(object: $object) {
|
||||
id
|
||||
subdomain
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
mutation insertRunServiceConfig(
|
||||
mutation InsertRunServiceConfig(
|
||||
$appID: uuid!
|
||||
$serviceID: uuid!
|
||||
$config: ConfigRunServiceConfigInsertInput!
|
||||
) {
|
||||
insertRunServiceConfig(
|
||||
appID: $appID
|
||||
serviceID: $serviceID
|
||||
config: $config
|
||||
) {
|
||||
name
|
||||
insertRunServiceConfig(appID: $appID, config: $config) {
|
||||
serviceID
|
||||
config {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ import { Form } from '@/components/form/Form';
|
||||
import { Container } from '@/components/layout/Container';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { Alert } from '@/components/ui/v2/Alert';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { Link } from '@/components/ui/v2/Link';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { TransferProject } from '@/features/orgs/components/TransferProject';
|
||||
import { ProjectLayout } from '@/features/orgs/layout/ProjectLayout';
|
||||
import { SettingsLayout } from '@/features/orgs/layout/SettingsLayout';
|
||||
@@ -12,6 +15,7 @@ import { RemoveApplicationModal } from '@/features/orgs/projects/common/componen
|
||||
import { useAppState } from '@/features/orgs/projects/common/hooks/useAppState';
|
||||
import { useIsCurrentUserOwner } from '@/features/orgs/projects/common/hooks/useIsCurrentUserOwner';
|
||||
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
|
||||
import { useRunServices } from '@/features/orgs/projects/common/hooks/useRunServices';
|
||||
import { useOrgs } from '@/features/orgs/projects/hooks/useOrgs';
|
||||
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
|
||||
@@ -25,7 +29,7 @@ import { ApplicationStatus } from '@/types/application';
|
||||
import { slugifyString } from '@/utils/helpers';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useRouter } from 'next/router';
|
||||
import type { ReactElement } from 'react';
|
||||
import { useMemo, type ReactElement } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -51,6 +55,20 @@ export default function SettingsGeneralPage() {
|
||||
const { project, loading, refetch: refetchProject } = useProject();
|
||||
const { state } = useAppState();
|
||||
|
||||
const { services } = useRunServices();
|
||||
|
||||
const showWarning = useMemo(() => {
|
||||
const isPlanFree = org?.plan?.isFree;
|
||||
|
||||
if (isPlanFree) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return services?.some(
|
||||
(service) => service?.config?.resources?.storage?.length > 0,
|
||||
);
|
||||
}, [org?.plan?.isFree, services]);
|
||||
|
||||
const [updateApp] = useUpdateApplicationMutation();
|
||||
const [deleteApplication] = useBillingDeleteAppMutation();
|
||||
const [pauseApplication, { loading: pauseApplicationLoading }] =
|
||||
@@ -242,9 +260,49 @@ export default function SettingsGeneralPage() {
|
||||
onClick: () => {
|
||||
openAlertDialog({
|
||||
title: 'Pause Project?',
|
||||
payload:
|
||||
'Are you sure you want to pause this project? It will not be accessible until you unpause it.',
|
||||
payload: (
|
||||
<div className="flex flex-col gap-2">
|
||||
{showWarning ? (
|
||||
<Alert
|
||||
severity="warning"
|
||||
className="flex flex-col gap-3 text-left"
|
||||
>
|
||||
<div className="flex flex-col gap-2 lg:flex-row lg:justify-between">
|
||||
<Text className="flex items-start gap-1 font-semibold">
|
||||
<span>⚠</span> Warning: This action will delete
|
||||
all volume data for your Run services.
|
||||
</Text>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Text>
|
||||
Pausing this project will delete all persistent
|
||||
volume data for your Run services. No automatic
|
||||
backups are made. Please backup your data
|
||||
manually to prevent loss. Contact{' '}
|
||||
<Link
|
||||
href="/support"
|
||||
target="_blank"
|
||||
className="underline"
|
||||
sx={{
|
||||
color: 'text.primary',
|
||||
}}
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
support
|
||||
</Link>{' '}
|
||||
with any questions.
|
||||
</Text>
|
||||
</div>
|
||||
</Alert>
|
||||
) : null}
|
||||
<p className="text-pretty">
|
||||
Are you sure you want to pause this project? It will
|
||||
not be accessible until you unpause it.
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
props: {
|
||||
maxWidth: 'sm',
|
||||
onPrimaryAction: handlePauseApplication,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ const validationSchema = Yup.object({
|
||||
email: Yup.string().label('Email').email().required(),
|
||||
});
|
||||
|
||||
export type ResetPasswordFormValues = Yup.InferType<typeof validationSchema>;
|
||||
export type NewPasswordFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
const StyledInput = styled(Input)({
|
||||
backgroundColor: 'transparent',
|
||||
@@ -28,10 +28,10 @@ const StyledInput = styled(Input)({
|
||||
},
|
||||
});
|
||||
|
||||
export default function ResetPasswordPage() {
|
||||
export default function NewPasswordPage() {
|
||||
const { resetPassword, error, isSent } = useResetPassword();
|
||||
|
||||
const form = useForm<ResetPasswordFormValues>({
|
||||
const form = useForm<NewPasswordFormValues>({
|
||||
reValidateMode: 'onSubmit',
|
||||
defaultValues: {
|
||||
email: '',
|
||||
@@ -52,9 +52,11 @@ export default function ResetPasswordPage() {
|
||||
);
|
||||
}, [error]);
|
||||
|
||||
async function handleSubmit({ email }: ResetPasswordFormValues) {
|
||||
async function handleSubmit({ email }: NewPasswordFormValues) {
|
||||
try {
|
||||
await resetPassword(email);
|
||||
await resetPassword(email, {
|
||||
redirectTo: '/password/reset',
|
||||
});
|
||||
} catch {
|
||||
toast.error(
|
||||
'An error occurred while signing up. Please try again.',
|
||||
@@ -124,8 +126,10 @@ export default function ResetPasswordPage() {
|
||||
);
|
||||
}
|
||||
|
||||
ResetPasswordPage.getLayout = function getLayout(page: ReactElement) {
|
||||
NewPasswordPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<UnauthenticatedLayout title="Reset Password">{page}</UnauthenticatedLayout>
|
||||
<UnauthenticatedLayout title="Request Password Reset">
|
||||
{page}
|
||||
</UnauthenticatedLayout>
|
||||
);
|
||||
};
|
||||
144
dashboard/src/pages/password/reset.tsx
Normal file
144
dashboard/src/pages/password/reset.tsx
Normal file
@@ -0,0 +1,144 @@
|
||||
import { NavLink } from '@/components/common/NavLink';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { UnauthenticatedLayout } from '@/components/layout/UnauthenticatedLayout';
|
||||
import { Box } from '@/components/ui/v2/Box';
|
||||
import { Button } from '@/components/ui/v2/Button';
|
||||
import { Input, inputClasses } from '@/components/ui/v2/Input';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { styled } from '@mui/material';
|
||||
import { useChangePassword } from '@nhost/nextjs';
|
||||
import { useRouter } from 'next/router';
|
||||
import type { ReactElement } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
newPassword: Yup.string()
|
||||
.label('New Password')
|
||||
.required('New Password is required'),
|
||||
confirmNewPassword: Yup.string()
|
||||
.label('Confirm New Password')
|
||||
.required('Confirm New Password is required')
|
||||
.oneOf([Yup.ref('newPassword')], 'Passwords must match'),
|
||||
});
|
||||
|
||||
export type ResetPasswordFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
const StyledInput = styled(Input)({
|
||||
backgroundColor: 'transparent',
|
||||
[`& .${inputClasses.input}`]: {
|
||||
backgroundColor: 'transparent !important',
|
||||
},
|
||||
});
|
||||
|
||||
export default function ResetPasswordPage() {
|
||||
const router = useRouter();
|
||||
const { changePassword } = useChangePassword();
|
||||
|
||||
const form = useForm<ResetPasswordFormValues>({
|
||||
reValidateMode: 'onSubmit',
|
||||
defaultValues: {
|
||||
newPassword: '',
|
||||
confirmNewPassword: '',
|
||||
},
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
const { register, formState } = form;
|
||||
|
||||
async function handleSubmit({ newPassword }: ResetPasswordFormValues) {
|
||||
try {
|
||||
const password = newPassword;
|
||||
|
||||
const { isError, error } = await changePassword(password);
|
||||
|
||||
if (isError) {
|
||||
toast.error(
|
||||
`An error occurred while changing your password: ${error.message}`,
|
||||
getToastStyleProps(),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success('Password was updated successfully.');
|
||||
router.push('/');
|
||||
} catch {
|
||||
toast.error(
|
||||
'An error occurred while updating your password. Please try again.',
|
||||
getToastStyleProps(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text
|
||||
variant="h2"
|
||||
component="h1"
|
||||
className="text-center text-3.5xl font-semibold lg:text-4.5xl"
|
||||
>
|
||||
Change password
|
||||
</Text>
|
||||
|
||||
<Box className="grid grid-flow-row gap-4 rounded-md border bg-transparent p-6 lg:p-12">
|
||||
<FormProvider {...form}>
|
||||
<Form
|
||||
onSubmit={handleSubmit}
|
||||
className="grid grid-flow-row gap-4 bg-transparent"
|
||||
>
|
||||
<StyledInput
|
||||
{...register('newPassword')}
|
||||
type="password"
|
||||
id="newPassword"
|
||||
label="New Password"
|
||||
fullWidth
|
||||
inputProps={{ min: 2, max: 128 }}
|
||||
error={!!formState.errors.newPassword}
|
||||
helperText={formState.errors.newPassword?.message}
|
||||
/>
|
||||
|
||||
<StyledInput
|
||||
{...register('confirmNewPassword')}
|
||||
type="password"
|
||||
id="confirmNewPassword"
|
||||
label="Confirm New Password"
|
||||
fullWidth
|
||||
inputProps={{ min: 2, max: 128 }}
|
||||
error={!!formState.errors.confirmNewPassword}
|
||||
helperText={formState.errors.confirmNewPassword?.message}
|
||||
/>
|
||||
|
||||
<Button
|
||||
className="!bg-white !text-black disabled:!text-black disabled:!text-opacity-60"
|
||||
size="large"
|
||||
type="submit"
|
||||
disabled={formState.isSubmitting}
|
||||
loading={formState.isSubmitting}
|
||||
>
|
||||
Change password
|
||||
</Button>
|
||||
</Form>
|
||||
</FormProvider>
|
||||
</Box>
|
||||
|
||||
<Text color="secondary" className="text-center text-base lg:text-lg">
|
||||
Go back to{' '}
|
||||
<NavLink href="/signin/email" color="white" className="font-medium">
|
||||
Sign In
|
||||
</NavLink>
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
ResetPasswordPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<UnauthenticatedLayout title="Request Password Reset">
|
||||
{page}
|
||||
</UnauthenticatedLayout>
|
||||
);
|
||||
};
|
||||
@@ -85,7 +85,7 @@ export default function EmailSignUpPage() {
|
||||
Sign In
|
||||
</Text>
|
||||
|
||||
<Box className="grid grid-flow-row gap-4 p-6 bg-transparent border rounded-md lg:p-12">
|
||||
<Box className="grid grid-flow-row gap-4 rounded-md border bg-transparent p-6 lg:p-12">
|
||||
<FormProvider {...form}>
|
||||
<Form
|
||||
onSubmit={handleSubmit}
|
||||
@@ -123,9 +123,9 @@ export default function EmailSignUpPage() {
|
||||
/>
|
||||
|
||||
<NavLink
|
||||
href="/reset-password"
|
||||
href="/password/new"
|
||||
color="white"
|
||||
className="font-semibold justify-self-start"
|
||||
className="justify-self-start font-semibold"
|
||||
>
|
||||
Forgot password?
|
||||
</NavLink>
|
||||
@@ -150,7 +150,7 @@ export default function EmailSignUpPage() {
|
||||
</FormProvider>
|
||||
</Box>
|
||||
|
||||
<Text color="secondary" className="text-base text-center lg:text-lg">
|
||||
<Text color="secondary" className="text-center text-base lg:text-lg">
|
||||
Don't have an account?{' '}
|
||||
<NavLink href="/signup" color="white">
|
||||
Sign Up
|
||||
|
||||
175
dashboard/src/utils/__generated__/graphql.ts
generated
175
dashboard/src/utils/__generated__/graphql.ts
generated
@@ -3191,6 +3191,12 @@ export type ContainerError = {
|
||||
name: Scalars['String'];
|
||||
};
|
||||
|
||||
export type InsertRunServiceConfigResponse = {
|
||||
__typename?: 'InsertRunServiceConfigResponse';
|
||||
config: ConfigRunServiceConfig;
|
||||
serviceID: Scalars['uuid'];
|
||||
};
|
||||
|
||||
/** Boolean expression to compare columns of type "Int". All fields are combined with logical 'AND'. */
|
||||
export type Int_Comparison_Exp = {
|
||||
_eq?: InputMaybe<Scalars['Int']>;
|
||||
@@ -13530,7 +13536,7 @@ export type Mutation_Root = {
|
||||
insertRegionsAllowedWorkspaces?: Maybe<Regions_Allowed_Workspace_Mutation_Response>;
|
||||
/** insert a single row into the table: "run_service" */
|
||||
insertRunService?: Maybe<Run_Service>;
|
||||
insertRunServiceConfig: ConfigRunServiceConfig;
|
||||
insertRunServiceConfig: InsertRunServiceConfigResponse;
|
||||
/** insert data into the table: "run_service" */
|
||||
insertRunServices?: Maybe<Run_Service_Mutation_Response>;
|
||||
insertSecret: ConfigEnvironmentVariable;
|
||||
@@ -15213,7 +15219,6 @@ export type Mutation_RootInsertRunServiceArgs = {
|
||||
export type Mutation_RootInsertRunServiceConfigArgs = {
|
||||
appID: Scalars['uuid'];
|
||||
config: ConfigRunServiceConfigInsertInput;
|
||||
serviceID: Scalars['uuid'];
|
||||
};
|
||||
|
||||
|
||||
@@ -16986,7 +16991,7 @@ export type Organization_Member_Invites = {
|
||||
role: Organization_Members_Role_Enum;
|
||||
updateAt: Scalars['timestamptz'];
|
||||
/** An object relationship */
|
||||
user: Users;
|
||||
user?: Maybe<Users>;
|
||||
};
|
||||
|
||||
/** aggregated selection of "organization_member_invites" */
|
||||
@@ -22517,9 +22522,6 @@ export type Run_Service = {
|
||||
appID: Scalars['uuid'];
|
||||
config?: Maybe<ConfigRunServiceConfig>;
|
||||
createdAt: Scalars['timestamptz'];
|
||||
/** An object relationship */
|
||||
creator: Users;
|
||||
creatorUserId: Scalars['uuid'];
|
||||
id: Scalars['uuid'];
|
||||
mimirConfigEnc?: Maybe<Scalars['String']>;
|
||||
subdomain: Scalars['String'];
|
||||
@@ -22587,8 +22589,6 @@ export type Run_Service_Bool_Exp = {
|
||||
app?: InputMaybe<Apps_Bool_Exp>;
|
||||
appID?: InputMaybe<Uuid_Comparison_Exp>;
|
||||
createdAt?: InputMaybe<Timestamptz_Comparison_Exp>;
|
||||
creator?: InputMaybe<Users_Bool_Exp>;
|
||||
creatorUserId?: InputMaybe<Uuid_Comparison_Exp>;
|
||||
id?: InputMaybe<Uuid_Comparison_Exp>;
|
||||
mimirConfigEnc?: InputMaybe<String_Comparison_Exp>;
|
||||
subdomain?: InputMaybe<String_Comparison_Exp>;
|
||||
@@ -22608,8 +22608,6 @@ export type Run_Service_Insert_Input = {
|
||||
app?: InputMaybe<Apps_Obj_Rel_Insert_Input>;
|
||||
appID?: InputMaybe<Scalars['uuid']>;
|
||||
createdAt?: InputMaybe<Scalars['timestamptz']>;
|
||||
creator?: InputMaybe<Users_Obj_Rel_Insert_Input>;
|
||||
creatorUserId?: InputMaybe<Scalars['uuid']>;
|
||||
id?: InputMaybe<Scalars['uuid']>;
|
||||
mimirConfigEnc?: InputMaybe<Scalars['String']>;
|
||||
subdomain?: InputMaybe<Scalars['String']>;
|
||||
@@ -22621,7 +22619,6 @@ export type Run_Service_Max_Fields = {
|
||||
__typename?: 'run_service_max_fields';
|
||||
appID?: Maybe<Scalars['uuid']>;
|
||||
createdAt?: Maybe<Scalars['timestamptz']>;
|
||||
creatorUserId?: Maybe<Scalars['uuid']>;
|
||||
id?: Maybe<Scalars['uuid']>;
|
||||
mimirConfigEnc?: Maybe<Scalars['String']>;
|
||||
subdomain?: Maybe<Scalars['String']>;
|
||||
@@ -22632,7 +22629,6 @@ export type Run_Service_Max_Fields = {
|
||||
export type Run_Service_Max_Order_By = {
|
||||
appID?: InputMaybe<Order_By>;
|
||||
createdAt?: InputMaybe<Order_By>;
|
||||
creatorUserId?: InputMaybe<Order_By>;
|
||||
id?: InputMaybe<Order_By>;
|
||||
mimirConfigEnc?: InputMaybe<Order_By>;
|
||||
subdomain?: InputMaybe<Order_By>;
|
||||
@@ -22644,7 +22640,6 @@ export type Run_Service_Min_Fields = {
|
||||
__typename?: 'run_service_min_fields';
|
||||
appID?: Maybe<Scalars['uuid']>;
|
||||
createdAt?: Maybe<Scalars['timestamptz']>;
|
||||
creatorUserId?: Maybe<Scalars['uuid']>;
|
||||
id?: Maybe<Scalars['uuid']>;
|
||||
mimirConfigEnc?: Maybe<Scalars['String']>;
|
||||
subdomain?: Maybe<Scalars['String']>;
|
||||
@@ -22655,7 +22650,6 @@ export type Run_Service_Min_Fields = {
|
||||
export type Run_Service_Min_Order_By = {
|
||||
appID?: InputMaybe<Order_By>;
|
||||
createdAt?: InputMaybe<Order_By>;
|
||||
creatorUserId?: InputMaybe<Order_By>;
|
||||
id?: InputMaybe<Order_By>;
|
||||
mimirConfigEnc?: InputMaybe<Order_By>;
|
||||
subdomain?: InputMaybe<Order_By>;
|
||||
@@ -22683,8 +22677,6 @@ export type Run_Service_Order_By = {
|
||||
app?: InputMaybe<Apps_Order_By>;
|
||||
appID?: InputMaybe<Order_By>;
|
||||
createdAt?: InputMaybe<Order_By>;
|
||||
creator?: InputMaybe<Users_Order_By>;
|
||||
creatorUserId?: InputMaybe<Order_By>;
|
||||
id?: InputMaybe<Order_By>;
|
||||
mimirConfigEnc?: InputMaybe<Order_By>;
|
||||
subdomain?: InputMaybe<Order_By>;
|
||||
@@ -22703,8 +22695,6 @@ export enum Run_Service_Select_Column {
|
||||
/** column name */
|
||||
CreatedAt = 'createdAt',
|
||||
/** column name */
|
||||
CreatorUserId = 'creatorUserId',
|
||||
/** column name */
|
||||
Id = 'id',
|
||||
/** column name */
|
||||
MimirConfigEnc = 'mimirConfigEnc',
|
||||
@@ -22718,7 +22708,6 @@ export enum Run_Service_Select_Column {
|
||||
export type Run_Service_Set_Input = {
|
||||
appID?: InputMaybe<Scalars['uuid']>;
|
||||
createdAt?: InputMaybe<Scalars['timestamptz']>;
|
||||
creatorUserId?: InputMaybe<Scalars['uuid']>;
|
||||
id?: InputMaybe<Scalars['uuid']>;
|
||||
mimirConfigEnc?: InputMaybe<Scalars['String']>;
|
||||
subdomain?: InputMaybe<Scalars['String']>;
|
||||
@@ -22737,7 +22726,6 @@ export type Run_Service_Stream_Cursor_Input = {
|
||||
export type Run_Service_Stream_Cursor_Value_Input = {
|
||||
appID?: InputMaybe<Scalars['uuid']>;
|
||||
createdAt?: InputMaybe<Scalars['timestamptz']>;
|
||||
creatorUserId?: InputMaybe<Scalars['uuid']>;
|
||||
id?: InputMaybe<Scalars['uuid']>;
|
||||
mimirConfigEnc?: InputMaybe<Scalars['String']>;
|
||||
subdomain?: InputMaybe<Scalars['String']>;
|
||||
@@ -22751,8 +22739,6 @@ export enum Run_Service_Update_Column {
|
||||
/** column name */
|
||||
CreatedAt = 'createdAt',
|
||||
/** column name */
|
||||
CreatorUserId = 'creatorUserId',
|
||||
/** column name */
|
||||
Id = 'id',
|
||||
/** column name */
|
||||
MimirConfigEnc = 'mimirConfigEnc',
|
||||
@@ -25315,10 +25301,6 @@ export type Users = {
|
||||
/** An aggregate relationship */
|
||||
roles_aggregate: AuthUserRoles_Aggregate;
|
||||
/** An array relationship */
|
||||
runServices: Array<Run_Service>;
|
||||
/** An aggregate relationship */
|
||||
runServices_aggregate: Run_Service_Aggregate;
|
||||
/** An array relationship */
|
||||
securityKeys: Array<AuthUserSecurityKeys>;
|
||||
/** An aggregate relationship */
|
||||
securityKeys_aggregate: AuthUserSecurityKeys_Aggregate;
|
||||
@@ -25511,26 +25493,6 @@ export type UsersRoles_AggregateArgs = {
|
||||
};
|
||||
|
||||
|
||||
/** User account information. Don't modify its structure as Hasura Auth relies on it to function properly. */
|
||||
export type UsersRunServicesArgs = {
|
||||
distinct_on?: InputMaybe<Array<Run_Service_Select_Column>>;
|
||||
limit?: InputMaybe<Scalars['Int']>;
|
||||
offset?: InputMaybe<Scalars['Int']>;
|
||||
order_by?: InputMaybe<Array<Run_Service_Order_By>>;
|
||||
where?: InputMaybe<Run_Service_Bool_Exp>;
|
||||
};
|
||||
|
||||
|
||||
/** User account information. Don't modify its structure as Hasura Auth relies on it to function properly. */
|
||||
export type UsersRunServices_AggregateArgs = {
|
||||
distinct_on?: InputMaybe<Array<Run_Service_Select_Column>>;
|
||||
limit?: InputMaybe<Scalars['Int']>;
|
||||
offset?: InputMaybe<Scalars['Int']>;
|
||||
order_by?: InputMaybe<Array<Run_Service_Order_By>>;
|
||||
where?: InputMaybe<Run_Service_Bool_Exp>;
|
||||
};
|
||||
|
||||
|
||||
/** User account information. Don't modify its structure as Hasura Auth relies on it to function properly. */
|
||||
export type UsersSecurityKeysArgs = {
|
||||
distinct_on?: InputMaybe<Array<AuthUserSecurityKeys_Select_Column>>;
|
||||
@@ -25742,8 +25704,6 @@ export type Users_Bool_Exp = {
|
||||
role?: InputMaybe<AuthRoles_Bool_Exp>;
|
||||
roles?: InputMaybe<AuthUserRoles_Bool_Exp>;
|
||||
roles_aggregate?: InputMaybe<AuthUserRoles_Aggregate_Bool_Exp>;
|
||||
runServices?: InputMaybe<Run_Service_Bool_Exp>;
|
||||
runServices_aggregate?: InputMaybe<Run_Service_Aggregate_Bool_Exp>;
|
||||
securityKeys?: InputMaybe<AuthUserSecurityKeys_Bool_Exp>;
|
||||
securityKeys_aggregate?: InputMaybe<AuthUserSecurityKeys_Aggregate_Bool_Exp>;
|
||||
ticket?: InputMaybe<String_Comparison_Exp>;
|
||||
@@ -25818,7 +25778,6 @@ export type Users_Insert_Input = {
|
||||
refreshTokens?: InputMaybe<AuthRefreshTokens_Arr_Rel_Insert_Input>;
|
||||
role?: InputMaybe<AuthRoles_Obj_Rel_Insert_Input>;
|
||||
roles?: InputMaybe<AuthUserRoles_Arr_Rel_Insert_Input>;
|
||||
runServices?: InputMaybe<Run_Service_Arr_Rel_Insert_Input>;
|
||||
securityKeys?: InputMaybe<AuthUserSecurityKeys_Arr_Rel_Insert_Input>;
|
||||
ticket?: InputMaybe<Scalars['String']>;
|
||||
ticketExpiresAt?: InputMaybe<Scalars['timestamptz']>;
|
||||
@@ -25984,7 +25943,6 @@ export type Users_Order_By = {
|
||||
refreshTokens_aggregate?: InputMaybe<AuthRefreshTokens_Aggregate_Order_By>;
|
||||
role?: InputMaybe<AuthRoles_Order_By>;
|
||||
roles_aggregate?: InputMaybe<AuthUserRoles_Aggregate_Order_By>;
|
||||
runServices_aggregate?: InputMaybe<Run_Service_Aggregate_Order_By>;
|
||||
securityKeys_aggregate?: InputMaybe<AuthUserSecurityKeys_Aggregate_Order_By>;
|
||||
ticket?: InputMaybe<Order_By>;
|
||||
ticketExpiresAt?: InputMaybe<Order_By>;
|
||||
@@ -27724,6 +27682,13 @@ export type GetBackupPresignedUrlQueryVariables = Exact<{
|
||||
|
||||
export type GetBackupPresignedUrlQuery = { __typename?: 'query_root', getBackupPresignedUrl: { __typename?: 'BackupPresignedURL', url: string, expiresAt: any } };
|
||||
|
||||
export type GetPersistentVolumesEncryptedQueryVariables = Exact<{
|
||||
appId: Scalars['uuid'];
|
||||
}>;
|
||||
|
||||
|
||||
export type GetPersistentVolumesEncryptedQuery = { __typename?: 'query_root', systemConfig?: { __typename?: 'ConfigSystemConfig', persistentVolumesEncrypted?: boolean | null } | null };
|
||||
|
||||
export type GetJwtSecretsQueryVariables = Exact<{
|
||||
appId: Scalars['uuid'];
|
||||
}>;
|
||||
@@ -27738,14 +27703,14 @@ export type GetObservabilitySettingsQueryVariables = Exact<{
|
||||
|
||||
export type GetObservabilitySettingsQuery = { __typename?: 'query_root', config?: { __typename: 'ConfigConfig', id: 'ConfigConfig', observability: { __typename?: 'ConfigObservability', grafana: { __typename?: 'ConfigGrafana', alerting?: { __typename?: 'ConfigGrafanaAlerting', enabled?: boolean | null } | null, smtp?: { __typename?: 'ConfigGrafanaSmtp', host: string, password: string, port: any, sender: string, user: string } | null, contacts?: { __typename?: 'ConfigGrafanaContacts', emails?: Array<string> | null, discord?: Array<{ __typename?: 'ConfigGrafanacontactsDiscord', avatarUrl: string, url: string }> | null, pagerduty?: Array<{ __typename?: 'ConfigGrafanacontactsPagerduty', integrationKey: string, severity: string, class: string, component: string, group: string }> | null, slack?: Array<{ __typename?: 'ConfigGrafanacontactsSlack', recipient: string, token: string, username: string, iconEmoji: string, iconURL: string, mentionUsers: Array<string>, mentionGroups: Array<string>, mentionChannel: string, url: string, endpointURL: string }> | null, webhook?: Array<{ __typename?: 'ConfigGrafanacontactsWebhook', url: string, httpMethod: string, username: string, password: string, authorizationScheme: string, authorizationCredentials: string, maxAlerts: number }> | null } | null } } } | null };
|
||||
|
||||
export type ServiceResourcesFragment = { __typename?: 'ConfigConfig', auth?: { __typename?: 'ConfigAuth', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null } | null } | null, hasura: { __typename?: 'ConfigHasura', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null } | null }, postgres?: { __typename?: 'ConfigPostgres', resources?: { __typename?: 'ConfigPostgresResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null } | null } | null, storage?: { __typename?: 'ConfigStorage', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null } | null } | null };
|
||||
export type ServiceResourcesFragment = { __typename?: 'ConfigConfig', auth?: { __typename?: 'ConfigAuth', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null, networking?: { __typename?: 'ConfigNetworking', ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null } | null } | null } | null, hasura: { __typename?: 'ConfigHasura', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null, networking?: { __typename?: 'ConfigNetworking', ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null } | null } | null }, postgres?: { __typename?: 'ConfigPostgres', resources?: { __typename?: 'ConfigPostgresResources', enablePublicAccess?: boolean | null, replicas?: any | null, storage?: { __typename?: 'ConfigPostgresStorage', capacity: any } | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null } | null } | null, storage?: { __typename?: 'ConfigStorage', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null } | null } | null };
|
||||
|
||||
export type GetResourcesQueryVariables = Exact<{
|
||||
appId: Scalars['uuid'];
|
||||
}>;
|
||||
|
||||
|
||||
export type GetResourcesQuery = { __typename?: 'query_root', config?: { __typename?: 'ConfigConfig', auth?: { __typename?: 'ConfigAuth', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null } | null } | null, hasura: { __typename?: 'ConfigHasura', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null } | null }, postgres?: { __typename?: 'ConfigPostgres', resources?: { __typename?: 'ConfigPostgresResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null } | null } | null, storage?: { __typename?: 'ConfigStorage', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null } | null } | null } | null };
|
||||
export type GetResourcesQuery = { __typename?: 'query_root', config?: { __typename?: 'ConfigConfig', auth?: { __typename?: 'ConfigAuth', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null, networking?: { __typename?: 'ConfigNetworking', ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null } | null } | null } | null, hasura: { __typename?: 'ConfigHasura', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null, networking?: { __typename?: 'ConfigNetworking', ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null } | null } | null }, postgres?: { __typename?: 'ConfigPostgres', resources?: { __typename?: 'ConfigPostgresResources', enablePublicAccess?: boolean | null, replicas?: any | null, storage?: { __typename?: 'ConfigPostgresStorage', capacity: any } | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null } | null } | null, storage?: { __typename?: 'ConfigStorage', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null } | null } | null } | null };
|
||||
|
||||
export type GetStorageSettingsQueryVariables = Exact<{
|
||||
appId: Scalars['uuid'];
|
||||
@@ -28592,21 +28557,13 @@ export type GetLocalRunServiceRateLimitQueryVariables = Exact<{
|
||||
|
||||
export type GetLocalRunServiceRateLimitQuery = { __typename?: 'query_root', runServiceConfigs: Array<{ __typename?: 'ConfigRunServiceConfigWithID', serviceID: any, config: { __typename?: 'ConfigRunServiceConfig', name: any, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null }> | null } }> };
|
||||
|
||||
export type InsertRunServiceMutationVariables = Exact<{
|
||||
object: Run_Service_Insert_Input;
|
||||
}>;
|
||||
|
||||
|
||||
export type InsertRunServiceMutation = { __typename?: 'mutation_root', insertRunService?: { __typename?: 'run_service', id: any, subdomain: string } | null };
|
||||
|
||||
export type InsertRunServiceConfigMutationVariables = Exact<{
|
||||
appID: Scalars['uuid'];
|
||||
serviceID: Scalars['uuid'];
|
||||
config: ConfigRunServiceConfigInsertInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type InsertRunServiceConfigMutation = { __typename?: 'mutation_root', insertRunServiceConfig: { __typename?: 'ConfigRunServiceConfig', name: any } };
|
||||
export type InsertRunServiceConfigMutation = { __typename?: 'mutation_root', insertRunServiceConfig: { __typename?: 'InsertRunServiceConfigResponse', serviceID: any, config: { __typename?: 'ConfigRunServiceConfig', name: any } } };
|
||||
|
||||
export type ReplaceRunServiceConfigMutationVariables = Exact<{
|
||||
appID: Scalars['uuid'];
|
||||
@@ -28730,6 +28687,11 @@ export const ServiceResourcesFragmentDoc = gql`
|
||||
autoscaler {
|
||||
maxReplicas
|
||||
}
|
||||
networking {
|
||||
ingresses {
|
||||
fqdn
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
hasura {
|
||||
@@ -28742,10 +28704,19 @@ export const ServiceResourcesFragmentDoc = gql`
|
||||
autoscaler {
|
||||
maxReplicas
|
||||
}
|
||||
networking {
|
||||
ingresses {
|
||||
fqdn
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
postgres {
|
||||
resources {
|
||||
storage {
|
||||
capacity
|
||||
}
|
||||
enablePublicAccess
|
||||
compute {
|
||||
cpu
|
||||
memory
|
||||
@@ -29692,6 +29663,44 @@ export type GetBackupPresignedUrlQueryResult = Apollo.QueryResult<GetBackupPresi
|
||||
export function refetchGetBackupPresignedUrlQuery(variables: GetBackupPresignedUrlQueryVariables) {
|
||||
return { query: GetBackupPresignedUrlDocument, variables: variables }
|
||||
}
|
||||
export const GetPersistentVolumesEncryptedDocument = gql`
|
||||
query GetPersistentVolumesEncrypted($appId: uuid!) {
|
||||
systemConfig(appID: $appId) {
|
||||
persistentVolumesEncrypted
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useGetPersistentVolumesEncryptedQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useGetPersistentVolumesEncryptedQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useGetPersistentVolumesEncryptedQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useGetPersistentVolumesEncryptedQuery({
|
||||
* variables: {
|
||||
* appId: // value for 'appId'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useGetPersistentVolumesEncryptedQuery(baseOptions: Apollo.QueryHookOptions<GetPersistentVolumesEncryptedQuery, GetPersistentVolumesEncryptedQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<GetPersistentVolumesEncryptedQuery, GetPersistentVolumesEncryptedQueryVariables>(GetPersistentVolumesEncryptedDocument, options);
|
||||
}
|
||||
export function useGetPersistentVolumesEncryptedLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetPersistentVolumesEncryptedQuery, GetPersistentVolumesEncryptedQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<GetPersistentVolumesEncryptedQuery, GetPersistentVolumesEncryptedQueryVariables>(GetPersistentVolumesEncryptedDocument, options);
|
||||
}
|
||||
export type GetPersistentVolumesEncryptedQueryHookResult = ReturnType<typeof useGetPersistentVolumesEncryptedQuery>;
|
||||
export type GetPersistentVolumesEncryptedLazyQueryHookResult = ReturnType<typeof useGetPersistentVolumesEncryptedLazyQuery>;
|
||||
export type GetPersistentVolumesEncryptedQueryResult = Apollo.QueryResult<GetPersistentVolumesEncryptedQuery, GetPersistentVolumesEncryptedQueryVariables>;
|
||||
export function refetchGetPersistentVolumesEncryptedQuery(variables: GetPersistentVolumesEncryptedQueryVariables) {
|
||||
return { query: GetPersistentVolumesEncryptedDocument, variables: variables }
|
||||
}
|
||||
export const GetJwtSecretsDocument = gql`
|
||||
query GetJWTSecrets($appId: uuid!) {
|
||||
config(appID: $appId, resolve: false) {
|
||||
@@ -34696,44 +34705,13 @@ export type GetLocalRunServiceRateLimitQueryResult = Apollo.QueryResult<GetLocal
|
||||
export function refetchGetLocalRunServiceRateLimitQuery(variables: GetLocalRunServiceRateLimitQueryVariables) {
|
||||
return { query: GetLocalRunServiceRateLimitDocument, variables: variables }
|
||||
}
|
||||
export const InsertRunServiceDocument = gql`
|
||||
mutation insertRunService($object: run_service_insert_input!) {
|
||||
insertRunService(object: $object) {
|
||||
id
|
||||
subdomain
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type InsertRunServiceMutationFn = Apollo.MutationFunction<InsertRunServiceMutation, InsertRunServiceMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useInsertRunServiceMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useInsertRunServiceMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useInsertRunServiceMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [insertRunServiceMutation, { data, loading, error }] = useInsertRunServiceMutation({
|
||||
* variables: {
|
||||
* object: // value for 'object'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useInsertRunServiceMutation(baseOptions?: Apollo.MutationHookOptions<InsertRunServiceMutation, InsertRunServiceMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<InsertRunServiceMutation, InsertRunServiceMutationVariables>(InsertRunServiceDocument, options);
|
||||
}
|
||||
export type InsertRunServiceMutationHookResult = ReturnType<typeof useInsertRunServiceMutation>;
|
||||
export type InsertRunServiceMutationResult = Apollo.MutationResult<InsertRunServiceMutation>;
|
||||
export type InsertRunServiceMutationOptions = Apollo.BaseMutationOptions<InsertRunServiceMutation, InsertRunServiceMutationVariables>;
|
||||
export const InsertRunServiceConfigDocument = gql`
|
||||
mutation insertRunServiceConfig($appID: uuid!, $serviceID: uuid!, $config: ConfigRunServiceConfigInsertInput!) {
|
||||
insertRunServiceConfig(appID: $appID, serviceID: $serviceID, config: $config) {
|
||||
name
|
||||
mutation InsertRunServiceConfig($appID: uuid!, $config: ConfigRunServiceConfigInsertInput!) {
|
||||
insertRunServiceConfig(appID: $appID, config: $config) {
|
||||
serviceID
|
||||
config {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -34753,7 +34731,6 @@ export type InsertRunServiceConfigMutationFn = Apollo.MutationFunction<InsertRun
|
||||
* const [insertRunServiceConfigMutation, { data, loading, error }] = useInsertRunServiceConfigMutation({
|
||||
* variables: {
|
||||
* appID: // value for 'appID'
|
||||
* serviceID: // value for 'serviceID'
|
||||
* config: // value for 'config'
|
||||
* },
|
||||
* });
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# @nhost/docs
|
||||
|
||||
## 2.26.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 04d2ce1: feat: add reference documentation for signin security key
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1fa6cc4: chore: added docs for pg_jsonschema
|
||||
|
||||
## 2.25.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 46fc520: chore: add support to next.js 15, update quickstart template commands in docs
|
||||
- cdf6776: fix: update links to create new project in dashboard
|
||||
|
||||
## 2.24.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -44,6 +44,7 @@ In the table below you can find a list of available extensions with Nhost Postgr
|
||||
| pg_freespacemap | 1.2 | examine the free space map (FSM) |
|
||||
| pg_hashids | 1.3 | pg_hashids |
|
||||
| pg_ivm | 1.9 | incremental view maintenance on PostgreSQL |
|
||||
| pg_jsonschema | 0.3.3 | pg_jsonschema |
|
||||
| pg_prewarm | 1.2 | prewarm relation data |
|
||||
| pg_repack | 1.5.1 | Reorganize tables in PostgreSQL databases with minimal locks |
|
||||
| pg_squeeze | 1.7 | A tool to remove unused space from a relation. |
|
||||
@@ -75,7 +76,6 @@ In the table below you can find a list of available extensions with Nhost Postgr
|
||||
|
||||
In addition, you can find more information about some of the extensions below
|
||||
|
||||
|
||||
## hypopg
|
||||
|
||||
HypoPG is a PostgreSQL extension adding support for hypothetical indexes.
|
||||
@@ -277,6 +277,30 @@ DROP EXTENSION pg_ivm;
|
||||
|
||||
- [GitHub](https://github.com/sraoss/pg_ivm)
|
||||
|
||||
## pg_jsonschema
|
||||
|
||||
pg_jsonschema is a PostgreSQL extension adding support for JSON schema validation on json and jsonb data types.
|
||||
|
||||
### Managing
|
||||
|
||||
To install the extension you can create a migration with the following contents:
|
||||
|
||||
```sql SQL
|
||||
SET ROLE postgres;
|
||||
CREATE EXTENSION pg_jsonschema;
|
||||
```
|
||||
|
||||
To uninstall it, you can use the following migration:
|
||||
|
||||
```sql SQL
|
||||
SET ROLE postgres;
|
||||
DROP EXTENSION pg_jsonschema;
|
||||
```
|
||||
|
||||
### Resources
|
||||
|
||||
- [GitHub](https://github.com/supabase/pg_jsonschema)
|
||||
|
||||
## pg_repack
|
||||
|
||||
pg_repack is a PostgreSQL extension which lets you remove bloat from tables and indexes, and optionally restore the physical order of clustered indexes. Unlike CLUSTER and VACUUM FULL it works online, without holding an exclusive lock on the processed tables during processing. pg_repack is efficient to boot, with performance comparable to using CLUSTER directly.
|
||||
|
||||
@@ -7,7 +7,7 @@ icon: react
|
||||
|
||||
<Steps>
|
||||
<Step title="Create Project">
|
||||
If you haven't, please create a project through the [Nhost Dashboard](https://app.nhost.io/new).
|
||||
If you haven't, please create a project through the [Nhost Dashboard](https://app.nhost.io).
|
||||
</Step>
|
||||
|
||||
<Step title="Setup Database">
|
||||
@@ -47,7 +47,7 @@ icon: react
|
||||
Create a Next.js application.
|
||||
|
||||
```bash Terminal
|
||||
npx create-next-app@latest --no-eslint \
|
||||
npx create-next-app@next-14 --no-eslint \
|
||||
--src-dir \
|
||||
--no-tailwind \
|
||||
--import-alias "@/*" \
|
||||
@@ -59,7 +59,7 @@ icon: react
|
||||
</Step>
|
||||
|
||||
<Step title="Install the Nhost package for Next.js">
|
||||
Navidate to the React application and install `@nhost/nextjs`.
|
||||
Navigate to the React application and install `@nhost/nextjs`.
|
||||
|
||||
```bash Terminal
|
||||
cd nhost-nextjs-quickstart && npm install @nhost/nextjs
|
||||
|
||||
@@ -20,7 +20,7 @@ icon: mobile-notch
|
||||
|
||||
<Steps>
|
||||
<Step title="Create Nhost Project">
|
||||
Create your project through the [Nhost Dashboard](https://app.nhost.io/new).
|
||||
Create your project through the [Nhost Dashboard](https://app.nhost.io).
|
||||
</Step>
|
||||
|
||||
<Step title="Setup Database">
|
||||
|
||||
@@ -7,7 +7,7 @@ icon: react
|
||||
|
||||
<Steps>
|
||||
<Step title="Create Nhost Project">
|
||||
Create your project through the [Nhost Dashboard](https://app.nhost.io/new).
|
||||
Create your project through the [Nhost Dashboard](https://app.nhost.io).
|
||||
</Step>
|
||||
|
||||
<Step title="Setup Database">
|
||||
|
||||
@@ -7,7 +7,7 @@ icon: vuejs
|
||||
|
||||
<Steps>
|
||||
<Step title="Create Project">
|
||||
If you haven't, please create a project through the [Nhost Dashboard](https://app.nhost.io/new).
|
||||
If you haven't, please create a project through the [Nhost Dashboard](https://app.nhost.io).
|
||||
</Step>
|
||||
|
||||
<Step title="Setup Database">
|
||||
@@ -53,7 +53,7 @@ icon: vuejs
|
||||
</Step>
|
||||
|
||||
<Step title="Install the Nhost package for Vue">
|
||||
Navidate to the React application and install `@nhost/vue`.
|
||||
Navigate to the React application and install `@nhost/vue`.
|
||||
|
||||
```bash Terminal
|
||||
cd nhost-vue-quickstart && npm install @nhost/vue
|
||||
|
||||
@@ -30,7 +30,7 @@ In this section, you will create and setup your first Nhost project.
|
||||
|
||||
### Create project
|
||||
|
||||
Create a new project in the [Nhost Dashboard](https://app.nhost.io/new).
|
||||
Create a new project in the [Nhost Dashboard](https://app.nhost.io).
|
||||
|
||||
Enter the details for your project and wait a couple of minutes while Nhost provisions your backend infrastructure:
|
||||
|
||||
@@ -156,7 +156,7 @@ Now that we have Nhost configured, let's move on to setup the React application
|
||||
Run the following command in your terminal to create a React application using Vite.
|
||||
|
||||
```bash Terminal
|
||||
npx create-next-app@latest --no-eslint \
|
||||
npx create-next-app@next-14 --no-eslint \
|
||||
--src-dir \
|
||||
--no-tailwind \
|
||||
--import-alias "@/*" \
|
||||
|
||||
@@ -30,7 +30,7 @@ In this section, you will create and setup your first Nhost project.
|
||||
|
||||
### Create project
|
||||
|
||||
Create a new project in the [Nhost Dashboard](https://app.nhost.io/new).
|
||||
Create a new project in the [Nhost Dashboard](https://app.nhost.io).
|
||||
|
||||
Enter the details for your project and wait a couple of minutes while Nhost provisions your backend infrastructure:
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ In this section, you will create and setup your first Nhost project.
|
||||
|
||||
### Create project
|
||||
|
||||
Create a new project in the [Nhost Dashboard](https://app.nhost.io/new).
|
||||
Create a new project in the [Nhost Dashboard](https://app.nhost.io).
|
||||
|
||||
Enter the details for your project and wait a couple of minutes while Nhost provisions your backend infrastructure:
|
||||
|
||||
|
||||
@@ -354,7 +354,8 @@
|
||||
"reference/javascript/auth/sign-in-email-otp",
|
||||
"reference/javascript/auth/verify-email-otp",
|
||||
"reference/javascript/auth/sign-in-id-token",
|
||||
"reference/javascript/auth/link-id-token"
|
||||
"reference/javascript/auth/link-id-token",
|
||||
"reference/javascript/auth/sign-in-security-key"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -440,7 +441,8 @@
|
||||
"reference/react/use-user-roles",
|
||||
"reference/react/use-sign-in-email-otp",
|
||||
"reference/react/use-sign-in-id-token",
|
||||
"reference/react/use-link-id-token"
|
||||
"reference/react/use-link-id-token",
|
||||
"reference/react/use-sign-in-security-key"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -490,7 +492,8 @@
|
||||
"reference/nextjs/use-user-roles",
|
||||
"reference/nextjs/use-sign-in-email-otp",
|
||||
"reference/nextjs/use-sign-in-id-token",
|
||||
"reference/nextjs/use-link-id-token"
|
||||
"reference/nextjs/use-link-id-token",
|
||||
"reference/nextjs/use-sign-in-security-key"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -535,7 +538,8 @@
|
||||
"reference/vue/use-sign-up-email-security-key",
|
||||
"reference/vue/use-sign-in-email-otp",
|
||||
"reference/vue/use-sign-in-id-token",
|
||||
"reference/vue/use-link-id-token"
|
||||
"reference/vue/use-link-id-token",
|
||||
"reference/vue/use-sign-in-security-key"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/docs",
|
||||
"version": "2.24.0",
|
||||
"version": "2.26.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "mintlify dev"
|
||||
|
||||
10
docs/reference/javascript/auth/sign-in-security-key.mdx
Normal file
10
docs/reference/javascript/auth/sign-in-security-key.mdx
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: signInSecurityKey()
|
||||
sidebarTitle: signInSecurityKey()
|
||||
---
|
||||
|
||||
Use `nhost.auth.signInSecurityKey` to sign in a user with a security key using the WebAuthn API
|
||||
|
||||
```ts
|
||||
nhost.auth.signInSecurityKey()
|
||||
```
|
||||
10
docs/reference/javascript/nhost-js/sign-in-security-key.mdx
Normal file
10
docs/reference/javascript/nhost-js/sign-in-security-key.mdx
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: signInSecurityKey()
|
||||
sidebarTitle: signInSecurityKey()
|
||||
---
|
||||
|
||||
Use `nhost.auth.signInSecurityKey` to sign in a user with a security key using the WebAuthn API
|
||||
|
||||
```ts
|
||||
nhost.auth.signInSecurityKey()
|
||||
```
|
||||
25
docs/reference/nextjs/use-sign-in-security-key.mdx
Normal file
25
docs/reference/nextjs/use-sign-in-security-key.mdx
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: useSignInSecurityKey()
|
||||
sidebarTitle: useSignInSecurityKey()
|
||||
---
|
||||
|
||||
Use the hook `useSignInSecurityKey` to sign in a user with a security key using the WebAuthn API.
|
||||
|
||||
```tsx
|
||||
const {
|
||||
signInSecurityKey,
|
||||
needsEmailVerification,
|
||||
isLoading,
|
||||
isSuccess,
|
||||
isError,
|
||||
error
|
||||
} = useSignInSecurityKey()
|
||||
|
||||
console.log({ needsEmailVerification, isLoading, isSuccess, isError, error })
|
||||
|
||||
const handleFormSubmit = async (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
await signInSecurityKey()
|
||||
}
|
||||
```
|
||||
25
docs/reference/react/use-sign-in-security-key.mdx
Normal file
25
docs/reference/react/use-sign-in-security-key.mdx
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: useSignInSecurityKey()
|
||||
sidebarTitle: useSignInSecurityKey()
|
||||
---
|
||||
|
||||
Use the hook `useSignInSecurityKey` to sign in a user with a security key using the WebAuthn API.
|
||||
|
||||
```tsx
|
||||
const {
|
||||
signInSecurityKey,
|
||||
needsEmailVerification,
|
||||
isLoading,
|
||||
isSuccess,
|
||||
isError,
|
||||
error
|
||||
} = useSignInSecurityKey()
|
||||
|
||||
console.log({ needsEmailVerification, isLoading, isSuccess, isError, error })
|
||||
|
||||
const handleFormSubmit = async (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
await signInSecurityKey()
|
||||
}
|
||||
```
|
||||
25
docs/reference/vue/use-sign-in-security-key.mdx
Normal file
25
docs/reference/vue/use-sign-in-security-key.mdx
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: useSignInSecurityKey()
|
||||
sidebarTitle: useSignInSecurityKey()
|
||||
---
|
||||
|
||||
Use the composable `useSignInSecurityKey` to sign in a user with a security key using the WebAuthn API
|
||||
|
||||
```tsx
|
||||
const {
|
||||
signInSecurityKey,
|
||||
needsEmailVerification,
|
||||
isLoading,
|
||||
isSuccess,
|
||||
isError,
|
||||
error
|
||||
} = useSignInSecurityKey()
|
||||
|
||||
console.log({ needsEmailVerification, isLoading, isSuccess, isError, error })
|
||||
|
||||
const handleFormSubmit = async (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
await signInSecurityKey()
|
||||
}
|
||||
```
|
||||
@@ -1,5 +1,17 @@
|
||||
# @nhost-examples/cli
|
||||
|
||||
## 0.3.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.2.3
|
||||
|
||||
## 0.3.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.2.2
|
||||
|
||||
## 0.3.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/cli",
|
||||
"version": "0.3.14",
|
||||
"version": "0.3.16",
|
||||
"main": "src/index.mjs",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @nhost-examples/codegen-react-apollo
|
||||
|
||||
## 0.4.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [04d2ce1]
|
||||
- @nhost/react@3.9.0
|
||||
- @nhost/react-apollo@16.0.0
|
||||
|
||||
## 0.4.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.8.1
|
||||
- @nhost/react-apollo@15.0.1
|
||||
|
||||
## 0.4.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/codegen-react-apollo",
|
||||
"version": "0.4.15",
|
||||
"version": "0.4.17",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"codegen": "graphql-codegen",
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# @nhost-examples/codegen-react-query
|
||||
|
||||
## 0.4.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [04d2ce1]
|
||||
- @nhost/react@3.9.0
|
||||
|
||||
## 0.4.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.8.1
|
||||
|
||||
## 0.4.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/codegen-react-query",
|
||||
"version": "0.4.15",
|
||||
"version": "0.4.17",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"codegen": "graphql-codegen",
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @nhost-examples/react-urql
|
||||
|
||||
## 0.3.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [04d2ce1]
|
||||
- @nhost/react@3.9.0
|
||||
- @nhost/react-urql@13.0.0
|
||||
|
||||
## 0.3.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.8.1
|
||||
- @nhost/react-urql@12.0.1
|
||||
|
||||
## 0.3.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/codegen-react-urql",
|
||||
"private": true,
|
||||
"version": "0.3.15",
|
||||
"version": "0.3.17",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @nhost-examples/multi-tenant-one-to-many
|
||||
|
||||
## 2.2.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.2.3
|
||||
|
||||
## 2.2.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.2.2
|
||||
|
||||
## 2.2.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/multi-tenant-one-to-many",
|
||||
"private": true,
|
||||
"version": "2.2.15",
|
||||
"version": "2.2.17",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {},
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
# @nhost-examples/nextjs
|
||||
|
||||
## 0.4.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [04d2ce1]
|
||||
- @nhost/react@3.9.0
|
||||
- @nhost/react-apollo@16.0.0
|
||||
- @nhost/nextjs@2.2.1
|
||||
|
||||
## 0.4.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 29d27e1: chore: update `next` to v14.2.22 to fix vulnerabilities
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [46fc520]
|
||||
- Updated dependencies [29d27e1]
|
||||
- @nhost/nextjs@2.2.0
|
||||
- @nhost/react@3.8.1
|
||||
- @nhost/react-apollo@15.0.1
|
||||
|
||||
## 0.3.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/nextjs",
|
||||
"version": "0.3.15",
|
||||
"version": "0.4.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -24,7 +24,7 @@
|
||||
"@nhost/react": "workspace:^",
|
||||
"@nhost/react-apollo": "workspace:^",
|
||||
"graphql": "16.8.1",
|
||||
"next": "^14.2.10",
|
||||
"next": "^14.2.22",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-icons": "^4.12.0"
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @nhost-examples/node-storage
|
||||
|
||||
## 0.2.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.2.3
|
||||
|
||||
## 0.2.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.2.2
|
||||
|
||||
## 0.2.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/node-storage",
|
||||
"version": "0.2.14",
|
||||
"version": "0.2.16",
|
||||
"private": true,
|
||||
"description": "This is an example of how to use the Storage with Node.js",
|
||||
"main": "src/index.mjs",
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# @nhost-examples/nextjs-server-components
|
||||
|
||||
## 0.5.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.2.3
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- b944d05: chore: simplify Nhost client initialization with session and remove xstate dependency
|
||||
- 29d27e1: chore: update `next` to v14.2.22 to fix vulnerabilities
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.2.2
|
||||
|
||||
## 0.4.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/nextjs-server-components",
|
||||
"version": "0.4.16",
|
||||
"version": "0.5.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -18,14 +18,13 @@
|
||||
"form-data": "^4.0.0",
|
||||
"graphql": "16.8.1",
|
||||
"js-cookie": "^3.0.5",
|
||||
"next": "^14.2.10",
|
||||
"next": "^14.2.22",
|
||||
"postcss": "^8.4.38",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"tailwindcss": "3.3.3",
|
||||
"typescript": "5.2.2",
|
||||
"xstate": "^4.38.3"
|
||||
"typescript": "5.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { AuthErrorPayload, NhostClient, NhostSession } from '@nhost/nhost-js'
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { type StateFrom } from 'xstate/lib/types'
|
||||
import { waitFor } from 'xstate/lib/waitFor'
|
||||
|
||||
export const NHOST_SESSION_KEY = 'nhostSession'
|
||||
|
||||
export const getNhost = async (request?: NextRequest) => {
|
||||
@@ -20,9 +16,7 @@ export const getNhost = async (request?: NextRequest) => {
|
||||
const sessionCookieValue = $cookies.get(NHOST_SESSION_KEY)?.value || ''
|
||||
const initialSession: NhostSession = JSON.parse(atob(sessionCookieValue) || 'null')
|
||||
|
||||
nhost.auth.client.start({ initialSession })
|
||||
await waitFor(nhost.auth.client.interpreter!, (state: StateFrom<any>) => !state.hasTag('loading'))
|
||||
|
||||
await nhost.auth.initWithSession({ session: initialSession })
|
||||
return nhost
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
# @nhost-examples/react-apollo
|
||||
|
||||
## 1.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 04d2ce1: feat: update signin components to use `useSignInSecuritykey` with user handle
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [04d2ce1]
|
||||
- @nhost/react@3.9.0
|
||||
- @nhost/react-apollo@16.0.0
|
||||
|
||||
## 1.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.8.1
|
||||
- @nhost/react-apollo@15.0.1
|
||||
|
||||
## 1.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -29,7 +29,7 @@ httpPoolSize = 100
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.32.1'
|
||||
version = '0.37.0-beta1'
|
||||
|
||||
[auth.elevatedPrivileges]
|
||||
mode = 'required'
|
||||
@@ -47,6 +47,27 @@ disableNewUsers = false
|
||||
default = 'user'
|
||||
allowed = ['user', 'me']
|
||||
|
||||
[auth.rateLimit]
|
||||
[auth.rateLimit.emails]
|
||||
limit = 100
|
||||
interval = '1h'
|
||||
|
||||
[auth.rateLimit.sms]
|
||||
limit = 100
|
||||
interval = '1h'
|
||||
|
||||
[auth.rateLimit.bruteForce]
|
||||
limit = 100
|
||||
interval = '5m'
|
||||
|
||||
[auth.rateLimit.signups]
|
||||
limit = 100
|
||||
interval = '5m'
|
||||
|
||||
[auth.rateLimit.global]
|
||||
limit = 1000
|
||||
interval = '1m'
|
||||
|
||||
[auth.user.locale]
|
||||
default = 'en'
|
||||
allowed = ['en']
|
||||
@@ -158,6 +179,14 @@ issuer = 'nhost'
|
||||
version = '16.2-20240718-1'
|
||||
|
||||
[provider]
|
||||
[provider.smtp]
|
||||
host = "smtp.test.com"
|
||||
method = "LOGIN"
|
||||
password = "test123123"
|
||||
port = 587
|
||||
secure = false
|
||||
sender = "test@nhost.io"
|
||||
user = "test"
|
||||
|
||||
[storage]
|
||||
version = '0.6.1'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/react-apollo",
|
||||
"version": "1.1.1",
|
||||
"version": "1.2.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,41 +1,21 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useSignInEmailSecurityKey } from '@nhost/react'
|
||||
import { ArrowLeft } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import { toast } from 'sonner'
|
||||
import { z } from 'zod'
|
||||
import { cn } from '@/lib/utils'
|
||||
import SignInFooter from '@/components/auth/sign-in-footer'
|
||||
import { Button, buttonVariants } from '@/components/ui/button'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { Form, FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
|
||||
const formSchema = z.object({
|
||||
email: z.string().email()
|
||||
})
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useSignInSecurityKey } from '@nhost/react'
|
||||
import { ArrowLeft } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
export default function SignInSecurityKey() {
|
||||
const navigate = useNavigate()
|
||||
const { signInEmailSecurityKey } = useSignInEmailSecurityKey()
|
||||
const { signInSecurityKey } = useSignInSecurityKey()
|
||||
const [showEmailVerificationDialog, setShowEmailVerificationDialog] = useState(false)
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
email: ''
|
||||
}
|
||||
})
|
||||
|
||||
const onSubmit = async (values: z.infer<typeof formSchema>) => {
|
||||
const { email } = values
|
||||
|
||||
const { isError, isSuccess, needsEmailVerification, error } = await signInEmailSecurityKey(
|
||||
email
|
||||
)
|
||||
const handleSignInSecurityKey = async () => {
|
||||
const { isError, isSuccess, needsEmailVerification, error } = await signInSecurityKey()
|
||||
|
||||
if (isError) {
|
||||
toast.error(error?.message)
|
||||
@@ -51,24 +31,9 @@ export default function SignInSecurityKey() {
|
||||
<div className="flex flex-col items-center justify-center w-full max-w-md p-8 bg-white rounded-md shadow">
|
||||
<h1 className="mb-8 text-3xl">Sign in with a security key</h1>
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col w-full space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input placeholder="email" type="email" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage className="text-xs" />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type="submit">Sign In</Button>
|
||||
</form>
|
||||
</Form>
|
||||
<Button onClick={handleSignInSecurityKey} className="w-full">
|
||||
Sign In
|
||||
</Button>
|
||||
|
||||
<Link to="/sign-in" className={cn(buttonVariants({ variant: 'link' }), 'my-2')}>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# @nhost-examples/react-gqty
|
||||
|
||||
## 1.2.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [04d2ce1]
|
||||
- @nhost/react@3.9.0
|
||||
|
||||
## 1.2.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.8.1
|
||||
|
||||
## 1.2.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/react-gqty",
|
||||
"private": true,
|
||||
"version": "1.2.15",
|
||||
"version": "1.2.17",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @nhost-examples/react-native
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [04d2ce1]
|
||||
- @nhost/react@3.9.0
|
||||
- @nhost/react-apollo@16.0.0
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.8.1
|
||||
- @nhost/react-apollo@15.0.1
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/react-native",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"android": "react-native run-android",
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# @nhost-examples/vue-apollo
|
||||
|
||||
## 0.8.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 04d2ce1: feat: update signin components to use `useSignInSecuritykey` with user handle
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [04d2ce1]
|
||||
- @nhost/vue@2.9.0
|
||||
- @nhost/nhost-js@3.2.3
|
||||
- @nhost/apollo@8.0.3
|
||||
|
||||
## 0.7.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.2.2
|
||||
- @nhost/apollo@8.0.2
|
||||
- @nhost/vue@2.8.1
|
||||
|
||||
## 0.7.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -29,7 +29,7 @@ httpPoolSize = 100
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.27.0-beta13'
|
||||
version = '0.37.0-beta1'
|
||||
|
||||
[auth.elevatedPrivileges]
|
||||
mode = 'required'
|
||||
|
||||
60
examples/vue-apollo/nhost/overlays/local.json
Normal file
60
examples/vue-apollo/nhost/overlays/local.json
Normal file
@@ -0,0 +1,60 @@
|
||||
[
|
||||
{
|
||||
"value": "disabled",
|
||||
"op": "replace",
|
||||
"path": "/auth/elevatedPrivileges/mode"
|
||||
},
|
||||
{
|
||||
"value": "localhost",
|
||||
"op": "replace",
|
||||
"path": "/auth/method/webauthn/relyingParty/id"
|
||||
},
|
||||
{
|
||||
"value": "http://localhost:5173",
|
||||
"op": "replace",
|
||||
"path": "/auth/method/webauthn/relyingParty/origins/0"
|
||||
},
|
||||
{
|
||||
"value": "http://localhost:5173",
|
||||
"op": "replace",
|
||||
"path": "/auth/redirections/allowedUrls/0"
|
||||
},
|
||||
{
|
||||
"value": "http://localhost:5173/profile",
|
||||
"op": "replace",
|
||||
"path": "/auth/redirections/allowedUrls/1"
|
||||
},
|
||||
{
|
||||
"op": "remove",
|
||||
"path": "/auth/redirections/allowedUrls/2"
|
||||
},
|
||||
{
|
||||
"op": "remove",
|
||||
"path": "/auth/redirections/allowedUrls/2"
|
||||
},
|
||||
{
|
||||
"op": "remove",
|
||||
"path": "/auth/redirections/allowedUrls/2"
|
||||
},
|
||||
{
|
||||
"op": "remove",
|
||||
"path": "/auth/redirections/allowedUrls/2"
|
||||
},
|
||||
{
|
||||
"op": "remove",
|
||||
"path": "/auth/redirections/allowedUrls/2"
|
||||
},
|
||||
{
|
||||
"op": "remove",
|
||||
"path": "/auth/redirections/allowedUrls/2"
|
||||
},
|
||||
{
|
||||
"op": "remove",
|
||||
"path": "/auth/redirections/allowedUrls/2"
|
||||
},
|
||||
{
|
||||
"value": "http://localhost:5173",
|
||||
"op": "replace",
|
||||
"path": "/auth/redirections/clientUrl"
|
||||
}
|
||||
]
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/vue-apollo",
|
||||
"private": true,
|
||||
"version": "0.7.1",
|
||||
"version": "0.8.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<form @submit="handleSignIn">
|
||||
<v-text-field v-model="email" label="Email" />
|
||||
<v-btn
|
||||
block
|
||||
color="primary"
|
||||
@@ -23,17 +22,15 @@
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { useSignInEmailSecurityKey } from '@nhost/vue'
|
||||
|
||||
const email = ref('')
|
||||
import { useSignInSecurityKey } from '@nhost/vue'
|
||||
const emailVerificationDialog = ref(false)
|
||||
|
||||
const router = useRouter()
|
||||
const { signInEmailSecurityKey, error, isLoading } = useSignInEmailSecurityKey()
|
||||
const { signInSecurityKey, error, isLoading } = useSignInSecurityKey()
|
||||
|
||||
const handleSignIn = async (e: Event) => {
|
||||
e.preventDefault()
|
||||
const { isSuccess, needsEmailVerification } = await signInEmailSecurityKey(email)
|
||||
const { isSuccess, needsEmailVerification } = await signInSecurityKey()
|
||||
if (isSuccess) {
|
||||
router.replace('/')
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
// "extends": "../../config/tsconfig.base.json",
|
||||
"extends": "../../config/tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @nhost-examples/vue-quickstart
|
||||
|
||||
## 0.2.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [04d2ce1]
|
||||
- @nhost/vue@2.9.0
|
||||
- @nhost/apollo@8.0.3
|
||||
|
||||
## 0.2.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/apollo@8.0.2
|
||||
- @nhost/vue@2.8.1
|
||||
|
||||
## 0.2.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/vue-quickstart",
|
||||
"version": "0.2.15",
|
||||
"version": "0.2.17",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @nhost/apollo
|
||||
|
||||
## 8.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.2.3
|
||||
|
||||
## 8.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.2.2
|
||||
|
||||
## 8.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/apollo",
|
||||
"version": "8.0.1",
|
||||
"version": "8.0.3",
|
||||
"description": "Nhost Apollo Client library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# @nhost/react-apollo
|
||||
|
||||
## 16.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [04d2ce1]
|
||||
- @nhost/react@3.9.0
|
||||
- @nhost/apollo@8.0.3
|
||||
|
||||
## 15.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/apollo@8.0.2
|
||||
- @nhost/react@3.8.1
|
||||
|
||||
## 15.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/react-apollo",
|
||||
"version": "15.0.0",
|
||||
"version": "16.0.0",
|
||||
"description": "Nhost React Apollo client",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# @nhost/react-urql
|
||||
|
||||
## 13.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [04d2ce1]
|
||||
- @nhost/react@3.9.0
|
||||
|
||||
## 12.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.8.1
|
||||
|
||||
## 12.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/react-urql",
|
||||
"version": "12.0.0",
|
||||
"version": "13.0.0",
|
||||
"description": "Nhost React URQL client",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
63
nix/nhost-cli.nix
Normal file
63
nix/nhost-cli.nix
Normal file
@@ -0,0 +1,63 @@
|
||||
{ final }:
|
||||
let
|
||||
version = "v1.28.3";
|
||||
dist = {
|
||||
aarch64-darwin = {
|
||||
url = "https://github.com/nhost/cli/releases/download/${version}/cli-${version}-darwin-arm64.tar.gz";
|
||||
sha256 = "042qkyv94x5iinqphqrh01gvlr5cy26855g6x03mr8zw96rnz1hg";
|
||||
};
|
||||
x86_64-darwin = {
|
||||
url = "https://github.com/nhost/cli/releases/download/${version}/cli-${version}-darwin-amd64.tar.gz";
|
||||
sha256 = "1p8ji6l1a2nbf7na9agzfpklvfyh8gvnhfx0ib1w8gn69y337060";
|
||||
};
|
||||
aarch64-linux = {
|
||||
url = "https://github.com/nhost/cli/releases/download/${version}/cli-${version}-linux-arm64.tar.gz";
|
||||
sha256 = "0vr7qp9wr49cgbcw45zl3x26yhl7vmraymqqzxbg3pk8ifkvm505";
|
||||
};
|
||||
x86_64-linux = {
|
||||
url = "https://github.com/nhost/cli/releases/download/${version}/cli-${version}-linux-amd64.tar.gz";
|
||||
sha256 = "17niy09gr9pf8wy1361pnq7p3ic53asrl6x32g151jhary0yy84r";
|
||||
};
|
||||
};
|
||||
|
||||
in
|
||||
final.stdenvNoCC.mkDerivation {
|
||||
pname = "nhost-cli";
|
||||
inherit version;
|
||||
|
||||
src = final.fetchurl {
|
||||
inherit (dist.${final.stdenvNoCC.hostPlatform.system} or
|
||||
(throw "Unsupported system: ${final.stdenvNoCC.hostPlatform.system}")) url sha256;
|
||||
};
|
||||
|
||||
|
||||
sourceRoot = ".";
|
||||
|
||||
nativeBuildInputs = [
|
||||
final.unzip
|
||||
final.makeWrapper
|
||||
final.installShellFiles
|
||||
];
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
mkdir -p $out/bin
|
||||
mv cli $out/bin/nhost
|
||||
|
||||
# installShellCompletion --cmd nhost \
|
||||
# --bash <($out/bin/nhost completion bash) \
|
||||
# --fish <($out/bin/nhost completion fish) \
|
||||
# --zsh <($out/bin/nhost completion zsh)
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
meta = with final.lib; {
|
||||
description = "Nhost CLI";
|
||||
homepage = "https://nhost.io";
|
||||
license = licenses.mit;
|
||||
maintainers = [ "@nhost" ];
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
(final: prev: rec {
|
||||
nodejs = final.nodejs-18_x;
|
||||
nodePackages = nodejs.pkgs;
|
||||
nhost-cli = final.callPackage ./nhost-cli.nix { inherit final; };
|
||||
})
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"build:all": "turbo run build",
|
||||
"build:@nhost-examples/nextjs-server-components": "turbo run build --filter=@nhost-examples/nextjs-server-components",
|
||||
"build:@nhost-examples/sveltekit": "turbo run build --filter=@nhost-examples/sveltekit",
|
||||
"dev": "turbo run dev --filter=!@nhost/dashboard --filter=!@nhost/docs --filter=!@nhost-examples/* --filter=!@nhost/docgen --no-deps",
|
||||
"dev": "turbo run dev --filter=!@nhost/dashboard --filter=!@nhost/docs --filter=!@nhost-examples/* --filter=!@nhost/docgen",
|
||||
"clean:all": "pnpm clean && rm -rf ./{{packages,examples/**,templates/**}/*,docs,dashboard}/{.nhost,node_modules} node_modules",
|
||||
"clean": "rm -rf ./{{packages,examples/**}/*,docs,dashboard}/{dist,umd,.next,.turbo,coverage}",
|
||||
"ci:version": "changeset version && pnpm install --frozen-lockfile false",
|
||||
@@ -36,14 +36,14 @@
|
||||
"prerelease": "pnpm clean && pnpm install && pnpm build",
|
||||
"release": "pnpm run prerelease && changeset publish",
|
||||
"snapshot": "pnpm prerelease && changeset version --snapshot preview && pnpm install && changeset publish --tag preview",
|
||||
"test": "turbo run test --filter=!@nhost/dashboard --filter=!@nhost/docs --filter=!@nhost-examples/* --no-deps",
|
||||
"test": "turbo run test --filter=!@nhost/dashboard --filter=!@nhost/docs --filter=!@nhost-examples/*",
|
||||
"test:all": "turbo run test",
|
||||
"test:dashboard": "turbo run test --filter=@nhost/dashboard",
|
||||
"e2e:dashboard": "turbo run e2e --filter=@nhost/dashboard",
|
||||
"e2e": "turbo run e2e --concurrency=1",
|
||||
"changeset": "changeset",
|
||||
"docgen": "turbo run build --filter=@nhost/docgen --no-deps && pnpm i && turbo run docgen --filter=!@nhost/docgen --filter=@nhost/* && :",
|
||||
"sync-versions": "turbo run start --filter=@nhost/sync-versions --no-deps",
|
||||
"docgen": "turbo run build --filter=@nhost/docgen && pnpm i && turbo run docgen --filter=!@nhost/docgen --filter=@nhost/* && :",
|
||||
"sync-versions": "turbo run start --filter=@nhost/sync-versions",
|
||||
"audit-ci": "pnpx audit-ci --config ./audit-ci.jsonc"
|
||||
},
|
||||
"workspaces": [
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user