Compare commits

..

15 Commits

Author SHA1 Message Date
github-actions[bot]
8a2bc98214 chore: update versions (#2648)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@1.13.0

### Minor Changes

-   dd5d262: feat: add model field to the auto-embeddings form
- 09962be: feat: enable settings and run services when running the
dashboard locally
- 9cdecb6: feat: enable users to update their email address from the
account settings page

## @nhost/docs@2.10.0

### Minor Changes

-   87ae23b: feat: added "advanced graphql" documentation

### Patch Changes

-   b2be364: feat: added postmark native integration

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-15 13:12:24 +01:00
Hassan Ben Jobrane
dd5d262062 feat: dashboard: add model field to auto-embeddings form (#2661)
fixes https://github.com/nhost/nhost/issues/2660
2024-04-15 12:51:57 +01:00
Hassan Ben Jobrane
09962bef37 feat: dashboard: enable local settings (#2647)
fixes https://github.com/nhost/projects/issues/66
2024-04-15 12:49:20 +01:00
Hassan Ben Jobrane
9cdecb6b23 feat: dashboard: add email field to account settings (#2612)
fixes https://github.com/nhost/nhost/issues/2561
2024-04-11 10:47:03 +01:00
David Barroso
e7eb90318e fix: observability: amend export of graphql dashboard (#2655) 2024-04-11 11:44:56 +02:00
Hassan Ben Jobrane
f67f22d321 chore: update @google-cloud/translate to 8.2.0 (#2654) 2024-04-11 10:00:13 +01:00
David Barroso
87ae23ba05 feat (docs/observability): added docs and observability dashboard for "advanced graphql" (#2653)
Co-authored-by: Nuno Pato <nunopato@gmail.com>
2024-04-11 10:22:54 +02:00
David Barroso
b2be3642aa feat (docs): added postmark native integration (#2636) 2024-04-09 09:36:48 +02:00
github-actions[bot]
1230081ce6 chore: update versions (#2640)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/dashboard@1.12.2

### Patch Changes

-   c195c51: fix: send email upon signin for unverified users

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-05 12:48:55 +01:00
Hassan Ben Jobrane
c195c517de fix: send email upon signin for unverified users (#2639) 2024-04-05 11:48:14 +01:00
github-actions[bot]
6f419be2c1 chore: update versions (#2634)
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @nhost/apollo@6.2.1

### Patch Changes

-   @nhost/nhost-js@3.0.11

## @nhost/react-apollo@11.0.1

### Patch Changes

-   @nhost/apollo@6.2.1
-   @nhost/react@3.4.1

## @nhost/react-urql@8.0.1

### Patch Changes

-   @nhost/react@3.4.1

## @nhost/hasura-auth-js@2.4.1

### Patch Changes

-   bcd889b: fix: change expiresAt format to RFC3339 in createPATPromise

## @nhost/nextjs@2.1.10

### Patch Changes

-   @nhost/react@3.4.1

## @nhost/nhost-js@3.0.11

### Patch Changes

-   Updated dependencies [bcd889b]
    -   @nhost/hasura-auth-js@2.4.1

## @nhost/react@3.4.1

### Patch Changes

-   @nhost/nhost-js@3.0.11

## @nhost/vue@2.5.1

### Patch Changes

-   @nhost/nhost-js@3.0.11

## @nhost/docs@2.9.0

### Minor Changes

-   3c31657: chore: update docs with provider connect

### Patch Changes

-   992939c: feat: added social connect docs

## @nhost/dashboard@1.12.1

### Patch Changes

- 93ebdf8: fix: use service urls when initilizaing NhostClient running
local dashboard
    -   @nhost/react-apollo@11.0.1
    -   @nhost/nextjs@2.1.10

## @nhost-examples/cli@0.3.1

### Patch Changes

-   @nhost/nhost-js@3.0.11

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

### Patch Changes

-   @nhost/react@3.4.1
-   @nhost/react-apollo@11.0.1

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

### Patch Changes

-   @nhost/react@3.4.1

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

### Patch Changes

-   @nhost/react@3.4.1
-   @nhost/react-urql@8.0.1

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

### Patch Changes

-   @nhost/nhost-js@3.0.11

## @nhost-examples/nextjs@0.3.1

### Patch Changes

-   @nhost/react@3.4.1
-   @nhost/react-apollo@11.0.1
-   @nhost/nextjs@2.1.10

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

### Patch Changes

-   @nhost/nhost-js@3.0.11

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

### Patch Changes

-   @nhost/nhost-js@3.0.11

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

### Patch Changes

-   @nhost/react@3.4.1
-   @nhost/react-apollo@11.0.1

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

### Patch Changes

-   @nhost/react@3.4.1

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

### Patch Changes

-   @nhost/nhost-js@3.0.11
-   @nhost/apollo@6.2.1
-   @nhost/vue@2.5.1

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

### Patch Changes

-   @nhost/apollo@6.2.1
-   @nhost/vue@2.5.1

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-04 10:49:02 +01:00
Hassan Ben Jobrane
93ebdf844f fix: dashboard: use service urls when running locally with the cli (#2622) 2024-04-04 10:28:38 +01:00
Hassan Ben Jobrane
bcd889b53a fix: hasura-auth-js: use RFC3339 format for expiresAt when creating a PAT (#2637) 2024-04-03 21:40:02 +01:00
David Barroso
992939cdcd feat (docs): added social connect docs (#2633)
Co-authored-by: Nuno Pato <nunopato@gmail.com>
2024-04-03 16:37:07 +02:00
Nuno Pato
3c31657c50 chore: update docs with provider connect (#2632)
Co-authored-by: David Barroso <dbarrosop@dravetech.com>
2024-04-03 14:27:49 +00:00
214 changed files with 4557 additions and 2244 deletions

View File

@@ -1,5 +1,27 @@
# @nhost/dashboard # @nhost/dashboard
## 1.13.0
### Minor Changes
- dd5d262: feat: add model field to the auto-embeddings form
- 09962be: feat: enable settings and run services when running the dashboard locally
- 9cdecb6: feat: enable users to update their email address from the account settings page
## 1.12.2
### Patch Changes
- c195c51: fix: send email upon signin for unverified users
## 1.12.1
### Patch Changes
- 93ebdf8: fix: use service urls when initilizaing NhostClient running local dashboard
- @nhost/react-apollo@11.0.1
- @nhost/nextjs@2.1.10
## 1.12.0 ## 1.12.0
### Minor Changes ### Minor Changes

View File

@@ -1,6 +1,6 @@
{ {
"name": "@nhost/dashboard", "name": "@nhost/dashboard",
"version": "1.12.0", "version": "1.13.0",
"private": true, "private": true,
"scripts": { "scripts": {
"preinstall": "npx only-allow pnpm", "preinstall": "npx only-allow pnpm",

View File

@@ -0,0 +1,30 @@
import { useDialog } from '@/components/common/DialogProvider';
import { Button } from '@/components/ui/v2/Button';
import { Text } from '@/components/ui/v2/Text';
export default function ApplyLocalSettingsDialog() {
const { closeDialog } = useDialog();
return (
<div className="flex flex-col gap-4 px-6 pb-6">
<div className="flex flex-col gap-2">
<Text color="secondary">
Run{' '}
<code className="px-1 py-px mx-1 rounded-md bg-slate-500 text-slate-100">
$ nhost up
</code>{' '}
using the cli to apply your changes
</Text>
</div>
<Button
className="w-full"
color="primary"
onClick={() => closeDialog()}
autoFocus
>
OK
</Button>
</div>
);
}

View File

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

View File

@@ -11,7 +11,6 @@ import { useNotFoundRedirect } from '@/features/projects/common/hooks/useNotFoun
import { useProjectRoutes } from '@/features/projects/common/hooks/useProjectRoutes'; import { useProjectRoutes } from '@/features/projects/common/hooks/useProjectRoutes';
import { NextSeo } from 'next-seo'; import { NextSeo } from 'next-seo';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useEffect } from 'react';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
export interface ProjectLayoutProps extends AuthenticatedLayoutProps { export interface ProjectLayoutProps extends AuthenticatedLayoutProps {
@@ -48,15 +47,16 @@ function ProjectLayoutContent({
useNotFoundRedirect(); useNotFoundRedirect();
useEffect(() => { // useEffect(() => {
if (isPlatform || !router.isReady) { // if (isPlatform || !router.isReady) {
return; // return;
} // }
if (isRestrictedPath) { // TODO // Double check what restricted path means here
router.push('/local/local'); // if (isRestrictedPath) {
} // router.push('/local/local');
}, [isPlatform, isRestrictedPath, router]); // }
// }, [isPlatform, isRestrictedPath, router]);
if (isRestrictedPath || loading) { if (isRestrictedPath || loading) {
return <LoadingScreen />; return <LoadingScreen />;

View File

@@ -7,6 +7,7 @@ import { List } from '@/components/ui/v2/List';
import type { ListItemButtonProps } from '@/components/ui/v2/ListItem'; import type { ListItemButtonProps } from '@/components/ui/v2/ListItem';
import { ListItem } from '@/components/ui/v2/ListItem'; import { ListItem } from '@/components/ui/v2/ListItem';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import Image from 'next/image'; import Image from 'next/image';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -61,6 +62,7 @@ export default function SettingsSidebar({
className, className,
...props ...props
}: SettingsSidebarProps) { }: SettingsSidebarProps) {
const isPlatform = useIsPlatform();
const [expanded, setExpanded] = useState(false); const [expanded, setExpanded] = useState(false);
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
@@ -95,7 +97,7 @@ export default function SettingsSidebar({
<> <>
<Backdrop <Backdrop
open={expanded} open={expanded}
className="absolute top-0 left-0 bottom-0 right-0 z-[34] md:hidden" className="absolute bottom-0 left-0 right-0 top-0 z-[34] md:hidden"
role="button" role="button"
tabIndex={-1} tabIndex={-1}
onClick={() => setExpanded(false)} onClick={() => setExpanded(false)}
@@ -112,7 +114,7 @@ export default function SettingsSidebar({
<Box <Box
component="aside" component="aside"
className={twMerge( className={twMerge(
'absolute top-0 z-[35] h-full w-full overflow-auto border-r-1 px-2 pt-2 pb-17 motion-safe:transition-transform md:relative md:z-0 md:h-full md:py-2.5 md:transition-none', 'absolute top-0 z-[35] h-full w-full overflow-auto border-r-1 px-2 pb-17 pt-2 motion-safe:transition-transform md:relative md:z-0 md:h-full md:py-2.5 md:transition-none',
expanded ? 'translate-x-0' : '-translate-x-full md:translate-x-0', expanded ? 'translate-x-0' : '-translate-x-full md:translate-x-0',
className, className,
)} )}
@@ -181,7 +183,12 @@ export default function SettingsSidebar({
SMTP SMTP
</SettingsNavLink> </SettingsNavLink>
<SettingsNavLink href="/git" exact={false} onClick={handleSelect}> <SettingsNavLink
href="/git"
exact={false}
onClick={handleSelect}
disabled={!isPlatform}
>
Git Git
</SettingsNavLink> </SettingsNavLink>

View File

@@ -0,0 +1,80 @@
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { Input } from '@/components/ui/v2/Input';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup';
import { useNhostClient, useUserData } from '@nhost/nextjs';
import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup';
const validationSchema = Yup.object({
email: Yup.string().label('Email').email().required(),
});
export type EmailSettingFormValues = Yup.InferType<typeof validationSchema>;
export default function EmailSetting() {
const nhost = useNhostClient();
const { email } = useUserData();
const form = useForm<EmailSettingFormValues>({
reValidateMode: 'onSubmit',
defaultValues: { email },
resolver: yupResolver(validationSchema),
});
const { register, formState } = form;
const isDirty = Object.keys(formState.dirtyFields).length > 0;
async function handleSubmit(formValues: EmailSettingFormValues) {
await execPromiseWithErrorToast(
async () => {
await nhost.auth.changeEmail({
newEmail: formValues.email,
options: {
redirectTo: `${window.location.origin}/account`,
},
});
form.reset({ email: formValues.email });
},
{
loadingMessage: 'Updating your email...',
successMessage:
'Please check your inbox. Follow the link to finalize changing your email.',
errorMessage:
'An error occurred while trying to update your email. Please try again.',
},
);
}
return (
<FormProvider {...form}>
<Form onSubmit={handleSubmit}>
<SettingsContainer
title="Update your email"
slotProps={{
submitButton: {
disabled: !isDirty,
loading: formState.isSubmitting,
},
}}
className="grid grid-flow-row lg:grid-cols-5"
>
<Input
{...register('email')}
className="col-span-2"
id="email"
spellCheck="false"
autoCapitalize="none"
type="email"
label="Email"
hideEmptyHelperText
fullWidth
helperText={formState.errors.email?.message}
error={Boolean(formState.errors.email)}
/>
</SettingsContainer>
</Form>
</FormProvider>
);
}

View File

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

View File

@@ -1,4 +1,5 @@
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
import { ControlledSelect } from '@/components/form/ControlledSelect';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
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';
@@ -6,6 +7,7 @@ import { ArrowsClockwise } from '@/components/ui/v2/icons/ArrowsClockwise';
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon'; import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon'; import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { Option } from '@/components/ui/v2/Option';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip'; import { Tooltip } from '@/components/ui/v2/Tooltip';
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient'; import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
@@ -20,11 +22,18 @@ import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
const AUTO_EMBEDDINGS_MODELS = [
'text-embedding-ada-002',
'text-embedding-3-small',
'text-embedding-3-large',
];
export const validationSchema = Yup.object({ export const validationSchema = Yup.object({
name: Yup.string().required('The name is required.'), name: Yup.string().required('The name field is required.'),
schemaName: Yup.string().required('The schema is required'), model: Yup.string().oneOf(AUTO_EMBEDDINGS_MODELS),
tableName: Yup.string().required('The table is required'), schemaName: Yup.string().required('The schema field is required'),
columnName: Yup.string().required('The column is required'), tableName: Yup.string().required('The table field is required'),
columnName: Yup.string().required('The column field is required'),
query: Yup.string(), query: Yup.string(),
mutation: Yup.string(), mutation: Yup.string(),
}); });
@@ -40,7 +49,7 @@ export interface AutoEmbeddingsFormProps extends DialogFormProps {
/** /**
* if there is initialData then it's an update operation * if there is initialData then it's an update operation
*/ */
initialData?: AutoEmbeddingsFormValues; initialData?: AutoEmbeddingsFormValues & { model: string };
/** /**
* Function to be called when the operation is cancelled. * Function to be called when the operation is cancelled.
@@ -74,7 +83,10 @@ export default function AutoEmbeddingsForm({
}); });
const form = useForm<AutoEmbeddingsFormValues>({ const form = useForm<AutoEmbeddingsFormValues>({
defaultValues: initialData, defaultValues: {
...initialData,
model: initialData?.model ?? 'text-embedding-ada-002',
},
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
@@ -106,7 +118,9 @@ export default function AutoEmbeddingsForm({
} }
await insertGraphiteAutoEmbeddingsConfiguration({ await insertGraphiteAutoEmbeddingsConfiguration({
variables: values, variables: {
...values,
},
}); });
}; };
@@ -129,9 +143,9 @@ export default function AutoEmbeddingsForm({
<FormProvider {...form}> <FormProvider {...form}>
<Form <Form
onSubmit={handleSubmit} onSubmit={handleSubmit}
className="flex h-full flex-col gap-4 overflow-hidden" className="flex flex-col h-full gap-4 overflow-hidden"
> >
<div className="flex flex-1 flex-col space-y-6 overflow-auto px-6"> <div className="flex flex-col flex-1 px-6 space-y-6 overflow-auto">
<Input <Input
{...register('name')} {...register('name')}
id="name" id="name"
@@ -141,7 +155,7 @@ export default function AutoEmbeddingsForm({
<Tooltip title="Name of the Auto-Embeddings"> <Tooltip title="Name of the Auto-Embeddings">
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
@@ -155,6 +169,36 @@ export default function AutoEmbeddingsForm({
autoComplete="off" autoComplete="off"
autoFocus autoFocus
/> />
<ControlledSelect
slotProps={{
popper: { disablePortal: false, className: 'z-[10000]' },
}}
id="model"
name="model"
label={
<Box className="flex flex-row items-center space-x-2">
<Text>Model</Text>
<Tooltip title="Auto-Embeddings Model">
<InfoIcon
aria-label="Info"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
</Box>
}
fullWidth
error={!!errors?.model?.message}
helperText={errors?.model?.message}
>
{AUTO_EMBEDDINGS_MODELS.map((model) => (
<Option key={model} value={model}>
{model}
</Option>
))}
</ControlledSelect>
<Input <Input
{...register('schemaName')} {...register('schemaName')}
id="schemaName" id="schemaName"
@@ -164,7 +208,7 @@ export default function AutoEmbeddingsForm({
<Tooltip title={<span>Schema where the table belongs to</span>}> <Tooltip title={<span>Schema where the table belongs to</span>}>
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
@@ -186,7 +230,7 @@ export default function AutoEmbeddingsForm({
<Tooltip title="Table Name"> <Tooltip title="Table Name">
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
@@ -208,7 +252,7 @@ export default function AutoEmbeddingsForm({
<Tooltip title="Column name"> <Tooltip title="Column name">
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
@@ -230,7 +274,7 @@ export default function AutoEmbeddingsForm({
<Tooltip title="Query"> <Tooltip title="Query">
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
@@ -254,7 +298,7 @@ export default function AutoEmbeddingsForm({
<Tooltip title="Mutation"> <Tooltip title="Mutation">
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
@@ -271,7 +315,7 @@ export default function AutoEmbeddingsForm({
/> />
</div> </div>
<Box className="flex w-full flex-row justify-between rounded border-t px-6 py-4"> <Box className="flex flex-row justify-between w-full px-6 py-4 border-t rounded">
<Button variant="outlined" color="secondary" onClick={onCancel}> <Button variant="outlined" color="secondary" onClick={onCancel}>
Cancel Cancel
</Button> </Button>

View File

@@ -1,3 +1,4 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete'; import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
@@ -12,6 +13,7 @@ import { Switch } from '@/components/ui/v2/Switch';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip'; import { Tooltip } from '@/components/ui/v2/Tooltip';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema'; import { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import { ComputeFormSection } from '@/features/services/components/ServiceForm/components/ComputeFormSection'; import { ComputeFormSection } from '@/features/services/components/ServiceForm/components/ComputeFormSection';
import { import {
@@ -20,6 +22,7 @@ import {
useGetSoftwareVersionsQuery, useGetSoftwareVersionsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common'; import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
import { getToastStyleProps } from '@/utils/constants/settings'; import { getToastStyleProps } from '@/utils/constants/settings';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
@@ -50,9 +53,13 @@ const validationSchema = Yup.object({
export type AISettingsFormValues = Yup.InferType<typeof validationSchema>; export type AISettingsFormValues = Yup.InferType<typeof validationSchema>;
export default function AISettings() { export default function AISettings() {
const { maintenanceActive } = useUI(); const isPlatform = useIsPlatform();
const { openDialog } = useDialog(); const { openDialog } = useDialog();
const [updateConfig] = useUpdateConfigMutation(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [aiServiceEnabled, setAIServiceEnabled] = useState(true); const [aiServiceEnabled, setAIServiceEnabled] = useState(true);
@@ -67,6 +74,7 @@ export default function AISettings() {
variables: { variables: {
appId: currentProject.id, appId: currentProject.id,
}, },
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data: graphiteVersionsData, loading: loadingGraphiteVersionsData } = const { data: graphiteVersionsData, loading: loadingGraphiteVersionsData } =
@@ -74,6 +82,7 @@ export default function AISettings() {
variables: { variables: {
software: Software_Type_Enum.Graphite, software: Software_Type_Enum.Graphite,
}, },
skip: !isPlatform,
}); });
const graphiteVersions = graphiteVersionsData?.softwareVersions || []; const graphiteVersions = graphiteVersionsData?.softwareVersions || [];
@@ -98,8 +107,8 @@ export default function AISettings() {
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
defaultValues: { defaultValues: {
version: { version: {
label: ai?.version ?? availableVersions?.at(0)?.label, label: ai?.version || availableVersions?.at(0)?.label || '',
value: ai?.version ?? availableVersions?.at(0)?.value, value: ai?.version || availableVersions?.at(0)?.value || '',
}, },
webhookSecret: '', webhookSecret: '',
organization: '', organization: '',
@@ -225,6 +234,18 @@ export default function AISettings() {
}); });
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'AI settings are being updated...', loadingMessage: 'AI settings are being updated...',
@@ -247,7 +268,7 @@ export default function AISettings() {
return ( return (
<Box className="space-y-4" sx={{ backgroundColor: 'background.default' }}> <Box className="space-y-4" sx={{ backgroundColor: 'background.default' }}>
<Box className="flex flex-row items-center justify-between rounded-lg border-1 p-4"> <Box className="flex flex-row items-center justify-between p-4 rounded-lg border-1">
<Text className="text-lg font-semibold">Enable AI service</Text> <Text className="text-lg font-semibold">Enable AI service</Text>
<Switch <Switch
checked={aiServiceEnabled} checked={aiServiceEnabled}
@@ -270,54 +291,58 @@ export default function AISettings() {
className="flex flex-col" className="flex flex-col"
> >
<Box className="space-y-4"> <Box className="space-y-4">
{availableVersions.length > 0 && ( <Box className="space-y-2">
<Box className="space-y-2"> <Box className="flex flex-row items-center space-x-2">
<Box className="flex flex-row items-center space-x-2"> <Text className="text-lg font-semibold">Version</Text>
<Text className="text-lg font-semibold">Version</Text> <Tooltip title="Version of the service to use.">
<Tooltip title="Version of the service to use."> <InfoIcon
<InfoIcon aria-label="Info"
aria-label="Info" className="w-4 h-4"
className="h-4 w-4" color="primary"
color="primary" />
/> </Tooltip>
</Tooltip>
</Box>
<ControlledAutocomplete
id="version"
name="version"
autoHighlight
isOptionEqualToValue={() => false}
filterOptions={(options, { inputValue }) => {
const inputValueLower = inputValue.toLowerCase();
const matched = [];
const otherOptions = [];
options.forEach((option) => {
const optionLabelLower = option.label.toLowerCase();
if (optionLabelLower.startsWith(inputValueLower)) {
matched.push(option);
} else {
otherOptions.push(option);
}
});
const result = [...matched, ...otherOptions];
return result;
}}
fullWidth
className="col-span-4"
options={availableVersions}
error={!!formState.errors?.version?.message}
helperText={formState.errors?.version?.message}
showCustomOption="auto"
customOptionLabel={(value) =>
`Use custom value: "${value}"`
}
/>
</Box> </Box>
)} <ControlledAutocomplete
id="version"
name="version"
autoHighlight
isOptionEqualToValue={() => false}
filterOptions={(options, { inputValue }) => {
const inputValueLower = inputValue.toLowerCase();
const matched = [];
const otherOptions = [];
options.forEach((option) => {
const optionLabelLower = option.label.toLowerCase();
if (optionLabelLower.startsWith(inputValueLower)) {
matched.push(option);
} else {
otherOptions.push(option);
}
});
const result = [...matched, ...otherOptions];
return result;
}}
fullWidth
className="col-span-4"
options={availableVersions}
error={
!!formState.errors?.version?.message ||
!!formState.errors?.version?.value?.message
}
helperText={
formState.errors?.version?.message ||
formState.errors?.version?.value?.message
}
showCustomOption="auto"
customOptionLabel={(value) =>
`Use custom value: "${value}"`
}
/>
</Box>
<Box className="space-y-2"> <Box className="space-y-2">
<Box className="flex flex-row items-center space-x-2"> <Box className="flex flex-row items-center space-x-2">
@@ -327,7 +352,7 @@ export default function AISettings() {
<Tooltip title="Used to validate requests between postgres and the AI service. The AI service will also include the header X-Graphite-Webhook-Secret with this value set when calling external webhooks so the source of the request can be validated."> <Tooltip title="Used to validate requests between postgres and the AI service. The AI service will also include the header X-Graphite-Webhook-Secret with this value set when calling external webhooks so the source of the request can be validated.">
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
@@ -351,26 +376,28 @@ export default function AISettings() {
<Tooltip title="Dedicated resources allocated for the service."> <Tooltip title="Dedicated resources allocated for the service.">
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
</Box> </Box>
<Alert {isPlatform ? (
severity="info" <Alert
className="flex items-center justify-between space-x-2" severity="info"
> className="flex items-center justify-between space-x-2"
<span>{getAIResourcesCost()}</span> >
<b> <span>{getAIResourcesCost()}</span>
$ <b>
{parseFloat( $
( {parseFloat(
aiSettingsFormValues.compute.cpu * COST_PER_VCPU (
).toFixed(2), aiSettingsFormValues.compute.cpu * COST_PER_VCPU
)} ).toFixed(2),
</b> )}
</Alert> </b>
</Alert>
) : null}
<ComputeFormSection /> <ComputeFormSection />
</Box> </Box>
@@ -389,7 +416,7 @@ export default function AISettings() {
<Tooltip title="Key to use for authenticating API requests to OpenAI"> <Tooltip title="Key to use for authenticating API requests to OpenAI">
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
@@ -412,7 +439,7 @@ export default function AISettings() {
<Tooltip title="Optional. OpenAI organization to use."> <Tooltip title="Optional. OpenAI organization to use.">
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
@@ -440,7 +467,7 @@ export default function AISettings() {
<Tooltip title="How often to run the job that keeps embeddings up to date."> <Tooltip title="How often to run the job that keeps embeddings up to date.">
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>

View File

@@ -1,8 +1,11 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
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 { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { useUpdateConfigMutation } from '@/utils/__generated__/graphql'; import { useUpdateConfigMutation } from '@/utils/__generated__/graphql';
import { useState } from 'react'; import { useState } from 'react';
@@ -23,11 +26,14 @@ export default function DisableAIServiceConfirmationDialog({
onCancel, onCancel,
onServiceDisabled, onServiceDisabled,
}: DisableAIServiceConfirmationDialogProps) { }: DisableAIServiceConfirmationDialogProps) {
const { closeDialog } = useDialog(); const isPlatform = useIsPlatform();
const { openDialog, closeDialog } = useDialog();
const localMimirClient = useLocalMimirClient();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({
const [updateConfig] = useUpdateConfigMutation(); ...(!isPlatform ? { client: localMimirClient } : {}),
});
async function handleClick() { async function handleClick() {
setLoading(true); setLoading(true);
@@ -45,6 +51,18 @@ export default function DisableAIServiceConfirmationDialog({
onServiceDisabled(); onServiceDisabled();
closeDialog(); closeDialog();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Disabling the AI service...', loadingMessage: 'Disabling the AI service...',

View File

@@ -1,16 +1,21 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetAuthenticationSettingsDocument, GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery, useGetAuthenticationSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -26,15 +31,19 @@ export type AllowedEmailSettingsFormValues = Yup.InferType<
>; >;
export default function AllowedEmailDomainsSettings() { export default function AllowedEmailDomainsSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument], refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetAuthenticationSettingsQuery({ const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { email, emailDomains } = data?.config?.auth?.user || {}; const { email, emailDomains } = data?.config?.auth?.user || {};
@@ -54,6 +63,17 @@ export default function AllowedEmailDomainsSettings() {
const isDirty = Object.keys(formState.dirtyFields).length > 0; const isDirty = Object.keys(formState.dirtyFields).length > 0;
useEffect(() => {
if (!loading && email && emailDomains) {
form.reset({
enabled:
email?.allowed?.length > 0 || emailDomains?.allowed?.length > 0,
allowedEmails: email?.allowed?.join(', ') || '',
allowedEmailDomains: emailDomains?.allowed?.join(', ') || '',
});
}
}, [loading, form, email, emailDomains]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -105,6 +125,18 @@ export default function AllowedEmailDomainsSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(values); form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Allowed email settings are being updated...', loadingMessage: 'Allowed email settings are being updated...',

View File

@@ -1,16 +1,21 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetAuthenticationSettingsDocument, GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery, useGetAuthenticationSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -23,15 +28,19 @@ export type AllowedRedirectURLFormValues = Yup.InferType<
>; >;
export default function AllowedRedirectURLsSettings() { export default function AllowedRedirectURLsSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument], refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetAuthenticationSettingsQuery({ const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { allowedUrls } = data?.config?.auth?.redirections || {}; const { allowedUrls } = data?.config?.auth?.redirections || {};
@@ -44,6 +53,14 @@ export default function AllowedRedirectURLsSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading && allowedUrls) {
form.reset({
allowedUrls: allowedUrls?.join(', ') || '',
});
}
}, [loading, allowedUrls, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -82,6 +99,18 @@ export default function AllowedRedirectURLsSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(values); form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Allowed redirect URL settings are being updated...', loadingMessage: 'Allowed redirect URL settings are being updated...',

View File

@@ -1,15 +1,20 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -20,15 +25,19 @@ const validationSchema = Yup.object({
export type AnonymousSignInFormValues = Yup.InferType<typeof validationSchema>; export type AnonymousSignInFormValues = Yup.InferType<typeof validationSchema>;
export default function AnonymousSignInSettings() { export default function AnonymousSignInSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { enabled } = data?.config?.auth?.method?.anonymous || {}; const { enabled } = data?.config?.auth?.method?.anonymous || {};
@@ -41,6 +50,12 @@ export default function AnonymousSignInSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({ enabled });
}
}, [loading, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -75,6 +90,18 @@ export default function AnonymousSignInSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(values); form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Anonymous sign-in settings are being updated...', loadingMessage: 'Anonymous sign-in settings are being updated...',

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -7,16 +9,19 @@ import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { InputAdornment } from '@/components/ui/v2/InputAdornment'; import { InputAdornment } from '@/components/ui/v2/InputAdornment';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useTheme } from '@mui/material'; import { useTheme } from '@mui/material';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -53,15 +58,19 @@ export type AppleProviderFormValues = Yup.InferType<typeof validationSchema>;
export default function AppleProviderSettings() { export default function AppleProviderSettings() {
const theme = useTheme(); const theme = useTheme();
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { clientId, enabled, keyId, privateKey, teamId } = const { clientId, enabled, keyId, privateKey, teamId } =
@@ -79,6 +88,18 @@ export default function AppleProviderSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
teamId: teamId || '',
keyId: keyId || '',
clientId: clientId || '',
privateKey: privateKey || '',
enabled: enabled || false,
});
}
}, [loading, teamId, keyId, clientId, privateKey, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -119,6 +140,18 @@ export default function AppleProviderSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Apple settings are being updated...', loadingMessage: 'Apple settings are being updated...',
@@ -236,7 +269,7 @@ export default function AppleProviderSettings() {
); );
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
} }

View File

@@ -1,9 +1,12 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete'; import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetAuthenticationSettingsDocument, GetAuthenticationSettingsDocument,
Software_Type_Enum, Software_Type_Enum,
@@ -11,8 +14,10 @@ import {
useGetSoftwareVersionsQuery, useGetSoftwareVersionsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -28,21 +33,26 @@ export type AuthServiceVersionFormValues = Yup.InferType<
>; >;
export default function AuthServiceVersionSettings() { export default function AuthServiceVersionSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument], refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetAuthenticationSettingsQuery({ const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data: authVersionsData } = useGetSoftwareVersionsQuery({ const { data: authVersionsData } = useGetSoftwareVersionsQuery({
variables: { variables: {
software: Software_Type_Enum.Auth, software: Software_Type_Enum.Auth,
}, },
skip: !isPlatform,
}); });
const { version } = data?.config?.auth || {}; const { version } = data?.config?.auth || {};
@@ -59,10 +69,21 @@ export default function AuthServiceVersionSettings() {
const form = useForm<AuthServiceVersionFormValues>({ const form = useForm<AuthServiceVersionFormValues>({
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
defaultValues: { version: { label: version, value: version } }, defaultValues: { version: { label: '', value: '' } },
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading && version) {
form.reset({
version: {
label: version,
value: version,
},
});
}
}, [loading, version, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -97,6 +118,18 @@ export default function AuthServiceVersionSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Auth version is being updated...', loadingMessage: 'Auth version is being updated...',
@@ -120,12 +153,20 @@ export default function AuthServiceVersionSettings() {
}} }}
docsLink="https://github.com/nhost/hasura-auth/releases" docsLink="https://github.com/nhost/hasura-auth/releases"
docsTitle="the latest releases" docsTitle="the latest releases"
className="grid grid-flow-row gap-x-4 gap-y-2 px-4 lg:grid-cols-5" className="grid grid-flow-row px-4 gap-x-4 gap-y-2 lg:grid-cols-5"
> >
<ControlledAutocomplete <ControlledAutocomplete
id="version" id="version"
name="version" name="version"
autoHighlight autoHighlight
freeSolo
getOptionLabel={(option) => {
if (typeof option === 'string') {
return option || '';
}
return option.value;
}}
isOptionEqualToValue={() => false} isOptionEqualToValue={() => false}
filterOptions={(options, { inputValue }) => { filterOptions={(options, { inputValue }) => {
const inputValueLower = inputValue.toLowerCase(); const inputValueLower = inputValue.toLowerCase();

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -8,15 +10,18 @@ import { Input } from '@/components/ui/v2/Input';
import { InputAdornment } from '@/components/ui/v2/InputAdornment'; import { InputAdornment } from '@/components/ui/v2/InputAdornment';
import { BaseProviderSettings } from '@/features/authentication/settings/components/BaseProviderSettings'; import { BaseProviderSettings } from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -46,15 +51,19 @@ const validationSchema = Yup.object({
export type AzureADProviderFormValues = Yup.InferType<typeof validationSchema>; export type AzureADProviderFormValues = Yup.InferType<typeof validationSchema>;
export default function AzureADProviderSettings() { export default function AzureADProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { clientId, clientSecret, tenant, enabled } = const { clientId, clientSecret, tenant, enabled } =
@@ -71,6 +80,17 @@ export default function AzureADProviderSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
tenant: tenant || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, tenant, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -108,6 +128,18 @@ export default function AzureADProviderSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Azure AD settings are being updated...', loadingMessage: 'Azure AD settings are being updated...',
@@ -182,7 +214,7 @@ export default function AzureADProviderSettings() {
); );
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
} }

View File

@@ -1,16 +1,21 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetAuthenticationSettingsDocument, GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery, useGetAuthenticationSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -24,15 +29,19 @@ const validationSchema = Yup.object({
export type BlockedEmailFormValues = Yup.InferType<typeof validationSchema>; export type BlockedEmailFormValues = Yup.InferType<typeof validationSchema>;
export default function BlockedEmailSettings() { export default function BlockedEmailSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument], refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetAuthenticationSettingsQuery({ const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { email, emailDomains } = data?.config?.auth?.user || {}; const { email, emailDomains } = data?.config?.auth?.user || {};
@@ -51,6 +60,17 @@ export default function BlockedEmailSettings() {
const enabled = watch('enabled'); const enabled = watch('enabled');
const isDirty = Object.keys(formState.dirtyFields).length > 0; const isDirty = Object.keys(formState.dirtyFields).length > 0;
useEffect(() => {
if (!loading && email && emailDomains) {
form.reset({
enabled:
email?.blocked?.length > 0 || emailDomains?.blocked?.length > 0,
blockedEmails: email?.blocked?.join(', ') || '',
blockedEmailDomains: emailDomains?.blocked?.join(', ') || '',
});
}
}, [loading, email, emailDomains, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -112,6 +132,18 @@ export default function BlockedEmailSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(values); form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: loadingMessage:

View File

@@ -1,16 +1,21 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetAuthenticationSettingsDocument, GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery, useGetAuthenticationSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -21,15 +26,19 @@ const validationSchema = Yup.object({
export type ClientURLFormValues = Yup.InferType<typeof validationSchema>; export type ClientURLFormValues = Yup.InferType<typeof validationSchema>;
export default function ClientURLSettings() { export default function ClientURLSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument], refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetAuthenticationSettingsQuery({ const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { clientUrl, allowedUrls } = data?.config?.auth?.redirections || {}; const { clientUrl, allowedUrls } = data?.config?.auth?.redirections || {};
@@ -42,6 +51,12 @@ export default function ClientURLSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading && clientUrl) {
form.reset({ clientUrl });
}
}, [loading, clientUrl, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -77,6 +92,18 @@ export default function ClientURLSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(values); form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Client URL is being updated...', loadingMessage: 'Client URL is being updated...',

View File

@@ -1,14 +1,19 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
GetAuthenticationSettingsDocument, GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery, useGetAuthenticationSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/utils/__generated__/graphql'; } from '@/utils/__generated__/graphql';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -19,15 +24,19 @@ const validationSchema = Yup.object({
export type DisableNewUsersFormValues = Yup.InferType<typeof validationSchema>; export type DisableNewUsersFormValues = Yup.InferType<typeof validationSchema>;
export default function DisableNewUsersSettings() { export default function DisableNewUsersSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument], refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetAuthenticationSettingsQuery({ const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const form = useForm<DisableNewUsersFormValues>({ const form = useForm<DisableNewUsersFormValues>({
@@ -37,6 +46,14 @@ export default function DisableNewUsersSettings() {
}, },
}); });
useEffect(() => {
if (!loading) {
form.reset({
disabled: !data?.config?.auth?.signUp?.enabled,
});
}
}, [loading, data, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -73,6 +90,18 @@ export default function DisableNewUsersSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(values); form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Disabling new user sign ups...', loadingMessage: 'Disabling new user sign ups...',

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,28 +14,35 @@ import {
baseProviderValidationSchema, baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings'; } from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
export default function DiscordProviderSettings() { export default function DiscordProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { clientId, clientSecret, enabled } = const { clientId, clientSecret, enabled } =
@@ -49,6 +58,16 @@ export default function DiscordProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema), resolver: yupResolver(baseProviderValidationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -89,6 +108,18 @@ export default function DiscordProviderSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Discord settings are being updated...', loadingMessage: 'Discord settings are being updated...',
@@ -153,7 +184,7 @@ export default function DiscordProviderSettings() {
); );
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
} }

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { ControlledCheckbox } from '@/components/form/ControlledCheckbox'; import { ControlledCheckbox } from '@/components/form/ControlledCheckbox';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
@@ -6,13 +8,16 @@ import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -29,15 +34,19 @@ const validationSchema = Yup.object({
export type EmailAndPasswordFormValues = Yup.InferType<typeof validationSchema>; export type EmailAndPasswordFormValues = Yup.InferType<typeof validationSchema>;
export default function EmailAndPasswordSettings() { export default function EmailAndPasswordSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, error, loading } = useGetSignInMethodsQuery({ const { data, error, loading } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { hibpEnabled, emailVerificationRequired, passwordMinLength } = const { hibpEnabled, emailVerificationRequired, passwordMinLength } =
@@ -53,6 +62,22 @@ export default function EmailAndPasswordSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
hibpEnabled,
emailVerificationRequired,
passwordMinLength,
});
}
}, [
loading,
hibpEnabled,
emailVerificationRequired,
passwordMinLength,
form,
]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -87,6 +112,18 @@ export default function EmailAndPasswordSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: `Email and password sign-in settings are being updated...`, loadingMessage: `Email and password sign-in settings are being updated...`,

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,28 +14,35 @@ import {
baseProviderValidationSchema, baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings'; } from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
export default function FacebookProviderSettings() { export default function FacebookProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { clientId, clientSecret, enabled } = const { clientId, clientSecret, enabled } =
@@ -49,6 +58,16 @@ export default function FacebookProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema), resolver: yupResolver(baseProviderValidationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -89,6 +108,18 @@ export default function FacebookProviderSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Facebook settings are being updated...', loadingMessage: 'Facebook settings are being updated...',
@@ -153,7 +184,7 @@ export default function FacebookProviderSettings() {
); );
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
} }

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,30 +14,37 @@ import {
baseProviderValidationSchema, baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings'; } from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useTheme } from '@mui/material'; import { useTheme } from '@mui/material';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
export default function GitHubProviderSettings() { export default function GitHubProviderSettings() {
const theme = useTheme(); const theme = useTheme();
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { clientId, clientSecret, enabled } = const { clientId, clientSecret, enabled } =
@@ -51,6 +60,16 @@ export default function GitHubProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema), resolver: yupResolver(baseProviderValidationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -91,6 +110,18 @@ export default function GitHubProviderSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'GitHub settings are being updated...', loadingMessage: 'GitHub settings are being updated...',
@@ -159,7 +190,7 @@ export default function GitHubProviderSettings() {
); );
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
} }

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,28 +14,35 @@ import {
baseProviderValidationSchema, baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings'; } from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
export default function GoogleProviderSettings() { export default function GoogleProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { clientId, clientSecret, enabled } = const { clientId, clientSecret, enabled } =
@@ -49,6 +58,16 @@ export default function GoogleProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema), resolver: yupResolver(baseProviderValidationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -89,6 +108,18 @@ export default function GoogleProviderSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Google settings are being updated...', loadingMessage: 'Google settings are being updated...',
@@ -153,7 +184,7 @@ export default function GoogleProviderSettings() {
); );
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
} }

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { ControlledSelect } from '@/components/form/ControlledSelect'; import { ControlledSelect } from '@/components/form/ControlledSelect';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
@@ -5,17 +7,20 @@ import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Option } from '@/components/ui/v2/Option'; import { Option } from '@/components/ui/v2/Option';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetAuthenticationSettingsDocument, GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery, useGetAuthenticationSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { import {
AUTH_GRAVATAR_DEFAULT, AUTH_GRAVATAR_DEFAULT,
AUTH_GRAVATAR_RATING, AUTH_GRAVATAR_RATING,
} from '@/utils/constants/settings'; } from '@/utils/constants/settings';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -29,15 +34,19 @@ const validationSchema = Yup.object({
export type GravatarFormValues = Yup.InferType<typeof validationSchema>; export type GravatarFormValues = Yup.InferType<typeof validationSchema>;
export default function GravatarSettings() { export default function GravatarSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument], refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetAuthenticationSettingsQuery({ const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { const {
@@ -56,6 +65,16 @@ export default function GravatarSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
default: defaultGravatar || '',
rating: rating || '',
enabled: enabled || false,
});
}
}, [loading, defaultGravatar, rating, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -91,6 +110,18 @@ export default function GravatarSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(values); form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Gravatar settings are being updated...', loadingMessage: 'Gravatar settings are being updated...',

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,28 +14,35 @@ import {
baseProviderValidationSchema, baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings'; } from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
export default function LinkedInProviderSettings() { export default function LinkedInProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { clientId, clientSecret, enabled } = const { clientId, clientSecret, enabled } =
@@ -49,6 +58,16 @@ export default function LinkedInProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema), resolver: yupResolver(baseProviderValidationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -89,6 +108,18 @@ export default function LinkedInProviderSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'LinkedIn settings are being updated...', loadingMessage: 'LinkedIn settings are being updated...',
@@ -153,7 +184,7 @@ export default function LinkedInProviderSettings() {
); );
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
} }

View File

@@ -1,16 +1,21 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetAuthenticationSettingsDocument, GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery, useGetAuthenticationSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -23,15 +28,19 @@ const validationSchema = Yup.object({
export type MFASettingsFormValues = Yup.InferType<typeof validationSchema>; export type MFASettingsFormValues = Yup.InferType<typeof validationSchema>;
export default function MFASettings() { export default function MFASettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument], refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetAuthenticationSettingsQuery({ const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { enabled, issuer } = data?.config?.auth?.totp || {}; const { enabled, issuer } = data?.config?.auth?.totp || {};
@@ -45,6 +54,15 @@ export default function MFASettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading && issuer && enabled) {
form.reset({
issuer,
enabled,
});
}
}, [loading, issuer, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -78,6 +96,18 @@ export default function MFASettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(values); form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: loadingMessage:

View File

@@ -1,15 +1,20 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -20,15 +25,19 @@ const validationSchema = Yup.object({
export type MagicLinkFormValues = Yup.InferType<typeof validationSchema>; export type MagicLinkFormValues = Yup.InferType<typeof validationSchema>;
export default function MagicLinkSettings() { export default function MagicLinkSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { enabled } = data?.config?.auth?.method?.emailPasswordless || {}; const { enabled } = data?.config?.auth?.method?.emailPasswordless || {};
@@ -41,6 +50,12 @@ export default function MagicLinkSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({ enabled });
}
}, [loading, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -75,6 +90,18 @@ export default function MagicLinkSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(values); form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Magic Link settings are being updated...', loadingMessage: 'Magic Link settings are being updated...',

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -7,14 +9,17 @@ import { Option } from '@/components/ui/v2/Option';
import { Select } from '@/components/ui/v2/Select'; import { Select } from '@/components/ui/v2/Select';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import Image from 'next/image'; import Image from 'next/image';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -44,15 +49,19 @@ const validationSchema = Yup.object({
export type SMSSettingsFormValues = Yup.InferType<typeof validationSchema>; export type SMSSettingsFormValues = Yup.InferType<typeof validationSchema>;
export default function SMSSettings() { export default function SMSSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, error, loading } = useGetSignInMethodsQuery({ const { data, error, loading } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { accountSid, authToken, messagingServiceId } = const { accountSid, authToken, messagingServiceId } =
@@ -70,6 +79,17 @@ export default function SMSSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
accountSid: accountSid || '',
authToken: authToken || '',
messagingServiceId: messagingServiceId || '',
enabled: enabled || false,
});
}
}, [loading, accountSid, authToken, messagingServiceId, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -114,6 +134,18 @@ export default function SMSSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(values); form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'SMS settings are being updated...', loadingMessage: 'SMS settings are being updated...',

View File

@@ -1,16 +1,21 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetAuthenticationSettingsDocument, GetAuthenticationSettingsDocument,
useGetAuthenticationSettingsQuery, useGetAuthenticationSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -28,15 +33,19 @@ const validationSchema = Yup.object({
export type SessionFormValues = Yup.InferType<typeof validationSchema>; export type SessionFormValues = Yup.InferType<typeof validationSchema>;
export default function SessionSettings() { export default function SessionSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetAuthenticationSettingsDocument], refetchQueries: [GetAuthenticationSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetAuthenticationSettingsQuery({ const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { accessToken, refreshToken } = data?.config?.auth?.session || {}; const { accessToken, refreshToken } = data?.config?.auth?.session || {};
@@ -50,6 +59,15 @@ export default function SessionSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading && accessToken && refreshToken) {
form.reset({
accessTokenExpiresIn: accessToken?.expiresIn || 900,
refreshTokenExpiresIn: refreshToken?.expiresIn || 43200,
});
}
}, [loading, accessToken, refreshToken, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -86,6 +104,18 @@ export default function SessionSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Session settings are being updated...', loadingMessage: 'Session settings are being updated...',

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,28 +14,35 @@ import {
baseProviderValidationSchema, baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings'; } from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
export default function SpotifyProviderSettings() { export default function SpotifyProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { clientId, clientSecret, enabled } = const { clientId, clientSecret, enabled } =
@@ -49,6 +58,16 @@ export default function SpotifyProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema), resolver: yupResolver(baseProviderValidationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -89,6 +108,18 @@ export default function SpotifyProviderSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Spotify settings are being updated...', loadingMessage: 'Spotify settings are being updated...',
@@ -153,7 +184,7 @@ export default function SpotifyProviderSettings() {
); );
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
} }

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,30 +14,37 @@ import {
baseProviderValidationSchema, baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings'; } from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useTheme } from '@mui/material'; import { useTheme } from '@mui/material';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
export default function TwitchProviderSettings() { export default function TwitchProviderSettings() {
const theme = useTheme(); const theme = useTheme();
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { clientId, clientSecret, enabled } = const { clientId, clientSecret, enabled } =
@@ -51,6 +60,16 @@ export default function TwitchProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema), resolver: yupResolver(baseProviderValidationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -91,6 +110,18 @@ export default function TwitchProviderSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Twitch settings are being updated...', loadingMessage: 'Twitch settings are being updated...',
@@ -159,7 +190,7 @@ export default function TwitchProviderSettings() {
); );
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
} }

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -7,15 +9,18 @@ import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { InputAdornment } from '@/components/ui/v2/InputAdornment'; import { InputAdornment } from '@/components/ui/v2/InputAdornment';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -39,15 +44,19 @@ const validationSchema = Yup.object({
export type TwitterProviderFormValues = Yup.InferType<typeof validationSchema>; export type TwitterProviderFormValues = Yup.InferType<typeof validationSchema>;
export default function TwitterProviderSettings() { export default function TwitterProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { consumerKey, consumerSecret, enabled } = const { consumerKey, consumerSecret, enabled } =
@@ -63,6 +72,16 @@ export default function TwitterProviderSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
consumerSecret: consumerSecret || '',
consumerKey: consumerKey || '',
enabled: enabled || false,
});
}
}, [loading, consumerKey, consumerSecret, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -100,6 +119,18 @@ export default function TwitterProviderSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Twitter settings are being updated...', loadingMessage: 'Twitter settings are being updated...',
@@ -185,7 +216,7 @@ export default function TwitterProviderSettings() {
); );
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
} }

View File

@@ -1,15 +1,20 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -20,15 +25,19 @@ const validationSchema = Yup.object({
export type WebAuthnFormValues = Yup.InferType<typeof validationSchema>; export type WebAuthnFormValues = Yup.InferType<typeof validationSchema>;
export default function WebAuthnSettings() { export default function WebAuthnSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { enabled } = data?.config?.auth?.method?.webauthn || {}; const { enabled } = data?.config?.auth?.method?.webauthn || {};
@@ -41,6 +50,12 @@ export default function WebAuthnSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({ enabled });
}
}, [loading, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -80,6 +95,18 @@ export default function WebAuthnSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(values); form.reset(values);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'WebAuthn settings are being updated...', loadingMessage: 'WebAuthn settings are being updated...',

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -12,28 +14,35 @@ import {
baseProviderValidationSchema, baseProviderValidationSchema,
} from '@/features/authentication/settings/components/BaseProviderSettings'; } from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
export default function WindowsLiveProviderSettings() { export default function WindowsLiveProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { clientId, clientSecret, enabled } = const { clientId, clientSecret, enabled } =
@@ -49,6 +58,16 @@ export default function WindowsLiveProviderSettings() {
resolver: yupResolver(baseProviderValidationSchema), resolver: yupResolver(baseProviderValidationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
enabled: enabled || false,
});
}
}, [loading, clientId, clientSecret, enabled, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -89,6 +108,18 @@ export default function WindowsLiveProviderSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Windows Live settings are being updated...', loadingMessage: 'Windows Live settings are being updated...',
@@ -151,7 +182,7 @@ export default function WindowsLiveProviderSettings() {
); );
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
} }

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -8,15 +10,18 @@ import { Input } from '@/components/ui/v2/Input';
import { InputAdornment } from '@/components/ui/v2/InputAdornment'; import { InputAdornment } from '@/components/ui/v2/InputAdornment';
import { BaseProviderSettings } from '@/features/authentication/settings/components/BaseProviderSettings'; import { BaseProviderSettings } from '@/features/authentication/settings/components/BaseProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl'; import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { import {
GetSignInMethodsDocument, GetSignInMethodsDocument,
useGetSignInMethodsQuery, useGetSignInMethodsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -52,15 +57,19 @@ const validationSchema = Yup.object({
export type WorkOsProviderFormValues = Yup.InferType<typeof validationSchema>; export type WorkOsProviderFormValues = Yup.InferType<typeof validationSchema>;
export default function WorkOsProviderSettings() { export default function WorkOsProviderSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSignInMethodsDocument], refetchQueries: [GetSignInMethodsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetSignInMethodsQuery({ const { data, loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { clientId, clientSecret, organization, connection, enabled } = const { clientId, clientSecret, organization, connection, enabled } =
@@ -78,6 +87,26 @@ export default function WorkOsProviderSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
clientId: clientId || '',
clientSecret: clientSecret || '',
organization: organization || '',
connection: connection || '',
enabled: enabled || false,
});
}
}, [
loading,
clientId,
clientSecret,
organization,
connection,
enabled,
form,
]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -115,6 +144,18 @@ export default function WorkOsProviderSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'WorkOS settings are being updated...', loadingMessage: 'WorkOS settings are being updated...',
@@ -203,7 +244,7 @@ export default function WorkOsProviderSettings() {
); );
}} }}
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</InputAdornment> </InputAdornment>
} }

View File

@@ -1,9 +1,12 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete'; import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetPostgresSettingsDocument, GetPostgresSettingsDocument,
Software_Type_Enum, Software_Type_Enum,
@@ -11,8 +14,10 @@ import {
useGetSoftwareVersionsQuery, useGetSoftwareVersionsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -30,24 +35,30 @@ export type DatabaseServiceVersionFormValues = Yup.InferType<
>; >;
export default function DatabaseServiceVersionSettings() { export default function DatabaseServiceVersionSettings() {
const isPlatform = useIsPlatform();
const { openDialog } = useDialog();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetPostgresSettingsDocument], refetchQueries: [GetPostgresSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetPostgresSettingsQuery({ const { data, loading, error } = useGetPostgresSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data: databaseVersionsData } = useGetSoftwareVersionsQuery({ const { data: databaseVersionsData } = useGetSoftwareVersionsQuery({
variables: { variables: {
software: Software_Type_Enum.PostgreSql, software: Software_Type_Enum.PostgreSql,
}, },
skip: !isPlatform,
}); });
const { version } = data?.config?.postgres || {}; const { version } = data?.config?.postgres || {};
const databaseVersions = databaseVersionsData?.softwareVersions || []; const databaseVersions = databaseVersionsData?.softwareVersions || [];
const availableVersions = Array.from( const availableVersions = Array.from(
new Set(databaseVersions.map((el) => el.version)).add(version), new Set(databaseVersions.map((el) => el.version)).add(version),
@@ -61,10 +72,21 @@ export default function DatabaseServiceVersionSettings() {
const form = useForm<DatabaseServiceVersionFormValues>({ const form = useForm<DatabaseServiceVersionFormValues>({
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
defaultValues: { version: { label: version, value: version } }, defaultValues: { version: { label: '', value: '' } },
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading && version) {
form.reset({
version: {
label: version,
value: version,
},
});
}
}, [loading, version, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -99,6 +121,18 @@ export default function DatabaseServiceVersionSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Postgres version is being updated...', loadingMessage: 'Postgres version is being updated...',
@@ -123,12 +157,20 @@ export default function DatabaseServiceVersionSettings() {
}} }}
docsLink="https://hub.docker.com/r/nhost/postgres/tags" docsLink="https://hub.docker.com/r/nhost/postgres/tags"
docsTitle="the latest releases" docsTitle="the latest releases"
className="grid grid-flow-row gap-x-4 gap-y-2 px-4 lg:grid-cols-5" className="grid grid-flow-row px-4 gap-x-4 gap-y-2 lg:grid-cols-5"
> >
<ControlledAutocomplete <ControlledAutocomplete
id="version" id="version"
name="version" name="version"
autoHighlight autoHighlight
freeSolo
getOptionLabel={(option) => {
if (typeof option === 'string') {
return option || '';
}
return option.value;
}}
isOptionEqualToValue={() => false} isOptionEqualToValue={() => false}
filterOptions={(options, { inputValue }) => { filterOptions={(options, { inputValue }) => {
const inputValueLower = inputValue.toLowerCase(); const inputValueLower = inputValue.toLowerCase();

View File

@@ -7,10 +7,12 @@ import { Box } from '@/components/ui/v2/Box';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { UpgradeNotification } from '@/features/projects/common/components/UpgradeNotification'; import { UpgradeNotification } from '@/features/projects/common/components/UpgradeNotification';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
useGetPostgresSettingsQuery, useGetPostgresSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react'; import { useEffect } from 'react';
@@ -18,13 +20,15 @@ import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
const validationSchema = Yup.object({ const validationSchema = Yup.object({
capacity: Yup.number().required(), capacity: Yup.number().required().min(10),
}); });
export type AuthDomainFormValues = Yup.InferType<typeof validationSchema>; export type AuthDomainFormValues = Yup.InferType<typeof validationSchema>;
export default function AuthDomain() { export default function AuthDomain() {
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { const {
@@ -34,14 +38,17 @@ export default function AuthDomain() {
refetch: refetchPostgresSettings, refetch: refetchPostgresSettings,
} = useGetPostgresSettingsQuery({ } = useGetPostgresSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const capacity = const capacity =
data?.config?.postgres?.resources?.storage?.capacity ?? (data?.config?.postgres?.resources?.storage?.capacity ??
currentProject.plan.featureMaxDbSize; currentProject?.plan?.featureMaxDbSize) ||
0;
const [updateConfig] = useUpdateConfigMutation(); const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const form = useForm<Yup.InferType<typeof validationSchema>>({ const form = useForm<Yup.InferType<typeof validationSchema>>({
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
@@ -117,7 +124,7 @@ export default function AuthDomain() {
}} }}
className="flex flex-col" className="flex flex-col"
> >
{currentProject.plan.isFree && ( {currentProject.plan?.isFree && (
<UpgradeNotification message="Unlock by upgrading your project to the Pro plan." /> <UpgradeNotification message="Unlock by upgrading your project to the Pro plan." />
)} )}
<Box className="grid grid-flow-row lg:grid-cols-5"> <Box className="grid grid-flow-row lg:grid-cols-5">
@@ -127,7 +134,7 @@ export default function AuthDomain() {
name="capacity" name="capacity"
type="number" type="number"
fullWidth fullWidth
disabled={currentProject.plan.isFree} disabled={currentProject.plan?.isFree}
className="lg:col-span-2" className="lg:col-span-2"
error={Boolean(formState.errors.capacity?.message)} error={Boolean(formState.errors.capacity?.message)}
helperText={formState.errors.capacity?.message} helperText={formState.errors.capacity?.message}
@@ -138,7 +145,7 @@ export default function AuthDomain() {
}} }}
/> />
</Box> </Box>
{!currentProject.plan.isFree && ( {!currentProject.plan?.isFree && (
<Alert severity="info" className="col-span-6 text-left"> <Alert severity="info" className="col-span-6 text-left">
Note that volumes can only be increased (not decreased). Also, due Note that volumes can only be increased (not decreased). Also, due
to an AWS limitation, the same volume can only be increased once to an AWS limitation, the same volume can only be increased once

View File

@@ -1,15 +1,20 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetHasuraSettingsDocument, GetHasuraSettingsDocument,
useGetHasuraSettingsQuery, useGetHasuraSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -20,16 +25,20 @@ const validationSchema = Yup.object({
export type HasuraAllowListFormValues = Yup.InferType<typeof validationSchema>; export type HasuraAllowListFormValues = Yup.InferType<typeof validationSchema>;
export default function HasuraAllowListSettings() { export default function HasuraAllowListSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument], refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetHasuraSettingsQuery({ const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-first', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { enableAllowList } = data?.config?.hasura.settings || {}; const { enableAllowList } = data?.config?.hasura.settings || {};
@@ -42,6 +51,14 @@ export default function HasuraAllowListSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
enabled: enableAllowList,
});
}
}, [loading, enableAllowList, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -75,6 +92,18 @@ export default function HasuraAllowListSettings() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Allow list settings are being updated...', loadingMessage: 'Allow list settings are being updated...',

View File

@@ -1,15 +1,20 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetHasuraSettingsDocument, GetHasuraSettingsDocument,
useGetHasuraSettingsQuery, useGetHasuraSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -20,16 +25,20 @@ const validationSchema = Yup.object({
export type HasuraConsoleFormValues = Yup.InferType<typeof validationSchema>; export type HasuraConsoleFormValues = Yup.InferType<typeof validationSchema>;
export default function HasuraConsoleSettings() { export default function HasuraConsoleSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument], refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetHasuraSettingsQuery({ const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-first', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { enableConsole } = data?.config?.hasura.settings || {}; const { enableConsole } = data?.config?.hasura.settings || {};
@@ -42,6 +51,14 @@ export default function HasuraConsoleSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
enabled: enableConsole,
});
}
}, [loading, enableConsole, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -75,6 +92,18 @@ export default function HasuraConsoleSettings() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Hasura Console settings are being updated...', loadingMessage: 'Hasura Console settings are being updated...',

View File

@@ -1,14 +1,18 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetHasuraSettingsDocument, GetHasuraSettingsDocument,
useGetHasuraSettingsQuery, useGetHasuraSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
@@ -28,16 +32,20 @@ const validationSchema = Yup.object({
export type HasuraCorsDomainFormValues = Yup.InferType<typeof validationSchema>; export type HasuraCorsDomainFormValues = Yup.InferType<typeof validationSchema>;
export default function HasuraCorsDomainSettings() { export default function HasuraCorsDomainSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument], refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetHasuraSettingsQuery({ const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-first', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { corsDomain } = data?.config?.hasura.settings || {}; const { corsDomain } = data?.config?.hasura.settings || {};
@@ -98,6 +106,18 @@ export default function HasuraCorsDomainSettings() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'CORS domain settings are being updated...', loadingMessage: 'CORS domain settings are being updated...',

View File

@@ -1,15 +1,20 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetHasuraSettingsDocument, GetHasuraSettingsDocument,
useGetHasuraSettingsQuery, useGetHasuraSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -20,16 +25,20 @@ const validationSchema = Yup.object({
export type HasuraDevModeFormValues = Yup.InferType<typeof validationSchema>; export type HasuraDevModeFormValues = Yup.InferType<typeof validationSchema>;
export default function HasuraDevModeSettings() { export default function HasuraDevModeSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument], refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetHasuraSettingsQuery({ const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-first', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { devMode } = data?.config?.hasura.settings || {}; const { devMode } = data?.config?.hasura.settings || {};
@@ -42,6 +51,14 @@ export default function HasuraDevModeSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
enabled: devMode,
});
}
}, [loading, devMode, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -63,7 +80,7 @@ export default function HasuraDevModeSettings() {
config: { config: {
hasura: { hasura: {
settings: { settings: {
enableConsole: formValues.enabled, devMode: formValues.enabled,
}, },
}, },
}, },
@@ -75,6 +92,18 @@ export default function HasuraDevModeSettings() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Dev Mode settings are being updated...', loadingMessage: 'Dev Mode settings are being updated...',

View File

@@ -1,16 +1,21 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete'; import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetHasuraSettingsDocument, GetHasuraSettingsDocument,
useGetHasuraSettingsQuery, useGetHasuraSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -30,31 +35,40 @@ export type HasuraEnabledAPIFormValues = Yup.InferType<typeof validationSchema>;
const AVAILABLE_HASURA_APIS = ['metadata', 'graphql', 'pgdump', 'config']; const AVAILABLE_HASURA_APIS = ['metadata', 'graphql', 'pgdump', 'config'];
export default function HasuraEnabledAPISettings() { export default function HasuraEnabledAPISettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument], refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetHasuraSettingsQuery({ const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { enabledAPIs } = data?.config?.hasura.settings || {}; const { enabledAPIs = [] } = data?.config?.hasura?.settings || {};
const form = useForm<HasuraEnabledAPIFormValues>({ const form = useForm<HasuraEnabledAPIFormValues>({
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
defaultValues: { defaultValues: {
enabledAPIs: enabledAPIs.map((api) => ({ enabledAPIs: [],
label: api,
value: api,
})),
}, },
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (enabledAPIs && !loading) {
form.reset({
enabledAPIs: enabledAPIs.map((api) => ({ label: api, value: api })),
});
}
}, [form, enabledAPIs, loading]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -96,6 +110,18 @@ export default function HasuraEnabledAPISettings() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Enabled APIs are being updated...', loadingMessage: 'Enabled APIs are being updated...',
@@ -117,7 +143,7 @@ export default function HasuraEnabledAPISettings() {
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
className="grid grid-flow-row gap-x-4 gap-y-2 px-4 lg:grid-cols-6" className="grid grid-flow-row px-4 gap-x-4 gap-y-2 lg:grid-cols-6"
> >
<ControlledAutocomplete <ControlledAutocomplete
id="enabledAPIs" id="enabledAPIs"

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete'; import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
@@ -5,13 +7,16 @@ import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { HighlightedText } from '@/components/presentational/HighlightedText'; import { HighlightedText } from '@/components/presentational/HighlightedText';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetHasuraSettingsDocument, GetHasuraSettingsDocument,
useGetHasuraSettingsQuery, useGetHasuraSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -29,16 +34,20 @@ export type HasuraLogLevelFormValues = Yup.InferType<typeof validationSchema>;
const AVAILABLE_HASURA_LOG_LEVELS = ['debug', 'info', 'warn', 'error']; const AVAILABLE_HASURA_LOG_LEVELS = ['debug', 'info', 'warn', 'error'];
export default function HasuraLogLevelSettings() { export default function HasuraLogLevelSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument], refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetHasuraSettingsQuery({ const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-first', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { level } = data?.config?.hasura.logs || {}; const { level } = data?.config?.hasura.logs || {};
@@ -56,6 +65,17 @@ export default function HasuraLogLevelSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading && level) {
form.reset({
logLevel: {
label: level,
value: level,
},
});
}
}, [form, loading, level]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -97,6 +117,18 @@ export default function HasuraLogLevelSettings() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Log level is being updated...', loadingMessage: 'Log level is being updated...',
@@ -128,7 +160,7 @@ export default function HasuraLogLevelSettings() {
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
className="grid grid-flow-row gap-x-4 gap-y-2 px-4 lg:grid-cols-5" className="grid grid-flow-row px-4 gap-x-4 gap-y-2 lg:grid-cols-5"
> >
<ControlledAutocomplete <ControlledAutocomplete
id="logLevel" id="logLevel"

View File

@@ -1,14 +1,18 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetHasuraSettingsDocument, GetHasuraSettingsDocument,
useGetHasuraSettingsQuery, useGetHasuraSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
@@ -26,16 +30,20 @@ const validationSchema = Yup.object({
export type HasuraPoolSizeFormValues = Yup.InferType<typeof validationSchema>; export type HasuraPoolSizeFormValues = Yup.InferType<typeof validationSchema>;
export default function HasuraPoolSizeSettings() { export default function HasuraPoolSizeSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument], refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetHasuraSettingsQuery({ const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-first', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { httpPoolSize } = data?.config?.hasura.events || {}; const { httpPoolSize } = data?.config?.hasura.events || {};
@@ -82,6 +90,18 @@ export default function HasuraPoolSizeSettings() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Pool size is being updated...', loadingMessage: 'Pool size is being updated...',
@@ -104,7 +124,7 @@ export default function HasuraPoolSizeSettings() {
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
className="grid grid-flow-row gap-x-4 gap-y-2 px-4 lg:grid-cols-5" className="grid grid-flow-row px-4 gap-x-4 gap-y-2 lg:grid-cols-5"
> >
<Input <Input
{...register('httpPoolSize')} {...register('httpPoolSize')}

View File

@@ -1,15 +1,20 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetHasuraSettingsDocument, GetHasuraSettingsDocument,
useGetHasuraSettingsQuery, useGetHasuraSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -22,16 +27,20 @@ export type HasuraRemoteSchemaPermissionsFormValues = Yup.InferType<
>; >;
export default function HasuraRemoteSchemaPermissionsSettings() { export default function HasuraRemoteSchemaPermissionsSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument], refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetHasuraSettingsQuery({ const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-first', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { enableRemoteSchemaPermissions } = data?.config?.hasura.settings || {}; const { enableRemoteSchemaPermissions } = data?.config?.hasura.settings || {};
@@ -44,6 +53,14 @@ export default function HasuraRemoteSchemaPermissionsSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
enabled: enableRemoteSchemaPermissions,
});
}
}, [loading, enableRemoteSchemaPermissions, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -67,7 +84,7 @@ export default function HasuraRemoteSchemaPermissionsSettings() {
config: { config: {
hasura: { hasura: {
settings: { settings: {
enableConsole: formValues.enabled, enableRemoteSchemaPermissions: formValues.enabled,
}, },
}, },
}, },
@@ -79,6 +96,18 @@ export default function HasuraRemoteSchemaPermissionsSettings() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: loadingMessage:

View File

@@ -1,9 +1,12 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete'; import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetHasuraSettingsDocument, GetHasuraSettingsDocument,
Software_Type_Enum, Software_Type_Enum,
@@ -11,8 +14,10 @@ import {
useGetSoftwareVersionsQuery, useGetSoftwareVersionsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -30,22 +35,27 @@ export type HasuraServiceVersionFormValues = Yup.InferType<
>; >;
export default function HasuraServiceVersionSettings() { export default function HasuraServiceVersionSettings() {
const isPlatform = useIsPlatform();
const { openDialog } = useDialog();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument], refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetHasuraSettingsQuery({ const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data: hasuraVersionsData } = useGetSoftwareVersionsQuery({ const { data: hasuraVersionsData } = useGetSoftwareVersionsQuery({
variables: { variables: {
software: Software_Type_Enum.Hasura, software: Software_Type_Enum.Hasura,
}, },
skip: !isPlatform,
}); });
const { version } = data?.config?.hasura || {}; const { version } = data?.config?.hasura || {};
@@ -53,6 +63,7 @@ export default function HasuraServiceVersionSettings() {
const availableVersions = Array.from( const availableVersions = Array.from(
new Set(versions.map((el) => el.version)).add(version), new Set(versions.map((el) => el.version)).add(version),
) )
.filter((v) => !!v)
.sort() .sort()
.reverse() .reverse()
.map((availableVersion) => ({ .map((availableVersion) => ({
@@ -60,12 +71,25 @@ export default function HasuraServiceVersionSettings() {
value: availableVersion, value: availableVersion,
})); }));
// TODO make sure the network request is made in the parent component
// also this request should be made against cache only and the data should be available right away
const form = useForm<HasuraServiceVersionFormValues>({ const form = useForm<HasuraServiceVersionFormValues>({
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
defaultValues: { version: { label: version, value: version } }, defaultValues: { version: { label: '', value: '' } },
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading && version) {
form.reset({
version: {
label: version,
value: version,
},
});
}
}, [loading, version, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -99,6 +123,18 @@ export default function HasuraServiceVersionSettings() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Hasura version is being updated...', loadingMessage: 'Hasura version is being updated...',
@@ -123,11 +159,19 @@ export default function HasuraServiceVersionSettings() {
}} }}
docsLink="https://hub.docker.com/r/nhost/graphql-engine/tags" docsLink="https://hub.docker.com/r/nhost/graphql-engine/tags"
docsTitle="the latest releases" docsTitle="the latest releases"
className="grid grid-flow-row gap-x-4 gap-y-2 px-4 lg:grid-cols-5" className="grid grid-flow-row px-4 gap-x-4 gap-y-2 lg:grid-cols-5"
> >
<ControlledAutocomplete <ControlledAutocomplete
id="version" id="version"
name="version" name="version"
freeSolo
getOptionLabel={(option) => {
if (typeof option === 'string') {
return option || '';
}
return option.value;
}}
autoHighlight autoHighlight
isOptionEqualToValue={() => false} isOptionEqualToValue={() => false}
filterOptions={(options, { inputValue }) => { filterOptions={(options, { inputValue }) => {

View File

@@ -3,12 +3,12 @@ import { Button } from '@/components/ui/v2/Button';
import { Checkbox } from '@/components/ui/v2/Checkbox'; import { Checkbox } from '@/components/ui/v2/Checkbox';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { type RunService } from '@/hooks/useRunServices';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
useDeleteRunServiceConfigMutation, useDeleteRunServiceConfigMutation,
useDeleteRunServiceMutation, useDeleteRunServiceMutation,
} from '@/utils/__generated__/graphql'; } from '@/utils/__generated__/graphql';
import { type RunService } from 'pages/[workspaceSlug]/[appSlug]/services';
import { useState } from 'react'; import { useState } from 'react';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';

View File

@@ -91,7 +91,7 @@ export default function useCurrentWorkspaceAndProject(): UseCurrentWorkspaceAndP
// Return a default project if working locally // Return a default project if working locally
if (!isPlatform) { if (!isPlatform) {
const localProject: Project = { const localProject: Project = {
id: 'local', id: '00000000-0000-0000-0000-000000000000',
slug: 'local', slug: 'local',
name: 'local', name: 'local',
appStates: [ appStates: [

View File

@@ -1,9 +1,12 @@
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useGetPlansQuery } from '@/utils/__generated__/graphql'; import { useGetPlansQuery } from '@/utils/__generated__/graphql';
/** /**
* Returns the Pro plan. * Returns the Pro plan.
*/ */
export default function useProPlan() { export default function useProPlan() {
const isPlatform = useIsPlatform();
const { data, ...rest } = useGetPlansQuery({ const { data, ...rest } = useGetPlansQuery({
variables: { variables: {
where: { where: {
@@ -13,6 +16,7 @@ export default function useProPlan() {
}, },
}, },
fetchPolicy: 'cache-first', fetchPolicy: 'cache-first',
skip: !isPlatform,
}); });
return { data: data?.plans?.at(0), ...rest }; return { data: data?.plans?.at(0), ...rest };

View File

@@ -90,14 +90,6 @@ export default function useProjectRoutes() {
icon: <GaugeIcon />, icon: <GaugeIcon />,
disabled: !isPlatform, disabled: !isPlatform,
}, },
{
relativeMainPath: '/settings',
relativePath: '/settings/general',
exact: false,
label: 'Settings',
icon: <CogIcon />,
disabled: !isPlatform || maintenanceActive,
},
]; ];
const allRoutes: ProjectRoute[] = [ const allRoutes: ProjectRoute[] = [
@@ -143,7 +135,6 @@ export default function useProjectRoutes() {
exact: false, exact: false,
label: 'Run', label: 'Run',
icon: <ServicesIcon />, icon: <ServicesIcon />,
disabled: !isPlatform,
}, },
{ {
relativeMainPath: '/ai', relativeMainPath: '/ai',
@@ -153,6 +144,14 @@ export default function useProjectRoutes() {
icon: <AIIcon />, icon: <AIIcon />,
}, },
...nhostRoutes, ...nhostRoutes,
{
relativeMainPath: '/settings',
relativePath: '/settings/general',
exact: false,
label: 'Settings',
icon: <CogIcon />,
disabled: maintenanceActive,
},
]; ];
return { return {

View File

@@ -1,15 +1,19 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { VerifyDomain } from '@/features/projects/custom-domains/settings/components/VerifyDomain'; import { VerifyDomain } from '@/features/projects/custom-domains/settings/components/VerifyDomain';
import { import {
useGetAuthenticationSettingsQuery, useGetAuthenticationSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
type ConfigIngressUpdateInput, type ConfigIngressUpdateInput,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -23,12 +27,18 @@ const validationSchema = Yup.object({
export type AuthDomainFormValues = Yup.InferType<typeof validationSchema>; export type AuthDomainFormValues = Yup.InferType<typeof validationSchema>;
export default function AuthDomain() { export default function AuthDomain() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const [isVerified, setIsVerified] = useState(false); const [isVerified, setIsVerified] = useState(false);
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation(); const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const form = useForm<Yup.InferType<typeof validationSchema>>({ const form = useForm<Yup.InferType<typeof validationSchema>>({
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
@@ -40,6 +50,7 @@ export default function AuthDomain() {
variables: { variables: {
appId: currentProject.id, appId: currentProject.id,
}, },
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { networking } = data?.config?.auth?.resources || {}; const { networking } = data?.config?.auth?.resources || {};
@@ -94,6 +105,18 @@ export default function AuthDomain() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Auth domain is being updated...', loadingMessage: 'Auth domain is being updated...',
@@ -104,6 +127,14 @@ export default function AuthDomain() {
); );
} }
const isDisabled = () => {
if (!isPlatform) {
return !isDirty || maintenanceActive;
}
return !isDirty || maintenanceActive || (!isVerified && !initialValue);
};
return ( return (
<FormProvider {...form}> <FormProvider {...form}>
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
@@ -112,12 +143,11 @@ export default function AuthDomain() {
description="Enter below your custom domain for the authentication service." description="Enter below your custom domain for the authentication service."
slotProps={{ slotProps={{
submitButton: { submitButton: {
disabled: disabled: isDisabled(),
!isDirty || maintenanceActive || (!isVerified && !initialValue),
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
className="grid grid-flow-row gap-x-4 gap-y-4 px-4 lg:grid-cols-5" className="grid grid-flow-row px-4 gap-x-4 gap-y-4 lg:grid-cols-5"
> >
<Input <Input
{...register('auth_fqdn')} {...register('auth_fqdn')}

View File

@@ -1,15 +1,19 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { VerifyDomain } from '@/features/projects/custom-domains/settings/components/VerifyDomain'; import { VerifyDomain } from '@/features/projects/custom-domains/settings/components/VerifyDomain';
import { import {
useGetHasuraSettingsQuery, useGetHasuraSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
type ConfigIngressUpdateInput, type ConfigIngressUpdateInput,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -23,12 +27,18 @@ const validationSchema = Yup.object({
export type HasuraDomainFormValues = Yup.InferType<typeof validationSchema>; export type HasuraDomainFormValues = Yup.InferType<typeof validationSchema>;
export default function HasuraDomain() { export default function HasuraDomain() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const [isVerified, setIsVerified] = useState(false); const [isVerified, setIsVerified] = useState(false);
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation(); const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const form = useForm<Yup.InferType<typeof validationSchema>>({ const form = useForm<Yup.InferType<typeof validationSchema>>({
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
@@ -40,6 +50,7 @@ export default function HasuraDomain() {
variables: { variables: {
appId: currentProject.id, appId: currentProject.id,
}, },
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { networking } = data?.config?.hasura?.resources || {}; const { networking } = data?.config?.hasura?.resources || {};
@@ -96,6 +107,18 @@ export default function HasuraDomain() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Hasura domain is being updated...', loadingMessage: 'Hasura domain is being updated...',
@@ -106,6 +129,14 @@ export default function HasuraDomain() {
); );
} }
const isDisabled = () => {
if (!isPlatform) {
return !isDirty || maintenanceActive;
}
return !isDirty || maintenanceActive || (!isVerified && !initialValue);
};
return ( return (
<FormProvider {...form}> <FormProvider {...form}>
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
@@ -114,12 +145,11 @@ export default function HasuraDomain() {
description="Enter below your custom domain for the Hasura/GraphQL service." description="Enter below your custom domain for the Hasura/GraphQL service."
slotProps={{ slotProps={{
submitButton: { submitButton: {
disabled: disabled: isDisabled(),
!isDirty || maintenanceActive || (!isVerified && !initialValue),
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
className="grid grid-flow-row gap-x-4 gap-y-4 px-4 lg:grid-cols-5" className="grid grid-flow-row px-4 gap-x-4 gap-y-4 lg:grid-cols-5"
> >
<Input <Input
{...register('hasura_fqdn')} {...register('hasura_fqdn')}

View File

@@ -5,29 +5,12 @@ import { Link } from '@/components/ui/v2/Link';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { RunServicePortDomain } from '@/features/projects/custom-domains/settings/components/RunServicePortDomain'; import { RunServicePortDomain } from '@/features/projects/custom-domains/settings/components/RunServicePortDomain';
import { useGetRunServicesQuery } from '@/utils/__generated__/graphql'; import { useRunServices } from '@/hooks/useRunServices';
import { useMemo } from 'react';
export default function RunServiceDomains() { export default function RunServiceDomains() {
const { currentProject, currentWorkspace } = useCurrentWorkspaceAndProject(); const { currentProject, currentWorkspace } = useCurrentWorkspaceAndProject();
const { const { services, loading } = useRunServices();
data,
loading,
// refetch: refetchServices, // TODO refetch after update
} = useGetRunServicesQuery({
variables: {
appID: currentProject.id,
resolve: false,
limit: 1000, // TODO consider pagination
offset: 0,
},
});
const services = useMemo(
() => data?.app?.runServices.map((service) => service) ?? [],
[data],
);
if (loading) { if (loading) {
return ( return (
@@ -45,7 +28,7 @@ export default function RunServiceDomains() {
.filter((service) => service.config?.ports?.length > 0) .filter((service) => service.config?.ports?.length > 0)
.map((service) => ( .map((service) => (
<SettingsContainer <SettingsContainer
key={service.id} key={service.id ?? service.serviceID}
title={ title={
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
<Text className="text-lg font-semibold"> <Text className="text-lg font-semibold">
@@ -58,7 +41,7 @@ export default function RunServiceDomains() {
underline="hover" underline="hover"
className="font-medium" className="font-medium"
> >
<ArrowSquareOutIcon className="mb-1 ml-1 h-4 w-4" /> <ArrowSquareOutIcon className="w-4 h-4 mb-1 ml-1" />
</Link> </Link>
</div> </div>
} }
@@ -72,7 +55,7 @@ export default function RunServiceDomains() {
className: 'hidden', className: 'hidden',
}, },
}} }}
className="grid gap-y-4 gap-x-4 px-4" className="grid px-4 gap-x-4 gap-y-4"
> >
{service.config?.ports?.map((port) => ( {service.config?.ports?.map((port) => (
<RunServicePortDomain <RunServicePortDomain

View File

@@ -1,14 +1,18 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { Button } from '@/components/ui/v2/Button'; import { Button } from '@/components/ui/v2/Button';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { VerifyDomain } from '@/features/projects/custom-domains/settings/components/VerifyDomain'; import { VerifyDomain } from '@/features/projects/custom-domains/settings/components/VerifyDomain';
import { useUpdateRunServiceConfigMutation } from '@/generated/graphql'; import { useUpdateRunServiceConfigMutation } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { type RunService } from '@/hooks/useRunServices';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { type RunService } from 'pages/[workspaceSlug]/[appSlug]/services';
import { useState } from 'react'; import { useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -28,12 +32,17 @@ export default function RunServicePortDomain({
service, service,
port, port,
}: RunServicePortProps) { }: RunServicePortProps) {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [isVerified, setIsVerified] = useState(false); const [isVerified, setIsVerified] = useState(false);
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateRunServiceConfig] = useUpdateRunServiceConfigMutation(); const [updateRunServiceConfig] = useUpdateRunServiceConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const runServicePort = service.config.ports.find((p) => p.port === port); const runServicePort = service.config.ports.find((p) => p.port === port);
const initialValue = runServicePort?.ingresses?.[0]?.fqdn?.[0]; const initialValue = runServicePort?.ingresses?.[0]?.fqdn?.[0];
@@ -59,7 +68,7 @@ export default function RunServicePortDomain({
await updateRunServiceConfig({ await updateRunServiceConfig({
variables: { variables: {
appID: currentProject.id, appID: currentProject.id,
serviceID: service.id, serviceID: service.id ?? service.serviceID,
config: { config: {
ports: service?.config?.ports?.map((p) => { ports: service?.config?.ports?.map((p) => {
// exclude the `__typename` because the mutation will fail otherwise // exclude the `__typename` because the mutation will fail otherwise
@@ -78,7 +87,7 @@ export default function RunServicePortDomain({
return { return {
...rest, ...rest,
// exclude the `__typename` because the mutation will fail otherwise // exclude the `__typename` because the mutation will fail otherwise
ingresses: rest.ingresses.map((item) => ({ ingresses: rest?.ingresses?.map((item) => ({
fqdn: item.fqdn, fqdn: item.fqdn,
})), })),
}; };
@@ -88,6 +97,18 @@ export default function RunServicePortDomain({
}); });
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: `Port ${port} is being updated...`, loadingMessage: `Port ${port} is being updated...`,
@@ -99,6 +120,16 @@ export default function RunServicePortDomain({
setLoading(false); setLoading(false);
} }
const isDisabled = () => {
if (!isPlatform) {
return loading || !isDirty || maintenanceActive;
}
return (
loading || !isDirty || maintenanceActive || (!isVerified && !initialValue)
);
};
return ( return (
<FormProvider {...form}> <FormProvider {...form}>
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
@@ -121,16 +152,7 @@ export default function RunServicePortDomain({
inputRoot: { min: 1, max: 100 }, inputRoot: { min: 1, max: 100 },
}} }}
/> />
<Button <Button variant="outlined" type="submit" disabled={isDisabled()}>
variant="outlined"
type="submit"
disabled={
loading ||
!isDirty ||
maintenanceActive ||
(!isVerified && !initialValue)
}
>
Save Save
</Button> </Button>
</div> </div>

View File

@@ -1,15 +1,19 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { VerifyDomain } from '@/features/projects/custom-domains/settings/components/VerifyDomain'; import { VerifyDomain } from '@/features/projects/custom-domains/settings/components/VerifyDomain';
import { import {
useGetServerlessFunctionsSettingsQuery, useGetServerlessFunctionsSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
type ConfigIngressUpdateInput, type ConfigIngressUpdateInput,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -25,12 +29,17 @@ export type ServerlessFunctionsDomainFormValues = Yup.InferType<
>; >;
export default function ServerlessFunctionsDomain() { export default function ServerlessFunctionsDomain() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const [isVerified, setIsVerified] = useState(false); const [isVerified, setIsVerified] = useState(false);
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation(); const [updateConfig] = useUpdateConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const form = useForm<ServerlessFunctionsDomainFormValues>({ const form = useForm<ServerlessFunctionsDomainFormValues>({
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
@@ -42,6 +51,7 @@ export default function ServerlessFunctionsDomain() {
variables: { variables: {
appId: currentProject.id, appId: currentProject.id,
}, },
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { networking } = data?.config?.functions?.resources || {}; const { networking } = data?.config?.functions?.resources || {};
@@ -98,6 +108,18 @@ export default function ServerlessFunctionsDomain() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Serverless Functions domain is being updated...', loadingMessage: 'Serverless Functions domain is being updated...',
@@ -109,6 +131,14 @@ export default function ServerlessFunctionsDomain() {
); );
} }
const isDisabled = () => {
if (!isPlatform) {
return !isDirty || maintenanceActive;
}
return !isDirty || maintenanceActive || (!isVerified && !initialValue);
};
return ( return (
<FormProvider {...form}> <FormProvider {...form}>
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
@@ -117,12 +147,11 @@ export default function ServerlessFunctionsDomain() {
description="Enter below your custom domain for Serverless Functions." description="Enter below your custom domain for Serverless Functions."
slotProps={{ slotProps={{
submitButton: { submitButton: {
disabled: disabled: isDisabled(),
!isDirty || maintenanceActive || (!isVerified && !initialValue),
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
className="grid grid-flow-row gap-x-4 gap-y-4 px-4 lg:grid-cols-5" className="grid grid-flow-row px-4 gap-x-4 gap-y-4 lg:grid-cols-5"
> >
<Input <Input
{...register('functions_fqdn')} {...register('functions_fqdn')}

View File

@@ -3,6 +3,7 @@ import { Button } from '@/components/ui/v2/Button';
import { IconButton } from '@/components/ui/v2/IconButton'; import { IconButton } from '@/components/ui/v2/IconButton';
import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon'; import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { useDnsLookupCnameLazyQuery } from '@/utils/__generated__/graphql'; import { useDnsLookupCnameLazyQuery } from '@/utils/__generated__/graphql';
@@ -21,6 +22,7 @@ export default function VerifyDomain({
value, value,
onHostNameVerified, onHostNameVerified,
}: VerifyDomainProps) { }: VerifyDomainProps) {
const isPlatform = useIsPlatform();
const [verificationFailed, setVerificationFailed] = useState(false); const [verificationFailed, setVerificationFailed] = useState(false);
const [verificationSucceeded, setVerificationSucceeded] = useState(false); const [verificationSucceeded, setVerificationSucceeded] = useState(false);
@@ -72,8 +74,11 @@ export default function VerifyDomain({
backgroundColor: 'success.light', backgroundColor: 'success.light',
color: 'success.dark', color: 'success.dark',
}, },
!isPlatform && {
backgroundColor: 'grey.300',
},
]} ]}
className="flex flex-col space-y-4 rounded-md p-4" className="flex flex-col p-4 space-y-4 rounded-md"
> >
<div className="flex flex-row items-center justify-between"> <div className="flex flex-row items-center justify-between">
{!verificationFailed && !verificationSucceeded && ( {!verificationFailed && !verificationSucceeded && (
@@ -110,23 +115,29 @@ export default function VerifyDomain({
</div> </div>
<div className="flex flex-row space-x-2"> <div className="flex flex-row space-x-2">
<Text>Value:</Text> <Text>Value:</Text>
<Text className="font-bold">{value}</Text> {isPlatform ? (
<IconButton <>
aria-label="Copy Personal Access Token" <Text className="font-bold">{value}</Text>
variant="borderless" <IconButton
color="secondary" aria-label="Copy Personal Access Token"
onClick={() => copy(value, 'CNAME Value')} variant="borderless"
> color="secondary"
<CopyIcon className="h-4 w-4" /> onClick={() => copy(value, 'CNAME Value')}
</IconButton> >
<CopyIcon className="w-4 h-4" />
</IconButton>
</>
) : null}
</div> </div>
<Button {isPlatform ? (
disabled={loading || !hostname} <Button
onClick={handleVerifyDomain} disabled={loading || !hostname || isPlatform}
className="mt-4 sm:absolute sm:bottom-0 sm:right-0 sm:mt-0" onClick={handleVerifyDomain}
> className="mt-4 sm:absolute sm:bottom-0 sm:right-0 sm:mt-0"
Verify >
</Button> Verify
</Button>
) : null}
</div> </div>
</Box> </Box>
); );

View File

@@ -1,5 +1,8 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import type { import type {
BaseEnvironmentVariableFormProps, BaseEnvironmentVariableFormProps,
BaseEnvironmentVariableFormValues, BaseEnvironmentVariableFormValues,
@@ -8,6 +11,7 @@ import {
BaseEnvironmentVariableForm, BaseEnvironmentVariableForm,
baseEnvironmentVariableFormValidationSchema, baseEnvironmentVariableFormValidationSchema,
} from '@/features/projects/environmentVariables/settings/components/BaseEnvironmentVariableForm'; } from '@/features/projects/environmentVariables/settings/components/BaseEnvironmentVariableForm';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
GetEnvironmentVariablesDocument, GetEnvironmentVariablesDocument,
@@ -22,13 +26,17 @@ export interface CreateEnvironmentVariableFormProps
/** /**
* Function to be called when the form is submitted. * Function to be called when the form is submitted.
*/ */
onSubmit?: () => Promise<void>; onSubmit?: () => Promise<any>;
} }
export default function CreateEnvironmentVariableForm({ export default function CreateEnvironmentVariableForm({
onSubmit, onSubmit,
...props ...props
}: CreateEnvironmentVariableFormProps) { }: CreateEnvironmentVariableFormProps) {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const form = useForm<BaseEnvironmentVariableFormValues>({ const form = useForm<BaseEnvironmentVariableFormValues>({
defaultValues: { defaultValues: {
name: '', name: '',
@@ -42,13 +50,14 @@ export default function CreateEnvironmentVariableForm({
const { data, loading, error } = useGetEnvironmentVariablesQuery({ const { data, loading, error } = useGetEnvironmentVariablesQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const availableEnvironmentVariables = data?.config?.global?.environment || []; const availableEnvironmentVariables = data?.config?.global?.environment || [];
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetEnvironmentVariablesDocument], refetchQueries: [GetEnvironmentVariablesDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (loading) { if (loading) {
@@ -103,7 +112,19 @@ export default function CreateEnvironmentVariableForm({
await execPromiseWithErrorToast( await execPromiseWithErrorToast(
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
onSubmit?.(); await onSubmit?.();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Creating environment variable...', loadingMessage: 'Creating environment variable...',

View File

@@ -1,5 +1,8 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import type { import type {
BaseEnvironmentVariableFormProps, BaseEnvironmentVariableFormProps,
BaseEnvironmentVariableFormValues, BaseEnvironmentVariableFormValues,
@@ -8,6 +11,7 @@ import {
BaseEnvironmentVariableForm, BaseEnvironmentVariableForm,
baseEnvironmentVariableFormValidationSchema, baseEnvironmentVariableFormValidationSchema,
} from '@/features/projects/environmentVariables/settings/components/BaseEnvironmentVariableForm'; } from '@/features/projects/environmentVariables/settings/components/BaseEnvironmentVariableForm';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import type { EnvironmentVariable } from '@/types/application'; import type { EnvironmentVariable } from '@/types/application';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
@@ -27,7 +31,7 @@ export interface EditEnvironmentVariableFormProps
/** /**
* Function to be called when the form is submitted. * Function to be called when the form is submitted.
*/ */
onSubmit?: () => Promise<void>; onSubmit?: () => Promise<any>;
} }
export default function EditEnvironmentVariableForm({ export default function EditEnvironmentVariableForm({
@@ -35,6 +39,10 @@ export default function EditEnvironmentVariableForm({
onSubmit, onSubmit,
...props ...props
}: EditEnvironmentVariableFormProps) { }: EditEnvironmentVariableFormProps) {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const form = useForm<BaseEnvironmentVariableFormValues>({ const form = useForm<BaseEnvironmentVariableFormValues>({
defaultValues: { defaultValues: {
id: originalEnvironmentVariable.id || '', id: originalEnvironmentVariable.id || '',
@@ -49,13 +57,14 @@ export default function EditEnvironmentVariableForm({
const { data, loading, error } = useGetEnvironmentVariablesQuery({ const { data, loading, error } = useGetEnvironmentVariablesQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const availableEnvironmentVariables = data?.config?.global?.environment || []; const availableEnvironmentVariables = data?.config?.global?.environment || [];
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetEnvironmentVariablesDocument], refetchQueries: [GetEnvironmentVariablesDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (loading) { if (loading) {
@@ -117,7 +126,19 @@ export default function EditEnvironmentVariableForm({
await execPromiseWithErrorToast( await execPromiseWithErrorToast(
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
onSubmit?.(); await onSubmit?.();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Updating environment variable...', loadingMessage: 'Updating environment variable...',

View File

@@ -3,6 +3,8 @@ import { Form } from '@/components/form/Form';
import { Button } from '@/components/ui/v2/Button'; import { Button } from '@/components/ui/v2/Button';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import type { DialogFormProps } from '@/types/common'; import type { DialogFormProps } from '@/types/common';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
@@ -63,9 +65,12 @@ export default function EditJwtSecretForm({
submitButtonText = 'Save', submitButtonText = 'Save',
location, location,
}: EditJwtSecretFormProps) { }: EditJwtSecretFormProps) {
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetEnvironmentVariablesDocument], refetchQueries: [GetEnvironmentVariablesDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { onDirtyStateChange } = useDialog(); const { onDirtyStateChange } = useDialog();
@@ -118,9 +123,9 @@ export default function EditJwtSecretForm({
<FormProvider {...form}> <FormProvider {...form}>
<Form <Form
onSubmit={handleSubmit} onSubmit={handleSubmit}
className="flex flex-auto flex-col content-between overflow-hidden pb-4" className="flex flex-col content-between flex-auto pb-4 overflow-hidden"
> >
<div className="flex-auto overflow-y-auto px-6"> <div className="flex-auto px-6 overflow-y-auto">
<Input <Input
{...register('jwtSecret')} {...register('jwtSecret')}
error={Boolean(errors.jwtSecret?.message)} error={Boolean(errors.jwtSecret?.message)}

View File

@@ -1,3 +1,4 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -13,8 +14,10 @@ import { List } from '@/components/ui/v2/List';
import { ListItem } from '@/components/ui/v2/ListItem'; import { ListItem } from '@/components/ui/v2/ListItem';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { CreateEnvironmentVariableForm } from '@/features/projects/environmentVariables/settings/components/CreateEnvironmentVariableForm'; import { CreateEnvironmentVariableForm } from '@/features/projects/environmentVariables/settings/components/CreateEnvironmentVariableForm';
import { EditEnvironmentVariableForm } from '@/features/projects/environmentVariables/settings/components/EditEnvironmentVariableForm'; import { EditEnvironmentVariableForm } from '@/features/projects/environmentVariables/settings/components/EditEnvironmentVariableForm';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import type { EnvironmentVariable } from '@/types/application'; import type { EnvironmentVariable } from '@/types/application';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
@@ -33,12 +36,14 @@ export interface EnvironmentVariableSettingsFormValues {
} }
export default function EnvironmentVariableSettings() { export default function EnvironmentVariableSettings() {
const { openDialog, openAlertDialog } = useDialog(); const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { openDialog, openAlertDialog } = useDialog();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { data, loading, error } = useGetEnvironmentVariablesQuery({ const { data, loading, error, refetch } = useGetEnvironmentVariablesQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const availableEnvironmentVariables = [ const availableEnvironmentVariables = [
@@ -57,6 +62,7 @@ export default function EnvironmentVariableSettings() {
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetEnvironmentVariablesDocument], refetchQueries: [GetEnvironmentVariablesDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (loading) { if (loading) {
@@ -92,6 +98,18 @@ export default function EnvironmentVariableSettings() {
await execPromiseWithErrorToast( await execPromiseWithErrorToast(
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Deleting environment variable...', loadingMessage: 'Deleting environment variable...',
@@ -105,7 +123,7 @@ export default function EnvironmentVariableSettings() {
function handleOpenCreator() { function handleOpenCreator() {
openDialog({ openDialog({
title: 'Create Environment Variable', title: 'Create Environment Variable',
component: <CreateEnvironmentVariableForm />, component: <CreateEnvironmentVariableForm onSubmit={refetch} />,
props: { props: {
titleProps: { className: '!pb-0' }, titleProps: { className: '!pb-0' },
PaperProps: { className: 'gap-2 max-w-sm' }, PaperProps: { className: 'gap-2 max-w-sm' },
@@ -119,6 +137,7 @@ export default function EnvironmentVariableSettings() {
component: ( component: (
<EditEnvironmentVariableForm <EditEnvironmentVariableForm
originalEnvironmentVariable={originalVariable} originalEnvironmentVariable={originalVariable}
onSubmit={refetch}
/> />
), ),
props: { props: {
@@ -159,7 +178,7 @@ export default function EnvironmentVariableSettings() {
)} )}
slotProps={{ submitButton: { className: 'hidden' } }} slotProps={{ submitButton: { className: 'hidden' } }}
> >
<Box className="grid grid-cols-2 gap-2 border-b-1 px-4 py-3 lg:grid-cols-3"> <Box className="grid grid-cols-2 gap-2 px-4 py-3 border-b-1 lg:grid-cols-3">
<Text className="font-medium">Variable Name</Text> <Text className="font-medium">Variable Name</Text>
</Box> </Box>
@@ -175,7 +194,7 @@ export default function EnvironmentVariableSettings() {
<Dropdown.Trigger <Dropdown.Trigger
asChild asChild
hideChevron hideChevron
className="absolute right-4 top-1/2 -translate-y-1/2" className="absolute -translate-y-1/2 right-4 top-1/2"
> >
<IconButton <IconButton
variant="borderless" variant="borderless"

View File

@@ -21,11 +21,15 @@ import {
} from '@/features/projects/common/utils/generateAppServiceUrl'; } from '@/features/projects/common/utils/generateAppServiceUrl';
import { EditJwtSecretForm } from '@/features/projects/environmentVariables/settings/components/EditJwtSecretForm'; import { EditJwtSecretForm } from '@/features/projects/environmentVariables/settings/components/EditJwtSecretForm';
import { getJwtSecretsWithoutFalsyValues } from '@/features/projects/environmentVariables/settings/utils/getJwtSecretsWithoutFalsyValues'; import { getJwtSecretsWithoutFalsyValues } from '@/features/projects/environmentVariables/settings/utils/getJwtSecretsWithoutFalsyValues';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { getHasuraConsoleServiceUrl } from '@/utils/env'; import { getHasuraConsoleServiceUrl } from '@/utils/env';
import { useGetEnvironmentVariablesQuery } from '@/utils/__generated__/graphql'; import { useGetEnvironmentVariablesQuery } from '@/utils/__generated__/graphql';
import { Fragment, useState } from 'react'; import { Fragment, useState } from 'react';
export default function SystemEnvironmentVariableSettings() { export default function SystemEnvironmentVariableSettings() {
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const [showAdminSecret, setShowAdminSecret] = useState(false); const [showAdminSecret, setShowAdminSecret] = useState(false);
const [showWebhookSecret, setShowWebhookSecret] = useState(false); const [showWebhookSecret, setShowWebhookSecret] = useState(false);
@@ -34,7 +38,7 @@ export default function SystemEnvironmentVariableSettings() {
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { data, loading, error } = useGetEnvironmentVariablesQuery({ const { data, loading, error } = useGetEnvironmentVariablesQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { jwtSecrets, webhookSecret, adminSecret } = data?.config?.hasura || {}; const { jwtSecrets, webhookSecret, adminSecret } = data?.config?.hasura || {};
@@ -46,8 +50,6 @@ export default function SystemEnvironmentVariableSettings() {
? JSON.stringify(jwtSecretsWithoutFalsyValues[0], null, 2) ? JSON.stringify(jwtSecretsWithoutFalsyValues[0], null, 2)
: JSON.stringify(jwtSecretsWithoutFalsyValues, null, 2); : JSON.stringify(jwtSecretsWithoutFalsyValues, null, 2);
const isPlatform = useIsPlatform();
const appClient = useAppClient(); const appClient = useAppClient();
if (loading) { if (loading) {
@@ -124,10 +126,10 @@ export default function SystemEnvironmentVariableSettings() {
description="System environment variables are automatically generated from the configuration file and your project's subdomain and region." description="System environment variables are automatically generated from the configuration file and your project's subdomain and region."
docsLink="https://docs.nhost.io/platform/environment-variables#system-environment-variables" docsLink="https://docs.nhost.io/platform/environment-variables#system-environment-variables"
rootClassName="gap-0" rootClassName="gap-0"
className="mt-2 mb-2.5 px-0" className="mb-2.5 mt-2 px-0"
slotProps={{ submitButton: { className: 'hidden' } }} slotProps={{ submitButton: { className: 'hidden' } }}
> >
<Box className="grid grid-cols-3 gap-2 border-b-1 px-4 py-3"> <Box className="grid grid-cols-3 gap-2 px-4 py-3 border-b-1">
<Text className="font-medium">Variable Name</Text> <Text className="font-medium">Variable Name</Text>
<Text className="font-medium lg:col-span-2">Value</Text> <Text className="font-medium lg:col-span-2">Value</Text>
</Box> </Box>
@@ -136,7 +138,7 @@ export default function SystemEnvironmentVariableSettings() {
<ListItem.Root className="grid grid-cols-2 gap-2 px-4 lg:grid-cols-3"> <ListItem.Root className="grid grid-cols-2 gap-2 px-4 lg:grid-cols-3">
<ListItem.Text>NHOST_ADMIN_SECRET</ListItem.Text> <ListItem.Text>NHOST_ADMIN_SECRET</ListItem.Text>
<div className="grid grid-flow-col items-center justify-start gap-2 lg:col-span-2"> <div className="grid items-center justify-start grid-flow-col gap-2 lg:col-span-2">
<Text className="truncate" color="secondary"> <Text className="truncate" color="secondary">
{showAdminSecret ? ( {showAdminSecret ? (
<InlineCode className="!text-sm font-medium"> <InlineCode className="!text-sm font-medium">
@@ -156,9 +158,9 @@ export default function SystemEnvironmentVariableSettings() {
onClick={() => setShowAdminSecret((show) => !show)} onClick={() => setShowAdminSecret((show) => !show)}
> >
{showAdminSecret ? ( {showAdminSecret ? (
<EyeOffIcon className="h-5 w-5" /> <EyeOffIcon className="w-5 h-5" />
) : ( ) : (
<EyeIcon className="h-5 w-5" /> <EyeIcon className="w-5 h-5" />
)} )}
</IconButton> </IconButton>
</div> </div>
@@ -169,7 +171,7 @@ export default function SystemEnvironmentVariableSettings() {
<ListItem.Root className="grid grid-cols-2 gap-2 px-4 lg:grid-cols-3"> <ListItem.Root className="grid grid-cols-2 gap-2 px-4 lg:grid-cols-3">
<ListItem.Text>NHOST_WEBHOOK_SECRET</ListItem.Text> <ListItem.Text>NHOST_WEBHOOK_SECRET</ListItem.Text>
<div className="grid grid-flow-col items-center justify-start gap-2 lg:col-span-2"> <div className="grid items-center justify-start grid-flow-col gap-2 lg:col-span-2">
<Text className="truncate" color="secondary"> <Text className="truncate" color="secondary">
{showWebhookSecret ? ( {showWebhookSecret ? (
<InlineCode className="!text-sm font-medium"> <InlineCode className="!text-sm font-medium">
@@ -191,9 +193,9 @@ export default function SystemEnvironmentVariableSettings() {
onClick={() => setShowWebhookSecret((show) => !show)} onClick={() => setShowWebhookSecret((show) => !show)}
> >
{showWebhookSecret ? ( {showWebhookSecret ? (
<EyeOffIcon className="h-5 w-5" /> <EyeOffIcon className="w-5 h-5" />
) : ( ) : (
<EyeIcon className="h-5 w-5" /> <EyeIcon className="w-5 h-5" />
)} )}
</IconButton> </IconButton>
</div> </div>
@@ -217,9 +219,9 @@ export default function SystemEnvironmentVariableSettings() {
</Fragment> </Fragment>
))} ))}
<Divider component="li" className="!mt-4 !mb-2.5" /> <Divider component="li" className="!mb-2.5 !mt-4" />
<ListItem.Root className="grid grid-cols-2 justify-start px-4 lg:grid-cols-3"> <ListItem.Root className="grid justify-start grid-cols-2 px-4 lg:grid-cols-3">
<ListItem.Text>NHOST_JWT_SECRET</ListItem.Text> <ListItem.Text>NHOST_JWT_SECRET</ListItem.Text>
<div className="grid grid-flow-row items-center justify-center gap-1.5 text-center md:grid-flow-col lg:col-span-2 lg:justify-start lg:text-left"> <div className="grid grid-flow-row items-center justify-center gap-1.5 text-center md:grid-flow-col lg:col-span-2 lg:justify-start lg:text-left">

View File

@@ -1,5 +1,8 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import type { import type {
BasePermissionVariableFormProps, BasePermissionVariableFormProps,
BasePermissionVariableFormValues, BasePermissionVariableFormValues,
@@ -9,6 +12,7 @@ import {
basePermissionVariableValidationSchema, basePermissionVariableValidationSchema,
} from '@/features/projects/permissions/settings/components/BasePermissionVariableForm'; } from '@/features/projects/permissions/settings/components/BasePermissionVariableForm';
import { getAllPermissionVariables } from '@/features/projects/permissions/settings/utils/getAllPermissionVariables'; import { getAllPermissionVariables } from '@/features/projects/permissions/settings/utils/getAllPermissionVariables';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
GetRolesPermissionsDocument, GetRolesPermissionsDocument,
@@ -23,18 +27,20 @@ export interface CreatePermissionVariableFormProps
/** /**
* Function to be called when the form is submitted. * Function to be called when the form is submitted.
*/ */
onSubmit?: () => Promise<void>; onSubmit?: () => Promise<any>;
} }
export default function CreatePermissionVariableForm({ export default function CreatePermissionVariableForm({
onSubmit, onSubmit,
...props ...props
}: CreatePermissionVariableFormProps) { }: CreatePermissionVariableFormProps) {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { data, error, loading } = useGetRolesPermissionsQuery({ const { data, error, loading } = useGetRolesPermissionsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { customClaims: permissionVariables } = const { customClaims: permissionVariables } =
@@ -51,6 +57,7 @@ export default function CreatePermissionVariableForm({
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetRolesPermissionsDocument], refetchQueries: [GetRolesPermissionsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (loading) { if (loading) {
@@ -106,6 +113,18 @@ export default function CreatePermissionVariableForm({
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
await onSubmit?.(); await onSubmit?.();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Creating permission variable...', loadingMessage: 'Creating permission variable...',

View File

@@ -1,5 +1,8 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import type { import type {
BasePermissionVariableFormProps, BasePermissionVariableFormProps,
BasePermissionVariableFormValues, BasePermissionVariableFormValues,
@@ -9,6 +12,7 @@ import {
basePermissionVariableValidationSchema, basePermissionVariableValidationSchema,
} from '@/features/projects/permissions/settings/components/BasePermissionVariableForm'; } from '@/features/projects/permissions/settings/components/BasePermissionVariableForm';
import { getAllPermissionVariables } from '@/features/projects/permissions/settings/utils/getAllPermissionVariables'; import { getAllPermissionVariables } from '@/features/projects/permissions/settings/utils/getAllPermissionVariables';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import type { PermissionVariable } from '@/types/application'; import type { PermissionVariable } from '@/types/application';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
@@ -28,7 +32,7 @@ export interface EditPermissionVariableFormProps
/** /**
* Function to be called when the form is submitted. * Function to be called when the form is submitted.
*/ */
onSubmit?: () => Promise<void>; onSubmit?: () => any;
} }
export default function EditPermissionVariableForm({ export default function EditPermissionVariableForm({
@@ -36,11 +40,14 @@ export default function EditPermissionVariableForm({
onSubmit, onSubmit,
...props ...props
}: EditPermissionVariableFormProps) { }: EditPermissionVariableFormProps) {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { data, error, loading } = useGetRolesPermissionsQuery({ const { data, error, loading } = useGetRolesPermissionsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { customClaims: permissionVariables } = const { customClaims: permissionVariables } =
@@ -57,6 +64,7 @@ export default function EditPermissionVariableForm({
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetRolesPermissionsDocument], refetchQueries: [GetRolesPermissionsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (loading) { if (loading) {
@@ -132,6 +140,19 @@ export default function EditPermissionVariableForm({
await execPromiseWithErrorToast( await execPromiseWithErrorToast(
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
await onSubmit?.();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Updating permission variable...', loadingMessage: 'Updating permission variable...',
@@ -140,8 +161,6 @@ export default function EditPermissionVariableForm({
'An error occurred while trying to update the permission variable.', 'An error occurred while trying to update the permission variable.',
}, },
); );
await onSubmit?.();
} }
return ( return (

View File

@@ -1,3 +1,4 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -15,9 +16,11 @@ import { ListItem } from '@/components/ui/v2/ListItem';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip'; import { Tooltip } from '@/components/ui/v2/Tooltip';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { CreatePermissionVariableForm } from '@/features/projects/permissions/settings/components/CreatePermissionVariableForm'; import { CreatePermissionVariableForm } from '@/features/projects/permissions/settings/components/CreatePermissionVariableForm';
import { EditPermissionVariableForm } from '@/features/projects/permissions/settings/components/EditPermissionVariableForm'; import { EditPermissionVariableForm } from '@/features/projects/permissions/settings/components/EditPermissionVariableForm';
import { getAllPermissionVariables } from '@/features/projects/permissions/settings/utils/getAllPermissionVariables'; import { getAllPermissionVariables } from '@/features/projects/permissions/settings/utils/getAllPermissionVariables';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import type { PermissionVariable } from '@/types/application'; import type { PermissionVariable } from '@/types/application';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
@@ -29,13 +32,15 @@ import { Fragment } from 'react';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
export default function PermissionVariableSettings() { export default function PermissionVariableSettings() {
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { openDialog, openAlertDialog } = useDialog(); const { openDialog, openAlertDialog } = useDialog();
const { data, loading, error } = useGetRolesPermissionsQuery({ const { data, loading, error, refetch } = useGetRolesPermissionsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { customClaims: permissionVariables } = const { customClaims: permissionVariables } =
@@ -43,6 +48,7 @@ export default function PermissionVariableSettings() {
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetRolesPermissionsDocument], refetchQueries: [GetRolesPermissionsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (loading) { if (loading) {
@@ -55,6 +61,20 @@ export default function PermissionVariableSettings() {
throw error; throw error;
} }
function showApplyChangesDialog() {
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}
async function handleDeleteVariable({ id }: PermissionVariable) { async function handleDeleteVariable({ id }: PermissionVariable) {
const updateConfigPromise = updateConfig({ const updateConfigPromise = updateConfig({
variables: { variables: {
@@ -79,6 +99,7 @@ export default function PermissionVariableSettings() {
await execPromiseWithErrorToast( await execPromiseWithErrorToast(
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
showApplyChangesDialog();
}, },
{ {
loadingMessage: 'Deleting permission variable...', loadingMessage: 'Deleting permission variable...',
@@ -92,7 +113,7 @@ export default function PermissionVariableSettings() {
function handleOpenCreator() { function handleOpenCreator() {
openDialog({ openDialog({
title: 'Create Permission Variable', title: 'Create Permission Variable',
component: <CreatePermissionVariableForm />, component: <CreatePermissionVariableForm onSubmit={refetch} />,
props: { props: {
titleProps: { className: '!pb-0' }, titleProps: { className: '!pb-0' },
PaperProps: { className: 'max-w-sm' }, PaperProps: { className: 'max-w-sm' },
@@ -104,7 +125,10 @@ export default function PermissionVariableSettings() {
openDialog({ openDialog({
title: 'Edit Permission Variable', title: 'Edit Permission Variable',
component: ( component: (
<EditPermissionVariableForm originalVariable={originalVariable} /> <EditPermissionVariableForm
originalVariable={originalVariable}
onSubmit={refetch}
/>
), ),
props: { props: {
titleProps: { className: '!pb-0' }, titleProps: { className: '!pb-0' },
@@ -140,10 +164,10 @@ export default function PermissionVariableSettings() {
description="Permission variables are used to define permission rules in the GraphQL API." description="Permission variables are used to define permission rules in the GraphQL API."
docsLink="https://docs.nhost.io/guides/api/permissions#permission-variables" docsLink="https://docs.nhost.io/guides/api/permissions#permission-variables"
rootClassName="gap-0" rootClassName="gap-0"
className="my-2 px-0" className="px-0 my-2"
slotProps={{ submitButton: { className: 'hidden' } }} slotProps={{ submitButton: { className: 'hidden' } }}
> >
<Box className="grid grid-cols-2 border-b-1 px-4 py-3"> <Box className="grid grid-cols-2 px-4 py-3 border-b-1">
<Text className="font-medium">Field name</Text> <Text className="font-medium">Field name</Text>
<Text className="font-medium">Path</Text> <Text className="font-medium">Path</Text>
</Box> </Box>
@@ -167,7 +191,7 @@ export default function PermissionVariableSettings() {
!permissionVariable.isSystemVariable !permissionVariable.isSystemVariable
} }
hasDisabledChildren={permissionVariable.isSystemVariable} hasDisabledChildren={permissionVariable.isSystemVariable}
className="absolute right-4 top-1/2 -translate-y-1/2" className="absolute -translate-y-1/2 right-4 top-1/2"
> >
<Dropdown.Trigger asChild hideChevron> <Dropdown.Trigger asChild hideChevron>
<IconButton <IconButton
@@ -224,7 +248,7 @@ export default function PermissionVariableSettings() {
<> <>
X-Hasura-{permissionVariable.key}{' '} X-Hasura-{permissionVariable.key}{' '}
{permissionVariable.isSystemVariable && ( {permissionVariable.isSystemVariable && (
<LockIcon className="h-4 w-4" /> <LockIcon className="w-4 h-4" />
)} )}
</> </>
} }

View File

@@ -1,3 +1,4 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -7,6 +8,7 @@ import { Box } from '@/components/ui/v2/Box';
import { Divider } from '@/components/ui/v2/Divider'; import { Divider } from '@/components/ui/v2/Divider';
import { Link } from '@/components/ui/v2/Link'; import { Link } from '@/components/ui/v2/Link';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useProPlan } from '@/features/projects/common/hooks/useProPlan'; import { useProPlan } from '@/features/projects/common/hooks/useProPlan';
import { ResourcesConfirmationDialog } from '@/features/projects/resources/settings/components/ResourcesConfirmationDialog'; import { ResourcesConfirmationDialog } from '@/features/projects/resources/settings/components/ResourcesConfirmationDialog';
import { ServiceResourcesFormFragment } from '@/features/projects/resources/settings/components/ServiceResourcesFormFragment'; import { ServiceResourcesFormFragment } from '@/features/projects/resources/settings/components/ServiceResourcesFormFragment';
@@ -14,6 +16,7 @@ import { TotalResourcesFormFragment } from '@/features/projects/resources/settin
import { calculateBillableResources } from '@/features/projects/resources/settings/utils/calculateBillableResources'; import { calculateBillableResources } from '@/features/projects/resources/settings/utils/calculateBillableResources';
import type { ResourceSettingsFormValues } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema'; import type { ResourceSettingsFormValues } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import { resourceSettingsValidationSchema } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema'; import { resourceSettingsValidationSchema } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { import {
RESOURCE_VCPU_MULTIPLIER, RESOURCE_VCPU_MULTIPLIER,
RESOURCE_VCPU_PRICE, RESOURCE_VCPU_PRICE,
@@ -44,6 +47,8 @@ function getInitialServiceResources(
} }
export default function ResourcesForm() { export default function ResourcesForm() {
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { openDialog, closeDialog } = useDialog(); const { openDialog, closeDialog } = useDialog();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
@@ -55,6 +60,7 @@ export default function ResourcesForm() {
variables: { variables: {
appId: currentProject?.id, appId: currentProject?.id,
}, },
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { const {
@@ -65,6 +71,7 @@ export default function ResourcesForm() {
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetResourcesDocument], refetchQueries: [GetResourcesDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const initialDatabaseResources = getInitialServiceResources(data, 'postgres'); const initialDatabaseResources = getInitialServiceResources(data, 'postgres');
@@ -113,7 +120,7 @@ export default function ResourcesForm() {
resolver: yupResolver(resourceSettingsValidationSchema), resolver: yupResolver(resourceSettingsValidationSchema),
}); });
if (!proPlan && !proPlanLoading) { if (isPlatform && !proPlan && !proPlanLoading) {
return ( return (
<Alert severity="error"> <Alert severity="error">
Couldn&apos;t load the plan for this project. Please try again. Couldn&apos;t load the plan for this project. Please try again.
@@ -121,7 +128,7 @@ export default function ResourcesForm() {
); );
} }
if (loading || proPlanLoading) { if (isPlatform && (loading || proPlanLoading)) {
return ( return (
<ActivityIndicator <ActivityIndicator
label="Loading resource settings..." label="Loading resource settings..."
@@ -156,9 +163,10 @@ export default function ResourcesForm() {
}, },
); );
const initialPrice = const initialPrice = isPlatform
proPlan.price + ? proPlan.price +
(billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) * RESOURCE_VCPU_PRICE; (billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) * RESOURCE_VCPU_PRICE
: 0;
async function handleSubmit(formValues: ResourceSettingsFormValues) { async function handleSubmit(formValues: ResourceSettingsFormValues) {
const updateConfigPromise = updateConfig({ const updateConfigPromise = updateConfig({
@@ -217,6 +225,18 @@ export default function ResourcesForm() {
await execPromiseWithErrorToast( await execPromiseWithErrorToast(
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Updating resources...', loadingMessage: 'Updating resources...',
@@ -260,7 +280,12 @@ export default function ResourcesForm() {
} }
} }
function handleConfirm(formValues: ResourceSettingsFormValues) { async function handleConfirm(formValues: ResourceSettingsFormValues) {
if (!isPlatform) {
await handleSubmit(formValues);
return;
}
openDialog({ openDialog({
title: formValues.enabled title: formValues.enabled
? 'Confirm Dedicated Resources' ? 'Confirm Dedicated Resources'

View File

@@ -6,6 +6,7 @@ import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
import { Link } from '@/components/ui/v2/Link'; import { Link } from '@/components/ui/v2/Link';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip'; import { Tooltip } from '@/components/ui/v2/Tooltip';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useProPlan } from '@/features/projects/common/hooks/useProPlan'; import { useProPlan } from '@/features/projects/common/hooks/useProPlan';
import { calculateBillableResources } from '@/features/projects/resources/settings/utils/calculateBillableResources'; import { calculateBillableResources } from '@/features/projects/resources/settings/utils/calculateBillableResources';
import type { ResourceSettingsFormValues } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema'; import type { ResourceSettingsFormValues } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
@@ -16,6 +17,8 @@ import {
import { useFormState, useWatch } from 'react-hook-form'; import { useFormState, useWatch } from 'react-hook-form';
export default function ResourcesFormFooter() { export default function ResourcesFormFooter() {
const isPlatform = useIsPlatform();
const { const {
data: proPlan, data: proPlan,
loading: proPlanLoading, loading: proPlanLoading,
@@ -63,17 +66,27 @@ export default function ResourcesFormFooter() {
}, },
); );
const updatedPrice = enabled const computeUpdatedPrice = () => {
? Math.max( if (!isPlatform) {
priceForTotalAvailableVCPU, return 0;
(billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) * }
RESOURCE_VCPU_PRICE,
) + proPlan.price if (enabled) {
: proPlan.price; return (
Math.max(
priceForTotalAvailableVCPU,
(billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) *
RESOURCE_VCPU_PRICE,
) + proPlan.price
);
}
return proPlan.price;
};
return ( return (
<Box <Box
className="grid items-center gap-4 border-t px-4 pt-4 lg:grid-flow-col lg:justify-between lg:gap-2" className="grid items-center gap-4 px-4 pt-4 border-t lg:grid-flow-col lg:justify-between lg:gap-2"
component="footer" component="footer"
> >
<Text> <Text>
@@ -86,20 +99,22 @@ export default function ResourcesFormFooter() {
className="font-medium" className="font-medium"
> >
Compute Resources Compute Resources
<ArrowSquareOutIcon className="ml-1 h-4 w-4" /> <ArrowSquareOutIcon className="w-4 h-4 ml-1" />
</Link> </Link>
</Text> </Text>
{(enabled || isDirty) && ( {(enabled || isDirty) && (
<Box className="grid grid-flow-col items-center justify-between gap-4"> <Box className="grid items-center justify-between grid-flow-col gap-4">
<Box className="grid grid-flow-col items-center gap-1.5"> <Box className="grid grid-flow-col items-center gap-1.5">
<Text> <Text>
Approximate cost:{' '} Approximate cost:{' '}
<span className="font-medium">${updatedPrice.toFixed(2)}/mo</span> <span className="font-medium">
${computeUpdatedPrice().toFixed(2)}/mo
</span>
</Text> </Text>
<Tooltip title="$0.0012/minute for every 1 vCPU and 2 GiB of RAM"> <Tooltip title="$0.0012/minute for every 1 vCPU and 2 GiB of RAM">
<InfoIcon aria-label="Info" className="h-4 w-4" color="primary" /> <InfoIcon aria-label="Info" className="w-4 h-4" color="primary" />
</Tooltip> </Tooltip>
</Box> </Box>

View File

@@ -3,6 +3,7 @@ import { Box } from '@/components/ui/v2/Box';
import { ArrowRightIcon } from '@/components/ui/v2/icons/ArrowRightIcon'; import { ArrowRightIcon } from '@/components/ui/v2/icons/ArrowRightIcon';
import { Slider, sliderClasses } from '@/components/ui/v2/Slider'; import { Slider, sliderClasses } from '@/components/ui/v2/Slider';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useProPlan } from '@/features/projects/common/hooks/useProPlan'; import { useProPlan } from '@/features/projects/common/hooks/useProPlan';
import { getAllocatedResources } from '@/features/projects/resources/settings/utils/getAllocatedResources'; import { getAllocatedResources } from '@/features/projects/resources/settings/utils/getAllocatedResources';
import { prettifyMemory } from '@/features/projects/resources/settings/utils/prettifyMemory'; import { prettifyMemory } from '@/features/projects/resources/settings/utils/prettifyMemory';
@@ -38,6 +39,8 @@ const StyledAvailableCpuSlider = styled(Slider)(({ theme }) => ({
export default function TotalResourcesFormFragment({ export default function TotalResourcesFormFragment({
initialPrice, initialPrice,
}: TotalResourcesFormFragmentProps) { }: TotalResourcesFormFragmentProps) {
const isPlatform = useIsPlatform();
const { const {
data: proPlan, data: proPlan,
error: proPlanError, error: proPlanError,
@@ -46,7 +49,7 @@ export default function TotalResourcesFormFragment({
const { setValue } = useFormContext<ResourceSettingsFormValues>(); const { setValue } = useFormContext<ResourceSettingsFormValues>();
const formValues = useWatch<ResourceSettingsFormValues>(); const formValues = useWatch<ResourceSettingsFormValues>();
if (!proPlan && !proPlanLoading) { if (isPlatform && !proPlan && !proPlanLoading) {
return ( return (
<Alert severity="error"> <Alert severity="error">
Couldn&apos;t load the plan for this projectee. Please try again. Couldn&apos;t load the plan for this projectee. Please try again.
@@ -62,7 +65,9 @@ export default function TotalResourcesFormFragment({
(formValues.totalAvailableVCPU / RESOURCE_VCPU_MULTIPLIER) * (formValues.totalAvailableVCPU / RESOURCE_VCPU_MULTIPLIER) *
RESOURCE_VCPU_PRICE; RESOURCE_VCPU_PRICE;
const updatedPrice = priceForTotalAvailableVCPU + proPlan.price; const updatedPrice = isPlatform
? priceForTotalAvailableVCPU + proPlan.price
: 0;
const { vcpu: allocatedVCPU, memory: allocatedMemory } = const { vcpu: allocatedVCPU, memory: allocatedMemory } =
getAllocatedResources(formValues); getAllocatedResources(formValues);
@@ -102,8 +107,8 @@ export default function TotalResourcesFormFragment({
return ( return (
<Box className="px-4 pb-4"> <Box className="px-4 pb-4">
<Box className="rounded-md border"> <Box className="border rounded-md">
<Box className="flex flex-col gap-4 bg-transparent p-4"> <Box className="flex flex-col gap-4 p-4 bg-transparent">
<Box className="flex flex-row items-center justify-between gap-4"> <Box className="flex flex-row items-center justify-between gap-4">
<Text color="secondary"> <Text color="secondary">
Total available compute for your project: Total available compute for your project:
@@ -151,7 +156,7 @@ export default function TotalResourcesFormFragment({
severity={ severity={
hasUnusedResources || hasOverallocatedResources ? 'warning' : 'info' hasUnusedResources || hasOverallocatedResources ? 'warning' : 'info'
} }
className="grid grid-flow-row gap-2 rounded-t-none rounded-b-[5px] text-left" className="grid grid-flow-row gap-2 rounded-b-[5px] rounded-t-none text-left"
> >
{hasUnusedResources && !hasOverallocatedResources && ( {hasUnusedResources && !hasOverallocatedResources && (
<> <>

View File

@@ -1,5 +1,8 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import type { import type {
BaseRoleFormProps, BaseRoleFormProps,
BaseRoleFormValues, BaseRoleFormValues,
@@ -9,6 +12,7 @@ import {
baseRoleFormValidationSchema, baseRoleFormValidationSchema,
} from '@/features/projects/roles/settings/components/BaseRoleForm'; } from '@/features/projects/roles/settings/components/BaseRoleForm';
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles'; import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
GetRolesPermissionsDocument, GetRolesPermissionsDocument,
@@ -23,17 +27,20 @@ export interface CreateRoleFormProps
/** /**
* Function to be called when the form is submitted. * Function to be called when the form is submitted.
*/ */
onSubmit?: () => Promise<void>; onSubmit?: () => Promise<any>;
} }
export default function CreateRoleForm({ export default function CreateRoleForm({
onSubmit, onSubmit,
...props ...props
}: CreateRoleFormProps) { }: CreateRoleFormProps) {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { data, loading, error } = useGetRolesPermissionsQuery({ const { data, loading, error } = useGetRolesPermissionsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { allowed: allowedRoles } = data?.config?.auth?.user?.roles || {}; const { allowed: allowedRoles } = data?.config?.auth?.user?.roles || {};
@@ -45,6 +52,7 @@ export default function CreateRoleForm({
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetRolesPermissionsDocument], refetchQueries: [GetRolesPermissionsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (loading) { if (loading) {
@@ -85,7 +93,19 @@ export default function CreateRoleForm({
await execPromiseWithErrorToast( await execPromiseWithErrorToast(
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
onSubmit?.(); await onSubmit?.();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Creating role...', loadingMessage: 'Creating role...',

View File

@@ -1,5 +1,8 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import type { import type {
BaseRoleFormProps, BaseRoleFormProps,
BaseRoleFormValues, BaseRoleFormValues,
@@ -9,6 +12,7 @@ import {
baseRoleFormValidationSchema, baseRoleFormValidationSchema,
} from '@/features/projects/roles/settings/components/BaseRoleForm'; } from '@/features/projects/roles/settings/components/BaseRoleForm';
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles'; import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import type { Role } from '@/types/application'; import type { Role } from '@/types/application';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
@@ -28,7 +32,7 @@ export interface EditRoleFormProps
/** /**
* Function to be called when the form is submitted. * Function to be called when the form is submitted.
*/ */
onSubmit?: () => Promise<void>; onSubmit?: () => Promise<any>;
} }
export default function EditRoleForm({ export default function EditRoleForm({
@@ -36,10 +40,13 @@ export default function EditRoleForm({
onSubmit, onSubmit,
...props ...props
}: EditRoleFormProps) { }: EditRoleFormProps) {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { data, loading, error } = useGetRolesPermissionsQuery({ const { data, loading, error } = useGetRolesPermissionsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { allowed: allowedRoles, default: defaultRole } = const { allowed: allowedRoles, default: defaultRole } =
@@ -55,6 +62,7 @@ export default function EditRoleForm({
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetRolesPermissionsDocument], refetchQueries: [GetRolesPermissionsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (loading) { if (loading) {
@@ -114,7 +122,19 @@ export default function EditRoleForm({
await execPromiseWithErrorToast( await execPromiseWithErrorToast(
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
onSubmit?.(); await onSubmit?.();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Updating role...', loadingMessage: 'Updating role...',

View File

@@ -1,3 +1,4 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
@@ -15,9 +16,11 @@ import { List } from '@/components/ui/v2/List';
import { ListItem } from '@/components/ui/v2/ListItem'; import { ListItem } from '@/components/ui/v2/ListItem';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { CreateRoleForm } from '@/features/projects/roles/settings/components/CreateRoleForm'; import { CreateRoleForm } from '@/features/projects/roles/settings/components/CreateRoleForm';
import { EditRoleForm } from '@/features/projects/roles/settings/components/EditRoleForm'; import { EditRoleForm } from '@/features/projects/roles/settings/components/EditRoleForm';
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles'; import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import type { Role } from '@/types/application'; import type { Role } from '@/types/application';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
@@ -40,13 +43,15 @@ export interface RoleSettingsFormValues {
} }
export default function RoleSettings() { export default function RoleSettings() {
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { openDialog, openAlertDialog } = useDialog(); const { openDialog, openAlertDialog } = useDialog();
const { data, loading, error } = useGetRolesPermissionsQuery({ const { data, loading, error, refetch } = useGetRolesPermissionsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { allowed: allowedRoles, default: defaultRole } = const { allowed: allowedRoles, default: defaultRole } =
@@ -54,6 +59,7 @@ export default function RoleSettings() {
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetRolesPermissionsDocument], refetchQueries: [GetRolesPermissionsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (loading) { if (loading) {
@@ -64,6 +70,20 @@ export default function RoleSettings() {
throw error; throw error;
} }
async function showApplyChangesDialog() {
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}
async function handleSetAsDefault({ name }: Role) { async function handleSetAsDefault({ name }: Role) {
const updateConfigPromise = updateConfig({ const updateConfigPromise = updateConfig({
variables: { variables: {
@@ -84,6 +104,7 @@ export default function RoleSettings() {
await execPromiseWithErrorToast( await execPromiseWithErrorToast(
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
showApplyChangesDialog();
}, },
{ {
loadingMessage: 'Updating default role...', loadingMessage: 'Updating default role...',
@@ -114,6 +135,7 @@ export default function RoleSettings() {
await execPromiseWithErrorToast( await execPromiseWithErrorToast(
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
showApplyChangesDialog();
}, },
{ {
loadingMessage: 'Deleting allowed role...', loadingMessage: 'Deleting allowed role...',
@@ -127,7 +149,7 @@ export default function RoleSettings() {
function handleOpenCreator() { function handleOpenCreator() {
openDialog({ openDialog({
title: 'Create Allowed Role', title: 'Create Allowed Role',
component: <CreateRoleForm />, component: <CreateRoleForm onSubmit={refetch} />,
props: { props: {
titleProps: { className: '!pb-0' }, titleProps: { className: '!pb-0' },
PaperProps: { className: 'max-w-sm' }, PaperProps: { className: 'max-w-sm' },
@@ -138,7 +160,9 @@ export default function RoleSettings() {
function handleOpenEditor(originalRole: Role) { function handleOpenEditor(originalRole: Role) {
openDialog({ openDialog({
title: 'Edit Allowed Role', title: 'Edit Allowed Role',
component: <EditRoleForm originalRole={originalRole} />, component: (
<EditRoleForm originalRole={originalRole} onSubmit={refetch} />
),
props: { props: {
titleProps: { className: '!pb-0' }, titleProps: { className: '!pb-0' },
PaperProps: { className: 'max-w-sm' }, PaperProps: { className: 'max-w-sm' },
@@ -177,7 +201,7 @@ export default function RoleSettings() {
)} )}
slotProps={{ submitButton: { className: 'hidden' } }} slotProps={{ submitButton: { className: 'hidden' } }}
> >
<Box className="border-b-1 px-4 py-3"> <Box className="px-4 py-3 border-b-1">
<Text className="font-medium">Name</Text> <Text className="font-medium">Name</Text>
</Box> </Box>
@@ -193,7 +217,7 @@ export default function RoleSettings() {
<Dropdown.Trigger <Dropdown.Trigger
asChild asChild
hideChevron hideChevron
className="absolute right-4 top-1/2 -translate-y-1/2" className="absolute -translate-y-1/2 right-4 top-1/2"
> >
<IconButton <IconButton
variant="borderless" variant="borderless"
@@ -252,7 +276,7 @@ export default function RoleSettings() {
<> <>
{role.name} {role.name}
{role.isSystemRole && <LockIcon className="h-4 w-4" />} {role.isSystemRole && <LockIcon className="w-4 h-4" />}
{defaultRole === role.name && ( {defaultRole === role.name && (
<Chip <Chip

View File

@@ -1,4 +1,7 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import type { import type {
BaseSecretFormProps, BaseSecretFormProps,
BaseSecretFormValues, BaseSecretFormValues,
@@ -7,6 +10,7 @@ import {
BaseSecretForm, BaseSecretForm,
baseSecretFormValidationSchema, baseSecretFormValidationSchema,
} from '@/features/projects/secrets/settings/components/BaseSecretForm'; } from '@/features/projects/secrets/settings/components/BaseSecretForm';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
GetSecretsDocument, GetSecretsDocument,
@@ -20,13 +24,17 @@ export interface CreateSecretFormProps
/** /**
* Function to be called when the form is submitted. * Function to be called when the form is submitted.
*/ */
onSubmit?: () => Promise<void>; onSubmit?: () => Promise<any>;
} }
export default function CreateSecretForm({ export default function CreateSecretForm({
onSubmit, onSubmit,
...props ...props
}: CreateSecretFormProps) { }: CreateSecretFormProps) {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const form = useForm<BaseSecretFormValues>({ const form = useForm<BaseSecretFormValues>({
defaultValues: { defaultValues: {
name: '', name: '',
@@ -39,6 +47,7 @@ export default function CreateSecretForm({
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [insertSecret] = useInsertSecretMutation({ const [insertSecret] = useInsertSecretMutation({
refetchQueries: [GetSecretsDocument], refetchQueries: [GetSecretsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
async function handleSubmit({ name, value }: BaseSecretFormValues) { async function handleSubmit({ name, value }: BaseSecretFormValues) {
@@ -56,7 +65,19 @@ export default function CreateSecretForm({
await execPromiseWithErrorToast( await execPromiseWithErrorToast(
async () => { async () => {
await insertSecretPromise; await insertSecretPromise;
onSubmit?.(); await onSubmit?.();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Creating secret...', loadingMessage: 'Creating secret...',

View File

@@ -1,4 +1,5 @@
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import type { import type {
BaseSecretFormProps, BaseSecretFormProps,
BaseSecretFormValues, BaseSecretFormValues,
@@ -7,6 +8,7 @@ import {
BaseSecretForm, BaseSecretForm,
baseSecretFormValidationSchema, baseSecretFormValidationSchema,
} from '@/features/projects/secrets/settings/components/BaseSecretForm'; } from '@/features/projects/secrets/settings/components/BaseSecretForm';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import type { Secret } from '@/types/application'; import type { Secret } from '@/types/application';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
@@ -33,6 +35,9 @@ export default function EditSecretForm({
onSubmit, onSubmit,
...props ...props
}: EditSecretFormProps) { }: EditSecretFormProps) {
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const form = useForm<BaseSecretFormValues>({ const form = useForm<BaseSecretFormValues>({
defaultValues: { defaultValues: {
name: originalSecret.name, name: originalSecret.name,
@@ -45,6 +50,7 @@ export default function EditSecretForm({
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateSecret] = useUpdateSecretMutation({ const [updateSecret] = useUpdateSecretMutation({
refetchQueries: [GetSecretsDocument], refetchQueries: [GetSecretsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
async function handleSubmit({ name, value }: BaseSecretFormValues) { async function handleSubmit({ name, value }: BaseSecretFormValues) {

View File

@@ -1,3 +1,4 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { Alert } from '@/components/ui/v2/Alert'; import { Alert } from '@/components/ui/v2/Alert';
@@ -12,6 +13,7 @@ import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip'; import { Tooltip } from '@/components/ui/v2/Tooltip';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useHostName } from '@/features/projects/common/hooks/useHostName'; import { useHostName } from '@/features/projects/common/hooks/useHostName';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { InfoCard } from '@/features/projects/overview/components/InfoCard'; import { InfoCard } from '@/features/projects/overview/components/InfoCard';
import { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema'; import { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
import { ComputeFormSection } from '@/features/services/components/ServiceForm/components/ComputeFormSection'; import { ComputeFormSection } from '@/features/services/components/ServiceForm/components/ComputeFormSection';
@@ -25,6 +27,7 @@ import {
type ServiceFormProps, type ServiceFormProps,
type ServiceFormValues, type ServiceFormValues,
} from '@/features/services/components/ServiceForm/ServiceFormTypes'; } from '@/features/services/components/ServiceForm/ServiceFormTypes';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common'; import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
@@ -50,11 +53,15 @@ export default function ServiceForm({
location, location,
}: ServiceFormProps) { }: ServiceFormProps) {
const hostName = useHostName(); const hostName = useHostName();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { onDirtyStateChange, openDialog, closeDialog } = useDialog(); const { onDirtyStateChange, openDialog, closeDialog } = useDialog();
const [insertRunService] = useInsertRunServiceMutation(); const [insertRunService] = useInsertRunServiceMutation();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [insertRunServiceConfig] = useInsertRunServiceConfigMutation(); const [insertRunServiceConfig] = useInsertRunServiceConfigMutation();
const [replaceRunServiceConfig] = useReplaceRunServiceConfigMutation(); const [replaceRunServiceConfig] = useReplaceRunServiceConfigMutation({
...(!isPlatform ? { client: localMimirClient } : {}),
});
const [detailsServiceId, setDetailsServiceId] = useState(''); const [detailsServiceId, setDetailsServiceId] = useState('');
const [detailsServiceSubdomain, setDetailsServiceSubdomain] = useState( const [detailsServiceSubdomain, setDetailsServiceSubdomain] = useState(
initialData?.subdomain, initialData?.subdomain,
@@ -145,6 +152,18 @@ export default function ServiceForm({
}); });
setDetailsServiceId(serviceID); setDetailsServiceId(serviceID);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
} else { } else {
// Insert service config // Insert service config
const { const {
@@ -197,7 +216,12 @@ export default function ServiceForm({
); );
}; };
const handleConfirm = (values: ServiceFormValues) => { const handleConfirm = async (values: ServiceFormValues) => {
if (!isPlatform) {
await handleSubmit(formValues);
return;
}
openDialog({ openDialog({
title: 'Confirm Resources', title: 'Confirm Resources',
component: ( component: (
@@ -213,26 +237,34 @@ export default function ServiceForm({
}; };
useEffect(() => { useEffect(() => {
(async () => { if (!isPlatform) {
if (detailsServiceId) { return;
openDialog({ }
title: 'Service Details',
component: ( if (detailsServiceId) {
<ServiceDetailsDialog openDialog({
serviceID={detailsServiceId} title: 'Service Details',
subdomain={detailsServiceSubdomain} component: (
ports={formValues.ports} <ServiceDetailsDialog
/> serviceID={detailsServiceId}
), subdomain={detailsServiceSubdomain}
props: { ports={formValues.ports}
PaperProps: { />
className: 'max-w-2xl', ),
}, props: {
PaperProps: {
className: 'max-w-2xl',
}, },
}); },
} });
})(); }
}, [detailsServiceId, detailsServiceSubdomain, formValues, openDialog]); }, [
detailsServiceId,
detailsServiceSubdomain,
formValues,
openDialog,
isPlatform,
]);
const pricingExplanation = () => { const pricingExplanation = () => {
const vCPUs = `${formValues.compute.cpu / RESOURCE_VCPU_MULTIPLIER} vCPUs`; const vCPUs = `${formValues.compute.cpu / RESOURCE_VCPU_MULTIPLIER} vCPUs`;
@@ -271,7 +303,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="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
@@ -311,7 +343,7 @@ export default function ServiceForm({
> >
<InfoIcon <InfoIcon
aria-label="Info" aria-label="Info"
className="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
@@ -325,8 +357,8 @@ export default function ServiceForm({
autoComplete="off" autoComplete="off"
/> />
{/* This shows only when trying to edit a service */} {/* This shows only when trying to edit a service and when running against the nhost platform */}
{serviceID && serviceImage && ( {isPlatform && serviceID && serviceImage && (
<InfoCard <InfoCard
title="Private registry" title="Private registry"
value={`registry.${currentProject.region.awsName}.${currentProject.region.domain}/${serviceID}`} value={`registry.${currentProject.region.awsName}.${currentProject.region.domain}/${serviceID}`}
@@ -342,7 +374,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="h-4 w-4" className="w-4 h-4"
color="primary" color="primary"
/> />
</Tooltip> </Tooltip>
@@ -356,22 +388,24 @@ export default function ServiceForm({
autoComplete="off" autoComplete="off"
/> />
<Alert {isPlatform ? (
severity="info" <Alert
className="flex items-center justify-between space-x-2" severity="info"
> className="flex items-center justify-between space-x-2"
<span>{pricingExplanation()}</span> >
<b> <span>{pricingExplanation()}</span>
$ <b>
{parseFloat( $
( {parseFloat(
formValues.compute.cpu * (
formValues.replicas * formValues.compute.cpu *
COST_PER_VCPU formValues.replicas *
).toFixed(2), COST_PER_VCPU
)} ).toFixed(2),
</b> )}
</Alert> </b>
</Alert>
) : null}
<ComputeFormSection showTooltip /> <ComputeFormSection showTooltip />
@@ -388,7 +422,7 @@ export default function ServiceForm({
{createServiceFormError && ( {createServiceFormError && (
<Alert <Alert
severity="error" severity="error"
className="grid grid-flow-col items-center justify-between px-4 py-3" className="grid items-center justify-between grid-flow-col px-4 py-3"
> >
<span className="text-left"> <span className="text-left">
<strong>Error:</strong> {createServiceFormError.message} <strong>Error:</strong> {createServiceFormError.message}

View File

@@ -11,11 +11,12 @@ import { UserIcon } from '@/components/ui/v2/icons/UserIcon';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip'; import { Tooltip } from '@/components/ui/v2/Tooltip';
import { DeleteServiceModal } from '@/features/projects/common/components/DeleteServiceModal'; import { DeleteServiceModal } from '@/features/projects/common/components/DeleteServiceModal';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { ServiceForm } from '@/features/services/components/ServiceForm'; import { ServiceForm } from '@/features/services/components/ServiceForm';
import { type PortTypes } from '@/features/services/components/ServiceForm/components/PortsFormSection/PortsFormSectionTypes'; import { type PortTypes } from '@/features/services/components/ServiceForm/components/PortsFormSection/PortsFormSectionTypes';
import { type RunService } from '@/hooks/useRunServices';
import { copy } from '@/utils/copy'; import { copy } from '@/utils/copy';
import { formatDistanceToNow } from 'date-fns'; import { formatDistanceToNow } from 'date-fns';
import type { RunService } from 'pages/[workspaceSlug]/[appSlug]/services';
interface ServicesListProps { interface ServicesListProps {
/** /**
@@ -42,19 +43,20 @@ export default function ServicesList({
onCreateOrUpdate, onCreateOrUpdate,
onDelete, onDelete,
}: ServicesListProps) { }: ServicesListProps) {
const isPlatform = useIsPlatform();
const { openDrawer, openDialog, closeDialog } = useDialog(); const { openDrawer, openDialog, closeDialog } = useDialog();
const viewService = async (service: RunService) => { const viewService = async (service: RunService) => {
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="h-5 w-5" /> <CubeIcon className="w-5 h-5" />
<Text>Edit {service.config?.name ?? 'unset'}</Text> <Text>Edit {service.config?.name ?? 'unset'}</Text>
</Box> </Box>
), ),
component: ( component: (
<ServiceForm <ServiceForm
serviceID={service.id} serviceID={service.id ?? service.serviceID}
initialData={{ initialData={{
...service.config, ...service.config,
image: service.config?.image?.image, image: service.config?.image?.image,
@@ -94,50 +96,52 @@ export default function ServicesList({
<Box className="flex flex-col"> <Box className="flex flex-col">
{services.map((service) => ( {services.map((service) => (
<Box <Box
key={service.id} key={service.id ?? service.serviceID}
className="flex h-[64px] w-full cursor-pointer items-center justify-between space-x-4 border-b-1 px-4 py-2 transition-colors" className="flex h-[64px] w-full cursor-pointer items-center justify-between space-x-4 border-b-1 px-4 py-2 transition-colors"
sx={{ sx={{
[`&:hover`]: { [`&:hover`]: {
backgroundColor: 'action.hover', backgroundColor: 'action.hover',
}, },
}} }}
onClick={() => viewService(service)}
> >
<Box <Box
onClick={() => viewService(service)} 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-1 flex-row items-center space-x-4"> <div className="flex flex-row items-center flex-1 space-x-4">
<CubeIcon className="h-5 w-5" /> <CubeIcon className="w-5 h-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'}
</Text> </Text>
<Tooltip title={service.updatedAt}> {isPlatform ? (
<span className="hidden cursor-pointer text-sm text-slate-500 xs+:flex"> <Tooltip title={service.updatedAt}>
Deployed {formatDistanceToNow(new Date(service.updatedAt))}{' '} <span className="hidden cursor-pointer text-sm text-slate-500 xs+:flex">
ago Deployed{' '}
</span> {formatDistanceToNow(new Date(service.updatedAt))} ago
</Tooltip> </span>
</Tooltip>
) : null}
</div> </div>
</div> </div>
<div className="hidden flex-row items-center space-x-2 md:flex"> <div className="flex-row items-center hidden space-x-2 md:flex">
<Text variant="subtitle1" className="font-mono text-xs"> <Text variant="subtitle1" className="font-mono text-xs">
{service.id} {service.id ?? service.serviceID}
</Text> </Text>
<IconButton <IconButton
variant="borderless" variant="borderless"
color="secondary" color="secondary"
onClick={(event) => { onClick={(event) => {
copy(service.id, 'Service Id'); copy(service.id ?? service.serviceID, 'Service Id');
event.stopPropagation(); event.stopPropagation();
}} }}
aria-label="Service Id" aria-label="Service Id"
> >
<CopyIcon className="h-4 w-4" /> <CopyIcon className="w-4 h-4" />
</IconButton> </IconButton>
</div> </div>
</Box> </Box>
@@ -167,7 +171,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="h-4 w-4" /> <UserIcon className="w-4 h-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" />
@@ -175,8 +179,9 @@ export default function ServicesList({
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium" className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
sx={{ color: 'error.main' }} sx={{ color: 'error.main' }}
onClick={() => deleteService(service)} onClick={() => deleteService(service)}
disabled={!isPlatform}
> >
<TrashIcon className="h-4 w-4" /> <TrashIcon className="w-4 h-4" />
<Text className="font-medium" color="error"> <Text className="font-medium" color="error">
Delete Service Delete Service
</Text> </Text>

View File

@@ -1,9 +1,12 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete'; import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetStorageSettingsDocument, GetStorageSettingsDocument,
Software_Type_Enum, Software_Type_Enum,
@@ -11,8 +14,10 @@ import {
useGetStorageSettingsQuery, useGetStorageSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -30,21 +35,26 @@ export type StorageServiceVersionFormValues = Yup.InferType<
>; >;
export default function StorageServiceVersionSettings() { export default function StorageServiceVersionSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetStorageSettingsDocument], refetchQueries: [GetStorageSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetStorageSettingsQuery({ const { data, loading, error } = useGetStorageSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data: storageVersionsData } = useGetSoftwareVersionsQuery({ const { data: storageVersionsData } = useGetSoftwareVersionsQuery({
variables: { variables: {
software: Software_Type_Enum.Storage, software: Software_Type_Enum.Storage,
}, },
skip: !isPlatform,
}); });
const { version } = data?.config?.storage || {}; const { version } = data?.config?.storage || {};
@@ -52,6 +62,7 @@ export default function StorageServiceVersionSettings() {
const availableVersions = Array.from( const availableVersions = Array.from(
new Set(versions.map((el) => el.version)).add(version), new Set(versions.map((el) => el.version)).add(version),
) )
.filter((v) => !!v)
.sort() .sort()
.reverse() .reverse()
.map((availableVersion) => ({ .map((availableVersion) => ({
@@ -61,10 +72,21 @@ export default function StorageServiceVersionSettings() {
const form = useForm<StorageServiceVersionFormValues>({ const form = useForm<StorageServiceVersionFormValues>({
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
defaultValues: { version: { label: version, value: version } }, defaultValues: { version: { label: '', value: '' } },
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
version: {
label: version,
value: version,
},
});
}
}, [loading, version, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -99,6 +121,18 @@ export default function StorageServiceVersionSettings() {
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Storage version is being updated...', loadingMessage: 'Storage version is being updated...',
@@ -123,12 +157,20 @@ export default function StorageServiceVersionSettings() {
}} }}
docsLink="https://github.com/nhost/hasura-storage/releases" docsLink="https://github.com/nhost/hasura-storage/releases"
docsTitle="the latest releases" docsTitle="the latest releases"
className="grid grid-flow-row gap-x-4 gap-y-2 px-4 lg:grid-cols-5" className="grid grid-flow-row px-4 gap-x-4 gap-y-2 lg:grid-cols-5"
> >
<ControlledAutocomplete <ControlledAutocomplete
id="version" id="version"
name="version" name="version"
autoHighlight autoHighlight
freeSolo
getOptionLabel={(option) => {
if (typeof option === 'string') {
return option || '';
}
return option.value;
}}
isOptionEqualToValue={() => false} isOptionEqualToValue={() => false}
filterOptions={(options, { inputValue }) => { filterOptions={(options, { inputValue }) => {
const inputValueLower = inputValue.toLowerCase(); const inputValueLower = inputValue.toLowerCase();

View File

@@ -1,15 +1,20 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer'; import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetHasuraSettingsDocument, GetHasuraSettingsDocument,
useGetStorageSettingsQuery, useGetStorageSettingsQuery,
useUpdateConfigMutation, useUpdateConfigMutation,
} from '@/generated/graphql'; } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form'; import { FormProvider, useForm } from 'react-hook-form';
import * as Yup from 'yup'; import * as Yup from 'yup';
@@ -20,16 +25,20 @@ const validationSchema = Yup.object({
export type HasuraStorageAVFormValues = Yup.InferType<typeof validationSchema>; export type HasuraStorageAVFormValues = Yup.InferType<typeof validationSchema>;
export default function HasuraStorageAVSettings() { export default function HasuraStorageAVSettings() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject, refetch: refetchWorkspaceAndProject } = const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject(); useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetHasuraSettingsDocument], refetchQueries: [GetHasuraSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { data, loading, error } = useGetStorageSettingsQuery({ const { data, loading, error } = useGetStorageSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'cache-first', ...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { server } = data?.config?.storage?.antivirus || {}; const { server } = data?.config?.storage?.antivirus || {};
@@ -42,6 +51,14 @@ export default function HasuraStorageAVSettings() {
resolver: yupResolver(validationSchema), resolver: yupResolver(validationSchema),
}); });
useEffect(() => {
if (!loading) {
form.reset({
enabled: !!server,
});
}
}, [loading, server, form]);
if (loading) { if (loading) {
return ( return (
<ActivityIndicator <ActivityIndicator
@@ -81,6 +98,18 @@ export default function HasuraStorageAVSettings() {
await updateConfigPromise; await updateConfigPromise;
form.reset(formValues); form.reset(formValues);
await refetchWorkspaceAndProject(); await refetchWorkspaceAndProject();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Antivirus settings are being updated...', loadingMessage: 'Antivirus settings are being updated...',

View File

@@ -2,6 +2,7 @@ query getGraphiteAutoEmbeddingsConfigurations($limit: Int!, $offset: Int!) {
graphiteAutoEmbeddingsConfigurations(limit: $limit, offset: $offset) { graphiteAutoEmbeddingsConfigurations(limit: $limit, offset: $offset) {
id id
name name
model
schemaName schemaName
tableName tableName
columnName columnName

View File

@@ -1,5 +1,6 @@
mutation insertGraphiteAutoEmbeddingsConfiguration( mutation insertGraphiteAutoEmbeddingsConfiguration(
$name: String $name: String
$model: embedding_model_enum
$schemaName: String $schemaName: String
$tableName: String $tableName: String
$columnName: String $columnName: String
@@ -9,6 +10,7 @@ mutation insertGraphiteAutoEmbeddingsConfiguration(
insertGraphiteAutoEmbeddingsConfiguration( insertGraphiteAutoEmbeddingsConfiguration(
object: { object: {
name: $name name: $name
model: $model
schemaName: $schemaName schemaName: $schemaName
tableName: $tableName tableName: $tableName
columnName: $columnName columnName: $columnName

View File

@@ -1,6 +1,7 @@
mutation updateGraphiteAutoEmbeddingsConfiguration( mutation updateGraphiteAutoEmbeddingsConfiguration(
$id: uuid! $id: uuid!
$name: String $name: String
$model: embedding_model_enum
$schemaName: String $schemaName: String
$tableName: String $tableName: String
$columnName: String $columnName: String
@@ -11,6 +12,7 @@ mutation updateGraphiteAutoEmbeddingsConfiguration(
pk_columns: { id: $id } pk_columns: { id: $id }
_set: { _set: {
name: $name name: $name
model: $model
schemaName: $schemaName schemaName: $schemaName
tableName: $tableName tableName: $tableName
columnName: $columnName columnName: $columnName
@@ -20,6 +22,7 @@ mutation updateGraphiteAutoEmbeddingsConfiguration(
) { ) {
id id
name name
model
schemaName schemaName
tableName tableName
columnName columnName

View File

@@ -1,3 +1,40 @@
fragment RunServiceConfig on ConfigRunServiceConfig {
name
image {
image
}
command
resources {
compute {
cpu
memory
}
storage {
name
path
capacity
}
replicas
}
environment {
name
value
}
ports {
port
type
publish
ingresses {
fqdn
}
}
healthCheck {
port
initialDelaySeconds
probePeriodSeconds
}
}
query getRunServices( query getRunServices(
$appID: uuid! $appID: uuid!
$resolve: Boolean! $resolve: Boolean!
@@ -11,40 +48,7 @@ query getRunServices(
updatedAt updatedAt
subdomain subdomain
config(resolve: $resolve) { config(resolve: $resolve) {
name ...RunServiceConfig
image {
image
}
command
resources {
compute {
cpu
memory
}
storage {
name
path
capacity
}
replicas
}
environment {
name
value
}
ports {
port
type
publish
ingresses {
fqdn
}
}
healthCheck {
port
initialDelaySeconds
probePeriodSeconds
}
} }
} }
runServices_aggregate { runServices_aggregate {
@@ -54,3 +58,12 @@ query getRunServices(
} }
} }
} }
query getLocalRunServiceConfigs($appID: uuid!, $resolve: Boolean!) {
runServiceConfigs(appID: $appID, resolve: $resolve) {
serviceID
config {
...RunServiceConfig
}
}
}

View File

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

View File

@@ -0,0 +1,19 @@
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
import { useMemo } from 'react';
/**
* It creates a new Apollo Client instance that connects to the local mimir when running the a local nhost project
* @returns A function that returns a new ApolloClient instance.
*/
export default function useLocalMimirClient() {
return useMemo(
() =>
new ApolloClient({
cache: new InMemoryCache(),
link: new HttpLink({
uri: 'https://local.dashboard.nhost.run/v1/configserver/graphql',
}),
}),
[],
);
}

View File

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

View File

@@ -0,0 +1,109 @@
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import {
useGetLocalRunServiceConfigsQuery,
useGetRunServicesQuery,
type GetRunServicesQuery,
} from '@/utils/__generated__/graphql';
import { useRouter } from 'next/router';
import { useEffect, useMemo, useRef, useState } from 'react';
export type RunService = Pick<
GetRunServicesQuery['app']['runServices'][0],
'config'
> & {
id?: string;
serviceID?: string;
createdAt?: string;
updatedAt?: string;
subdomain?: string;
};
export type RunServiceConfig = Omit<
GetRunServicesQuery['app']['runServices'][0]['config'],
'__typename'
>;
export default function useRunServices() {
const limit = useRef(25);
const router = useRouter();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject();
const [nrOfPages, setNrOfPages] = useState(0);
const [totalServicesCount, setTotalServicesCount] = useState(0);
const [currentPage, setCurrentPage] = useState(
parseInt(router.query.page as string, 10) || 1,
);
const offset = useMemo(() => currentPage - 1, [currentPage]);
const {
data,
loading: loadingPlatformServices,
refetch: refetchPlatformServices,
} = useGetRunServicesQuery({
variables: {
appID: currentProject.id,
resolve: false,
limit: limit.current,
offset,
},
skip: !isPlatform,
});
const {
loading: loadingLocalServices,
data: localServicesData,
refetch: refetchLocalServices,
} = useGetLocalRunServiceConfigsQuery({
variables: { appID: currentProject.id as any, 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 refetch = isPlatform ? refetchPlatformServices : refetchLocalServices;
useEffect(() => {
if (!isPlatform) {
return;
}
if (loading) {
return;
}
const userCount = data?.app?.runServices_aggregate.aggregate.count ?? 0;
setTotalServicesCount(
data?.app?.runServices_aggregate.aggregate.count ?? 0,
);
setNrOfPages(Math.ceil(userCount / limit.current));
}, [data, loading, isPlatform]);
return {
services,
loading,
refetch,
limit,
totalServicesCount,
nrOfPages,
currentPage,
setCurrentPage,
};
}

View File

@@ -9,75 +9,34 @@ import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import { ServicesIcon } from '@/components/ui/v2/icons/ServicesIcon'; import { ServicesIcon } from '@/components/ui/v2/icons/ServicesIcon';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import type { GetRunServicesQuery } from '@/utils/__generated__/graphql';
import { useGetRunServicesQuery } from '@/utils/__generated__/graphql';
import { UpgradeNotification } from '@/features/projects/common/components/UpgradeNotification'; import { UpgradeNotification } from '@/features/projects/common/components/UpgradeNotification';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { ServiceForm } from '@/features/services/components/ServiceForm'; import { ServiceForm } from '@/features/services/components/ServiceForm';
import { type PortTypes } from '@/features/services/components/ServiceForm/components/PortsFormSection/PortsFormSectionTypes'; import { type PortTypes } from '@/features/services/components/ServiceForm/components/PortsFormSection/PortsFormSectionTypes';
import ServicesList from '@/features/services/components/ServicesList/ServicesList'; import ServicesList from '@/features/services/components/ServicesList/ServicesList';
import { useRunServices, type RunServiceConfig } from '@/hooks/useRunServices';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { import { useCallback, useEffect, type ReactElement } from 'react';
useCallback,
useEffect,
useMemo,
useRef,
useState,
type ReactElement,
} from 'react';
export type RunService = Omit<
GetRunServicesQuery['app']['runServices'][0],
'__typename'
>;
export type RunServiceConfig = Omit<
GetRunServicesQuery['app']['runServices'][0]['config'],
'__typename'
>;
export default function ServicesPage() { export default function ServicesPage() {
const limit = useRef(25);
const router = useRouter(); const router = useRouter();
const isPlatform = useIsPlatform();
const { openDrawer, openAlertDialog } = useDialog(); const { openDrawer, openAlertDialog } = useDialog();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const isPlanFree = currentProject?.plan?.isFree;
const [currentPage, setCurrentPage] = useState(
parseInt(router.query.page as string, 10) || 1,
);
const [nrOfPages, setNrOfPages] = useState(0);
const offset = useMemo(() => currentPage - 1, [currentPage]);
const { const {
data,
loading, loading,
refetch: refetchServices, services,
} = useGetRunServicesQuery({ totalServicesCount,
variables: { limit,
appID: currentProject.id, nrOfPages,
resolve: false, currentPage,
limit: limit.current, setCurrentPage,
offset, refetch,
}, } = useRunServices();
});
useEffect(() => { const isPlanFree = currentProject?.plan?.isFree;
if (loading) {
return;
}
const userCount = data?.app?.runServices_aggregate.aggregate.count ?? 0;
setNrOfPages(Math.ceil(userCount / limit.current));
}, [data, loading]);
const services = useMemo(
() => data?.app?.runServices.map((service) => service) ?? [],
[data],
);
const checkConfigFromQuery = useCallback( const checkConfigFromQuery = useCallback(
(base64Config: string) => { (base64Config: string) => {
@@ -89,7 +48,7 @@ export default function ServicesPage() {
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="h-5 w-5" /> <CubeIcon className="w-5 h-5" />
<Text>Create a new run service</Text> <Text>Create a new run service</Text>
</Box> </Box>
), ),
@@ -111,7 +70,7 @@ export default function ServicesPage() {
replicas: parsedConfig?.resources?.replicas, replicas: parsedConfig?.resources?.replicas,
storage: parsedConfig?.resources?.storage, storage: parsedConfig?.resources?.storage,
}} }}
onSubmit={refetchServices} onSubmit={refetch}
/> />
), ),
}); });
@@ -127,7 +86,7 @@ export default function ServicesPage() {
} }
} }
}, },
[router.query.config, openDrawer, refetchServices, openAlertDialog], [router.query.config, openDrawer, refetch, openAlertDialog],
); );
useEffect(() => { useEffect(() => {
@@ -137,18 +96,23 @@ export default function ServicesPage() {
}, [checkConfigFromQuery, router.query]); }, [checkConfigFromQuery, router.query]);
const openCreateServiceDialog = () => { const openCreateServiceDialog = () => {
// creating services using the local dashboard is not supported
if (isPlatform) {
return;
}
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="h-5 w-5" /> <CubeIcon className="w-5 h-5" />
<Text>Create a new service</Text> <Text>Create a new service</Text>
</Box> </Box>
), ),
component: <ServiceForm onSubmit={refetchServices} />, component: <ServiceForm onSubmit={refetch} />,
}); });
}; };
if (isPlanFree) { if (isPlatform && isPlanFree) {
return ( return (
<Container> <Container>
<UpgradeNotification <UpgradeNotification
@@ -159,41 +123,44 @@ export default function ServicesPage() {
); );
} }
if (data?.app.runServices.length === 0 && !loading) { if (services.length === 0 && !loading) {
return ( return (
<Container className="mx-auto max-w-9xl space-y-5 overflow-x-hidden"> <Container className="mx-auto space-y-5 overflow-x-hidden max-w-9xl">
<div className="flex flex-row place-content-end"> <div className="flex flex-row place-content-end">
<Button <Button
variant="contained" variant="contained"
color="primary" color="primary"
onClick={openCreateServiceDialog} onClick={openCreateServiceDialog}
startIcon={<PlusIcon className="h-4 w-4" />} startIcon={<PlusIcon className="w-4 h-4" />}
disabled={!isPlatform}
> >
Add service Add service
</Button> </Button>
</div> </div>
<Box className="flex flex-col items-center justify-center space-y-5 rounded-lg border px-48 py-12 shadow-sm"> <Box className="flex flex-col items-center justify-center px-48 py-12 space-y-5 border rounded-lg shadow-sm">
<ServicesIcon className="h-10 w-10" /> <ServicesIcon className="w-10 h-10" />
<div className="flex flex-col space-y-1"> <div className="flex flex-col space-y-1">
<Text className="text-center font-medium" variant="h3"> <Text className="font-medium text-center" variant="h3">
No custom services are available No custom services are available
</Text> </Text>
<Text variant="subtitle1" className="text-center"> <Text variant="subtitle1" className="text-center">
All your projects custom services will be listed here. All your project&apos;s custom services will be listed here.
</Text> </Text>
</div> </div>
<div className="flex flex-row place-content-between rounded-lg "> {isPlatform ? (
<Button <div className="flex flex-row rounded-lg place-content-between ">
variant="contained" <Button
color="primary" variant="contained"
className="w-full" color="primary"
onClick={openCreateServiceDialog} className="w-full"
startIcon={<PlusIcon className="h-4 w-4" />} onClick={openCreateServiceDialog}
> startIcon={<PlusIcon className="w-4 h-4" />}
Add service >
</Button> Add service
</div> </Button>
</div>
) : null}
</Box> </Box>
</Container> </Container>
); );
@@ -201,12 +168,13 @@ export default function ServicesPage() {
return ( return (
<div className="flex flex-col"> <div className="flex flex-col">
<Box className="flex flex-row place-content-end border-b-1 p-4"> <Box className="flex flex-row p-4 place-content-end border-b-1">
<Button <Button
variant="contained" variant="contained"
color="primary" color="primary"
onClick={openCreateServiceDialog} onClick={openCreateServiceDialog}
startIcon={<PlusIcon className="h-4 w-4" />} startIcon={<PlusIcon className="w-4 h-4" />}
disabled={!isPlatform}
> >
Add service Add service
</Button> </Button>
@@ -214,42 +182,42 @@ export default function ServicesPage() {
<Box className="space-y-4"> <Box className="space-y-4">
<ServicesList <ServicesList
services={services} services={services}
onDelete={() => refetchServices()} onDelete={() => refetch()}
onCreateOrUpdate={() => refetchServices()} onCreateOrUpdate={() => refetch()}
/> />
<Pagination {isPlatform ? (
className="px-2" <Pagination
totalNrOfPages={nrOfPages} className="px-2"
currentPageNumber={currentPage} totalNrOfPages={nrOfPages}
totalNrOfElements={ currentPageNumber={currentPage}
data?.app?.runServices_aggregate.aggregate.count ?? 0 totalNrOfElements={totalServicesCount}
} itemsLabel="services"
itemsLabel="services" elementsPerPage={limit.current}
elementsPerPage={limit.current} onPrevPageClick={async () => {
onPrevPageClick={async () => { setCurrentPage((page) => page - 1);
setCurrentPage((page) => page - 1); if (currentPage - 1 !== 1) {
if (currentPage - 1 !== 1) { await router.push({
pathname: router.pathname,
query: { ...router.query, page: currentPage - 1 },
});
}
}}
onNextPageClick={async () => {
setCurrentPage((page) => page + 1);
await router.push({ await router.push({
pathname: router.pathname, pathname: router.pathname,
query: { ...router.query, page: currentPage - 1 }, query: { ...router.query, page: currentPage + 1 },
}); });
} }}
}} onPageChange={async (page) => {
onNextPageClick={async () => { setCurrentPage(page);
setCurrentPage((page) => page + 1); await router.push({
await router.push({ pathname: router.pathname,
pathname: router.pathname, query: { ...router.query, page },
query: { ...router.query, page: currentPage + 1 }, });
}); }}
}} />
onPageChange={async (page) => { ) : null}
setCurrentPage(page);
await router.push({
pathname: router.pathname,
query: { ...router.query, page },
});
}}
/>
</Box> </Box>
</div> </div>
); );

View File

@@ -11,7 +11,7 @@ import type { ReactElement } from 'react';
export default function StorageSettingsPage() { export default function StorageSettingsPage() {
const { currentProject, loading, error } = useCurrentWorkspaceAndProject(); const { currentProject, loading, error } = useCurrentWorkspaceAndProject();
if (currentProject.plan.isFree) { if (currentProject?.plan?.isFree) {
return ( return (
<Box className="p-4" sx={{ backgroundColor: 'background.default' }}> <Box className="p-4" sx={{ backgroundColor: 'background.default' }}>
<UpgradeToProBanner <UpgradeToProBanner
@@ -43,7 +43,7 @@ export default function StorageSettingsPage() {
return ( return (
<Container <Container
className="grid max-w-5xl grid-flow-row gap-y-6 bg-transparent" className="grid max-w-5xl grid-flow-row bg-transparent gap-y-6"
rootClassName="bg-transparent" rootClassName="bg-transparent"
> >
<AISettings /> <AISettings />

View File

@@ -11,15 +11,20 @@ import { GravatarSettings } from '@/features/authentication/settings/components/
import { MFASettings } from '@/features/authentication/settings/components/MFASettings'; import { MFASettings } from '@/features/authentication/settings/components/MFASettings';
import { SessionSettings } from '@/features/authentication/settings/components/SessionSettings'; import { SessionSettings } from '@/features/authentication/settings/components/SessionSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { useGetAuthenticationSettingsQuery } from '@/utils/__generated__/graphql'; import { useGetAuthenticationSettingsQuery } from '@/utils/__generated__/graphql';
import type { ReactElement } from 'react'; import type { ReactElement } from 'react';
export default function SettingsAuthenticationPage() { export default function SettingsAuthenticationPage() {
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { data, loading, error } = useGetAuthenticationSettingsQuery({ const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
skip: !currentProject, skip: !currentProject,
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (!data && loading) { if (!data && loading) {
@@ -38,7 +43,7 @@ export default function SettingsAuthenticationPage() {
return ( return (
<Container <Container
className="grid max-w-5xl grid-flow-row gap-y-6 bg-transparent" className="grid max-w-5xl grid-flow-row bg-transparent gap-y-6"
rootClassName="bg-transparent" rootClassName="bg-transparent"
> >
<AuthServiceVersionSettings /> <AuthServiceVersionSettings />

View File

@@ -16,7 +16,7 @@ import { type ReactElement } from 'react';
export default function CustomDomains() { export default function CustomDomains() {
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
if (currentProject.plan.isFree) { if (currentProject?.plan?.isFree) {
return ( return (
<Container <Container
className="grid grid-flow-row gap-6 bg-transparent" className="grid grid-flow-row gap-6 bg-transparent"
@@ -35,7 +35,7 @@ export default function CustomDomains() {
className="grid max-w-5xl grid-flow-row gap-6 bg-transparent" className="grid max-w-5xl grid-flow-row gap-6 bg-transparent"
rootClassName="bg-transparent" rootClassName="bg-transparent"
> >
<Box className="flex flex-row items-center gap-4 overflow-hidden rounded-lg border-1 p-4"> <Box className="flex flex-row items-center gap-4 p-4 overflow-hidden rounded-lg border-1">
<div className="flex flex-col space-y-2"> <div className="flex flex-col space-y-2">
<Text className="text-lg font-semibold">Custom Domains</Text> <Text className="text-lg font-semibold">Custom Domains</Text>
@@ -50,7 +50,7 @@ export default function CustomDomains() {
className="ml-1 font-medium" className="ml-1 font-medium"
> >
Custom Domains Custom Domains
<ArrowSquareOutIcon className="ml-1 h-4 w-4" /> <ArrowSquareOutIcon className="w-4 h-4 ml-1" />
</Link> </Link>
</Text> </Text>
</div> </div>

View File

@@ -6,15 +6,20 @@ import { DatabaseServiceVersionSettings } from '@/features/database/settings/com
import { DatabaseStorageCapacity } from '@/features/database/settings/components/DatabaseStorageCapacity'; import { DatabaseStorageCapacity } from '@/features/database/settings/components/DatabaseStorageCapacity';
import { ResetDatabasePasswordSettings } from '@/features/database/settings/components/ResetDatabasePasswordSettings'; import { ResetDatabasePasswordSettings } from '@/features/database/settings/components/ResetDatabasePasswordSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useGetPostgresSettingsQuery } from '@/generated/graphql'; import { useGetPostgresSettingsQuery } from '@/generated/graphql';
import useLocalMimirClient from '@/hooks/useLocalMimirClient/useLocalMimirClient';
import type { ReactElement } from 'react'; import type { ReactElement } from 'react';
export default function DatabaseSettingsPage() { export default function DatabaseSettingsPage() {
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { loading, error } = useGetPostgresSettingsQuery({ const { loading, error } = useGetPostgresSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
skip: !currentProject, skip: !currentProject,
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (loading) { if (loading) {
@@ -38,8 +43,13 @@ export default function DatabaseSettingsPage() {
> >
<DatabaseServiceVersionSettings /> <DatabaseServiceVersionSettings />
<DatabaseStorageCapacity /> <DatabaseStorageCapacity />
<DatabaseConnectionInfo />
<ResetDatabasePasswordSettings /> {isPlatform && (
<>
<DatabaseConnectionInfo />
<ResetDatabasePasswordSettings />
</>
)}
</Container> </Container>
); );
} }

View File

@@ -2,15 +2,20 @@ import { Container } from '@/components/layout/Container';
import { SettingsLayout } from '@/components/layout/SettingsLayout'; import { SettingsLayout } from '@/components/layout/SettingsLayout';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { EnvironmentVariableSettings } from '@/features/projects/environmentVariables/settings/components/EnvironmentVariableSettings'; import { EnvironmentVariableSettings } from '@/features/projects/environmentVariables/settings/components/EnvironmentVariableSettings';
import { SystemEnvironmentVariableSettings } from '@/features/projects/environmentVariables/settings/components/SystemEnvironmentVariableSettings'; import { SystemEnvironmentVariableSettings } from '@/features/projects/environmentVariables/settings/components/SystemEnvironmentVariableSettings';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { useGetEnvironmentVariablesQuery } from '@/utils/__generated__/graphql'; import { useGetEnvironmentVariablesQuery } from '@/utils/__generated__/graphql';
import type { ReactElement } from 'react'; import type { ReactElement } from 'react';
export default function EnvironmentVariablesPage() { export default function EnvironmentVariablesPage() {
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { loading, error } = useGetEnvironmentVariablesQuery({ const { loading, error } = useGetEnvironmentVariablesQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (loading) { if (loading) {

View File

@@ -9,6 +9,7 @@ import { Input } from '@/components/ui/v2/Input';
import { RemoveApplicationModal } from '@/features/projects/common/components/RemoveApplicationModal'; import { RemoveApplicationModal } from '@/features/projects/common/components/RemoveApplicationModal';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsCurrentUserOwner } from '@/features/projects/common/hooks/useIsCurrentUserOwner'; import { useIsCurrentUserOwner } from '@/features/projects/common/hooks/useIsCurrentUserOwner';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { import {
GetAllWorkspacesAndProjectsDocument, GetAllWorkspacesAndProjectsDocument,
useDeleteApplicationMutation, useDeleteApplicationMutation,
@@ -42,6 +43,7 @@ export default function SettingsGeneralPage() {
loading, loading,
refetch: refetchWorkspaceAndProject, refetch: refetchWorkspaceAndProject,
} = useCurrentWorkspaceAndProject(); } = useCurrentWorkspaceAndProject();
const isOwner = useIsCurrentUserOwner(); const isOwner = useIsCurrentUserOwner();
const { openDialog, openAlertDialog, closeDialog } = useDialog(); const { openDialog, openAlertDialog, closeDialog } = useDialog();
const [updateApp] = useUpdateApplicationMutation(); const [updateApp] = useUpdateApplicationMutation();
@@ -56,6 +58,8 @@ export default function SettingsGeneralPage() {
const router = useRouter(); const router = useRouter();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const isPlatform = useIsPlatform();
const form = useForm<ProjectNameValidationSchema>({ const form = useForm<ProjectNameValidationSchema>({
mode: 'onSubmit', mode: 'onSubmit',
reValidateMode: 'onSubmit', reValidateMode: 'onSubmit',
@@ -173,7 +177,8 @@ export default function SettingsGeneralPage() {
className="grid grid-flow-row px-4 lg:grid-cols-4" className="grid grid-flow-row px-4 lg:grid-cols-4"
slotProps={{ slotProps={{
submitButton: { submitButton: {
disabled: !formState.isDirty || maintenanceActive, disabled:
!formState.isDirty || maintenanceActive || !isPlatform,
loading: formState.isSubmitting, loading: formState.isSubmitting,
}, },
}} }}
@@ -194,7 +199,7 @@ export default function SettingsGeneralPage() {
</Form> </Form>
</FormProvider> </FormProvider>
{currentProject?.plan.isFree && ( {currentProject?.plan?.isFree && (
<SettingsContainer <SettingsContainer
title="Pause Project" title="Pause Project"
description="While your project is paused, it will not be accessible. You can wake it up anytime after." description="While your project is paused, it will not be accessible. You can wake it up anytime after."

View File

@@ -11,15 +11,20 @@ import { HasuraPoolSizeSettings } from '@/features/hasura/settings/components/Ha
import { HasuraRemoteSchemaPermissionsSettings } from '@/features/hasura/settings/components/HasuraRemoteSchemaPermissionsSettings'; import { HasuraRemoteSchemaPermissionsSettings } from '@/features/hasura/settings/components/HasuraRemoteSchemaPermissionsSettings';
import { HasuraServiceVersionSettings } from '@/features/hasura/settings/components/HasuraServiceVersionSettings'; import { HasuraServiceVersionSettings } from '@/features/hasura/settings/components/HasuraServiceVersionSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { useGetHasuraSettingsQuery } from '@/utils/__generated__/graphql'; import { useGetHasuraSettingsQuery } from '@/utils/__generated__/graphql';
import type { ReactElement } from 'react'; import type { ReactElement } from 'react';
export default function HasuraSettingsPage() { export default function HasuraSettingsPage() {
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { data, loading, error } = useGetHasuraSettingsQuery({ const { data, loading, error } = useGetHasuraSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
skip: !currentProject, skip: !currentProject,
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (!data && loading) { if (!data && loading) {
@@ -38,7 +43,7 @@ export default function HasuraSettingsPage() {
return ( return (
<Container <Container
className="grid max-w-5xl grid-flow-row gap-y-6 bg-transparent" className="grid max-w-5xl grid-flow-row bg-transparent gap-y-6"
rootClassName="bg-transparent" rootClassName="bg-transparent"
> >
<HasuraServiceVersionSettings /> <HasuraServiceVersionSettings />

View File

@@ -13,7 +13,7 @@ export default function ResourceSettingsPage() {
return <ActivityIndicator delay={1000} label="Loading project..." />; return <ActivityIndicator delay={1000} label="Loading project..." />;
} }
if (currentProject?.plan.isFree) { if (currentProject?.plan?.isFree) {
return ( return (
<UpgradeNotification message="Unlock Compute settings by upgrading your project to the Pro plan." /> <UpgradeNotification message="Unlock Compute settings by upgrading your project to the Pro plan." />
); );

View File

@@ -2,17 +2,23 @@ import { Container } from '@/components/layout/Container';
import { SettingsLayout } from '@/components/layout/SettingsLayout'; import { SettingsLayout } from '@/components/layout/SettingsLayout';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { PermissionVariableSettings } from '@/features/projects/permissions/settings/components/PermissionVariableSettings'; import { PermissionVariableSettings } from '@/features/projects/permissions/settings/components/PermissionVariableSettings';
import { RoleSettings } from '@/features/projects/roles/settings/components/RoleSettings'; import { RoleSettings } from '@/features/projects/roles/settings/components/RoleSettings';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { useGetRolesPermissionsQuery } from '@/utils/__generated__/graphql'; import { useGetRolesPermissionsQuery } from '@/utils/__generated__/graphql';
import type { ReactElement } from 'react'; import type { ReactElement } from 'react';
export default function RolesAndPermissionsPage() { export default function RolesAndPermissionsPage() {
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { loading, error } = useGetRolesPermissionsQuery({ const { loading, error } = useGetRolesPermissionsQuery({
variables: { variables: {
appId: currentProject?.id, appId: currentProject?.id,
}, },
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (loading) { if (loading) {

View File

@@ -1,3 +1,4 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider'; import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { Container } from '@/components/layout/Container'; import { Container } from '@/components/layout/Container';
@@ -16,8 +17,10 @@ import { List } from '@/components/ui/v2/List';
import { ListItem } from '@/components/ui/v2/ListItem'; import { ListItem } from '@/components/ui/v2/ListItem';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { CreateSecretForm } from '@/features/projects/secrets/settings/components/CreateSecretForm'; import { CreateSecretForm } from '@/features/projects/secrets/settings/components/CreateSecretForm';
import { EditSecretForm } from '@/features/projects/secrets/settings/components/EditSecretForm'; import { EditSecretForm } from '@/features/projects/secrets/settings/components/EditSecretForm';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import type { Secret } from '@/types/application'; import type { Secret } from '@/types/application';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
@@ -30,16 +33,20 @@ import { Fragment } from 'react';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
export default function SecretsPage() { export default function SecretsPage() {
const { openDialog, openAlertDialog } = useDialog(); const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { openDialog, openAlertDialog } = useDialog();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { data, loading, error } = useGetSecretsQuery({ const { data, loading, error, refetch } = useGetSecretsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const [deleteSecret] = useDeleteSecretMutation({ const [deleteSecret] = useDeleteSecretMutation({
refetchQueries: [GetSecretsDocument], refetchQueries: [GetSecretsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (loading) { if (loading) {
@@ -61,6 +68,19 @@ export default function SecretsPage() {
await execPromiseWithErrorToast( await execPromiseWithErrorToast(
async () => { async () => {
await deleteSecretPromise; await deleteSecretPromise;
await refetch();
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'Deleting secret...', loadingMessage: 'Deleting secret...',
@@ -73,7 +93,7 @@ export default function SecretsPage() {
function handleOpenCreator() { function handleOpenCreator() {
openDialog({ openDialog({
title: 'Create Secret', title: 'Create Secret',
component: <CreateSecretForm />, component: <CreateSecretForm onSubmit={refetch} />,
props: { props: {
titleProps: { className: '!pb-0' }, titleProps: { className: '!pb-0' },
PaperProps: { className: 'gap-2 max-w-md' }, PaperProps: { className: 'gap-2 max-w-md' },
@@ -136,7 +156,7 @@ export default function SecretsPage() {
footer: { className: 'hidden' }, footer: { className: 'hidden' },
}} }}
> >
<Box className="grid grid-cols-2 gap-2 border-b-1 px-4 py-3"> <Box className="grid grid-cols-2 gap-2 px-4 py-3 border-b-1">
<Text className="font-medium">Secret Name</Text> <Text className="font-medium">Secret Name</Text>
</Box> </Box>
@@ -152,7 +172,7 @@ export default function SecretsPage() {
<Dropdown.Trigger <Dropdown.Trigger
asChild asChild
hideChevron hideChevron
className="absolute right-4 top-1/2 -translate-y-1/2" className="absolute -translate-y-1/2 right-4 top-1/2"
> >
<IconButton <IconButton
variant="borderless" variant="borderless"

View File

@@ -19,15 +19,20 @@ import { WebAuthnSettings } from '@/features/authentication/settings/components/
import { WindowsLiveProviderSettings } from '@/features/authentication/settings/components/WindowsLiveProviderSettings'; import { WindowsLiveProviderSettings } from '@/features/authentication/settings/components/WindowsLiveProviderSettings';
import { WorkOsProviderSettings } from '@/features/authentication/settings/components/WorkOsProviderSettings'; import { WorkOsProviderSettings } from '@/features/authentication/settings/components/WorkOsProviderSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useGetSignInMethodsQuery } from '@/generated/graphql'; import { useGetSignInMethodsQuery } from '@/generated/graphql';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import type { ReactElement } from 'react'; import type { ReactElement } from 'react';
export default function SettingsSignInMethodsPage() { export default function SettingsSignInMethodsPage() {
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { loading, error } = useGetSignInMethodsQuery({ const { loading, error } = useGetSignInMethodsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
fetchPolicy: 'network-only', fetchPolicy: 'network-only',
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (loading) { if (loading) {

View File

@@ -1,3 +1,5 @@
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
import { useDialog } from '@/components/common/DialogProvider';
import { useUI } from '@/components/common/UIProvider'; import { useUI } from '@/components/common/UIProvider';
import { ControlledCheckbox } from '@/components/form/ControlledCheckbox'; import { ControlledCheckbox } from '@/components/form/ControlledCheckbox';
import { Form } from '@/components/form/Form'; import { Form } from '@/components/form/Form';
@@ -8,6 +10,8 @@ import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input'; import { Input } from '@/components/ui/v2/Input';
import { UpgradeNotification } from '@/features/projects/common/components/UpgradeNotification'; import { UpgradeNotification } from '@/features/projects/common/components/UpgradeNotification';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast'; import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
import { import {
GetSmtpSettingsDocument, GetSmtpSettingsDocument,
@@ -45,11 +49,15 @@ const smtpValidationSchema = yup
export type SmtpFormValues = yup.InferType<typeof smtpValidationSchema>; export type SmtpFormValues = yup.InferType<typeof smtpValidationSchema>;
export default function SMTPSettingsPage() { export default function SMTPSettingsPage() {
const { openDialog } = useDialog();
const isPlatform = useIsPlatform();
const { maintenanceActive } = useUI(); const { maintenanceActive } = useUI();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { data, loading, error } = useGetSmtpSettingsQuery({ const { data, loading, error } = useGetSmtpSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
const { secure, host, port, user, method, sender } = const { secure, host, port, user, method, sender } =
@@ -85,9 +93,10 @@ export default function SMTPSettingsPage() {
const [updateConfig] = useUpdateConfigMutation({ const [updateConfig] = useUpdateConfigMutation({
refetchQueries: [GetSmtpSettingsDocument], refetchQueries: [GetSmtpSettingsDocument],
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (currentProject.plan.isFree) { if (isPlatform && currentProject?.plan?.isFree) {
return ( return (
<Container <Container
className="grid max-w-5xl grid-flow-row gap-4 bg-transparent" className="grid max-w-5xl grid-flow-row gap-4 bg-transparent"
@@ -129,6 +138,18 @@ export default function SMTPSettingsPage() {
await execPromiseWithErrorToast( await execPromiseWithErrorToast(
async () => { async () => {
await updateConfigPromise; await updateConfigPromise;
if (!isPlatform) {
openDialog({
title: 'Apply your changes',
component: <ApplyLocalSettingsDialog />,
props: {
PaperProps: {
className: 'max-w-2xl',
},
},
});
}
}, },
{ {
loadingMessage: 'SMTP settings are being updated...', loadingMessage: 'SMTP settings are being updated...',

View File

@@ -2,17 +2,22 @@ import { Container } from '@/components/layout/Container';
import { SettingsLayout } from '@/components/layout/SettingsLayout'; import { SettingsLayout } from '@/components/layout/SettingsLayout';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject'; import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { StorageServiceVersionSettings } from '@/features/storage/settings/components/HasuraServiceVersionSettings'; import { StorageServiceVersionSettings } from '@/features/storage/settings/components/HasuraServiceVersionSettings';
import { HasuraStorageAVSettings } from '@/features/storage/settings/components/HasuraStorageAVSettings'; import { HasuraStorageAVSettings } from '@/features/storage/settings/components/HasuraStorageAVSettings';
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
import { useGetStorageSettingsQuery } from '@/utils/__generated__/graphql'; import { useGetStorageSettingsQuery } from '@/utils/__generated__/graphql';
import type { ReactElement } from 'react'; import type { ReactElement } from 'react';
export default function StorageSettingsPage() { export default function StorageSettingsPage() {
const isPlatform = useIsPlatform();
const localMimirClient = useLocalMimirClient();
const { currentProject } = useCurrentWorkspaceAndProject(); const { currentProject } = useCurrentWorkspaceAndProject();
const { loading, error } = useGetStorageSettingsQuery({ const { loading, error } = useGetStorageSettingsQuery({
variables: { appId: currentProject?.id }, variables: { appId: currentProject?.id },
skip: !currentProject, skip: !currentProject,
...(!isPlatform ? { client: localMimirClient } : {}),
}); });
if (loading) { if (loading) {
@@ -31,7 +36,7 @@ export default function StorageSettingsPage() {
return ( return (
<Container <Container
className="grid max-w-5xl grid-flow-row gap-y-6 bg-transparent" className="grid max-w-5xl grid-flow-row bg-transparent gap-y-6"
rootClassName="bg-transparent" rootClassName="bg-transparent"
> >
<StorageServiceVersionSettings /> <StorageServiceVersionSettings />

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