Compare commits
8 Commits
@nhost/das
...
@nhost/rea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
852f13b273 | ||
|
|
a44a1d48d6 | ||
|
|
b63250d1cb | ||
|
|
caa8bd75ec | ||
|
|
40c0d7b914 | ||
|
|
3773ad7cca | ||
|
|
6f122521e9 | ||
|
|
a18b545d2a |
@@ -2,5 +2,5 @@
|
|||||||
// $schema provides code completion hints to IDEs.
|
// $schema provides code completion hints to IDEs.
|
||||||
"$schema": "https://github.com/IBM/audit-ci/raw/main/docs/schema.json",
|
"$schema": "https://github.com/IBM/audit-ci/raw/main/docs/schema.json",
|
||||||
"moderate": true,
|
"moderate": true,
|
||||||
"allowlist": ["trim-newlines", "vue-template-compiler"]
|
"allowlist": ["vue-template-compiler", "micromatch"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
# @nhost/dashboard
|
# @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
|
## 1.25.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ async function globalTeardown() {
|
|||||||
await adminSecretInput.press('Enter');
|
await adminSecretInput.press('Enter');
|
||||||
|
|
||||||
// note: getByRole doesn't work here
|
// 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();
|
await hasuraPage.locator('[data-test="sql-link"]').click();
|
||||||
|
|
||||||
// Set the value of the Ace code editor using JavaScript evaluation in the browser context
|
// Set the value of the Ace code editor using JavaScript evaluation in the browser context
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/dashboard",
|
"name": "@nhost/dashboard",
|
||||||
"version": "1.25.0",
|
"version": "1.26.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
|
|||||||
@@ -221,6 +221,13 @@ export default function SettingsSidebar({
|
|||||||
>
|
>
|
||||||
Custom Domains
|
Custom Domains
|
||||||
</SettingsNavLink>
|
</SettingsNavLink>
|
||||||
|
<SettingsNavLink
|
||||||
|
href="/rate-limiting"
|
||||||
|
exact={false}
|
||||||
|
onClick={handleSelect}
|
||||||
|
>
|
||||||
|
Rate Limiting
|
||||||
|
</SettingsNavLink>
|
||||||
<SettingsNavLink href="/ai" exact={false} onClick={handleSelect}>
|
<SettingsNavLink href="/ai" exact={false} onClick={handleSelect}>
|
||||||
AI
|
AI
|
||||||
</SettingsNavLink>
|
</SettingsNavLink>
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { useDialog } from '@/components/common/DialogProvider';
|
|||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { Box } from '@/components/ui/v2/Box';
|
import { Box } from '@/components/ui/v2/Box';
|
||||||
import { Button } from '@/components/ui/v2/Button';
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
import { Checkbox } from '@/components/ui/v2/Checkbox';
|
|
||||||
import { BaseDialog } from '@/components/ui/v2/Dialog';
|
import { BaseDialog } from '@/components/ui/v2/Dialog';
|
||||||
|
import { Radio } from '@/components/ui/v2/Radio';
|
||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
import { useAppState } from '@/features/projects/common/hooks/useAppState';
|
import { useAppState } from '@/features/projects/common/hooks/useAppState';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
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-row gap-y-0.5">
|
||||||
<div className="grid grid-flow-col items-center justify-start gap-2">
|
<div className="grid grid-flow-col items-center justify-start gap-2">
|
||||||
<Checkbox
|
<Radio
|
||||||
onChange={setPlan}
|
onChange={setPlan}
|
||||||
checked={selectedPlanId === planId}
|
checked={selectedPlanId === planId}
|
||||||
aria-label={planName}
|
aria-label={planName}
|
||||||
@@ -241,7 +241,21 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</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
|
<Button
|
||||||
onClick={handleChangePlanClick}
|
onClick={handleChangePlanClick}
|
||||||
disabled={!selectedPlan}
|
disabled={!selectedPlan}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const planDescriptions = {
|
const planDescriptions = {
|
||||||
Starter: '1 GB database, 5 GB of file storage, 10 GB of network traffic.',
|
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.',
|
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) => {
|
const getFormattedConfig = (values: ServiceFormValues) => {
|
||||||
// Remove any __typename property from the values
|
// Remove any __typename property from the values
|
||||||
const sanitizedValues = removeTypename(values) as ServiceFormValues;
|
const sanitizedValues = removeTypename(values) as ServiceFormValues;
|
||||||
|
const sanitizedInitialDataPorts = initialData?.ports
|
||||||
|
? removeTypename(initialData.ports)
|
||||||
|
: [];
|
||||||
|
|
||||||
const config: ConfigRunServiceConfigInsertInput = {
|
const config: ConfigRunServiceConfigInsertInput = {
|
||||||
name: sanitizedValues.name,
|
name: sanitizedValues.name,
|
||||||
@@ -130,6 +133,10 @@ export default function ServiceForm({
|
|||||||
type: item.type,
|
type: item.type,
|
||||||
publish: item.publish,
|
publish: item.publish,
|
||||||
ingresses: item.ingresses,
|
ingresses: item.ingresses,
|
||||||
|
rateLimit:
|
||||||
|
sanitizedInitialDataPorts.find(
|
||||||
|
(port) => port.port === item.port && port.type === item.type,
|
||||||
|
)?.rateLimit ?? null,
|
||||||
})),
|
})),
|
||||||
healthCheck: sanitizedValues.healthCheck
|
healthCheck: sanitizedValues.healthCheck
|
||||||
? {
|
? {
|
||||||
@@ -309,7 +316,7 @@ export default function ServiceForm({
|
|||||||
<Tooltip title="Name of the service, must be unique per project.">
|
<Tooltip title="Name of the service, must be unique per project.">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="w-4 h-4"
|
className="h-4 w-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -349,7 +356,7 @@ export default function ServiceForm({
|
|||||||
>
|
>
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="w-4 h-4"
|
className="h-4 w-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</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.">
|
<Tooltip title="Command to run when to start the service. This is optional as the image may already have a baked-in command.">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="w-4 h-4"
|
className="h-4 w-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -428,7 +435,7 @@ export default function ServiceForm({
|
|||||||
{createServiceFormError && (
|
{createServiceFormError && (
|
||||||
<Alert
|
<Alert
|
||||||
severity="error"
|
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">
|
<span className="text-left">
|
||||||
<strong>Error:</strong> {createServiceFormError.message}
|
<strong>Error:</strong> {createServiceFormError.message}
|
||||||
|
|||||||
@@ -69,7 +69,16 @@ export interface ServiceFormProps extends DialogFormProps {
|
|||||||
/**
|
/**
|
||||||
* if there is initialData then it's an update operation
|
* 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.
|
* 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 { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
||||||
import type { ServiceFormValues } from '@/features/services/components/ServiceForm/ServiceFormTypes';
|
import type { ServiceFormValues } from '@/features/services/components/ServiceForm/ServiceFormTypes';
|
||||||
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
|
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
export interface ServiceConfirmationDialogProps {
|
export interface ServiceConfirmationDialogProps {
|
||||||
/**
|
/**
|
||||||
@@ -28,10 +29,21 @@ export default function ServiceConfirmationDialog({
|
|||||||
onCancel,
|
onCancel,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
}: ServiceConfirmationDialogProps) {
|
}: ServiceConfirmationDialogProps) {
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
const approximatePriceForService = parseFloat(
|
const approximatePriceForService = parseFloat(
|
||||||
(formValues.compute.cpu * formValues.replicas * COST_PER_VCPU).toFixed(2),
|
(formValues.compute.cpu * formValues.replicas * COST_PER_VCPU).toFixed(2),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
setIsSubmitting(true);
|
||||||
|
try {
|
||||||
|
await onSubmit();
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-flow-row gap-6 px-6 pb-6">
|
<div className="grid grid-flow-row gap-6 px-6 pb-6">
|
||||||
<Box className="grid grid-flow-row gap-4">
|
<Box className="grid grid-flow-row gap-4">
|
||||||
@@ -74,7 +86,12 @@ export default function ServiceConfirmationDialog({
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box className="grid grid-flow-row gap-2">
|
<Box className="grid grid-flow-row gap-2">
|
||||||
<Button color="primary" onClick={onSubmit} autoFocus>
|
<Button
|
||||||
|
loading={isSubmitting}
|
||||||
|
color="primary"
|
||||||
|
onClick={handleSubmit}
|
||||||
|
autoFocus
|
||||||
|
>
|
||||||
Confirm
|
Confirm
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export default function ServicesList({
|
|||||||
openDrawer({
|
openDrawer({
|
||||||
title: (
|
title: (
|
||||||
<Box className="flex flex-row items-center space-x-2">
|
<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>
|
<Text>Edit {service.config?.name ?? 'unset'}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
),
|
),
|
||||||
@@ -67,6 +67,7 @@ export default function ServicesList({
|
|||||||
type: item.type as PortTypes,
|
type: item.type as PortTypes,
|
||||||
publish: item.publish,
|
publish: item.publish,
|
||||||
ingresses: item.ingresses,
|
ingresses: item.ingresses,
|
||||||
|
rateLimit: item.rateLimit,
|
||||||
})),
|
})),
|
||||||
compute: service.config?.resources?.compute ?? {
|
compute: service.config?.resources?.compute ?? {
|
||||||
cpu: 62,
|
cpu: 62,
|
||||||
@@ -107,13 +108,13 @@ export default function ServicesList({
|
|||||||
onClick={() => viewService(service)}
|
onClick={() => viewService(service)}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
className="flex flex-row justify-between w-full"
|
className="flex w-full flex-row justify-between"
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center flex-1 space-x-4">
|
<div className="flex flex-1 flex-row items-center space-x-4">
|
||||||
<CubeIcon className="w-5 h-5" />
|
<CubeIcon className="h-5 w-5" />
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<Text variant="h4" className="font-semibold">
|
<Text variant="h4" className="font-semibold">
|
||||||
{service.config?.name ?? 'unset'}
|
{service.config?.name ?? 'unset'}
|
||||||
@@ -129,7 +130,7 @@ export default function ServicesList({
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<Text variant="subtitle1" className="font-mono text-xs">
|
||||||
{service.id ?? service.serviceID}
|
{service.id ?? service.serviceID}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -142,7 +143,7 @@ export default function ServicesList({
|
|||||||
}}
|
}}
|
||||||
aria-label="Service Id"
|
aria-label="Service Id"
|
||||||
>
|
>
|
||||||
<CopyIcon className="w-4 h-4" />
|
<CopyIcon className="h-4 w-4" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -172,7 +173,7 @@ export default function ServicesList({
|
|||||||
onClick={() => viewService(service)}
|
onClick={() => viewService(service)}
|
||||||
className="z-50 grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
|
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>
|
<Text className="font-medium">View Service</Text>
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
<Divider component="li" />
|
<Divider component="li" />
|
||||||
@@ -182,7 +183,7 @@ export default function ServicesList({
|
|||||||
onClick={() => deleteService(service)}
|
onClick={() => deleteService(service)}
|
||||||
disabled={!isPlatform}
|
disabled={!isPlatform}
|
||||||
>
|
>
|
||||||
<TrashIcon className="w-4 h-4" />
|
<TrashIcon className="h-4 w-4" />
|
||||||
<Text className="font-medium" color="error">
|
<Text className="font-medium" color="error">
|
||||||
Delete Service
|
Delete Service
|
||||||
</Text>
|
</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 {
|
ingresses {
|
||||||
fqdn
|
fqdn
|
||||||
}
|
}
|
||||||
|
rateLimit {
|
||||||
|
limit
|
||||||
|
interval
|
||||||
|
}
|
||||||
}
|
}
|
||||||
healthCheck {
|
healthCheck {
|
||||||
port
|
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';
|
__typename?: 'ConfigAuth';
|
||||||
elevatedPrivileges?: Maybe<ConfigAuthElevatedPrivileges>;
|
elevatedPrivileges?: Maybe<ConfigAuthElevatedPrivileges>;
|
||||||
method?: Maybe<ConfigAuthMethod>;
|
method?: Maybe<ConfigAuthMethod>;
|
||||||
|
rateLimit?: Maybe<ConfigAuthRateLimit>;
|
||||||
redirections?: Maybe<ConfigAuthRedirections>;
|
redirections?: Maybe<ConfigAuthRedirections>;
|
||||||
/** Resources for the service */
|
/** Resources for the service */
|
||||||
resources?: Maybe<ConfigResources>;
|
resources?: Maybe<ConfigResources>;
|
||||||
@@ -223,6 +224,7 @@ export type ConfigAuthComparisonExp = {
|
|||||||
_or?: InputMaybe<Array<ConfigAuthComparisonExp>>;
|
_or?: InputMaybe<Array<ConfigAuthComparisonExp>>;
|
||||||
elevatedPrivileges?: InputMaybe<ConfigAuthElevatedPrivilegesComparisonExp>;
|
elevatedPrivileges?: InputMaybe<ConfigAuthElevatedPrivilegesComparisonExp>;
|
||||||
method?: InputMaybe<ConfigAuthMethodComparisonExp>;
|
method?: InputMaybe<ConfigAuthMethodComparisonExp>;
|
||||||
|
rateLimit?: InputMaybe<ConfigAuthRateLimitComparisonExp>;
|
||||||
redirections?: InputMaybe<ConfigAuthRedirectionsComparisonExp>;
|
redirections?: InputMaybe<ConfigAuthRedirectionsComparisonExp>;
|
||||||
resources?: InputMaybe<ConfigResourcesComparisonExp>;
|
resources?: InputMaybe<ConfigResourcesComparisonExp>;
|
||||||
session?: InputMaybe<ConfigAuthSessionComparisonExp>;
|
session?: InputMaybe<ConfigAuthSessionComparisonExp>;
|
||||||
@@ -255,6 +257,7 @@ export type ConfigAuthElevatedPrivilegesUpdateInput = {
|
|||||||
export type ConfigAuthInsertInput = {
|
export type ConfigAuthInsertInput = {
|
||||||
elevatedPrivileges?: InputMaybe<ConfigAuthElevatedPrivilegesInsertInput>;
|
elevatedPrivileges?: InputMaybe<ConfigAuthElevatedPrivilegesInsertInput>;
|
||||||
method?: InputMaybe<ConfigAuthMethodInsertInput>;
|
method?: InputMaybe<ConfigAuthMethodInsertInput>;
|
||||||
|
rateLimit?: InputMaybe<ConfigAuthRateLimitInsertInput>;
|
||||||
redirections?: InputMaybe<ConfigAuthRedirectionsInsertInput>;
|
redirections?: InputMaybe<ConfigAuthRedirectionsInsertInput>;
|
||||||
resources?: InputMaybe<ConfigResourcesInsertInput>;
|
resources?: InputMaybe<ConfigResourcesInsertInput>;
|
||||||
session?: InputMaybe<ConfigAuthSessionInsertInput>;
|
session?: InputMaybe<ConfigAuthSessionInsertInput>;
|
||||||
@@ -684,6 +687,42 @@ export type ConfigAuthMethodWebauthnUpdateInput = {
|
|||||||
relyingParty?: InputMaybe<ConfigAuthMethodWebauthnRelyingPartyUpdateInput>;
|
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 = {
|
export type ConfigAuthRedirections = {
|
||||||
__typename?: 'ConfigAuthRedirections';
|
__typename?: 'ConfigAuthRedirections';
|
||||||
/** AUTH_ACCESS_CONTROL_ALLOWED_REDIRECT_URLS */
|
/** AUTH_ACCESS_CONTROL_ALLOWED_REDIRECT_URLS */
|
||||||
@@ -834,6 +873,7 @@ export type ConfigAuthTotpUpdateInput = {
|
|||||||
export type ConfigAuthUpdateInput = {
|
export type ConfigAuthUpdateInput = {
|
||||||
elevatedPrivileges?: InputMaybe<ConfigAuthElevatedPrivilegesUpdateInput>;
|
elevatedPrivileges?: InputMaybe<ConfigAuthElevatedPrivilegesUpdateInput>;
|
||||||
method?: InputMaybe<ConfigAuthMethodUpdateInput>;
|
method?: InputMaybe<ConfigAuthMethodUpdateInput>;
|
||||||
|
rateLimit?: InputMaybe<ConfigAuthRateLimitUpdateInput>;
|
||||||
redirections?: InputMaybe<ConfigAuthRedirectionsUpdateInput>;
|
redirections?: InputMaybe<ConfigAuthRedirectionsUpdateInput>;
|
||||||
resources?: InputMaybe<ConfigResourcesUpdateInput>;
|
resources?: InputMaybe<ConfigResourcesUpdateInput>;
|
||||||
session?: InputMaybe<ConfigAuthSessionUpdateInput>;
|
session?: InputMaybe<ConfigAuthSessionUpdateInput>;
|
||||||
@@ -1233,6 +1273,7 @@ export type ConfigFloatComparisonExp = {
|
|||||||
export type ConfigFunctions = {
|
export type ConfigFunctions = {
|
||||||
__typename?: 'ConfigFunctions';
|
__typename?: 'ConfigFunctions';
|
||||||
node?: Maybe<ConfigFunctionsNode>;
|
node?: Maybe<ConfigFunctionsNode>;
|
||||||
|
rateLimit?: Maybe<ConfigRateLimit>;
|
||||||
resources?: Maybe<ConfigFunctionsResources>;
|
resources?: Maybe<ConfigFunctionsResources>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1241,11 +1282,13 @@ export type ConfigFunctionsComparisonExp = {
|
|||||||
_not?: InputMaybe<ConfigFunctionsComparisonExp>;
|
_not?: InputMaybe<ConfigFunctionsComparisonExp>;
|
||||||
_or?: InputMaybe<Array<ConfigFunctionsComparisonExp>>;
|
_or?: InputMaybe<Array<ConfigFunctionsComparisonExp>>;
|
||||||
node?: InputMaybe<ConfigFunctionsNodeComparisonExp>;
|
node?: InputMaybe<ConfigFunctionsNodeComparisonExp>;
|
||||||
|
rateLimit?: InputMaybe<ConfigRateLimitComparisonExp>;
|
||||||
resources?: InputMaybe<ConfigFunctionsResourcesComparisonExp>;
|
resources?: InputMaybe<ConfigFunctionsResourcesComparisonExp>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ConfigFunctionsInsertInput = {
|
export type ConfigFunctionsInsertInput = {
|
||||||
node?: InputMaybe<ConfigFunctionsNodeInsertInput>;
|
node?: InputMaybe<ConfigFunctionsNodeInsertInput>;
|
||||||
|
rateLimit?: InputMaybe<ConfigRateLimitInsertInput>;
|
||||||
resources?: InputMaybe<ConfigFunctionsResourcesInsertInput>;
|
resources?: InputMaybe<ConfigFunctionsResourcesInsertInput>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1291,6 +1334,7 @@ export type ConfigFunctionsResourcesUpdateInput = {
|
|||||||
|
|
||||||
export type ConfigFunctionsUpdateInput = {
|
export type ConfigFunctionsUpdateInput = {
|
||||||
node?: InputMaybe<ConfigFunctionsNodeUpdateInput>;
|
node?: InputMaybe<ConfigFunctionsNodeUpdateInput>;
|
||||||
|
rateLimit?: InputMaybe<ConfigRateLimitUpdateInput>;
|
||||||
resources?: InputMaybe<ConfigFunctionsResourcesUpdateInput>;
|
resources?: InputMaybe<ConfigFunctionsResourcesUpdateInput>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1415,6 +1459,7 @@ export type ConfigHasura = {
|
|||||||
/** JWT Secrets configuration */
|
/** JWT Secrets configuration */
|
||||||
jwtSecrets?: Maybe<Array<ConfigJwtSecret>>;
|
jwtSecrets?: Maybe<Array<ConfigJwtSecret>>;
|
||||||
logs?: Maybe<ConfigHasuraLogs>;
|
logs?: Maybe<ConfigHasuraLogs>;
|
||||||
|
rateLimit?: Maybe<ConfigRateLimit>;
|
||||||
/** Resources for the service */
|
/** Resources for the service */
|
||||||
resources?: Maybe<ConfigResources>;
|
resources?: Maybe<ConfigResources>;
|
||||||
/**
|
/**
|
||||||
@@ -1477,6 +1522,7 @@ export type ConfigHasuraComparisonExp = {
|
|||||||
events?: InputMaybe<ConfigHasuraEventsComparisonExp>;
|
events?: InputMaybe<ConfigHasuraEventsComparisonExp>;
|
||||||
jwtSecrets?: InputMaybe<ConfigJwtSecretComparisonExp>;
|
jwtSecrets?: InputMaybe<ConfigJwtSecretComparisonExp>;
|
||||||
logs?: InputMaybe<ConfigHasuraLogsComparisonExp>;
|
logs?: InputMaybe<ConfigHasuraLogsComparisonExp>;
|
||||||
|
rateLimit?: InputMaybe<ConfigRateLimitComparisonExp>;
|
||||||
resources?: InputMaybe<ConfigResourcesComparisonExp>;
|
resources?: InputMaybe<ConfigResourcesComparisonExp>;
|
||||||
settings?: InputMaybe<ConfigHasuraSettingsComparisonExp>;
|
settings?: InputMaybe<ConfigHasuraSettingsComparisonExp>;
|
||||||
version?: InputMaybe<ConfigStringComparisonExp>;
|
version?: InputMaybe<ConfigStringComparisonExp>;
|
||||||
@@ -1510,6 +1556,7 @@ export type ConfigHasuraInsertInput = {
|
|||||||
events?: InputMaybe<ConfigHasuraEventsInsertInput>;
|
events?: InputMaybe<ConfigHasuraEventsInsertInput>;
|
||||||
jwtSecrets?: InputMaybe<Array<ConfigJwtSecretInsertInput>>;
|
jwtSecrets?: InputMaybe<Array<ConfigJwtSecretInsertInput>>;
|
||||||
logs?: InputMaybe<ConfigHasuraLogsInsertInput>;
|
logs?: InputMaybe<ConfigHasuraLogsInsertInput>;
|
||||||
|
rateLimit?: InputMaybe<ConfigRateLimitInsertInput>;
|
||||||
resources?: InputMaybe<ConfigResourcesInsertInput>;
|
resources?: InputMaybe<ConfigResourcesInsertInput>;
|
||||||
settings?: InputMaybe<ConfigHasuraSettingsInsertInput>;
|
settings?: InputMaybe<ConfigHasuraSettingsInsertInput>;
|
||||||
version?: InputMaybe<Scalars['String']>;
|
version?: InputMaybe<Scalars['String']>;
|
||||||
@@ -1602,6 +1649,7 @@ export type ConfigHasuraUpdateInput = {
|
|||||||
events?: InputMaybe<ConfigHasuraEventsUpdateInput>;
|
events?: InputMaybe<ConfigHasuraEventsUpdateInput>;
|
||||||
jwtSecrets?: InputMaybe<Array<ConfigJwtSecretUpdateInput>>;
|
jwtSecrets?: InputMaybe<Array<ConfigJwtSecretUpdateInput>>;
|
||||||
logs?: InputMaybe<ConfigHasuraLogsUpdateInput>;
|
logs?: InputMaybe<ConfigHasuraLogsUpdateInput>;
|
||||||
|
rateLimit?: InputMaybe<ConfigRateLimitUpdateInput>;
|
||||||
resources?: InputMaybe<ConfigResourcesUpdateInput>;
|
resources?: InputMaybe<ConfigResourcesUpdateInput>;
|
||||||
settings?: InputMaybe<ConfigHasuraSettingsUpdateInput>;
|
settings?: InputMaybe<ConfigHasuraSettingsUpdateInput>;
|
||||||
version?: InputMaybe<Scalars['String']>;
|
version?: InputMaybe<Scalars['String']>;
|
||||||
@@ -2013,6 +2061,30 @@ export type ConfigProviderUpdateInput = {
|
|||||||
smtp?: InputMaybe<ConfigSmtpUpdateInput>;
|
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 */
|
/** Resource configuration for a service */
|
||||||
export type ConfigResources = {
|
export type ConfigResources = {
|
||||||
__typename?: 'ConfigResources';
|
__typename?: 'ConfigResources';
|
||||||
@@ -2155,6 +2227,7 @@ export type ConfigRunServicePort = {
|
|||||||
ingresses?: Maybe<Array<ConfigIngress>>;
|
ingresses?: Maybe<Array<ConfigIngress>>;
|
||||||
port: Scalars['ConfigPort'];
|
port: Scalars['ConfigPort'];
|
||||||
publish?: Maybe<Scalars['Boolean']>;
|
publish?: Maybe<Scalars['Boolean']>;
|
||||||
|
rateLimit?: Maybe<ConfigRateLimit>;
|
||||||
type: Scalars['String'];
|
type: Scalars['String'];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2165,6 +2238,7 @@ export type ConfigRunServicePortComparisonExp = {
|
|||||||
ingresses?: InputMaybe<ConfigIngressComparisonExp>;
|
ingresses?: InputMaybe<ConfigIngressComparisonExp>;
|
||||||
port?: InputMaybe<ConfigPortComparisonExp>;
|
port?: InputMaybe<ConfigPortComparisonExp>;
|
||||||
publish?: InputMaybe<ConfigBooleanComparisonExp>;
|
publish?: InputMaybe<ConfigBooleanComparisonExp>;
|
||||||
|
rateLimit?: InputMaybe<ConfigRateLimitComparisonExp>;
|
||||||
type?: InputMaybe<ConfigStringComparisonExp>;
|
type?: InputMaybe<ConfigStringComparisonExp>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2172,6 +2246,7 @@ export type ConfigRunServicePortInsertInput = {
|
|||||||
ingresses?: InputMaybe<Array<ConfigIngressInsertInput>>;
|
ingresses?: InputMaybe<Array<ConfigIngressInsertInput>>;
|
||||||
port: Scalars['ConfigPort'];
|
port: Scalars['ConfigPort'];
|
||||||
publish?: InputMaybe<Scalars['Boolean']>;
|
publish?: InputMaybe<Scalars['Boolean']>;
|
||||||
|
rateLimit?: InputMaybe<ConfigRateLimitInsertInput>;
|
||||||
type: Scalars['String'];
|
type: Scalars['String'];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2179,6 +2254,7 @@ export type ConfigRunServicePortUpdateInput = {
|
|||||||
ingresses?: InputMaybe<Array<ConfigIngressUpdateInput>>;
|
ingresses?: InputMaybe<Array<ConfigIngressUpdateInput>>;
|
||||||
port?: InputMaybe<Scalars['ConfigPort']>;
|
port?: InputMaybe<Scalars['ConfigPort']>;
|
||||||
publish?: InputMaybe<Scalars['Boolean']>;
|
publish?: InputMaybe<Scalars['Boolean']>;
|
||||||
|
rateLimit?: InputMaybe<ConfigRateLimitUpdateInput>;
|
||||||
type?: InputMaybe<Scalars['String']>;
|
type?: InputMaybe<Scalars['String']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2386,6 +2462,7 @@ export type ConfigStandardOauthProviderWithScopeUpdateInput = {
|
|||||||
export type ConfigStorage = {
|
export type ConfigStorage = {
|
||||||
__typename?: 'ConfigStorage';
|
__typename?: 'ConfigStorage';
|
||||||
antivirus?: Maybe<ConfigStorageAntivirus>;
|
antivirus?: Maybe<ConfigStorageAntivirus>;
|
||||||
|
rateLimit?: Maybe<ConfigRateLimit>;
|
||||||
/**
|
/**
|
||||||
* Networking (custom domains at the moment) are not allowed as we need to do further
|
* 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.
|
* configurations in the CDN. We will enable it again in the future.
|
||||||
@@ -2427,18 +2504,21 @@ export type ConfigStorageComparisonExp = {
|
|||||||
_not?: InputMaybe<ConfigStorageComparisonExp>;
|
_not?: InputMaybe<ConfigStorageComparisonExp>;
|
||||||
_or?: InputMaybe<Array<ConfigStorageComparisonExp>>;
|
_or?: InputMaybe<Array<ConfigStorageComparisonExp>>;
|
||||||
antivirus?: InputMaybe<ConfigStorageAntivirusComparisonExp>;
|
antivirus?: InputMaybe<ConfigStorageAntivirusComparisonExp>;
|
||||||
|
rateLimit?: InputMaybe<ConfigRateLimitComparisonExp>;
|
||||||
resources?: InputMaybe<ConfigResourcesComparisonExp>;
|
resources?: InputMaybe<ConfigResourcesComparisonExp>;
|
||||||
version?: InputMaybe<ConfigStringComparisonExp>;
|
version?: InputMaybe<ConfigStringComparisonExp>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ConfigStorageInsertInput = {
|
export type ConfigStorageInsertInput = {
|
||||||
antivirus?: InputMaybe<ConfigStorageAntivirusInsertInput>;
|
antivirus?: InputMaybe<ConfigStorageAntivirusInsertInput>;
|
||||||
|
rateLimit?: InputMaybe<ConfigRateLimitInsertInput>;
|
||||||
resources?: InputMaybe<ConfigResourcesInsertInput>;
|
resources?: InputMaybe<ConfigResourcesInsertInput>;
|
||||||
version?: InputMaybe<Scalars['String']>;
|
version?: InputMaybe<Scalars['String']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ConfigStorageUpdateInput = {
|
export type ConfigStorageUpdateInput = {
|
||||||
antivirus?: InputMaybe<ConfigStorageAntivirusUpdateInput>;
|
antivirus?: InputMaybe<ConfigStorageAntivirusUpdateInput>;
|
||||||
|
rateLimit?: InputMaybe<ConfigRateLimitUpdateInput>;
|
||||||
resources?: InputMaybe<ConfigResourcesUpdateInput>;
|
resources?: InputMaybe<ConfigResourcesUpdateInput>;
|
||||||
version?: InputMaybe<Scalars['String']>;
|
version?: InputMaybe<Scalars['String']>;
|
||||||
};
|
};
|
||||||
@@ -23010,6 +23090,22 @@ export type GetConfigRawJsonQueryVariables = Exact<{
|
|||||||
|
|
||||||
export type GetConfigRawJsonQuery = { __typename?: 'query_root', configRawJSON: string };
|
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<{
|
export type ReplaceConfigRawJsonMutationVariables = Exact<{
|
||||||
appID: Scalars['uuid'];
|
appID: Scalars['uuid'];
|
||||||
rawJSON: Scalars['String'];
|
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 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<{
|
export type GetRunServicesQueryVariables = Exact<{
|
||||||
appID: Scalars['uuid'];
|
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<{
|
export type GetLocalRunServiceConfigsQueryVariables = Exact<{
|
||||||
appID: Scalars['uuid'];
|
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<{
|
export type InsertRunServiceMutationVariables = Exact<{
|
||||||
object: Run_Service_Insert_Input;
|
object: Run_Service_Insert_Input;
|
||||||
@@ -23874,6 +23988,10 @@ export const RunServiceConfigFragmentDoc = gql`
|
|||||||
ingresses {
|
ingresses {
|
||||||
fqdn
|
fqdn
|
||||||
}
|
}
|
||||||
|
rateLimit {
|
||||||
|
limit
|
||||||
|
interval
|
||||||
|
}
|
||||||
}
|
}
|
||||||
healthCheck {
|
healthCheck {
|
||||||
port
|
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`
|
export const GetWorkspaceMembersWorkspaceMemberFragmentDoc = gql`
|
||||||
fragment getWorkspaceMembersWorkspaceMember on workspaceMembers {
|
fragment getWorkspaceMembersWorkspaceMember on workspaceMembers {
|
||||||
id
|
id
|
||||||
@@ -25371,6 +25506,161 @@ export type GetConfigRawJsonQueryResult = Apollo.QueryResult<GetConfigRawJsonQue
|
|||||||
export function refetchGetConfigRawJsonQuery(variables: GetConfigRawJsonQueryVariables) {
|
export function refetchGetConfigRawJsonQuery(variables: GetConfigRawJsonQueryVariables) {
|
||||||
return { query: GetConfigRawJsonDocument, variables: variables }
|
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`
|
export const ReplaceConfigRawJsonDocument = gql`
|
||||||
mutation ReplaceConfigRawJSON($appID: uuid!, $rawJSON: String!) {
|
mutation ReplaceConfigRawJSON($appID: uuid!, $rawJSON: String!) {
|
||||||
replaceConfigRawJSON(appID: $appID, rawJSON: $rawJSON)
|
replaceConfigRawJSON(appID: $appID, rawJSON: $rawJSON)
|
||||||
@@ -27572,6 +27862,95 @@ export type GetLocalRunServiceConfigsQueryResult = Apollo.QueryResult<GetLocalRu
|
|||||||
export function refetchGetLocalRunServiceConfigsQuery(variables: GetLocalRunServiceConfigsQueryVariables) {
|
export function refetchGetLocalRunServiceConfigsQuery(variables: GetLocalRunServiceConfigsQueryVariables) {
|
||||||
return { query: GetLocalRunServiceConfigsDocument, variables: variables }
|
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`
|
export const InsertRunServiceDocument = gql`
|
||||||
mutation insertRunService($object: run_service_insert_input!) {
|
mutation insertRunService($object: run_service_insert_input!) {
|
||||||
insertRunService(object: $object) {
|
insertRunService(object: $object) {
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @nhost/docs
|
# @nhost/docs
|
||||||
|
|
||||||
|
## 2.15.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 40c0d7b: │feat: added subdomain/region information
|
||||||
|
- a18b545: feat: added postgres upgrade docs
|
||||||
|
|
||||||
## 2.14.3
|
## 2.14.3
|
||||||
|
|
||||||
### Patch Changes
|
### 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",
|
"group": "Platform",
|
||||||
"pages": [
|
"pages": [
|
||||||
|
"platform/subdomain",
|
||||||
"platform/compute-resources",
|
"platform/compute-resources",
|
||||||
"platform/service-replicas",
|
"platform/service-replicas",
|
||||||
{
|
{
|
||||||
@@ -117,7 +118,8 @@
|
|||||||
"guides/database/configuring-postgres",
|
"guides/database/configuring-postgres",
|
||||||
"guides/database/access",
|
"guides/database/access",
|
||||||
"guides/database/extensions",
|
"guides/database/extensions",
|
||||||
"guides/database/performance"
|
"guides/database/performance",
|
||||||
|
"guides/database/upgrade-major"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -197,11 +199,11 @@
|
|||||||
"group": "CLI",
|
"group": "CLI",
|
||||||
"pages": [
|
"pages": [
|
||||||
"guides/cli/local-development",
|
"guides/cli/local-development",
|
||||||
|
"guides/cli/subdomain",
|
||||||
"guides/cli/migrate-config",
|
"guides/cli/migrate-config",
|
||||||
"guides/cli/multiple-projects",
|
"guides/cli/multiple-projects",
|
||||||
"guides/cli/configuration-overlays",
|
"guides/cli/configuration-overlays",
|
||||||
"guides/cli/seeds",
|
"guides/cli/seeds"
|
||||||
"guides/cli/connect-devices-to-local-nhost-project"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/docs",
|
"name": "@nhost/docs",
|
||||||
"version": "2.14.3",
|
"version": "2.15.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "mintlify dev"
|
"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
|
```ts
|
||||||
// Create a new Nhost client for local development.
|
// Create a new Nhost client for local development.
|
||||||
const nhost = new NhostClient({ subdomain: 'local' })
|
const nhost = new NhostClient({ region: 'local', subdomain: 'local' })
|
||||||
```
|
```
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @nhost-examples/cli
|
# @nhost-examples/cli
|
||||||
|
|
||||||
|
## 0.3.10
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/nhost-js@3.1.8
|
||||||
|
|
||||||
## 0.3.9
|
## 0.3.9
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/cli",
|
"name": "@nhost-examples/cli",
|
||||||
"version": "0.3.9",
|
"version": "0.3.10",
|
||||||
"main": "src/index.mjs",
|
"main": "src/index.mjs",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @nhost-examples/codegen-react-apollo
|
# @nhost-examples/codegen-react-apollo
|
||||||
|
|
||||||
|
## 0.4.10
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react@3.5.5
|
||||||
|
- @nhost/react-apollo@12.0.5
|
||||||
|
|
||||||
## 0.4.9
|
## 0.4.9
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/codegen-react-apollo",
|
"name": "@nhost-examples/codegen-react-apollo",
|
||||||
"version": "0.4.9",
|
"version": "0.4.10",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"codegen": "graphql-codegen",
|
"codegen": "graphql-codegen",
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @nhost-examples/codegen-react-query
|
# @nhost-examples/codegen-react-query
|
||||||
|
|
||||||
|
## 0.4.10
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react@3.5.5
|
||||||
|
|
||||||
## 0.4.9
|
## 0.4.9
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/codegen-react-query",
|
"name": "@nhost-examples/codegen-react-query",
|
||||||
"version": "0.4.9",
|
"version": "0.4.10",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"codegen": "graphql-codegen",
|
"codegen": "graphql-codegen",
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @nhost-examples/react-urql
|
# @nhost-examples/react-urql
|
||||||
|
|
||||||
|
## 0.3.10
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react@3.5.5
|
||||||
|
- @nhost/react-urql@9.0.5
|
||||||
|
|
||||||
## 0.3.9
|
## 0.3.9
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/codegen-react-urql",
|
"name": "@nhost-examples/codegen-react-urql",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.3.9",
|
"version": "0.3.10",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @nhost-examples/multi-tenant-one-to-many
|
# @nhost-examples/multi-tenant-one-to-many
|
||||||
|
|
||||||
|
## 2.2.10
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/nhost-js@3.1.8
|
||||||
|
|
||||||
## 2.2.9
|
## 2.2.9
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/multi-tenant-one-to-many",
|
"name": "@nhost-examples/multi-tenant-one-to-many",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.2.9",
|
"version": "2.2.10",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# @nhost-examples/nextjs
|
# @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
|
## 0.3.9
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/nextjs",
|
"name": "@nhost-examples/nextjs",
|
||||||
"version": "0.3.9",
|
"version": "0.3.10",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @nhost-examples/node-storage
|
# @nhost-examples/node-storage
|
||||||
|
|
||||||
|
## 0.2.10
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/nhost-js@3.1.8
|
||||||
|
|
||||||
## 0.2.9
|
## 0.2.9
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/node-storage",
|
"name": "@nhost-examples/node-storage",
|
||||||
"version": "0.2.9",
|
"version": "0.2.10",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "This is an example of how to use the Storage with Node.js",
|
"description": "This is an example of how to use the Storage with Node.js",
|
||||||
"main": "src/index.mjs",
|
"main": "src/index.mjs",
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @nhost-examples/nextjs-server-components
|
# @nhost-examples/nextjs-server-components
|
||||||
|
|
||||||
|
## 0.4.11
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/nhost-js@3.1.8
|
||||||
|
|
||||||
## 0.4.10
|
## 0.4.10
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/nextjs-server-components",
|
"name": "@nhost-examples/nextjs-server-components",
|
||||||
"version": "0.4.10",
|
"version": "0.4.11",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @nhost-examples/react-apollo
|
# @nhost-examples/react-apollo
|
||||||
|
|
||||||
|
## 0.8.11
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react@3.5.5
|
||||||
|
- @nhost/react-apollo@12.0.5
|
||||||
|
|
||||||
## 0.8.10
|
## 0.8.10
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/react-apollo",
|
"name": "@nhost-examples/react-apollo",
|
||||||
"version": "0.8.10",
|
"version": "0.8.11",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.9.9",
|
"@apollo/client": "^3.9.9",
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @nhost-examples/react-gqty
|
# @nhost-examples/react-gqty
|
||||||
|
|
||||||
|
## 1.2.10
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react@3.5.5
|
||||||
|
|
||||||
## 1.2.9
|
## 1.2.9
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/react-gqty",
|
"name": "@nhost-examples/react-gqty",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.2.9",
|
"version": "1.2.10",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @nhost-examples/react-native
|
# @nhost-examples/react-native
|
||||||
|
|
||||||
|
## 0.0.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react@3.5.5
|
||||||
|
- @nhost/react-apollo@12.0.5
|
||||||
|
|
||||||
## 0.0.3
|
## 0.0.3
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/react-native",
|
"name": "@nhost-examples/react-native",
|
||||||
"version": "0.0.3",
|
"version": "0.0.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"android": "react-native run-android",
|
"android": "react-native run-android",
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# @nhost-examples/vue-apollo
|
# @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
|
## 0.6.9
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/vue-apollo",
|
"name": "@nhost-examples/vue-apollo",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.6.9",
|
"version": "0.6.10",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @nhost-examples/vue-quickstart
|
# @nhost-examples/vue-quickstart
|
||||||
|
|
||||||
|
## 0.2.10
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/apollo@7.1.5
|
||||||
|
- @nhost/vue@2.6.5
|
||||||
|
|
||||||
## 0.2.9
|
## 0.2.9
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/vue-quickstart",
|
"name": "@nhost-examples/vue-quickstart",
|
||||||
"version": "0.2.9",
|
"version": "0.2.10",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @nhost/apollo
|
# @nhost/apollo
|
||||||
|
|
||||||
|
## 7.1.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/nhost-js@3.1.8
|
||||||
|
|
||||||
## 7.1.4
|
## 7.1.4
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/apollo",
|
"name": "@nhost/apollo",
|
||||||
"version": "7.1.4",
|
"version": "7.1.5",
|
||||||
"description": "Nhost Apollo Client library",
|
"description": "Nhost Apollo Client library",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @nhost/react-apollo
|
# @nhost/react-apollo
|
||||||
|
|
||||||
|
## 12.0.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/apollo@7.1.5
|
||||||
|
- @nhost/react@3.5.5
|
||||||
|
|
||||||
## 12.0.4
|
## 12.0.4
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/react-apollo",
|
"name": "@nhost/react-apollo",
|
||||||
"version": "12.0.4",
|
"version": "12.0.5",
|
||||||
"description": "Nhost React Apollo client",
|
"description": "Nhost React Apollo client",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @nhost/react-urql
|
# @nhost/react-urql
|
||||||
|
|
||||||
|
## 9.0.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react@3.5.5
|
||||||
|
|
||||||
## 9.0.4
|
## 9.0.4
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/react-urql",
|
"name": "@nhost/react-urql",
|
||||||
"version": "9.0.4",
|
"version": "9.0.5",
|
||||||
"description": "Nhost React URQL client",
|
"description": "Nhost React URQL client",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -149,7 +149,8 @@
|
|||||||
"@grpc/grpc-js@>=1.10.0 <1.10.9": ">=1.10.9",
|
"@grpc/grpc-js@>=1.10.0 <1.10.9": ">=1.10.9",
|
||||||
"undici@>=6.0.0 <6.11.1": "6.11.1",
|
"undici@>=6.0.0 <6.11.1": "6.11.1",
|
||||||
"undici@<5.28.4": "5.28.4",
|
"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
|
# @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
|
## 2.5.4
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/hasura-auth-js",
|
"name": "@nhost/hasura-auth-js",
|
||||||
"version": "2.5.4",
|
"version": "2.5.5",
|
||||||
"description": "Hasura-auth client",
|
"description": "Hasura-auth client",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type {
|
|||||||
PublicKeyCredentialRequestOptionsJSON,
|
PublicKeyCredentialRequestOptionsJSON,
|
||||||
RegistrationCredentialJSON
|
RegistrationCredentialJSON
|
||||||
} from '@simplewebauthn/typescript-types'
|
} from '@simplewebauthn/typescript-types'
|
||||||
import { InterpreterFrom, assign, createMachine, send } from 'xstate'
|
import { assign, createMachine, InterpreterFrom, send } from 'xstate'
|
||||||
import {
|
import {
|
||||||
NHOST_JWT_EXPIRES_AT_KEY,
|
NHOST_JWT_EXPIRES_AT_KEY,
|
||||||
NHOST_REFRESH_TOKEN_ID_KEY,
|
NHOST_REFRESH_TOKEN_ID_KEY,
|
||||||
@@ -341,7 +341,13 @@ export const createAuthMachine = ({
|
|||||||
actions: ['saveSession', 'resetTimer', 'reportTokenChanged'],
|
actions: ['saveSession', 'resetTimer', 'reportTokenChanged'],
|
||||||
target: 'pending'
|
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
|
// * Event guards
|
||||||
hasSession: (_, e) => !!e.data?.session,
|
hasSession: (_, e) => !!e.data?.session,
|
||||||
hasMfaTicket: (_, e) => !!e.data?.mfa
|
hasMfaTicket: (_, e) => !!e.data?.mfa,
|
||||||
|
isUnauthorizedError: (_, { data: { error } }: any) => error.status === 401
|
||||||
},
|
},
|
||||||
|
|
||||||
services: {
|
services: {
|
||||||
|
|||||||
@@ -213,6 +213,7 @@ export interface Typegen0 {
|
|||||||
| 'error.platform.authenticateWithPAT'
|
| 'error.platform.authenticateWithPAT'
|
||||||
| 'error.platform.authenticateWithToken'
|
| 'error.platform.authenticateWithToken'
|
||||||
| 'error.platform.importRefreshToken'
|
| 'error.platform.importRefreshToken'
|
||||||
|
| 'error.platform.refreshToken'
|
||||||
| 'error.platform.signInMfaTotp'
|
| 'error.platform.signInMfaTotp'
|
||||||
reportTokenChanged:
|
reportTokenChanged:
|
||||||
| 'SESSION_UPDATE'
|
| 'SESSION_UPDATE'
|
||||||
@@ -305,6 +306,7 @@ export interface Typegen0 {
|
|||||||
isAutoRefreshDisabled: ''
|
isAutoRefreshDisabled: ''
|
||||||
isRefreshTokenPAT: ''
|
isRefreshTokenPAT: ''
|
||||||
isSignedIn: '' | 'error.platform.authenticateWithToken'
|
isSignedIn: '' | 'error.platform.authenticateWithToken'
|
||||||
|
isUnauthorizedError: 'error.platform.refreshToken'
|
||||||
noToken: ''
|
noToken: ''
|
||||||
refreshTimerShouldRefresh: ''
|
refreshTimerShouldRefresh: ''
|
||||||
shouldRetryImportToken: 'error.platform.importRefreshToken'
|
shouldRetryImportToken: 'error.platform.importRefreshToken'
|
||||||
|
|||||||
@@ -87,21 +87,17 @@ describe(`Time based token refresh`, () => {
|
|||||||
server.resetHandlers()
|
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)
|
server.use(authTokenUnauthorizedHandler)
|
||||||
|
|
||||||
// Fast forwarding to initial expiration date
|
// Fast forwarding to initial expiration date
|
||||||
vi.setSystemTime(initialExpiration)
|
vi.setSystemTime(initialExpiration)
|
||||||
|
|
||||||
await waitFor(authServiceWithInitialSession, (state) =>
|
const state = await waitFor(authServiceWithInitialSession, (state) =>
|
||||||
state.matches({ authentication: { signedIn: { refreshTimer: { running: 'refreshing' } } } })
|
state.matches({ authentication: { signedIn: { refreshTimer: { running: 'refreshing' } } } })
|
||||||
)
|
)
|
||||||
|
|
||||||
const state = await waitFor(authServiceWithInitialSession, (state) =>
|
expect(state.matches({ authentication: 'signedOut' }))
|
||||||
state.matches({ authentication: { signedIn: { refreshTimer: { running: 'pending' } } } })
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(state.context.refreshTimer.attempts).toBeGreaterThan(0)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test(`access token should always be refreshed when reaching the expiration margin`, async () => {
|
test(`access token should always be refreshed when reaching the expiration margin`, async () => {
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @nhost/nextjs
|
# @nhost/nextjs
|
||||||
|
|
||||||
|
## 2.1.19
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react@3.5.5
|
||||||
|
|
||||||
## 2.1.18
|
## 2.1.18
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/nextjs",
|
"name": "@nhost/nextjs",
|
||||||
"version": "2.1.18",
|
"version": "2.1.19",
|
||||||
"description": "Nhost NextJS library",
|
"description": "Nhost NextJS library",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @nhost/nhost-js
|
# @nhost/nhost-js
|
||||||
|
|
||||||
|
## 3.1.8
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [caa8bd7]
|
||||||
|
- @nhost/hasura-auth-js@2.5.5
|
||||||
|
|
||||||
## 3.1.7
|
## 3.1.7
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/nhost-js",
|
"name": "@nhost/nhost-js",
|
||||||
"version": "3.1.7",
|
"version": "3.1.8",
|
||||||
"description": "Nhost JavaScript SDK",
|
"description": "Nhost JavaScript SDK",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @nhost/react
|
# @nhost/react
|
||||||
|
|
||||||
|
## 3.5.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/nhost-js@3.1.8
|
||||||
|
|
||||||
## 3.5.4
|
## 3.5.4
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/react",
|
"name": "@nhost/react",
|
||||||
"version": "3.5.4",
|
"version": "3.5.5",
|
||||||
"description": "Nhost React library",
|
"description": "Nhost React library",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @nhost/vue
|
# @nhost/vue
|
||||||
|
|
||||||
|
## 2.6.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/nhost-js@3.1.8
|
||||||
|
|
||||||
## 2.6.4
|
## 2.6.4
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/vue",
|
"name": "@nhost/vue",
|
||||||
"version": "2.6.4",
|
"version": "2.6.5",
|
||||||
"description": "Nhost Vue library",
|
"description": "Nhost Vue library",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"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@>=6.0.0 <6.11.1: 6.11.1
|
||||||
undici@<5.28.4: 5.28.4
|
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
|
||||||
|
|
||||||
importers:
|
importers:
|
||||||
|
|
||||||
@@ -1035,7 +1036,7 @@ importers:
|
|||||||
devDependencies:
|
devDependencies:
|
||||||
'@nhost/nhost-js':
|
'@nhost/nhost-js':
|
||||||
specifier: ^3.1.5
|
specifier: ^3.1.5
|
||||||
version: 3.1.6(graphql@16.8.1)
|
version: 3.1.7(graphql@16.8.1)
|
||||||
'@playwright/test':
|
'@playwright/test':
|
||||||
specifier: ^1.42.1
|
specifier: ^1.42.1
|
||||||
version: 1.42.1
|
version: 1.42.1
|
||||||
@@ -9783,7 +9784,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-MMAdhT6DrylDg1doi2oK2Zw0b7gMspr3Cq8stFGTLl8qOGJo9RT8KCNEWUDzR5eurSGcwE1660rq3Qibe4bOag==}
|
resolution: {integrity: sha512-MMAdhT6DrylDg1doi2oK2Zw0b7gMspr3Cq8stFGTLl8qOGJo9RT8KCNEWUDzR5eurSGcwE1660rq3Qibe4bOag==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
axios: 1.7.2
|
axios: 1.7.4
|
||||||
openapi-types: 12.1.3
|
openapi-types: 12.1.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- debug
|
- debug
|
||||||
@@ -10299,8 +10300,8 @@ packages:
|
|||||||
- encoding
|
- encoding
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@nhost/hasura-auth-js@2.5.3:
|
/@nhost/hasura-auth-js@2.5.4:
|
||||||
resolution: {integrity: sha512-WPDmF7vMU32I/G4Ytlo+ZUE+INzcjp1Av5KNLH/iBWDw/0HB6Vj5/+AWq+IjxQm0+HmKE+hvnc5Hc5rn0oPreg==}
|
resolution: {integrity: sha512-w9DVBDWamV6KSgO2q6mhA8MEhhFnne4c7fzj2JNEIDUfvc/QMaLpL93ZJII2s4Dc8QeeJM4Vcsjx7zDXI+RnsQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@simplewebauthn/browser': 9.0.1
|
'@simplewebauthn/browser': 9.0.1
|
||||||
fetch-ponyfill: 7.1.0
|
fetch-ponyfill: 7.1.0
|
||||||
@@ -10322,13 +10323,13 @@ packages:
|
|||||||
- encoding
|
- encoding
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@nhost/nhost-js@3.1.6(graphql@16.8.1):
|
/@nhost/nhost-js@3.1.7(graphql@16.8.1):
|
||||||
resolution: {integrity: sha512-5DPm3vvsiMzJxYzMSxHwqIsn1GfDsZ1LHq5vndHILL8OH5LHPh3tVlUc4EcElbkqN81yzZ++umcDxxH+w8pkAg==}
|
resolution: {integrity: sha512-9ifZ2qvlJFp+Xk/Frm8do3nkGg5L7s2K11Rr9iZG92LnJP4InqtELwhSxxfN80Gt/aQkgEOxyQ21bJXW3XCSxg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
graphql: '>=16.8.1'
|
graphql: '>=16.8.1'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nhost/graphql-js': 0.3.0(graphql@16.8.1)
|
'@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
|
'@nhost/hasura-storage-js': 2.5.1
|
||||||
graphql: 16.8.1
|
graphql: 16.8.1
|
||||||
isomorphic-unfetch: 3.1.0
|
isomorphic-unfetch: 3.1.0
|
||||||
@@ -15953,7 +15954,7 @@ packages:
|
|||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@segment/loosely-validate-event': 2.0.0
|
'@segment/loosely-validate-event': 2.0.0
|
||||||
axios: 1.7.2
|
axios: 1.7.4
|
||||||
axios-retry: 3.2.0
|
axios-retry: 3.2.0
|
||||||
lodash.isstring: 4.0.1
|
lodash.isstring: 4.0.1
|
||||||
md5: 2.3.0
|
md5: 2.3.0
|
||||||
@@ -16433,8 +16434,8 @@ packages:
|
|||||||
is-retry-allowed: 1.2.0
|
is-retry-allowed: 1.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/axios@1.7.2:
|
/axios@1.7.4:
|
||||||
resolution: {integrity: sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==}
|
resolution: {integrity: sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects: 1.15.6
|
follow-redirects: 1.15.6
|
||||||
form-data: 4.0.0
|
form-data: 4.0.0
|
||||||
|
|||||||
Reference in New Issue
Block a user