Compare commits
8 Commits
@nhost/rea
...
@nhost/nex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
852f13b273 | ||
|
|
a44a1d48d6 | ||
|
|
b63250d1cb | ||
|
|
caa8bd75ec | ||
|
|
40c0d7b914 | ||
|
|
3773ad7cca | ||
|
|
6f122521e9 | ||
|
|
a18b545d2a |
@@ -2,5 +2,5 @@
|
||||
// $schema provides code completion hints to IDEs.
|
||||
"$schema": "https://github.com/IBM/audit-ci/raw/main/docs/schema.json",
|
||||
"moderate": true,
|
||||
"allowlist": ["trim-newlines", "vue-template-compiler"]
|
||||
"allowlist": ["vue-template-compiler", "micromatch"]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# @nhost/dashboard
|
||||
|
||||
## 1.26.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 3773ad7: chore: update pricing information
|
||||
- b63250d: fix: not allow run service creation form resubmission while creating a run service
|
||||
- a44a1d4: feat: add rate limits settings page
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react-apollo@12.0.5
|
||||
- @nhost/nextjs@2.1.19
|
||||
|
||||
## 1.25.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -43,7 +43,7 @@ async function globalTeardown() {
|
||||
await adminSecretInput.press('Enter');
|
||||
|
||||
// note: getByRole doesn't work here
|
||||
await hasuraPage.locator('a', { hasText: /data/i }).click();
|
||||
await hasuraPage.locator('a', { hasText: /data/i }).nth(0).click();
|
||||
await hasuraPage.locator('[data-test="sql-link"]').click();
|
||||
|
||||
// Set the value of the Ace code editor using JavaScript evaluation in the browser context
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/dashboard",
|
||||
"version": "1.25.0",
|
||||
"version": "1.26.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
|
||||
@@ -221,6 +221,13 @@ export default function SettingsSidebar({
|
||||
>
|
||||
Custom Domains
|
||||
</SettingsNavLink>
|
||||
<SettingsNavLink
|
||||
href="/rate-limiting"
|
||||
exact={false}
|
||||
onClick={handleSelect}
|
||||
>
|
||||
Rate Limiting
|
||||
</SettingsNavLink>
|
||||
<SettingsNavLink href="/ai" exact={false} onClick={handleSelect}>
|
||||
AI
|
||||
</SettingsNavLink>
|
||||
|
||||
@@ -2,8 +2,8 @@ import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { Box } from '@/components/ui/v2/Box';
|
||||
import { Button } from '@/components/ui/v2/Button';
|
||||
import { Checkbox } from '@/components/ui/v2/Checkbox';
|
||||
import { BaseDialog } from '@/components/ui/v2/Dialog';
|
||||
import { Radio } from '@/components/ui/v2/Radio';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { useAppState } from '@/features/projects/common/hooks/useAppState';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
@@ -31,7 +31,7 @@ function Plan({ planName, price, setPlan, planId, selectedPlanId }: any) {
|
||||
>
|
||||
<div className="grid grid-flow-row gap-y-0.5">
|
||||
<div className="grid grid-flow-col items-center justify-start gap-2">
|
||||
<Checkbox
|
||||
<Radio
|
||||
onChange={setPlan}
|
||||
checked={selectedPlanId === planId}
|
||||
aria-label={planName}
|
||||
@@ -241,7 +241,21 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-2 grid grid-flow-row gap-2">
|
||||
<div className="mt-0">
|
||||
<Text variant="subtitle2" className="w-full px-1">
|
||||
For a complete list of features, visit our{' '}
|
||||
<a
|
||||
href="https://nhost.io/pricing"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline"
|
||||
>
|
||||
pricing page
|
||||
</a>
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 grid grid-flow-row gap-2">
|
||||
<Button
|
||||
onClick={handleChangePlanClick}
|
||||
disabled={!selectedPlan}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const planDescriptions = {
|
||||
Starter: '1 GB database, 5 GB of file storage, 10 GB of network traffic.',
|
||||
Pro: '10 GB database, 25 GB of file storage, 50 GB of network traffic, and backups.',
|
||||
Pro: '10 GB database, 50 GB of file storage, 50 GB of network traffic, and backups.',
|
||||
Team: 'Reach out to us at support@nhost.io to have your private channel set up.',
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { Divider } from '@/components/ui/v2/Divider';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import { rateLimitingItemValidationSchema } from '@/features/projects/rate-limiting/settings/components/validationSchemas';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { useUpdateRateLimitConfigMutation } from '@/utils/__generated__/graphql';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { RateLimitField } from 'features/projects/rate-limiting/settings/components/RateLimitField';
|
||||
import { useGetRateLimits } from 'features/projects/rate-limiting/settings/hooks/useGetRateLimits';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
export const validationSchema = Yup.object({
|
||||
enabled: Yup.boolean().label('Enabled'),
|
||||
bruteForce: rateLimitingItemValidationSchema,
|
||||
emails: rateLimitingItemValidationSchema,
|
||||
global: rateLimitingItemValidationSchema,
|
||||
signups: rateLimitingItemValidationSchema,
|
||||
sms: rateLimitingItemValidationSchema,
|
||||
});
|
||||
|
||||
export type AuthLimitingFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function AuthLimitingForm() {
|
||||
const { openDialog } = useDialog();
|
||||
const { maintenanceActive } = useUI();
|
||||
const isPlatform = useIsPlatform();
|
||||
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
|
||||
const [updateRateLimitConfig] = useUpdateRateLimitConfigMutation({
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { authRateLimit, loading } = useGetRateLimits();
|
||||
const {
|
||||
bruteForce,
|
||||
emails,
|
||||
global,
|
||||
signups,
|
||||
sms,
|
||||
enabled: authRateEnabled,
|
||||
} = authRateLimit;
|
||||
|
||||
const {
|
||||
limit: bruteForceLimit,
|
||||
interval: bruteForceInterval,
|
||||
intervalUnit: bruteForceIntervalUnit,
|
||||
} = bruteForce;
|
||||
const {
|
||||
limit: emailsLimit,
|
||||
interval: emailsInterval,
|
||||
intervalUnit: emailsIntervalUnit,
|
||||
} = emails;
|
||||
const {
|
||||
limit: globalLimit,
|
||||
interval: globalInterval,
|
||||
intervalUnit: globalIntervalUnit,
|
||||
} = global;
|
||||
const {
|
||||
limit: signupsLimit,
|
||||
interval: signupsInterval,
|
||||
intervalUnit: signupsIntervalUnit,
|
||||
} = signups;
|
||||
const {
|
||||
limit: smsLimit,
|
||||
interval: smsInterval,
|
||||
intervalUnit: smsIntervalUnit,
|
||||
} = sms;
|
||||
|
||||
const form = useForm<AuthLimitingFormValues>({
|
||||
defaultValues: {
|
||||
enabled: authRateEnabled,
|
||||
bruteForce: {
|
||||
limit: bruteForceLimit,
|
||||
interval: bruteForceInterval,
|
||||
intervalUnit: bruteForceIntervalUnit,
|
||||
},
|
||||
emails: {
|
||||
limit: emailsLimit,
|
||||
interval: emailsInterval,
|
||||
intervalUnit: emailsIntervalUnit,
|
||||
},
|
||||
global: {
|
||||
limit: globalLimit,
|
||||
interval: globalInterval,
|
||||
intervalUnit: globalIntervalUnit,
|
||||
},
|
||||
signups: {
|
||||
limit: signupsLimit,
|
||||
interval: signupsInterval,
|
||||
intervalUnit: signupsIntervalUnit,
|
||||
},
|
||||
sms: {
|
||||
limit: smsLimit,
|
||||
interval: smsInterval,
|
||||
intervalUnit: smsIntervalUnit,
|
||||
},
|
||||
},
|
||||
reValidateMode: 'onSubmit',
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && authRateEnabled) {
|
||||
form.reset({
|
||||
enabled: authRateEnabled,
|
||||
bruteForce: {
|
||||
limit: bruteForceLimit,
|
||||
interval: bruteForceInterval,
|
||||
intervalUnit: bruteForceIntervalUnit,
|
||||
},
|
||||
emails: {
|
||||
limit: emailsLimit,
|
||||
interval: emailsInterval,
|
||||
intervalUnit: emailsIntervalUnit,
|
||||
},
|
||||
global: {
|
||||
limit: globalLimit,
|
||||
interval: globalInterval,
|
||||
intervalUnit: globalIntervalUnit,
|
||||
},
|
||||
signups: {
|
||||
limit: signupsLimit,
|
||||
interval: signupsInterval,
|
||||
intervalUnit: signupsIntervalUnit,
|
||||
},
|
||||
sms: {
|
||||
limit: smsLimit,
|
||||
interval: smsInterval,
|
||||
intervalUnit: smsIntervalUnit,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [
|
||||
loading,
|
||||
form,
|
||||
authRateEnabled,
|
||||
bruteForceLimit,
|
||||
bruteForceInterval,
|
||||
bruteForceIntervalUnit,
|
||||
emailsLimit,
|
||||
emailsInterval,
|
||||
emailsIntervalUnit,
|
||||
globalLimit,
|
||||
globalInterval,
|
||||
globalIntervalUnit,
|
||||
signupsLimit,
|
||||
signupsInterval,
|
||||
signupsIntervalUnit,
|
||||
smsLimit,
|
||||
smsInterval,
|
||||
smsIntervalUnit,
|
||||
]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
delay={1000}
|
||||
label="Loading rate limits..."
|
||||
className="justify-center"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
register,
|
||||
formState: { errors },
|
||||
formState,
|
||||
watch,
|
||||
} = form;
|
||||
|
||||
const enabled = watch('enabled');
|
||||
|
||||
const handleSubmit = async (formValues: AuthLimitingFormValues) => {
|
||||
const updateConfigPromise = updateRateLimitConfig({
|
||||
variables: {
|
||||
appId: currentProject.id,
|
||||
config: {
|
||||
auth: {
|
||||
rateLimit: formValues.enabled
|
||||
? {
|
||||
bruteForce: {
|
||||
limit: formValues.bruteForce.limit,
|
||||
interval: `${formValues.bruteForce.interval}${formValues.bruteForce.intervalUnit}`,
|
||||
},
|
||||
emails: {
|
||||
limit: formValues.emails.limit,
|
||||
interval: `${formValues.emails.interval}${formValues.emails.intervalUnit}`,
|
||||
},
|
||||
global: {
|
||||
limit: formValues.global.limit,
|
||||
interval: `${formValues.global.interval}${formValues.global.intervalUnit}`,
|
||||
},
|
||||
signups: {
|
||||
limit: formValues.signups.limit,
|
||||
interval: `${formValues.signups.interval}${formValues.signups.intervalUnit}`,
|
||||
},
|
||||
sms: {
|
||||
limit: formValues.sms.limit,
|
||||
interval: `${formValues.sms.interval}${formValues.sms.intervalUnit}`,
|
||||
},
|
||||
}
|
||||
: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Updating Auth rate limit settings...',
|
||||
successMessage: 'Auth rate limit settings updated successfully',
|
||||
errorMessage: 'Failed to update Auth rate limit settings',
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<Form
|
||||
onSubmit={handleSubmit}
|
||||
className="flex h-full flex-col overflow-hidden"
|
||||
>
|
||||
<SettingsContainer
|
||||
title="Auth"
|
||||
switchId="enabled"
|
||||
showSwitch
|
||||
slotProps={{
|
||||
submitButton: {
|
||||
disabled: !formState.isDirty || maintenanceActive,
|
||||
loading: formState.isSubmitting,
|
||||
},
|
||||
}}
|
||||
className="flex flex-col px-0"
|
||||
>
|
||||
<Divider />
|
||||
<RateLimitField
|
||||
disabled={!enabled}
|
||||
register={register}
|
||||
errors={errors.bruteForce}
|
||||
id="bruteForce"
|
||||
title="Brute Force"
|
||||
/>
|
||||
<Divider />
|
||||
<RateLimitField
|
||||
disabled={!enabled}
|
||||
register={register}
|
||||
errors={errors.emails}
|
||||
id="emails"
|
||||
title="Emails"
|
||||
/>
|
||||
<Divider />
|
||||
<RateLimitField
|
||||
disabled={!enabled}
|
||||
register={register}
|
||||
errors={errors.global}
|
||||
id="global"
|
||||
title="Global"
|
||||
/>
|
||||
<Divider />
|
||||
<RateLimitField
|
||||
disabled={!enabled}
|
||||
register={register}
|
||||
errors={errors.signups}
|
||||
id="signups"
|
||||
title="Signups"
|
||||
/>
|
||||
<Divider />
|
||||
<RateLimitField
|
||||
disabled={!enabled}
|
||||
register={register}
|
||||
errors={errors.sms}
|
||||
id="sms"
|
||||
title="SMS"
|
||||
/>
|
||||
</SettingsContainer>
|
||||
</Form>
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as AuthLimitingForm } from './AuthLimitingForm';
|
||||
@@ -0,0 +1,88 @@
|
||||
import { ControlledSelect } from '@/components/form/ControlledSelect';
|
||||
import { Box } from '@/components/ui/v2/Box';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { Option } from '@/components/ui/v2/Option';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { intervalUnitOptions } from '@/features/projects/rate-limiting/settings/components/validationSchemas';
|
||||
import type {
|
||||
FieldError,
|
||||
FieldErrorsImpl,
|
||||
Merge,
|
||||
UseFormRegister,
|
||||
} from 'react-hook-form';
|
||||
|
||||
interface RateLimitFieldProps {
|
||||
register: UseFormRegister<any>;
|
||||
errors: Merge<
|
||||
FieldError,
|
||||
FieldErrorsImpl<{
|
||||
limit: number;
|
||||
interval: number;
|
||||
intervalUnit: string;
|
||||
}>
|
||||
>;
|
||||
disabled?: boolean;
|
||||
title?: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export default function RateLimitField({
|
||||
register,
|
||||
disabled,
|
||||
id,
|
||||
errors,
|
||||
title,
|
||||
}: RateLimitFieldProps) {
|
||||
return (
|
||||
<Box className="px-4">
|
||||
{title ? <Text className="py-4 font-semibold">{title}</Text> : null}
|
||||
<div className="flex flex-col gap-8 lg:flex-row">
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Text>Limit</Text>
|
||||
<Input
|
||||
{...register(`${id}.limit`)}
|
||||
disabled={disabled}
|
||||
id={`${id}.limit`}
|
||||
type="number"
|
||||
placeholder=""
|
||||
className="max-w-60"
|
||||
hideEmptyHelperText
|
||||
error={!!errors?.limit}
|
||||
helperText={errors?.limit?.message}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Text>Interval</Text>
|
||||
<Input
|
||||
{...register(`${id}.interval`)}
|
||||
disabled={disabled}
|
||||
id={`${id}.interval`}
|
||||
type="number"
|
||||
placeholder=""
|
||||
hideEmptyHelperText
|
||||
className="max-w-32"
|
||||
error={!!errors?.interval}
|
||||
helperText={errors?.interval?.message}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<ControlledSelect
|
||||
{...register(`${id}.intervalUnit`)}
|
||||
disabled={disabled}
|
||||
variant="normal"
|
||||
id={`${id}.intervalUnit`}
|
||||
className="w-27"
|
||||
defaultValue="m"
|
||||
hideEmptyHelperText
|
||||
>
|
||||
{intervalUnitOptions.map(({ value, label }) => (
|
||||
<Option key={`${id}.intervalUnit.${value}`} value={value}>
|
||||
{label}
|
||||
</Option>
|
||||
))}
|
||||
</ControlledSelect>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as RateLimitField } from './RateLimitField';
|
||||
@@ -0,0 +1,169 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { Divider } from '@/components/ui/v2/Divider';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import { rateLimitingItemValidationSchema } from '@/features/projects/rate-limiting/settings/components/validationSchemas';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
useUpdateRateLimitConfigMutation,
|
||||
type ConfigConfigUpdateInput,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { RateLimitField } from 'features/projects/rate-limiting/settings/components/RateLimitField';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
export const validationSchema = Yup.object({
|
||||
enabled: Yup.boolean().label('Enabled'),
|
||||
rateLimit: rateLimitingItemValidationSchema,
|
||||
});
|
||||
|
||||
export interface RateLimitDefaultValues {
|
||||
enabled: boolean;
|
||||
rateLimit: { limit: number; interval: number; intervalUnit: string };
|
||||
}
|
||||
|
||||
export interface RateLimitingFormProps {
|
||||
defaultValues: RateLimitDefaultValues;
|
||||
serviceName: keyof ConfigConfigUpdateInput;
|
||||
title: string;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export type RateLimitingFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function RateLimitingForm({
|
||||
defaultValues,
|
||||
serviceName,
|
||||
title,
|
||||
loading,
|
||||
}: RateLimitingFormProps) {
|
||||
const { openDialog } = useDialog();
|
||||
const { maintenanceActive } = useUI();
|
||||
const isPlatform = useIsPlatform();
|
||||
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
|
||||
const [updateRateLimitConfig] = useUpdateRateLimitConfigMutation({
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const form = useForm<RateLimitingFormValues>({
|
||||
defaultValues: defaultValues.enabled
|
||||
? defaultValues
|
||||
: {
|
||||
enabled: false,
|
||||
rateLimit: {
|
||||
limit: 0,
|
||||
interval: 0,
|
||||
intervalUnit: 's',
|
||||
},
|
||||
},
|
||||
reValidateMode: 'onSubmit',
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && defaultValues.enabled) {
|
||||
form.reset(defaultValues);
|
||||
}
|
||||
}, [loading, defaultValues, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
delay={1000}
|
||||
label="Loading rate limits..."
|
||||
className="justify-center"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
register,
|
||||
formState: { errors },
|
||||
formState,
|
||||
watch,
|
||||
} = form;
|
||||
|
||||
const enabled = watch('enabled');
|
||||
|
||||
const handleSubmit = async (formValues: RateLimitingFormValues) => {
|
||||
const updateConfigPromise = updateRateLimitConfig({
|
||||
variables: {
|
||||
appId: currentProject.id,
|
||||
config: {
|
||||
[serviceName]: {
|
||||
rateLimit: formValues.enabled
|
||||
? {
|
||||
limit: formValues.rateLimit.limit,
|
||||
interval: `${formValues.rateLimit.interval}${formValues.rateLimit.intervalUnit}`,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: `Updating ${title} rate limit settings...`,
|
||||
successMessage: `${title} rate limit settings updated successfully`,
|
||||
errorMessage: `Failed to update ${title} rate limit settings`,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<Form
|
||||
onSubmit={handleSubmit}
|
||||
className="flex h-full flex-col overflow-hidden"
|
||||
>
|
||||
<SettingsContainer
|
||||
title={title}
|
||||
switchId="enabled"
|
||||
showSwitch
|
||||
slotProps={{
|
||||
submitButton: {
|
||||
disabled: !formState.isDirty || maintenanceActive,
|
||||
loading: formState.isSubmitting,
|
||||
},
|
||||
}}
|
||||
className="flex flex-col px-0"
|
||||
>
|
||||
<Divider />
|
||||
<RateLimitField
|
||||
disabled={!enabled}
|
||||
register={register}
|
||||
errors={errors.rateLimit}
|
||||
id="rateLimit"
|
||||
/>
|
||||
</SettingsContainer>
|
||||
</Form>
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as RateLimitingForm } from './RateLimitingForm';
|
||||
@@ -0,0 +1,194 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { Divider } from '@/components/ui/v2/Divider';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import { rateLimitingItemValidationSchema } from '@/features/projects/rate-limiting/settings/components/validationSchemas';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { useUpdateRunServiceConfigMutation } from '@/utils/__generated__/graphql';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { RateLimitField } from 'features/projects/rate-limiting/settings/components/RateLimitField';
|
||||
import type { UseGetRunServiceRateLimitsReturn } from 'features/projects/rate-limiting/settings/hooks/useGetRunServiceRateLimits/useGetRunServiceRateLimits';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
export const validationSchema = Yup.object({
|
||||
enabled: Yup.boolean().label('Enabled'),
|
||||
ports: Yup.array().of(rateLimitingItemValidationSchema),
|
||||
});
|
||||
|
||||
export type RunServiceLimitingFormValues = Yup.InferType<
|
||||
typeof validationSchema
|
||||
>;
|
||||
|
||||
export interface RunServiceLimitingFormProps {
|
||||
title?: string;
|
||||
serviceId?: string;
|
||||
loading?: boolean;
|
||||
enabledDefault?: boolean;
|
||||
ports?: UseGetRunServiceRateLimitsReturn['services'][0]['ports'];
|
||||
}
|
||||
|
||||
export default function RunServiceLimitingForm({
|
||||
title,
|
||||
serviceId,
|
||||
ports,
|
||||
loading,
|
||||
enabledDefault,
|
||||
}: RunServiceLimitingFormProps) {
|
||||
const { openDialog } = useDialog();
|
||||
const { maintenanceActive } = useUI();
|
||||
const isPlatform = useIsPlatform();
|
||||
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
|
||||
const [updateRunServiceRateLimit] = useUpdateRunServiceConfigMutation({
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const form = useForm<RunServiceLimitingFormValues>({
|
||||
defaultValues: {
|
||||
enabled: enabledDefault,
|
||||
ports: [
|
||||
...ports.map((port) => ({
|
||||
limit: port?.rateLimit?.limit,
|
||||
interval: port?.rateLimit?.interval,
|
||||
intervalUnit: port?.rateLimit?.intervalUnit,
|
||||
})),
|
||||
],
|
||||
},
|
||||
reValidateMode: 'onSubmit',
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && enabledDefault) {
|
||||
form.reset({
|
||||
enabled: enabledDefault,
|
||||
ports: [
|
||||
...ports.map((port) => ({
|
||||
limit: port?.rateLimit?.limit,
|
||||
interval: port?.rateLimit?.interval,
|
||||
intervalUnit: port?.rateLimit?.intervalUnit,
|
||||
})),
|
||||
],
|
||||
});
|
||||
}
|
||||
}, [loading, enabledDefault, ports, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
delay={1000}
|
||||
label="Loading rate limits..."
|
||||
className="justify-center"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
register,
|
||||
formState: { errors },
|
||||
formState,
|
||||
watch,
|
||||
} = form;
|
||||
|
||||
const enabled = watch('enabled');
|
||||
|
||||
const handleSubmit = async (formValues: RunServiceLimitingFormValues) => {
|
||||
const updateConfigPromise = updateRunServiceRateLimit({
|
||||
variables: {
|
||||
appID: currentProject?.id,
|
||||
serviceID: serviceId,
|
||||
config: {
|
||||
ports: ports.map((port, index) => {
|
||||
const rateLimit = formValues.ports[index];
|
||||
return {
|
||||
...port,
|
||||
rateLimit: enabled
|
||||
? {
|
||||
limit: rateLimit.limit,
|
||||
interval: `${rateLimit.interval}${rateLimit.intervalUnit}`,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Updating Run service rate limit settings...',
|
||||
successMessage: 'Run service rate limit settings updated successfully',
|
||||
errorMessage: 'Failed to update Run service rate limit settings',
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<Form
|
||||
onSubmit={handleSubmit}
|
||||
className="flex h-full flex-col overflow-hidden"
|
||||
>
|
||||
<SettingsContainer
|
||||
title={title}
|
||||
switchId="enabled"
|
||||
showSwitch
|
||||
slotProps={{
|
||||
submitButton: {
|
||||
disabled: !formState.isDirty || maintenanceActive,
|
||||
loading: formState.isSubmitting,
|
||||
},
|
||||
}}
|
||||
className="flex flex-col px-0"
|
||||
>
|
||||
<Divider />
|
||||
{ports.map((port, index) => {
|
||||
if (port?.type !== 'http' || !port?.publish) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fieldTitle = `${port.type} <-> ${port.port}`.toUpperCase();
|
||||
const showDivider = index < ports.length - 1;
|
||||
return (
|
||||
<div key={`ports.${port.port}`}>
|
||||
<RateLimitField
|
||||
title={fieldTitle}
|
||||
disabled={!enabled}
|
||||
register={register}
|
||||
errors={errors.ports}
|
||||
id={`ports.${index}`}
|
||||
/>
|
||||
{showDivider && <Divider />}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</SettingsContainer>
|
||||
</Form>
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as RunServiceLimitingForm } from './RunServiceLimitingForm';
|
||||
@@ -0,0 +1,23 @@
|
||||
import * as Yup from 'yup';
|
||||
|
||||
export const rateLimitingItemValidationSchema = Yup.object({
|
||||
limit: Yup.number()
|
||||
.required('Limit is required.')
|
||||
.min(1)
|
||||
.positive('Limit must be a positive number')
|
||||
.typeError('Limit must be a number.'),
|
||||
interval: Yup.number()
|
||||
.required('Interval is required.')
|
||||
.min(1)
|
||||
.positive('Interval must be a positive number')
|
||||
.typeError('Interval must be a number.'),
|
||||
intervalUnit: Yup.string()
|
||||
.required('Interval unit is required.')
|
||||
.oneOf(['s', 'm', 'h']),
|
||||
});
|
||||
|
||||
export const intervalUnitOptions = [
|
||||
{ value: 's', label: 'seconds' },
|
||||
{ value: 'm', label: 'minutes' },
|
||||
{ value: 'h', label: 'hours' },
|
||||
];
|
||||
@@ -0,0 +1 @@
|
||||
export { default as useGetRateLimits } from './useGetRateLimits';
|
||||
@@ -0,0 +1,120 @@
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { useGetRateLimitConfigQuery } from '@/utils/__generated__/graphql';
|
||||
import { DEFAULT_RATE_LIMITS } from 'features/projects/rate-limiting/settings/utils/constants';
|
||||
import { parseIntervalNameUnit } from 'features/projects/rate-limiting/settings/utils/parseIntervalNameUnit';
|
||||
|
||||
export default function useGetRateLimits() {
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
|
||||
const { data, loading } = useGetRateLimitConfigQuery({
|
||||
variables: {
|
||||
appId: currentProject?.id,
|
||||
resolve: false,
|
||||
},
|
||||
skip: !currentProject,
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const authRateLimit = data?.config?.auth?.rateLimit;
|
||||
const hasuraRateLimit = data?.config?.hasura?.rateLimit;
|
||||
const storageRateLimit = data?.config?.storage?.rateLimit;
|
||||
const functionsRateLimit = data?.config?.functions?.rateLimit;
|
||||
|
||||
const { bruteForce, emails, global, signups, sms } = authRateLimit || {};
|
||||
const { limit: bruteForceLimit, interval: bruteForceIntervalStr } =
|
||||
bruteForce || {};
|
||||
const { interval: bruteForceInterval, intervalUnit: bruteForceIntervalUnit } =
|
||||
parseIntervalNameUnit(bruteForceIntervalStr);
|
||||
|
||||
const { limit: emailsLimit, interval: emailsIntervalStr } = emails || {};
|
||||
const { interval: emailsInterval, intervalUnit: emailsIntervalUnit } =
|
||||
parseIntervalNameUnit(emailsIntervalStr);
|
||||
|
||||
const { limit: globalLimit, interval: globalIntervalStr } = global || {};
|
||||
const { interval: globalInterval, intervalUnit: globalIntervalUnit } =
|
||||
parseIntervalNameUnit(globalIntervalStr);
|
||||
|
||||
const { limit: signupsLimit, interval: signupsIntervalStr } = signups || {};
|
||||
const { interval: signupsInterval, intervalUnit: signupsIntervalUnit } =
|
||||
parseIntervalNameUnit(signupsIntervalStr);
|
||||
|
||||
const { limit: smsLimit, interval: smsIntervalStr } = sms || {};
|
||||
const { interval: smsInterval, intervalUnit: smsIntervalUnit } =
|
||||
parseIntervalNameUnit(smsIntervalStr);
|
||||
|
||||
const { limit: hasuraLimit, interval: hasuraIntervalStr } =
|
||||
hasuraRateLimit || {};
|
||||
const { interval: hasuraInterval, intervalUnit: hasuraIntervalUnit } =
|
||||
parseIntervalNameUnit(hasuraIntervalStr);
|
||||
|
||||
const { limit: storageLimit, interval: storageIntervalStr } =
|
||||
storageRateLimit || {};
|
||||
const { interval: storageInterval, intervalUnit: storageIntervalUnit } =
|
||||
parseIntervalNameUnit(storageIntervalStr);
|
||||
|
||||
const { limit: functionsLimit, interval: functionsIntervalStr } =
|
||||
functionsRateLimit || {};
|
||||
const { interval: functionsInterval, intervalUnit: functionsIntervalUnit } =
|
||||
parseIntervalNameUnit(functionsIntervalStr);
|
||||
|
||||
return {
|
||||
authRateLimit: {
|
||||
enabled: !!authRateLimit,
|
||||
bruteForce: {
|
||||
limit: bruteForceLimit || DEFAULT_RATE_LIMITS.limit,
|
||||
interval: bruteForceInterval || DEFAULT_RATE_LIMITS.interval,
|
||||
intervalUnit:
|
||||
bruteForceIntervalUnit || DEFAULT_RATE_LIMITS.intervalUnit,
|
||||
},
|
||||
emails: {
|
||||
limit: emailsLimit || DEFAULT_RATE_LIMITS.limit,
|
||||
interval: emailsInterval || DEFAULT_RATE_LIMITS.interval,
|
||||
intervalUnit: emailsIntervalUnit || DEFAULT_RATE_LIMITS.intervalUnit,
|
||||
},
|
||||
global: {
|
||||
limit: globalLimit || DEFAULT_RATE_LIMITS.limit,
|
||||
interval: globalInterval || DEFAULT_RATE_LIMITS.interval,
|
||||
intervalUnit: globalIntervalUnit || DEFAULT_RATE_LIMITS.intervalUnit,
|
||||
},
|
||||
signups: {
|
||||
limit: signupsLimit || DEFAULT_RATE_LIMITS.limit,
|
||||
interval: signupsInterval || DEFAULT_RATE_LIMITS.interval,
|
||||
intervalUnit: signupsIntervalUnit || DEFAULT_RATE_LIMITS.intervalUnit,
|
||||
},
|
||||
sms: {
|
||||
limit: smsLimit || DEFAULT_RATE_LIMITS.limit,
|
||||
interval: smsInterval || DEFAULT_RATE_LIMITS.interval,
|
||||
intervalUnit: smsIntervalUnit || DEFAULT_RATE_LIMITS.intervalUnit,
|
||||
},
|
||||
},
|
||||
hasuraDefaultValues: {
|
||||
enabled: !!hasuraRateLimit,
|
||||
rateLimit: {
|
||||
limit: hasuraLimit || DEFAULT_RATE_LIMITS.limit,
|
||||
interval: hasuraInterval || DEFAULT_RATE_LIMITS.interval,
|
||||
intervalUnit: hasuraIntervalUnit || DEFAULT_RATE_LIMITS.intervalUnit,
|
||||
},
|
||||
},
|
||||
storageDefaultValues: {
|
||||
enabled: !!storageRateLimit,
|
||||
rateLimit: {
|
||||
limit: storageLimit || DEFAULT_RATE_LIMITS.limit,
|
||||
interval: storageInterval || DEFAULT_RATE_LIMITS.interval,
|
||||
intervalUnit: storageIntervalUnit || DEFAULT_RATE_LIMITS.intervalUnit,
|
||||
},
|
||||
},
|
||||
functionsDefaultValues: {
|
||||
enabled: !!functionsRateLimit,
|
||||
rateLimit: {
|
||||
limit: functionsLimit || DEFAULT_RATE_LIMITS.limit,
|
||||
interval: functionsInterval || DEFAULT_RATE_LIMITS.interval,
|
||||
intervalUnit: functionsIntervalUnit || DEFAULT_RATE_LIMITS.intervalUnit,
|
||||
},
|
||||
},
|
||||
loading,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as useGetRunServiceRateLimits } from './useGetRunServiceRateLimits';
|
||||
@@ -0,0 +1,107 @@
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import {
|
||||
useGetLocalRunServiceRateLimitQuery,
|
||||
useGetRunServicesRateLimitQuery,
|
||||
type GetRunServicesRateLimitQuery,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import { DEFAULT_RATE_LIMITS } from 'features/projects/rate-limiting/settings/utils/constants';
|
||||
import { parseIntervalNameUnit } from 'features/projects/rate-limiting/settings/utils/parseIntervalNameUnit';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
type RunService = Pick<
|
||||
GetRunServicesRateLimitQuery['app']['runServices'][0],
|
||||
'config'
|
||||
> & {
|
||||
id?: string;
|
||||
serviceID?: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
subdomain?: string;
|
||||
};
|
||||
|
||||
export interface UseGetRunServiceRateLimitsReturn {
|
||||
services: {
|
||||
name?: string;
|
||||
id?: string;
|
||||
enabled?: boolean;
|
||||
ports?: {
|
||||
type?: string;
|
||||
port?: string;
|
||||
publish?: boolean;
|
||||
rateLimit?: {
|
||||
limit?: number;
|
||||
interval?: number;
|
||||
intervalUnit?: string;
|
||||
};
|
||||
}[];
|
||||
}[];
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export default function useGetRunServiceRateLimits(): UseGetRunServiceRateLimitsReturn {
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { data, loading: loadingPlatformServices } =
|
||||
useGetRunServicesRateLimitQuery({
|
||||
variables: {
|
||||
appID: currentProject?.id,
|
||||
resolve: false,
|
||||
},
|
||||
skip: !isPlatform,
|
||||
});
|
||||
|
||||
const { loading: loadingLocalServices, data: localServicesData } =
|
||||
useGetLocalRunServiceRateLimitQuery({
|
||||
variables: { appID: currentProject?.id, resolve: false },
|
||||
skip: isPlatform,
|
||||
client: localMimirClient,
|
||||
});
|
||||
|
||||
const platformServices = useMemo(
|
||||
() => data?.app?.runServices.map((service) => service) ?? [],
|
||||
[data],
|
||||
);
|
||||
|
||||
const localServices = useMemo(
|
||||
() => localServicesData?.runServiceConfigs.map((service) => service) ?? [],
|
||||
[localServicesData],
|
||||
);
|
||||
|
||||
const services: RunService[] = isPlatform ? platformServices : localServices;
|
||||
const loading = isPlatform ? loadingPlatformServices : loadingLocalServices;
|
||||
|
||||
const servicesInfo = services.map((service) => {
|
||||
const enabled = service?.config?.ports?.some(
|
||||
(port) => port?.rateLimit && port?.type === 'http' && port?.publish,
|
||||
);
|
||||
|
||||
const ports = service?.config?.ports?.map((port) => {
|
||||
const { interval, intervalUnit } = parseIntervalNameUnit(
|
||||
port?.rateLimit?.interval,
|
||||
);
|
||||
const rateLimit = {
|
||||
limit: port?.rateLimit?.limit || DEFAULT_RATE_LIMITS.limit,
|
||||
interval: interval || DEFAULT_RATE_LIMITS.interval,
|
||||
intervalUnit: intervalUnit || DEFAULT_RATE_LIMITS.intervalUnit,
|
||||
};
|
||||
return {
|
||||
type: port?.type,
|
||||
publish: port?.publish,
|
||||
port: port?.port,
|
||||
rateLimit,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
enabled,
|
||||
name: service.config?.name,
|
||||
id: service.id ?? service.serviceID,
|
||||
ports,
|
||||
};
|
||||
});
|
||||
|
||||
return { services: servicesInfo, loading };
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export const DEFAULT_RATE_LIMITS = {
|
||||
limit: 1000,
|
||||
interval: 5,
|
||||
intervalUnit: 'm',
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './constants';
|
||||
@@ -0,0 +1 @@
|
||||
export { default as parseIntervalNameUnit } from './parseIntervalNameUnit';
|
||||
@@ -0,0 +1,18 @@
|
||||
export default function parseIntervalNameUnit(interval: string) {
|
||||
if (!interval) {
|
||||
return {};
|
||||
}
|
||||
const regex = /^(\d+)([a-zA-Z])$/;
|
||||
const match = interval.match(regex);
|
||||
|
||||
if (!match) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const [, intervalValue, intervalUnit] = match;
|
||||
|
||||
return {
|
||||
interval: parseInt(intervalValue, 10),
|
||||
intervalUnit,
|
||||
};
|
||||
}
|
||||
@@ -102,6 +102,9 @@ export default function ServiceForm({
|
||||
const getFormattedConfig = (values: ServiceFormValues) => {
|
||||
// Remove any __typename property from the values
|
||||
const sanitizedValues = removeTypename(values) as ServiceFormValues;
|
||||
const sanitizedInitialDataPorts = initialData?.ports
|
||||
? removeTypename(initialData.ports)
|
||||
: [];
|
||||
|
||||
const config: ConfigRunServiceConfigInsertInput = {
|
||||
name: sanitizedValues.name,
|
||||
@@ -130,6 +133,10 @@ export default function ServiceForm({
|
||||
type: item.type,
|
||||
publish: item.publish,
|
||||
ingresses: item.ingresses,
|
||||
rateLimit:
|
||||
sanitizedInitialDataPorts.find(
|
||||
(port) => port.port === item.port && port.type === item.type,
|
||||
)?.rateLimit ?? null,
|
||||
})),
|
||||
healthCheck: sanitizedValues.healthCheck
|
||||
? {
|
||||
@@ -309,7 +316,7 @@ export default function ServiceForm({
|
||||
<Tooltip title="Name of the service, must be unique per project.">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="w-4 h-4"
|
||||
className="h-4 w-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -349,7 +356,7 @@ export default function ServiceForm({
|
||||
>
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="w-4 h-4"
|
||||
className="h-4 w-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -380,7 +387,7 @@ export default function ServiceForm({
|
||||
<Tooltip title="Command to run when to start the service. This is optional as the image may already have a baked-in command.">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="w-4 h-4"
|
||||
className="h-4 w-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -428,7 +435,7 @@ export default function ServiceForm({
|
||||
{createServiceFormError && (
|
||||
<Alert
|
||||
severity="error"
|
||||
className="grid items-center justify-between grid-flow-col px-4 py-3"
|
||||
className="grid grid-flow-col items-center justify-between px-4 py-3"
|
||||
>
|
||||
<span className="text-left">
|
||||
<strong>Error:</strong> {createServiceFormError.message}
|
||||
|
||||
@@ -69,7 +69,16 @@ export interface ServiceFormProps extends DialogFormProps {
|
||||
/**
|
||||
* if there is initialData then it's an update operation
|
||||
*/
|
||||
initialData?: ServiceFormValues & { subdomain?: string }; // subdomain is only set on the backend
|
||||
initialData?: Omit<ServiceFormValues, 'ports'> & {
|
||||
subdomain?: string;
|
||||
ports: {
|
||||
port: number;
|
||||
type: PortTypes;
|
||||
publish: boolean;
|
||||
ingresses?: { fqdn?: string[] }[] | null;
|
||||
rateLimit?: { limit: number; interval: string } | null;
|
||||
}[];
|
||||
}; // subdomain is only set on the backend
|
||||
|
||||
/**
|
||||
* Function to be called when the operation is cancelled.
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||
import { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
||||
import type { ServiceFormValues } from '@/features/services/components/ServiceForm/ServiceFormTypes';
|
||||
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
|
||||
import { useState } from 'react';
|
||||
|
||||
export interface ServiceConfirmationDialogProps {
|
||||
/**
|
||||
@@ -28,10 +29,21 @@ export default function ServiceConfirmationDialog({
|
||||
onCancel,
|
||||
onSubmit,
|
||||
}: ServiceConfirmationDialogProps) {
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const approximatePriceForService = parseFloat(
|
||||
(formValues.compute.cpu * formValues.replicas * COST_PER_VCPU).toFixed(2),
|
||||
);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
await onSubmit();
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid grid-flow-row gap-6 px-6 pb-6">
|
||||
<Box className="grid grid-flow-row gap-4">
|
||||
@@ -74,7 +86,12 @@ export default function ServiceConfirmationDialog({
|
||||
</Box>
|
||||
|
||||
<Box className="grid grid-flow-row gap-2">
|
||||
<Button color="primary" onClick={onSubmit} autoFocus>
|
||||
<Button
|
||||
loading={isSubmitting}
|
||||
color="primary"
|
||||
onClick={handleSubmit}
|
||||
autoFocus
|
||||
>
|
||||
Confirm
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ export default function ServicesList({
|
||||
openDrawer({
|
||||
title: (
|
||||
<Box className="flex flex-row items-center space-x-2">
|
||||
<CubeIcon className="w-5 h-5" />
|
||||
<CubeIcon className="h-5 w-5" />
|
||||
<Text>Edit {service.config?.name ?? 'unset'}</Text>
|
||||
</Box>
|
||||
),
|
||||
@@ -67,6 +67,7 @@ export default function ServicesList({
|
||||
type: item.type as PortTypes,
|
||||
publish: item.publish,
|
||||
ingresses: item.ingresses,
|
||||
rateLimit: item.rateLimit,
|
||||
})),
|
||||
compute: service.config?.resources?.compute ?? {
|
||||
cpu: 62,
|
||||
@@ -107,13 +108,13 @@ export default function ServicesList({
|
||||
onClick={() => viewService(service)}
|
||||
>
|
||||
<Box
|
||||
className="flex flex-row justify-between w-full"
|
||||
className="flex w-full flex-row justify-between"
|
||||
sx={{
|
||||
backgroundColor: 'transparent',
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-row items-center flex-1 space-x-4">
|
||||
<CubeIcon className="w-5 h-5" />
|
||||
<div className="flex flex-1 flex-row items-center space-x-4">
|
||||
<CubeIcon className="h-5 w-5" />
|
||||
<div className="flex flex-col">
|
||||
<Text variant="h4" className="font-semibold">
|
||||
{service.config?.name ?? 'unset'}
|
||||
@@ -129,7 +130,7 @@ export default function ServicesList({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-row items-center hidden space-x-2 md:flex">
|
||||
<div className="hidden flex-row items-center space-x-2 md:flex">
|
||||
<Text variant="subtitle1" className="font-mono text-xs">
|
||||
{service.id ?? service.serviceID}
|
||||
</Text>
|
||||
@@ -142,7 +143,7 @@ export default function ServicesList({
|
||||
}}
|
||||
aria-label="Service Id"
|
||||
>
|
||||
<CopyIcon className="w-4 h-4" />
|
||||
<CopyIcon className="h-4 w-4" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</Box>
|
||||
@@ -172,7 +173,7 @@ export default function ServicesList({
|
||||
onClick={() => viewService(service)}
|
||||
className="z-50 grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
|
||||
>
|
||||
<UserIcon className="w-4 h-4" />
|
||||
<UserIcon className="h-4 w-4" />
|
||||
<Text className="font-medium">View Service</Text>
|
||||
</Dropdown.Item>
|
||||
<Divider component="li" />
|
||||
@@ -182,7 +183,7 @@ export default function ServicesList({
|
||||
onClick={() => deleteService(service)}
|
||||
disabled={!isPlatform}
|
||||
>
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
<Text className="font-medium" color="error">
|
||||
Delete Service
|
||||
</Text>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
query getRateLimitConfig($appId: uuid!, $resolve: Boolean!) {
|
||||
config(appID: $appId, resolve: $resolve) {
|
||||
hasura {
|
||||
rateLimit {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
}
|
||||
storage {
|
||||
rateLimit {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
}
|
||||
functions {
|
||||
rateLimit {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
}
|
||||
auth {
|
||||
rateLimit {
|
||||
bruteForce {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
emails {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
global {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
signups {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
sms {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
mutation UpdateRateLimitConfig(
|
||||
$appId: uuid!
|
||||
$config: ConfigConfigUpdateInput!
|
||||
) {
|
||||
updateConfig(appID: $appId, config: $config) {
|
||||
hasura {
|
||||
rateLimit {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
}
|
||||
storage {
|
||||
rateLimit {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
}
|
||||
functions {
|
||||
rateLimit {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
}
|
||||
auth {
|
||||
rateLimit {
|
||||
bruteForce {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
emails {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
global {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
signups {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
sms {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,10 @@ fragment RunServiceConfig on ConfigRunServiceConfig {
|
||||
ingresses {
|
||||
fqdn
|
||||
}
|
||||
rateLimit {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
}
|
||||
healthCheck {
|
||||
port
|
||||
|
||||
38
dashboard/src/gql/services/getRunServicesRateLimit.gql
Normal file
38
dashboard/src/gql/services/getRunServicesRateLimit.gql
Normal file
@@ -0,0 +1,38 @@
|
||||
fragment RunServiceRateLimit on ConfigRunServiceConfig {
|
||||
name
|
||||
ports {
|
||||
port
|
||||
type
|
||||
publish
|
||||
rateLimit {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
ingresses {
|
||||
fqdn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query getRunServicesRateLimit($appID: uuid!, $resolve: Boolean!) {
|
||||
app(id: $appID) {
|
||||
runServices {
|
||||
id
|
||||
createdAt
|
||||
updatedAt
|
||||
subdomain
|
||||
config(resolve: $resolve) {
|
||||
...RunServiceRateLimit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query getLocalRunServiceRateLimit($appID: uuid!, $resolve: Boolean!) {
|
||||
runServiceConfigs(appID: $appID, resolve: $resolve) {
|
||||
serviceID
|
||||
config {
|
||||
...RunServiceRateLimit
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { Container } from '@/components/layout/Container';
|
||||
import { SettingsLayout } from '@/components/layout/SettingsLayout';
|
||||
import { Box } from '@/components/ui/v2/Box';
|
||||
import { ArrowSquareOutIcon } from '@/components/ui/v2/icons/ArrowSquareOutIcon';
|
||||
import { Link } from '@/components/ui/v2/Link';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { AuthLimitingForm } from '@/features/projects/rate-limiting/settings/components/AuthLimitingForm';
|
||||
import { RateLimitingForm } from '@/features/projects/rate-limiting/settings/components/RateLimitingForm';
|
||||
import { RunServiceLimitingForm } from '@/features/projects/rate-limiting/settings/components/RunServiceLimitingForm';
|
||||
import { useGetRateLimits } from '@/features/projects/rate-limiting/settings/hooks/useGetRateLimits';
|
||||
import { useGetRunServiceRateLimits } from '@/features/projects/rate-limiting/settings/hooks/useGetRunServiceRateLimits';
|
||||
import { type ReactElement } from 'react';
|
||||
|
||||
export default function RateLimiting() {
|
||||
const { services, loading } = useGetRunServiceRateLimits();
|
||||
|
||||
const {
|
||||
hasuraDefaultValues,
|
||||
functionsDefaultValues,
|
||||
storageDefaultValues,
|
||||
loading: loadingBaseServices,
|
||||
} = useGetRateLimits();
|
||||
|
||||
return (
|
||||
<Container
|
||||
className="grid max-w-5xl grid-flow-row gap-6 bg-transparent"
|
||||
rootClassName="bg-transparent"
|
||||
>
|
||||
<Box className="flex flex-row items-center gap-4 overflow-hidden rounded-lg border-1 p-4">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<Text className="text-lg font-semibold">Rate Limiting</Text>
|
||||
|
||||
<Text color="secondary">
|
||||
Learn more about
|
||||
<Link
|
||||
href="https://docs.nhost.io/platform/rate-limits"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
underline="hover"
|
||||
className="ml-1 font-medium"
|
||||
>
|
||||
Rate Limiting
|
||||
<ArrowSquareOutIcon className="ml-1 h-4 w-4" />
|
||||
</Link>
|
||||
</Text>
|
||||
</div>
|
||||
</Box>
|
||||
<AuthLimitingForm />
|
||||
<RateLimitingForm
|
||||
defaultValues={hasuraDefaultValues}
|
||||
loading={loadingBaseServices}
|
||||
serviceName="hasura"
|
||||
title="Hasura"
|
||||
/>
|
||||
<RateLimitingForm
|
||||
defaultValues={storageDefaultValues}
|
||||
loading={loadingBaseServices}
|
||||
serviceName="storage"
|
||||
title="Storage"
|
||||
/>
|
||||
<RateLimitingForm
|
||||
defaultValues={functionsDefaultValues}
|
||||
loading={loadingBaseServices}
|
||||
serviceName="functions"
|
||||
title="Functions"
|
||||
/>
|
||||
{services?.map((service) => {
|
||||
if (
|
||||
service?.ports?.some((port) => port?.type === 'http' && port?.publish)
|
||||
) {
|
||||
return (
|
||||
<RunServiceLimitingForm
|
||||
enabledDefault={service.enabled}
|
||||
key={service.id}
|
||||
title={service.name}
|
||||
serviceId={service.id}
|
||||
ports={service.ports}
|
||||
loading={loading}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
RateLimiting.getLayout = function getLayout(page: ReactElement) {
|
||||
return <SettingsLayout>{page}</SettingsLayout>;
|
||||
};
|
||||
385
dashboard/src/utils/__generated__/graphql.ts
generated
385
dashboard/src/utils/__generated__/graphql.ts
generated
@@ -199,6 +199,7 @@ export type ConfigAuth = {
|
||||
__typename?: 'ConfigAuth';
|
||||
elevatedPrivileges?: Maybe<ConfigAuthElevatedPrivileges>;
|
||||
method?: Maybe<ConfigAuthMethod>;
|
||||
rateLimit?: Maybe<ConfigAuthRateLimit>;
|
||||
redirections?: Maybe<ConfigAuthRedirections>;
|
||||
/** Resources for the service */
|
||||
resources?: Maybe<ConfigResources>;
|
||||
@@ -223,6 +224,7 @@ export type ConfigAuthComparisonExp = {
|
||||
_or?: InputMaybe<Array<ConfigAuthComparisonExp>>;
|
||||
elevatedPrivileges?: InputMaybe<ConfigAuthElevatedPrivilegesComparisonExp>;
|
||||
method?: InputMaybe<ConfigAuthMethodComparisonExp>;
|
||||
rateLimit?: InputMaybe<ConfigAuthRateLimitComparisonExp>;
|
||||
redirections?: InputMaybe<ConfigAuthRedirectionsComparisonExp>;
|
||||
resources?: InputMaybe<ConfigResourcesComparisonExp>;
|
||||
session?: InputMaybe<ConfigAuthSessionComparisonExp>;
|
||||
@@ -255,6 +257,7 @@ export type ConfigAuthElevatedPrivilegesUpdateInput = {
|
||||
export type ConfigAuthInsertInput = {
|
||||
elevatedPrivileges?: InputMaybe<ConfigAuthElevatedPrivilegesInsertInput>;
|
||||
method?: InputMaybe<ConfigAuthMethodInsertInput>;
|
||||
rateLimit?: InputMaybe<ConfigAuthRateLimitInsertInput>;
|
||||
redirections?: InputMaybe<ConfigAuthRedirectionsInsertInput>;
|
||||
resources?: InputMaybe<ConfigResourcesInsertInput>;
|
||||
session?: InputMaybe<ConfigAuthSessionInsertInput>;
|
||||
@@ -684,6 +687,42 @@ export type ConfigAuthMethodWebauthnUpdateInput = {
|
||||
relyingParty?: InputMaybe<ConfigAuthMethodWebauthnRelyingPartyUpdateInput>;
|
||||
};
|
||||
|
||||
export type ConfigAuthRateLimit = {
|
||||
__typename?: 'ConfigAuthRateLimit';
|
||||
bruteForce?: Maybe<ConfigRateLimit>;
|
||||
emails?: Maybe<ConfigRateLimit>;
|
||||
global?: Maybe<ConfigRateLimit>;
|
||||
signups?: Maybe<ConfigRateLimit>;
|
||||
sms?: Maybe<ConfigRateLimit>;
|
||||
};
|
||||
|
||||
export type ConfigAuthRateLimitComparisonExp = {
|
||||
_and?: InputMaybe<Array<ConfigAuthRateLimitComparisonExp>>;
|
||||
_not?: InputMaybe<ConfigAuthRateLimitComparisonExp>;
|
||||
_or?: InputMaybe<Array<ConfigAuthRateLimitComparisonExp>>;
|
||||
bruteForce?: InputMaybe<ConfigRateLimitComparisonExp>;
|
||||
emails?: InputMaybe<ConfigRateLimitComparisonExp>;
|
||||
global?: InputMaybe<ConfigRateLimitComparisonExp>;
|
||||
signups?: InputMaybe<ConfigRateLimitComparisonExp>;
|
||||
sms?: InputMaybe<ConfigRateLimitComparisonExp>;
|
||||
};
|
||||
|
||||
export type ConfigAuthRateLimitInsertInput = {
|
||||
bruteForce?: InputMaybe<ConfigRateLimitInsertInput>;
|
||||
emails?: InputMaybe<ConfigRateLimitInsertInput>;
|
||||
global?: InputMaybe<ConfigRateLimitInsertInput>;
|
||||
signups?: InputMaybe<ConfigRateLimitInsertInput>;
|
||||
sms?: InputMaybe<ConfigRateLimitInsertInput>;
|
||||
};
|
||||
|
||||
export type ConfigAuthRateLimitUpdateInput = {
|
||||
bruteForce?: InputMaybe<ConfigRateLimitUpdateInput>;
|
||||
emails?: InputMaybe<ConfigRateLimitUpdateInput>;
|
||||
global?: InputMaybe<ConfigRateLimitUpdateInput>;
|
||||
signups?: InputMaybe<ConfigRateLimitUpdateInput>;
|
||||
sms?: InputMaybe<ConfigRateLimitUpdateInput>;
|
||||
};
|
||||
|
||||
export type ConfigAuthRedirections = {
|
||||
__typename?: 'ConfigAuthRedirections';
|
||||
/** AUTH_ACCESS_CONTROL_ALLOWED_REDIRECT_URLS */
|
||||
@@ -834,6 +873,7 @@ export type ConfigAuthTotpUpdateInput = {
|
||||
export type ConfigAuthUpdateInput = {
|
||||
elevatedPrivileges?: InputMaybe<ConfigAuthElevatedPrivilegesUpdateInput>;
|
||||
method?: InputMaybe<ConfigAuthMethodUpdateInput>;
|
||||
rateLimit?: InputMaybe<ConfigAuthRateLimitUpdateInput>;
|
||||
redirections?: InputMaybe<ConfigAuthRedirectionsUpdateInput>;
|
||||
resources?: InputMaybe<ConfigResourcesUpdateInput>;
|
||||
session?: InputMaybe<ConfigAuthSessionUpdateInput>;
|
||||
@@ -1233,6 +1273,7 @@ export type ConfigFloatComparisonExp = {
|
||||
export type ConfigFunctions = {
|
||||
__typename?: 'ConfigFunctions';
|
||||
node?: Maybe<ConfigFunctionsNode>;
|
||||
rateLimit?: Maybe<ConfigRateLimit>;
|
||||
resources?: Maybe<ConfigFunctionsResources>;
|
||||
};
|
||||
|
||||
@@ -1241,11 +1282,13 @@ export type ConfigFunctionsComparisonExp = {
|
||||
_not?: InputMaybe<ConfigFunctionsComparisonExp>;
|
||||
_or?: InputMaybe<Array<ConfigFunctionsComparisonExp>>;
|
||||
node?: InputMaybe<ConfigFunctionsNodeComparisonExp>;
|
||||
rateLimit?: InputMaybe<ConfigRateLimitComparisonExp>;
|
||||
resources?: InputMaybe<ConfigFunctionsResourcesComparisonExp>;
|
||||
};
|
||||
|
||||
export type ConfigFunctionsInsertInput = {
|
||||
node?: InputMaybe<ConfigFunctionsNodeInsertInput>;
|
||||
rateLimit?: InputMaybe<ConfigRateLimitInsertInput>;
|
||||
resources?: InputMaybe<ConfigFunctionsResourcesInsertInput>;
|
||||
};
|
||||
|
||||
@@ -1291,6 +1334,7 @@ export type ConfigFunctionsResourcesUpdateInput = {
|
||||
|
||||
export type ConfigFunctionsUpdateInput = {
|
||||
node?: InputMaybe<ConfigFunctionsNodeUpdateInput>;
|
||||
rateLimit?: InputMaybe<ConfigRateLimitUpdateInput>;
|
||||
resources?: InputMaybe<ConfigFunctionsResourcesUpdateInput>;
|
||||
};
|
||||
|
||||
@@ -1415,6 +1459,7 @@ export type ConfigHasura = {
|
||||
/** JWT Secrets configuration */
|
||||
jwtSecrets?: Maybe<Array<ConfigJwtSecret>>;
|
||||
logs?: Maybe<ConfigHasuraLogs>;
|
||||
rateLimit?: Maybe<ConfigRateLimit>;
|
||||
/** Resources for the service */
|
||||
resources?: Maybe<ConfigResources>;
|
||||
/**
|
||||
@@ -1477,6 +1522,7 @@ export type ConfigHasuraComparisonExp = {
|
||||
events?: InputMaybe<ConfigHasuraEventsComparisonExp>;
|
||||
jwtSecrets?: InputMaybe<ConfigJwtSecretComparisonExp>;
|
||||
logs?: InputMaybe<ConfigHasuraLogsComparisonExp>;
|
||||
rateLimit?: InputMaybe<ConfigRateLimitComparisonExp>;
|
||||
resources?: InputMaybe<ConfigResourcesComparisonExp>;
|
||||
settings?: InputMaybe<ConfigHasuraSettingsComparisonExp>;
|
||||
version?: InputMaybe<ConfigStringComparisonExp>;
|
||||
@@ -1510,6 +1556,7 @@ export type ConfigHasuraInsertInput = {
|
||||
events?: InputMaybe<ConfigHasuraEventsInsertInput>;
|
||||
jwtSecrets?: InputMaybe<Array<ConfigJwtSecretInsertInput>>;
|
||||
logs?: InputMaybe<ConfigHasuraLogsInsertInput>;
|
||||
rateLimit?: InputMaybe<ConfigRateLimitInsertInput>;
|
||||
resources?: InputMaybe<ConfigResourcesInsertInput>;
|
||||
settings?: InputMaybe<ConfigHasuraSettingsInsertInput>;
|
||||
version?: InputMaybe<Scalars['String']>;
|
||||
@@ -1602,6 +1649,7 @@ export type ConfigHasuraUpdateInput = {
|
||||
events?: InputMaybe<ConfigHasuraEventsUpdateInput>;
|
||||
jwtSecrets?: InputMaybe<Array<ConfigJwtSecretUpdateInput>>;
|
||||
logs?: InputMaybe<ConfigHasuraLogsUpdateInput>;
|
||||
rateLimit?: InputMaybe<ConfigRateLimitUpdateInput>;
|
||||
resources?: InputMaybe<ConfigResourcesUpdateInput>;
|
||||
settings?: InputMaybe<ConfigHasuraSettingsUpdateInput>;
|
||||
version?: InputMaybe<Scalars['String']>;
|
||||
@@ -2013,6 +2061,30 @@ export type ConfigProviderUpdateInput = {
|
||||
smtp?: InputMaybe<ConfigSmtpUpdateInput>;
|
||||
};
|
||||
|
||||
export type ConfigRateLimit = {
|
||||
__typename?: 'ConfigRateLimit';
|
||||
interval: Scalars['String'];
|
||||
limit: Scalars['ConfigUint32'];
|
||||
};
|
||||
|
||||
export type ConfigRateLimitComparisonExp = {
|
||||
_and?: InputMaybe<Array<ConfigRateLimitComparisonExp>>;
|
||||
_not?: InputMaybe<ConfigRateLimitComparisonExp>;
|
||||
_or?: InputMaybe<Array<ConfigRateLimitComparisonExp>>;
|
||||
interval?: InputMaybe<ConfigStringComparisonExp>;
|
||||
limit?: InputMaybe<ConfigUint32ComparisonExp>;
|
||||
};
|
||||
|
||||
export type ConfigRateLimitInsertInput = {
|
||||
interval: Scalars['String'];
|
||||
limit: Scalars['ConfigUint32'];
|
||||
};
|
||||
|
||||
export type ConfigRateLimitUpdateInput = {
|
||||
interval?: InputMaybe<Scalars['String']>;
|
||||
limit?: InputMaybe<Scalars['ConfigUint32']>;
|
||||
};
|
||||
|
||||
/** Resource configuration for a service */
|
||||
export type ConfigResources = {
|
||||
__typename?: 'ConfigResources';
|
||||
@@ -2155,6 +2227,7 @@ export type ConfigRunServicePort = {
|
||||
ingresses?: Maybe<Array<ConfigIngress>>;
|
||||
port: Scalars['ConfigPort'];
|
||||
publish?: Maybe<Scalars['Boolean']>;
|
||||
rateLimit?: Maybe<ConfigRateLimit>;
|
||||
type: Scalars['String'];
|
||||
};
|
||||
|
||||
@@ -2165,6 +2238,7 @@ export type ConfigRunServicePortComparisonExp = {
|
||||
ingresses?: InputMaybe<ConfigIngressComparisonExp>;
|
||||
port?: InputMaybe<ConfigPortComparisonExp>;
|
||||
publish?: InputMaybe<ConfigBooleanComparisonExp>;
|
||||
rateLimit?: InputMaybe<ConfigRateLimitComparisonExp>;
|
||||
type?: InputMaybe<ConfigStringComparisonExp>;
|
||||
};
|
||||
|
||||
@@ -2172,6 +2246,7 @@ export type ConfigRunServicePortInsertInput = {
|
||||
ingresses?: InputMaybe<Array<ConfigIngressInsertInput>>;
|
||||
port: Scalars['ConfigPort'];
|
||||
publish?: InputMaybe<Scalars['Boolean']>;
|
||||
rateLimit?: InputMaybe<ConfigRateLimitInsertInput>;
|
||||
type: Scalars['String'];
|
||||
};
|
||||
|
||||
@@ -2179,6 +2254,7 @@ export type ConfigRunServicePortUpdateInput = {
|
||||
ingresses?: InputMaybe<Array<ConfigIngressUpdateInput>>;
|
||||
port?: InputMaybe<Scalars['ConfigPort']>;
|
||||
publish?: InputMaybe<Scalars['Boolean']>;
|
||||
rateLimit?: InputMaybe<ConfigRateLimitUpdateInput>;
|
||||
type?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
@@ -2386,6 +2462,7 @@ export type ConfigStandardOauthProviderWithScopeUpdateInput = {
|
||||
export type ConfigStorage = {
|
||||
__typename?: 'ConfigStorage';
|
||||
antivirus?: Maybe<ConfigStorageAntivirus>;
|
||||
rateLimit?: Maybe<ConfigRateLimit>;
|
||||
/**
|
||||
* Networking (custom domains at the moment) are not allowed as we need to do further
|
||||
* configurations in the CDN. We will enable it again in the future.
|
||||
@@ -2427,18 +2504,21 @@ export type ConfigStorageComparisonExp = {
|
||||
_not?: InputMaybe<ConfigStorageComparisonExp>;
|
||||
_or?: InputMaybe<Array<ConfigStorageComparisonExp>>;
|
||||
antivirus?: InputMaybe<ConfigStorageAntivirusComparisonExp>;
|
||||
rateLimit?: InputMaybe<ConfigRateLimitComparisonExp>;
|
||||
resources?: InputMaybe<ConfigResourcesComparisonExp>;
|
||||
version?: InputMaybe<ConfigStringComparisonExp>;
|
||||
};
|
||||
|
||||
export type ConfigStorageInsertInput = {
|
||||
antivirus?: InputMaybe<ConfigStorageAntivirusInsertInput>;
|
||||
rateLimit?: InputMaybe<ConfigRateLimitInsertInput>;
|
||||
resources?: InputMaybe<ConfigResourcesInsertInput>;
|
||||
version?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ConfigStorageUpdateInput = {
|
||||
antivirus?: InputMaybe<ConfigStorageAntivirusUpdateInput>;
|
||||
rateLimit?: InputMaybe<ConfigRateLimitUpdateInput>;
|
||||
resources?: InputMaybe<ConfigResourcesUpdateInput>;
|
||||
version?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
@@ -23010,6 +23090,22 @@ export type GetConfigRawJsonQueryVariables = Exact<{
|
||||
|
||||
export type GetConfigRawJsonQuery = { __typename?: 'query_root', configRawJSON: string };
|
||||
|
||||
export type GetRateLimitConfigQueryVariables = Exact<{
|
||||
appId: Scalars['uuid'];
|
||||
resolve: Scalars['Boolean'];
|
||||
}>;
|
||||
|
||||
|
||||
export type GetRateLimitConfigQuery = { __typename?: 'query_root', config?: { __typename?: 'ConfigConfig', hasura: { __typename?: 'ConfigHasura', rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null }, storage?: { __typename?: 'ConfigStorage', rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null } | null, functions?: { __typename?: 'ConfigFunctions', rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null } | null, auth?: { __typename?: 'ConfigAuth', rateLimit?: { __typename?: 'ConfigAuthRateLimit', bruteForce?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null, emails?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null, global?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null, signups?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null, sms?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null } | null } | null } | null };
|
||||
|
||||
export type UpdateRateLimitConfigMutationVariables = Exact<{
|
||||
appId: Scalars['uuid'];
|
||||
config: ConfigConfigUpdateInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateRateLimitConfigMutation = { __typename?: 'mutation_root', updateConfig: { __typename?: 'ConfigConfig', hasura: { __typename?: 'ConfigHasura', rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null }, storage?: { __typename?: 'ConfigStorage', rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null } | null, functions?: { __typename?: 'ConfigFunctions', rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null } | null, auth?: { __typename?: 'ConfigAuth', rateLimit?: { __typename?: 'ConfigAuthRateLimit', bruteForce?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null, emails?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null, global?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null, signups?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null, sms?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null } | null } | null } };
|
||||
|
||||
export type ReplaceConfigRawJsonMutationVariables = Exact<{
|
||||
appID: Scalars['uuid'];
|
||||
rawJSON: Scalars['String'];
|
||||
@@ -23393,7 +23489,7 @@ export type GetRunServiceQueryVariables = Exact<{
|
||||
|
||||
export type GetRunServiceQuery = { __typename?: 'query_root', runService?: { __typename?: 'run_service', id: any, subdomain: string, config?: { __typename?: 'ConfigRunServiceConfig', name: any, command?: Array<string> | null, image: { __typename?: 'ConfigRunServiceImage', image: string }, resources: { __typename?: 'ConfigRunServiceResources', replicas: any, compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any }, storage?: Array<{ __typename?: 'ConfigRunServiceResourcesStorage', name: any, path: string, capacity: any }> | null }, environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string }> | null, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null }> | null } | null } | null };
|
||||
|
||||
export type RunServiceConfigFragment = { __typename?: 'ConfigRunServiceConfig', name: any, command?: Array<string> | null, image: { __typename?: 'ConfigRunServiceImage', image: string }, resources: { __typename?: 'ConfigRunServiceResources', replicas: any, compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any }, storage?: Array<{ __typename?: 'ConfigRunServiceResourcesStorage', name: any, path: string, capacity: any }> | null }, environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string }> | null, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null }> | null, healthCheck?: { __typename?: 'ConfigHealthCheck', port: any, initialDelaySeconds?: number | null, probePeriodSeconds?: number | null } | null };
|
||||
export type RunServiceConfigFragment = { __typename?: 'ConfigRunServiceConfig', name: any, command?: Array<string> | null, image: { __typename?: 'ConfigRunServiceImage', image: string }, resources: { __typename?: 'ConfigRunServiceResources', replicas: any, compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any }, storage?: Array<{ __typename?: 'ConfigRunServiceResourcesStorage', name: any, path: string, capacity: any }> | null }, environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string }> | null, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null, rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null }> | null, healthCheck?: { __typename?: 'ConfigHealthCheck', port: any, initialDelaySeconds?: number | null, probePeriodSeconds?: number | null } | null };
|
||||
|
||||
export type GetRunServicesQueryVariables = Exact<{
|
||||
appID: Scalars['uuid'];
|
||||
@@ -23403,7 +23499,7 @@ export type GetRunServicesQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type GetRunServicesQuery = { __typename?: 'query_root', app?: { __typename?: 'apps', runServices: Array<{ __typename?: 'run_service', id: any, createdAt: any, updatedAt: any, subdomain: string, config?: { __typename?: 'ConfigRunServiceConfig', name: any, command?: Array<string> | null, image: { __typename?: 'ConfigRunServiceImage', image: string }, resources: { __typename?: 'ConfigRunServiceResources', replicas: any, compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any }, storage?: Array<{ __typename?: 'ConfigRunServiceResourcesStorage', name: any, path: string, capacity: any }> | null }, environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string }> | null, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null }> | null, healthCheck?: { __typename?: 'ConfigHealthCheck', port: any, initialDelaySeconds?: number | null, probePeriodSeconds?: number | null } | null } | null }>, runServices_aggregate: { __typename?: 'run_service_aggregate', aggregate?: { __typename?: 'run_service_aggregate_fields', count: number } | null } } | null };
|
||||
export type GetRunServicesQuery = { __typename?: 'query_root', app?: { __typename?: 'apps', runServices: Array<{ __typename?: 'run_service', id: any, createdAt: any, updatedAt: any, subdomain: string, config?: { __typename?: 'ConfigRunServiceConfig', name: any, command?: Array<string> | null, image: { __typename?: 'ConfigRunServiceImage', image: string }, resources: { __typename?: 'ConfigRunServiceResources', replicas: any, compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any }, storage?: Array<{ __typename?: 'ConfigRunServiceResourcesStorage', name: any, path: string, capacity: any }> | null }, environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string }> | null, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null, rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null }> | null, healthCheck?: { __typename?: 'ConfigHealthCheck', port: any, initialDelaySeconds?: number | null, probePeriodSeconds?: number | null } | null } | null }>, runServices_aggregate: { __typename?: 'run_service_aggregate', aggregate?: { __typename?: 'run_service_aggregate_fields', count: number } | null } } | null };
|
||||
|
||||
export type GetLocalRunServiceConfigsQueryVariables = Exact<{
|
||||
appID: Scalars['uuid'];
|
||||
@@ -23411,7 +23507,25 @@ export type GetLocalRunServiceConfigsQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type GetLocalRunServiceConfigsQuery = { __typename?: 'query_root', runServiceConfigs: Array<{ __typename?: 'ConfigRunServiceConfigWithID', serviceID: any, config: { __typename?: 'ConfigRunServiceConfig', name: any, command?: Array<string> | null, image: { __typename?: 'ConfigRunServiceImage', image: string }, resources: { __typename?: 'ConfigRunServiceResources', replicas: any, compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any }, storage?: Array<{ __typename?: 'ConfigRunServiceResourcesStorage', name: any, path: string, capacity: any }> | null }, environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string }> | null, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null }> | null, healthCheck?: { __typename?: 'ConfigHealthCheck', port: any, initialDelaySeconds?: number | null, probePeriodSeconds?: number | null } | null } }> };
|
||||
export type GetLocalRunServiceConfigsQuery = { __typename?: 'query_root', runServiceConfigs: Array<{ __typename?: 'ConfigRunServiceConfigWithID', serviceID: any, config: { __typename?: 'ConfigRunServiceConfig', name: any, command?: Array<string> | null, image: { __typename?: 'ConfigRunServiceImage', image: string }, resources: { __typename?: 'ConfigRunServiceResources', replicas: any, compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any }, storage?: Array<{ __typename?: 'ConfigRunServiceResourcesStorage', name: any, path: string, capacity: any }> | null }, environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string }> | null, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null, rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null }> | null, healthCheck?: { __typename?: 'ConfigHealthCheck', port: any, initialDelaySeconds?: number | null, probePeriodSeconds?: number | null } | null } }> };
|
||||
|
||||
export type RunServiceRateLimitFragment = { __typename?: 'ConfigRunServiceConfig', name: any, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null }> | null };
|
||||
|
||||
export type GetRunServicesRateLimitQueryVariables = Exact<{
|
||||
appID: Scalars['uuid'];
|
||||
resolve: Scalars['Boolean'];
|
||||
}>;
|
||||
|
||||
|
||||
export type GetRunServicesRateLimitQuery = { __typename?: 'query_root', app?: { __typename?: 'apps', runServices: Array<{ __typename?: 'run_service', id: any, createdAt: any, updatedAt: any, subdomain: string, config?: { __typename?: 'ConfigRunServiceConfig', name: any, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null }> | null } | null }> } | null };
|
||||
|
||||
export type GetLocalRunServiceRateLimitQueryVariables = Exact<{
|
||||
appID: Scalars['uuid'];
|
||||
resolve: Scalars['Boolean'];
|
||||
}>;
|
||||
|
||||
|
||||
export type GetLocalRunServiceRateLimitQuery = { __typename?: 'query_root', runServiceConfigs: Array<{ __typename?: 'ConfigRunServiceConfigWithID', serviceID: any, config: { __typename?: 'ConfigRunServiceConfig', name: any, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null }> | null } }> };
|
||||
|
||||
export type InsertRunServiceMutationVariables = Exact<{
|
||||
object: Run_Service_Insert_Input;
|
||||
@@ -23874,6 +23988,10 @@ export const RunServiceConfigFragmentDoc = gql`
|
||||
ingresses {
|
||||
fqdn
|
||||
}
|
||||
rateLimit {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
}
|
||||
healthCheck {
|
||||
port
|
||||
@@ -23882,6 +24000,23 @@ export const RunServiceConfigFragmentDoc = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const RunServiceRateLimitFragmentDoc = gql`
|
||||
fragment RunServiceRateLimit on ConfigRunServiceConfig {
|
||||
name
|
||||
ports {
|
||||
port
|
||||
type
|
||||
publish
|
||||
rateLimit {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
ingresses {
|
||||
fqdn
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const GetWorkspaceMembersWorkspaceMemberFragmentDoc = gql`
|
||||
fragment getWorkspaceMembersWorkspaceMember on workspaceMembers {
|
||||
id
|
||||
@@ -25371,6 +25506,161 @@ export type GetConfigRawJsonQueryResult = Apollo.QueryResult<GetConfigRawJsonQue
|
||||
export function refetchGetConfigRawJsonQuery(variables: GetConfigRawJsonQueryVariables) {
|
||||
return { query: GetConfigRawJsonDocument, variables: variables }
|
||||
}
|
||||
export const GetRateLimitConfigDocument = gql`
|
||||
query getRateLimitConfig($appId: uuid!, $resolve: Boolean!) {
|
||||
config(appID: $appId, resolve: $resolve) {
|
||||
hasura {
|
||||
rateLimit {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
}
|
||||
storage {
|
||||
rateLimit {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
}
|
||||
functions {
|
||||
rateLimit {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
}
|
||||
auth {
|
||||
rateLimit {
|
||||
bruteForce {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
emails {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
global {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
signups {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
sms {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useGetRateLimitConfigQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useGetRateLimitConfigQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useGetRateLimitConfigQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useGetRateLimitConfigQuery({
|
||||
* variables: {
|
||||
* appId: // value for 'appId'
|
||||
* resolve: // value for 'resolve'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useGetRateLimitConfigQuery(baseOptions: Apollo.QueryHookOptions<GetRateLimitConfigQuery, GetRateLimitConfigQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<GetRateLimitConfigQuery, GetRateLimitConfigQueryVariables>(GetRateLimitConfigDocument, options);
|
||||
}
|
||||
export function useGetRateLimitConfigLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetRateLimitConfigQuery, GetRateLimitConfigQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<GetRateLimitConfigQuery, GetRateLimitConfigQueryVariables>(GetRateLimitConfigDocument, options);
|
||||
}
|
||||
export type GetRateLimitConfigQueryHookResult = ReturnType<typeof useGetRateLimitConfigQuery>;
|
||||
export type GetRateLimitConfigLazyQueryHookResult = ReturnType<typeof useGetRateLimitConfigLazyQuery>;
|
||||
export type GetRateLimitConfigQueryResult = Apollo.QueryResult<GetRateLimitConfigQuery, GetRateLimitConfigQueryVariables>;
|
||||
export function refetchGetRateLimitConfigQuery(variables: GetRateLimitConfigQueryVariables) {
|
||||
return { query: GetRateLimitConfigDocument, variables: variables }
|
||||
}
|
||||
export const UpdateRateLimitConfigDocument = gql`
|
||||
mutation UpdateRateLimitConfig($appId: uuid!, $config: ConfigConfigUpdateInput!) {
|
||||
updateConfig(appID: $appId, config: $config) {
|
||||
hasura {
|
||||
rateLimit {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
}
|
||||
storage {
|
||||
rateLimit {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
}
|
||||
functions {
|
||||
rateLimit {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
}
|
||||
auth {
|
||||
rateLimit {
|
||||
bruteForce {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
emails {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
global {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
signups {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
sms {
|
||||
limit
|
||||
interval
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type UpdateRateLimitConfigMutationFn = Apollo.MutationFunction<UpdateRateLimitConfigMutation, UpdateRateLimitConfigMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useUpdateRateLimitConfigMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useUpdateRateLimitConfigMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useUpdateRateLimitConfigMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [updateRateLimitConfigMutation, { data, loading, error }] = useUpdateRateLimitConfigMutation({
|
||||
* variables: {
|
||||
* appId: // value for 'appId'
|
||||
* config: // value for 'config'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useUpdateRateLimitConfigMutation(baseOptions?: Apollo.MutationHookOptions<UpdateRateLimitConfigMutation, UpdateRateLimitConfigMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<UpdateRateLimitConfigMutation, UpdateRateLimitConfigMutationVariables>(UpdateRateLimitConfigDocument, options);
|
||||
}
|
||||
export type UpdateRateLimitConfigMutationHookResult = ReturnType<typeof useUpdateRateLimitConfigMutation>;
|
||||
export type UpdateRateLimitConfigMutationResult = Apollo.MutationResult<UpdateRateLimitConfigMutation>;
|
||||
export type UpdateRateLimitConfigMutationOptions = Apollo.BaseMutationOptions<UpdateRateLimitConfigMutation, UpdateRateLimitConfigMutationVariables>;
|
||||
export const ReplaceConfigRawJsonDocument = gql`
|
||||
mutation ReplaceConfigRawJSON($appID: uuid!, $rawJSON: String!) {
|
||||
replaceConfigRawJSON(appID: $appID, rawJSON: $rawJSON)
|
||||
@@ -27572,6 +27862,95 @@ export type GetLocalRunServiceConfigsQueryResult = Apollo.QueryResult<GetLocalRu
|
||||
export function refetchGetLocalRunServiceConfigsQuery(variables: GetLocalRunServiceConfigsQueryVariables) {
|
||||
return { query: GetLocalRunServiceConfigsDocument, variables: variables }
|
||||
}
|
||||
export const GetRunServicesRateLimitDocument = gql`
|
||||
query getRunServicesRateLimit($appID: uuid!, $resolve: Boolean!) {
|
||||
app(id: $appID) {
|
||||
runServices {
|
||||
id
|
||||
createdAt
|
||||
updatedAt
|
||||
subdomain
|
||||
config(resolve: $resolve) {
|
||||
...RunServiceRateLimit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${RunServiceRateLimitFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useGetRunServicesRateLimitQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useGetRunServicesRateLimitQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useGetRunServicesRateLimitQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useGetRunServicesRateLimitQuery({
|
||||
* variables: {
|
||||
* appID: // value for 'appID'
|
||||
* resolve: // value for 'resolve'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useGetRunServicesRateLimitQuery(baseOptions: Apollo.QueryHookOptions<GetRunServicesRateLimitQuery, GetRunServicesRateLimitQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<GetRunServicesRateLimitQuery, GetRunServicesRateLimitQueryVariables>(GetRunServicesRateLimitDocument, options);
|
||||
}
|
||||
export function useGetRunServicesRateLimitLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetRunServicesRateLimitQuery, GetRunServicesRateLimitQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<GetRunServicesRateLimitQuery, GetRunServicesRateLimitQueryVariables>(GetRunServicesRateLimitDocument, options);
|
||||
}
|
||||
export type GetRunServicesRateLimitQueryHookResult = ReturnType<typeof useGetRunServicesRateLimitQuery>;
|
||||
export type GetRunServicesRateLimitLazyQueryHookResult = ReturnType<typeof useGetRunServicesRateLimitLazyQuery>;
|
||||
export type GetRunServicesRateLimitQueryResult = Apollo.QueryResult<GetRunServicesRateLimitQuery, GetRunServicesRateLimitQueryVariables>;
|
||||
export function refetchGetRunServicesRateLimitQuery(variables: GetRunServicesRateLimitQueryVariables) {
|
||||
return { query: GetRunServicesRateLimitDocument, variables: variables }
|
||||
}
|
||||
export const GetLocalRunServiceRateLimitDocument = gql`
|
||||
query getLocalRunServiceRateLimit($appID: uuid!, $resolve: Boolean!) {
|
||||
runServiceConfigs(appID: $appID, resolve: $resolve) {
|
||||
serviceID
|
||||
config {
|
||||
...RunServiceRateLimit
|
||||
}
|
||||
}
|
||||
}
|
||||
${RunServiceRateLimitFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useGetLocalRunServiceRateLimitQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useGetLocalRunServiceRateLimitQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useGetLocalRunServiceRateLimitQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useGetLocalRunServiceRateLimitQuery({
|
||||
* variables: {
|
||||
* appID: // value for 'appID'
|
||||
* resolve: // value for 'resolve'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useGetLocalRunServiceRateLimitQuery(baseOptions: Apollo.QueryHookOptions<GetLocalRunServiceRateLimitQuery, GetLocalRunServiceRateLimitQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<GetLocalRunServiceRateLimitQuery, GetLocalRunServiceRateLimitQueryVariables>(GetLocalRunServiceRateLimitDocument, options);
|
||||
}
|
||||
export function useGetLocalRunServiceRateLimitLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetLocalRunServiceRateLimitQuery, GetLocalRunServiceRateLimitQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<GetLocalRunServiceRateLimitQuery, GetLocalRunServiceRateLimitQueryVariables>(GetLocalRunServiceRateLimitDocument, options);
|
||||
}
|
||||
export type GetLocalRunServiceRateLimitQueryHookResult = ReturnType<typeof useGetLocalRunServiceRateLimitQuery>;
|
||||
export type GetLocalRunServiceRateLimitLazyQueryHookResult = ReturnType<typeof useGetLocalRunServiceRateLimitLazyQuery>;
|
||||
export type GetLocalRunServiceRateLimitQueryResult = Apollo.QueryResult<GetLocalRunServiceRateLimitQuery, GetLocalRunServiceRateLimitQueryVariables>;
|
||||
export function refetchGetLocalRunServiceRateLimitQuery(variables: GetLocalRunServiceRateLimitQueryVariables) {
|
||||
return { query: GetLocalRunServiceRateLimitDocument, variables: variables }
|
||||
}
|
||||
export const InsertRunServiceDocument = gql`
|
||||
mutation insertRunService($object: run_service_insert_input!) {
|
||||
insertRunService(object: $object) {
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @nhost/docs
|
||||
|
||||
## 2.15.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 40c0d7b: │feat: added subdomain/region information
|
||||
- a18b545: feat: added postgres upgrade docs
|
||||
|
||||
## 2.14.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
---
|
||||
title: Connect Devices to Local Nhost Project
|
||||
description: Configuring dnsmasq for network device connectivity to a local Nhost project
|
||||
icon: ethernet
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
If you want to connect to your local environment from other devices on the same network, such as Android emulators
|
||||
or iPhone devices, you can use **dnsmasq**. Follow this guide for the necessary configuration steps to enable this
|
||||
functionality for your local Nhost project running on your machine.
|
||||
|
||||
<Note>
|
||||
Make sure to install **dnsmasq**. If you're using another OS, please refer to the [dnsmasq website](https://thekelleys.org.uk/dnsmasq/doc.html).
|
||||
|
||||
<Tabs>
|
||||
<Tab title="macOS">
|
||||
```shell Terminal
|
||||
brew install dnsmasq
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Debian">
|
||||
```shell Terminal
|
||||
apt-get install dnsmasq
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Nix">
|
||||
```shell Terminal
|
||||
nix-env -iA nixpkgs.dnsmasq
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Note>
|
||||
|
||||
# Configure dnsmasq for Android
|
||||
<Warning>These steps are necessary when running on both an **Android emulator** or **physical Android device**</Warning>
|
||||
<Steps>
|
||||
<Step title="Configure dnsmasq">
|
||||
Configure `dnsmasq` to resolve nhost service urls to your machine's special [loopback address](https://developer.android.com/studio/run/emulator-networking) `10.0.2.2`
|
||||
```shell Terminal
|
||||
sudo dnsmasq -d \
|
||||
--address=/local.auth.nhost.run/10.0.2.2 \
|
||||
--address=/local.graphql.nhost.run/10.0.2.2 \
|
||||
--address=/local.storage.nhost.run/10.0.2.2 \
|
||||
--address=/local.functions.nhost.run/10.0.2.2
|
||||
```
|
||||
</Step>
|
||||
<Step title="Restart dnsmasq">
|
||||
If you're using another OS, please refer to the [dnsmasq website](https://thekelleys.org.uk/dnsmasq/doc.html).
|
||||
<Tabs>
|
||||
<Tab title="macOS">
|
||||
```shell Terminal
|
||||
sudo brew services restart dnsmasq
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Debian">
|
||||
```shell Terminal
|
||||
sudo systemctl restart dnsmasq
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Step>
|
||||
<Step title="Configure the android device/emulator's DNS settings">
|
||||
1. Edit your network settings: Settings > Network & Internet > Internet > AndroidWifi
|
||||
2. set `IP settings` to `Static`
|
||||
3. set `DNS 1` and `DNS 2` to `10.0.2.2`
|
||||
4. Save
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
# Configure dnsmasq for iOS
|
||||
<Warning>These steps are only necessary when running on physical iOS device, the iOS simulator uses the host machine's network, so no additional configuration is typically needed.</Warning>
|
||||
<Steps>
|
||||
<Step title="Inspect your machine's IP address on your network">
|
||||
<Tabs>
|
||||
<Tab title="macOS">
|
||||
```shell Terminal
|
||||
ipconfig getifaddr en0
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Debian">
|
||||
```shell Terminal
|
||||
ip addr show dev en0 | grep 'inet ' | awk '{print $2}' | cut -d/ -f1
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Step>
|
||||
<Step title="Configure dnsmasq">
|
||||
Configure `dnsmasq` to resolve nhost service urls to your machine's ip address.
|
||||
<Warning>Make sure to replace every occurrence of **[your-machine-s-up-address]** with the address printed in Step `1`</Warning>
|
||||
|
||||
```shell Terminal
|
||||
sudo dnsmasq -d \
|
||||
--address=/local.auth.nhost.run/[your-machine-s-up-address] \
|
||||
--address=/local.graphql.nhost.run/[your-machine-s-up-address] \
|
||||
--address=/local.storage.nhost.run/[your-machine-s-up-address] \
|
||||
--address=/local.functions.nhost.run/[your-machine-s-up-address]
|
||||
```
|
||||
</Step>
|
||||
<Step title="Restart dnsmasq">
|
||||
If you're using another OS, please refer to the [dnsmasq website](https://thekelleys.org.uk/dnsmasq/doc.html).
|
||||
<Tabs>
|
||||
<Tab title="macOS">
|
||||
```shell Terminal
|
||||
sudo brew services restart dnsmasq
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Debian">
|
||||
```shell Terminal
|
||||
sudo systemctl restart dnsmasq
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Step>
|
||||
<Step title="Configure the iPhone's DNS settings">
|
||||
1. Select the wifi you're connected and select `Configure DNS`
|
||||
2. Select `Manual`
|
||||
3. Click on `Add server` and type your local machine's IP address printed in Step `1`
|
||||
4. Save
|
||||
</Step>
|
||||
</Steps>
|
||||
119
docs/guides/cli/subdomain.mdx
Normal file
119
docs/guides/cli/subdomain.mdx
Normal file
@@ -0,0 +1,119 @@
|
||||
---
|
||||
title: Subdomain/Region
|
||||
description: Connecting to your local environment
|
||||
icon: compass
|
||||
---
|
||||
|
||||
When you start the CLI the services are exposed similarly to the way they are exposed in the [cloud](/platform/subdomain). For instance, the following information is shown on your terminal after running `nhost up`
|
||||
|
||||
```
|
||||
> nhost up
|
||||
...
|
||||
URLs:
|
||||
- Postgres: postgres://postgres:postgres@localhost:5432/local
|
||||
- Hasura: https://local.hasura.local.nhost.run
|
||||
- GraphQL: https://local.graphql.local.nhost.run
|
||||
- Auth: https://local.auth.local.nhost.run
|
||||
- Storage: https://local.storage.local.nhost.run
|
||||
- Functions: https://local.functions.local.nhost.run
|
||||
- Dashboard: https://local.dashboard.local.nhost.run
|
||||
- Mailhog: https://local.mailhog.local.nhost.run
|
||||
|
||||
SDK Configuration:
|
||||
Subdomain: local
|
||||
Region: local
|
||||
```
|
||||
|
||||
There you can see the various URLs you can use to access each service plus the region and subdomain you can use to configure the SDK:
|
||||
|
||||
```ts
|
||||
// Create a new Nhost client for local development.
|
||||
const nhost = new NhostClient(
|
||||
{ region: 'local', subdomain: 'local' }
|
||||
)
|
||||
```
|
||||
|
||||
The domains in the URLs above will all return the IP address for localhost, `127.0.0.1`, which should suffice for most development environments. For instance:
|
||||
|
||||
```
|
||||
> host local.auth.local.nhost.run
|
||||
local.auth.local.nhost.run has address 127.0.0.1
|
||||
```
|
||||
|
||||
However, those URLs are powered by a dynamic DNS that can return any IPv4 address you need, you just need to replace the subdomain `local` with a `subdomain` that contains the 4 octets of the IPv4 adress you want separated by `-`. For instance:
|
||||
|
||||
```
|
||||
> host 192-168-100-1.auth.local.nhost.run
|
||||
192-168-100-1.auth.local.nhost.run has address 192.168.100.1
|
||||
|
||||
> host 10-10-1-108.auth.local.nhost.run
|
||||
10-10-1-108.auth.local.nhost.run has address 10.10.1.108
|
||||
```
|
||||
|
||||
This is useful if you need to connect to your environment from a different device, a VM or a mobile device emulator.
|
||||
|
||||
To make use of this functionality you can start your development environment after setting the environment variable `NHOST_LOCAL_SUBDOMAIN` or passing the flag `--local-subdomain` :
|
||||
|
||||
```
|
||||
> export NHOST_LOCAL_SUBDOMAIN=192-168-1-1-8 # either this or --local-subdomain 192-168-1-108
|
||||
> nhost --local-subdomain 192-168-1-108 up
|
||||
...
|
||||
Nhost development environment started.
|
||||
URLs:
|
||||
- Postgres: postgres://postgres:postgres@localhost:5432/local
|
||||
- Hasura: https://192-168-1-108.hasura.local.nhost.run
|
||||
- GraphQL: https://192-168-1-108.graphql.local.nhost.run
|
||||
- Auth: https://192-168-1-108.auth.local.nhost.run
|
||||
- Storage: https://192-168-1-108.storage.local.nhost.run
|
||||
- Functions: https://192-168-1-108.functions.local.nhost.run
|
||||
- Dashboard: https://192-168-1-108.dashboard.local.nhost.run
|
||||
- Mailhog: https://192-168-1-108.mailhog.local.nhost.run
|
||||
|
||||
SDK Configuration:
|
||||
Subdomain: 192-168-1-108
|
||||
Region: local
|
||||
Run `nhost up` to reload the development environment
|
||||
Run `nhost down` to stop the development environment
|
||||
Run `nhost logs` to watch the logs
|
||||
```
|
||||
|
||||
Now you can configure the SDK with:
|
||||
|
||||
```ts
|
||||
// Create a new Nhost client for local development.
|
||||
const nhost = new NhostClient(
|
||||
{ region: 'local', subdomain: '192-168-1-108' }
|
||||
)
|
||||
```
|
||||
|
||||
<Warning>
|
||||
If you are trying to connect to your local environment from an external device or VM make sure that:
|
||||
|
||||
- The IP address you are using is reachable from this device/VM
|
||||
- That your firewall isn't blocking requests
|
||||
</Warning>
|
||||
|
||||
<Warning>
|
||||
If you are testing a social provider don't forget you will need to configure the callback URL to match the subdomain/region you are using. The dashboard should be able to provide this information in settings page.
|
||||
</Warning>
|
||||
|
||||
## Offline access
|
||||
|
||||
All the URLs in this document are resolved by a public DNS, which means you need Internet access to resolve them. If you need to use any of those URLs without Internet access you can add them to your `/etc/hosts` file. For instance:
|
||||
|
||||
```
|
||||
> cat /etc/hosts
|
||||
##
|
||||
# Host Database
|
||||
#
|
||||
# localhost is used to configure the loopback interface
|
||||
# when the system is booting. Do not change this entry.
|
||||
##
|
||||
127.0.0.1 localhost
|
||||
255.255.255.255 broadcasthost
|
||||
# ::1 localhost
|
||||
|
||||
127.0.0.1 local.auth.local.nhost.run local.storage.local.nhost.run ...
|
||||
```
|
||||
|
||||
Just start with the IP you want to resolve followed by all the entries you need separated by spaces.
|
||||
37
docs/guides/database/upgrade-major.mdx
Normal file
37
docs/guides/database/upgrade-major.mdx
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
title: "Upgrade Major Version"
|
||||
description: Upgrade to Postgres 15.x or 16.y
|
||||
icon: circle-up
|
||||
---
|
||||
|
||||
# Upgrade process
|
||||
|
||||
<Info>
|
||||
This document only applies when changing Postgres major version (i.e. from 14 to 15/16 or from 15 to 16). It doesn'e apply when upgrading minor versions (i.e. from 14.5 to 14.11).
|
||||
</Info>
|
||||
|
||||
While new cloud projects ship with Postgres 14 by default, versions 15 and 16 are also supported. To change your major version you can go to Settings -> Database, select the new major version and start the process:
|
||||
|
||||

|
||||
|
||||
<Warning>
|
||||
Keep in mind that the upgrade process requires downtime. Pay attention to all the information provided to you in the settings page.
|
||||
</Warning>
|
||||
|
||||
After starting the process you can follow it on the same page:
|
||||
|
||||

|
||||
|
||||
Finally, you can confirm the upgrade by executing the SQL query `SELECT version();`
|
||||
|
||||

|
||||
|
||||
## Projects with connected repos
|
||||
|
||||
This process can only be triggered from the dashboard. If you have a project with a connected repository and want to upgrade postgres to either 15 or 16 you will have to follow the steps below:
|
||||
|
||||
1. Upgrade the major version using the dashboard
|
||||
2. Run `nhost config pull` or edit the `nhost.toml` by hand.
|
||||
3. Push to git (this step should be a NOOP and can be skipped)
|
||||
|
||||
If you attempt to change major versions via a deployment the deployment will fail. This is done on purpose to avoid unintended upgrades which can lead to downtime.
|
||||
BIN
docs/images/guides/database/upgrade_01.png
Normal file
BIN
docs/images/guides/database/upgrade_01.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
BIN
docs/images/guides/database/upgrade_02.png
Normal file
BIN
docs/images/guides/database/upgrade_02.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
BIN
docs/images/guides/database/upgrade_03.png
Normal file
BIN
docs/images/guides/database/upgrade_03.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 892 KiB |
BIN
docs/images/platform/subdomain.png
Normal file
BIN
docs/images/platform/subdomain.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 971 KiB |
@@ -73,6 +73,7 @@
|
||||
{
|
||||
"group": "Platform",
|
||||
"pages": [
|
||||
"platform/subdomain",
|
||||
"platform/compute-resources",
|
||||
"platform/service-replicas",
|
||||
{
|
||||
@@ -117,7 +118,8 @@
|
||||
"guides/database/configuring-postgres",
|
||||
"guides/database/access",
|
||||
"guides/database/extensions",
|
||||
"guides/database/performance"
|
||||
"guides/database/performance",
|
||||
"guides/database/upgrade-major"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -197,11 +199,11 @@
|
||||
"group": "CLI",
|
||||
"pages": [
|
||||
"guides/cli/local-development",
|
||||
"guides/cli/subdomain",
|
||||
"guides/cli/migrate-config",
|
||||
"guides/cli/multiple-projects",
|
||||
"guides/cli/configuration-overlays",
|
||||
"guides/cli/seeds",
|
||||
"guides/cli/connect-devices-to-local-nhost-project"
|
||||
"guides/cli/seeds"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/docs",
|
||||
"version": "2.14.3",
|
||||
"version": "2.15.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "mintlify dev"
|
||||
|
||||
40
docs/platform/subdomain.mdx
Normal file
40
docs/platform/subdomain.mdx
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: Subdomain/Region
|
||||
description: Connecting to your Cloud project
|
||||
icon: compass
|
||||
---
|
||||
|
||||
Services in the Nhost Cloud adhere to the following format:
|
||||
|
||||
- https://<subdomain>.<service>.<region>.nhost.run
|
||||
|
||||
When you set up a new project in Nhost Cloud, you're assigned a subdomain that, along with the region where the project was created, allows you to generate the different URLs for your services. For example:
|
||||
|
||||

|
||||
|
||||
With that information at hand generating the URLs for the various services is trivial:
|
||||
|
||||
- https://xglwkhjnufblhgtwtfwz.auth.us-west-2.nhost.run
|
||||
- https://xglwkhjnufblhgtwtfwz.db.us-west-2.nhost.run
|
||||
- https://xglwkhjnufblhgtwtfwz.functions.us-west-2.nhost.run
|
||||
- https://xglwkhjnufblhgtwtfwz.graphql.us-west-2.nhost.run
|
||||
- https://xglwkhjnufblhgtwtfwz.hasura.us-west-2.nhost.run
|
||||
- https://xglwkhjnufblhgtwtfwz.storage.us-west-2.nhost.run
|
||||
|
||||
If you are using our SDK you can just specify the region and subdomain and the SDK will automatically generate these URLs for you. For instance:
|
||||
|
||||
|
||||
```ts
|
||||
// Create a new Nhost client for local development.
|
||||
const nhost = new NhostClient(
|
||||
{ region: 'xglwkhjnufblhgtwtfwz', subdomain: 'us-west-2' }
|
||||
)
|
||||
```
|
||||
|
||||
<Note>
|
||||
If you want to use your own domain you can head to the [custom domains documentation](/platform/custom-domains)
|
||||
</Note>
|
||||
|
||||
<Note>
|
||||
For information on how to access your local environment check the [cli documentation](/guides/cli/subdomain)
|
||||
</Note>
|
||||
@@ -22,7 +22,7 @@ const nhost = new NhostClient({
|
||||
|
||||
```ts
|
||||
// Create a new Nhost client for local development.
|
||||
const nhost = new NhostClient({ subdomain: 'local' })
|
||||
const nhost = new NhostClient({ region: 'local', subdomain: 'local' })
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost-examples/cli
|
||||
|
||||
## 0.3.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.1.8
|
||||
|
||||
## 0.3.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/cli",
|
||||
"version": "0.3.9",
|
||||
"version": "0.3.10",
|
||||
"main": "src/index.mjs",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @nhost-examples/codegen-react-apollo
|
||||
|
||||
## 0.4.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.5.5
|
||||
- @nhost/react-apollo@12.0.5
|
||||
|
||||
## 0.4.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/codegen-react-apollo",
|
||||
"version": "0.4.9",
|
||||
"version": "0.4.10",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"codegen": "graphql-codegen",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost-examples/codegen-react-query
|
||||
|
||||
## 0.4.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.5.5
|
||||
|
||||
## 0.4.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/codegen-react-query",
|
||||
"version": "0.4.9",
|
||||
"version": "0.4.10",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"codegen": "graphql-codegen",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @nhost-examples/react-urql
|
||||
|
||||
## 0.3.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.5.5
|
||||
- @nhost/react-urql@9.0.5
|
||||
|
||||
## 0.3.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/codegen-react-urql",
|
||||
"private": true,
|
||||
"version": "0.3.9",
|
||||
"version": "0.3.10",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost-examples/multi-tenant-one-to-many
|
||||
|
||||
## 2.2.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.1.8
|
||||
|
||||
## 2.2.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/multi-tenant-one-to-many",
|
||||
"private": true,
|
||||
"version": "2.2.9",
|
||||
"version": "2.2.10",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {},
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @nhost-examples/nextjs
|
||||
|
||||
## 0.3.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.5.5
|
||||
- @nhost/react-apollo@12.0.5
|
||||
- @nhost/nextjs@2.1.19
|
||||
|
||||
## 0.3.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/nextjs",
|
||||
"version": "0.3.9",
|
||||
"version": "0.3.10",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost-examples/node-storage
|
||||
|
||||
## 0.2.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.1.8
|
||||
|
||||
## 0.2.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/node-storage",
|
||||
"version": "0.2.9",
|
||||
"version": "0.2.10",
|
||||
"private": true,
|
||||
"description": "This is an example of how to use the Storage with Node.js",
|
||||
"main": "src/index.mjs",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost-examples/nextjs-server-components
|
||||
|
||||
## 0.4.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.1.8
|
||||
|
||||
## 0.4.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/nextjs-server-components",
|
||||
"version": "0.4.10",
|
||||
"version": "0.4.11",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @nhost-examples/react-apollo
|
||||
|
||||
## 0.8.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.5.5
|
||||
- @nhost/react-apollo@12.0.5
|
||||
|
||||
## 0.8.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/react-apollo",
|
||||
"version": "0.8.10",
|
||||
"version": "0.8.11",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.9.9",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost-examples/react-gqty
|
||||
|
||||
## 1.2.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.5.5
|
||||
|
||||
## 1.2.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/react-gqty",
|
||||
"private": true,
|
||||
"version": "1.2.9",
|
||||
"version": "1.2.10",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @nhost-examples/react-native
|
||||
|
||||
## 0.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.5.5
|
||||
- @nhost/react-apollo@12.0.5
|
||||
|
||||
## 0.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/react-native",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.4",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"android": "react-native run-android",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @nhost-examples/vue-apollo
|
||||
|
||||
## 0.6.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.1.8
|
||||
- @nhost/apollo@7.1.5
|
||||
- @nhost/vue@2.6.5
|
||||
|
||||
## 0.6.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/vue-apollo",
|
||||
"private": true,
|
||||
"version": "0.6.9",
|
||||
"version": "0.6.10",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @nhost-examples/vue-quickstart
|
||||
|
||||
## 0.2.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/apollo@7.1.5
|
||||
- @nhost/vue@2.6.5
|
||||
|
||||
## 0.2.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/vue-quickstart",
|
||||
"version": "0.2.9",
|
||||
"version": "0.2.10",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/apollo
|
||||
|
||||
## 7.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.1.8
|
||||
|
||||
## 7.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/apollo",
|
||||
"version": "7.1.4",
|
||||
"version": "7.1.5",
|
||||
"description": "Nhost Apollo Client library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @nhost/react-apollo
|
||||
|
||||
## 12.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/apollo@7.1.5
|
||||
- @nhost/react@3.5.5
|
||||
|
||||
## 12.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/react-apollo",
|
||||
"version": "12.0.4",
|
||||
"version": "12.0.5",
|
||||
"description": "Nhost React Apollo client",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/react-urql
|
||||
|
||||
## 9.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.5.5
|
||||
|
||||
## 9.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/react-urql",
|
||||
"version": "9.0.4",
|
||||
"version": "9.0.5",
|
||||
"description": "Nhost React URQL client",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -149,7 +149,8 @@
|
||||
"@grpc/grpc-js@>=1.10.0 <1.10.9": ">=1.10.9",
|
||||
"undici@>=6.0.0 <6.11.1": "6.11.1",
|
||||
"undici@<5.28.4": "5.28.4",
|
||||
"fast-xml-parser@<4.4.1": ">=4.4.1"
|
||||
"fast-xml-parser@<4.4.1": ">=4.4.1",
|
||||
"axios": "1.7.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/hasura-auth-js
|
||||
|
||||
## 2.5.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- caa8bd7: fix: add error handling logic to transition to the signedOut state when the token is invalid or expired
|
||||
|
||||
## 2.5.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/hasura-auth-js",
|
||||
"version": "2.5.4",
|
||||
"version": "2.5.5",
|
||||
"description": "Hasura-auth client",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
PublicKeyCredentialRequestOptionsJSON,
|
||||
RegistrationCredentialJSON
|
||||
} from '@simplewebauthn/typescript-types'
|
||||
import { InterpreterFrom, assign, createMachine, send } from 'xstate'
|
||||
import { assign, createMachine, InterpreterFrom, send } from 'xstate'
|
||||
import {
|
||||
NHOST_JWT_EXPIRES_AT_KEY,
|
||||
NHOST_REFRESH_TOKEN_ID_KEY,
|
||||
@@ -341,7 +341,13 @@ export const createAuthMachine = ({
|
||||
actions: ['saveSession', 'resetTimer', 'reportTokenChanged'],
|
||||
target: 'pending'
|
||||
},
|
||||
onError: [{ actions: 'saveRefreshAttempt', target: 'pending' }]
|
||||
onError: [
|
||||
{
|
||||
cond: 'isUnauthorizedError',
|
||||
target: '#nhost.authentication.signedOut'
|
||||
},
|
||||
{ actions: 'saveRefreshAttempt', target: 'pending' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -755,7 +761,8 @@ export const createAuthMachine = ({
|
||||
|
||||
// * Event guards
|
||||
hasSession: (_, e) => !!e.data?.session,
|
||||
hasMfaTicket: (_, e) => !!e.data?.mfa
|
||||
hasMfaTicket: (_, e) => !!e.data?.mfa,
|
||||
isUnauthorizedError: (_, { data: { error } }: any) => error.status === 401
|
||||
},
|
||||
|
||||
services: {
|
||||
|
||||
@@ -213,6 +213,7 @@ export interface Typegen0 {
|
||||
| 'error.platform.authenticateWithPAT'
|
||||
| 'error.platform.authenticateWithToken'
|
||||
| 'error.platform.importRefreshToken'
|
||||
| 'error.platform.refreshToken'
|
||||
| 'error.platform.signInMfaTotp'
|
||||
reportTokenChanged:
|
||||
| 'SESSION_UPDATE'
|
||||
@@ -305,6 +306,7 @@ export interface Typegen0 {
|
||||
isAutoRefreshDisabled: ''
|
||||
isRefreshTokenPAT: ''
|
||||
isSignedIn: '' | 'error.platform.authenticateWithToken'
|
||||
isUnauthorizedError: 'error.platform.refreshToken'
|
||||
noToken: ''
|
||||
refreshTimerShouldRefresh: ''
|
||||
shouldRetryImportToken: 'error.platform.importRefreshToken'
|
||||
|
||||
@@ -87,21 +87,17 @@ describe(`Time based token refresh`, () => {
|
||||
server.resetHandlers()
|
||||
})
|
||||
|
||||
test(`token refresh should fail if the signed-in user's refresh token was invalid`, async () => {
|
||||
test(`token refresh should fail and sign out the user when the server returns an unauthorized error`, async () => {
|
||||
server.use(authTokenUnauthorizedHandler)
|
||||
|
||||
// Fast forwarding to initial expiration date
|
||||
vi.setSystemTime(initialExpiration)
|
||||
|
||||
await waitFor(authServiceWithInitialSession, (state) =>
|
||||
const state = await waitFor(authServiceWithInitialSession, (state) =>
|
||||
state.matches({ authentication: { signedIn: { refreshTimer: { running: 'refreshing' } } } })
|
||||
)
|
||||
|
||||
const state = await waitFor(authServiceWithInitialSession, (state) =>
|
||||
state.matches({ authentication: { signedIn: { refreshTimer: { running: 'pending' } } } })
|
||||
)
|
||||
|
||||
expect(state.context.refreshTimer.attempts).toBeGreaterThan(0)
|
||||
expect(state.matches({ authentication: 'signedOut' }))
|
||||
})
|
||||
|
||||
test(`access token should always be refreshed when reaching the expiration margin`, async () => {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/nextjs
|
||||
|
||||
## 2.1.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.5.5
|
||||
|
||||
## 2.1.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/nextjs",
|
||||
"version": "2.1.18",
|
||||
"version": "2.1.19",
|
||||
"description": "Nhost NextJS library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @nhost/nhost-js
|
||||
|
||||
## 3.1.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [caa8bd7]
|
||||
- @nhost/hasura-auth-js@2.5.5
|
||||
|
||||
## 3.1.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/nhost-js",
|
||||
"version": "3.1.7",
|
||||
"version": "3.1.8",
|
||||
"description": "Nhost JavaScript SDK",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/react
|
||||
|
||||
## 3.5.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.1.8
|
||||
|
||||
## 3.5.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/react",
|
||||
"version": "3.5.4",
|
||||
"version": "3.5.5",
|
||||
"description": "Nhost React library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/vue
|
||||
|
||||
## 2.6.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.1.8
|
||||
|
||||
## 2.6.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/vue",
|
||||
"version": "2.6.4",
|
||||
"version": "2.6.5",
|
||||
"description": "Nhost Vue library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
21
pnpm-lock.yaml
generated
21
pnpm-lock.yaml
generated
@@ -55,6 +55,7 @@ overrides:
|
||||
undici@>=6.0.0 <6.11.1: 6.11.1
|
||||
undici@<5.28.4: 5.28.4
|
||||
fast-xml-parser@<4.4.1: '>=4.4.1'
|
||||
axios: 1.7.4
|
||||
|
||||
importers:
|
||||
|
||||
@@ -1035,7 +1036,7 @@ importers:
|
||||
devDependencies:
|
||||
'@nhost/nhost-js':
|
||||
specifier: ^3.1.5
|
||||
version: 3.1.6(graphql@16.8.1)
|
||||
version: 3.1.7(graphql@16.8.1)
|
||||
'@playwright/test':
|
||||
specifier: ^1.42.1
|
||||
version: 1.42.1
|
||||
@@ -9783,7 +9784,7 @@ packages:
|
||||
resolution: {integrity: sha512-MMAdhT6DrylDg1doi2oK2Zw0b7gMspr3Cq8stFGTLl8qOGJo9RT8KCNEWUDzR5eurSGcwE1660rq3Qibe4bOag==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
dependencies:
|
||||
axios: 1.7.2
|
||||
axios: 1.7.4
|
||||
openapi-types: 12.1.3
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
@@ -10299,8 +10300,8 @@ packages:
|
||||
- encoding
|
||||
dev: true
|
||||
|
||||
/@nhost/hasura-auth-js@2.5.3:
|
||||
resolution: {integrity: sha512-WPDmF7vMU32I/G4Ytlo+ZUE+INzcjp1Av5KNLH/iBWDw/0HB6Vj5/+AWq+IjxQm0+HmKE+hvnc5Hc5rn0oPreg==}
|
||||
/@nhost/hasura-auth-js@2.5.4:
|
||||
resolution: {integrity: sha512-w9DVBDWamV6KSgO2q6mhA8MEhhFnne4c7fzj2JNEIDUfvc/QMaLpL93ZJII2s4Dc8QeeJM4Vcsjx7zDXI+RnsQ==}
|
||||
dependencies:
|
||||
'@simplewebauthn/browser': 9.0.1
|
||||
fetch-ponyfill: 7.1.0
|
||||
@@ -10322,13 +10323,13 @@ packages:
|
||||
- encoding
|
||||
dev: true
|
||||
|
||||
/@nhost/nhost-js@3.1.6(graphql@16.8.1):
|
||||
resolution: {integrity: sha512-5DPm3vvsiMzJxYzMSxHwqIsn1GfDsZ1LHq5vndHILL8OH5LHPh3tVlUc4EcElbkqN81yzZ++umcDxxH+w8pkAg==}
|
||||
/@nhost/nhost-js@3.1.7(graphql@16.8.1):
|
||||
resolution: {integrity: sha512-9ifZ2qvlJFp+Xk/Frm8do3nkGg5L7s2K11Rr9iZG92LnJP4InqtELwhSxxfN80Gt/aQkgEOxyQ21bJXW3XCSxg==}
|
||||
peerDependencies:
|
||||
graphql: '>=16.8.1'
|
||||
dependencies:
|
||||
'@nhost/graphql-js': 0.3.0(graphql@16.8.1)
|
||||
'@nhost/hasura-auth-js': 2.5.3
|
||||
'@nhost/hasura-auth-js': 2.5.4
|
||||
'@nhost/hasura-storage-js': 2.5.1
|
||||
graphql: 16.8.1
|
||||
isomorphic-unfetch: 3.1.0
|
||||
@@ -15953,7 +15954,7 @@ packages:
|
||||
engines: {node: '>=4'}
|
||||
dependencies:
|
||||
'@segment/loosely-validate-event': 2.0.0
|
||||
axios: 1.7.2
|
||||
axios: 1.7.4
|
||||
axios-retry: 3.2.0
|
||||
lodash.isstring: 4.0.1
|
||||
md5: 2.3.0
|
||||
@@ -16433,8 +16434,8 @@ packages:
|
||||
is-retry-allowed: 1.2.0
|
||||
dev: false
|
||||
|
||||
/axios@1.7.2:
|
||||
resolution: {integrity: sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==}
|
||||
/axios@1.7.4:
|
||||
resolution: {integrity: sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==}
|
||||
dependencies:
|
||||
follow-redirects: 1.15.6
|
||||
form-data: 4.0.0
|
||||
|
||||
Reference in New Issue
Block a user