Compare commits
23 Commits
release-20
...
@nhost/das
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cec04bd6f | ||
|
|
a448d7d182 | ||
|
|
948048940e | ||
|
|
5e91221d5a | ||
|
|
7278991a59 | ||
|
|
5924bc3248 | ||
|
|
c5ad634799 | ||
|
|
426b93a19f | ||
|
|
026f84f466 | ||
|
|
384fac00b1 | ||
|
|
7e9a2ce136 | ||
|
|
076fd4a7c0 | ||
|
|
9525fd74b3 | ||
|
|
8a2bc98214 | ||
|
|
dd5d262062 | ||
|
|
09962bef37 | ||
|
|
9cdecb6b23 | ||
|
|
e7eb90318e | ||
|
|
f67f22d321 | ||
|
|
87ae23ba05 | ||
|
|
b2be3642aa | ||
|
|
1230081ce6 | ||
|
|
c195c517de |
@@ -1,5 +1,45 @@
|
||||
# @nhost/dashboard
|
||||
|
||||
## 1.14.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- a448d7d: feat: allow configuring postmark and delete SMTP settings
|
||||
|
||||
## 1.13.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5924bc3: fix: include password in `GetSmtpSettings` query
|
||||
- c5ad634: fix: resolved an issue where one-click install links were broken on Safari
|
||||
- 7278991: fix: update graphql auto-embeddings configuration to use String type for model field
|
||||
|
||||
## 1.13.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 026f84f: fix: use configuration server URL from environment variable
|
||||
|
||||
## 1.13.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 7e9a2ce: fix: resolve issue where run services form fails to open
|
||||
|
||||
## 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
|
||||
|
||||
@@ -28,6 +28,7 @@ ENV NEXT_PUBLIC_NHOST_STORAGE_URL __NEXT_PUBLIC_NHOST_STORAGE_URL__
|
||||
ENV NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL __NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL__
|
||||
ENV NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL __NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__
|
||||
ENV NEXT_PUBLIC_NHOST_HASURA_API_URL __NEXT_PUBLIC_NHOST_HASURA_API_URL__
|
||||
ENV NEXT_PUBLIC_NHOST_CONFIGSERVER_URL __NEXT_PUBLIC_NHOST_CONFIGSERVER_URL__
|
||||
|
||||
RUN yarn global add pnpm@8.10.5
|
||||
COPY .gitignore .gitignore
|
||||
|
||||
@@ -21,5 +21,6 @@ find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_STORAGE_URL__~${NEXT_
|
||||
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL__~${NEXT_PUBLIC_NHOST_HASURA_CONSOLE_URL}~g" {} +
|
||||
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL__~${NEXT_PUBLIC_NHOST_HASURA_MIGRATIONS_API_URL}~g" {} +
|
||||
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_HASURA_API_URL__~${NEXT_PUBLIC_NHOST_HASURA_API_URL}~g" {} +
|
||||
find dashboard -type f -exec sed -i "s~__NEXT_PUBLIC_NHOST_CONFIGSERVER_URL__~${NEXT_PUBLIC_NHOST_CONFIGSERVER_URL}~g" {} +
|
||||
|
||||
exec "$@"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/dashboard",
|
||||
"version": "1.12.1",
|
||||
"version": "1.14.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as ApplyLocalSettingsDialog } from './ApplyLocalSettingsDialog';
|
||||
@@ -11,7 +11,6 @@ import { useNotFoundRedirect } from '@/features/projects/common/hooks/useNotFoun
|
||||
import { useProjectRoutes } from '@/features/projects/common/hooks/useProjectRoutes';
|
||||
import { NextSeo } from 'next-seo';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export interface ProjectLayoutProps extends AuthenticatedLayoutProps {
|
||||
@@ -48,15 +47,16 @@ function ProjectLayoutContent({
|
||||
|
||||
useNotFoundRedirect();
|
||||
|
||||
useEffect(() => {
|
||||
if (isPlatform || !router.isReady) {
|
||||
return;
|
||||
}
|
||||
// useEffect(() => {
|
||||
// if (isPlatform || !router.isReady) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (isRestrictedPath) {
|
||||
router.push('/local/local');
|
||||
}
|
||||
}, [isPlatform, isRestrictedPath, router]);
|
||||
// TODO // Double check what restricted path means here
|
||||
// if (isRestrictedPath) {
|
||||
// router.push('/local/local');
|
||||
// }
|
||||
// }, [isPlatform, isRestrictedPath, router]);
|
||||
|
||||
if (isRestrictedPath || loading) {
|
||||
return <LoadingScreen />;
|
||||
|
||||
@@ -7,6 +7,7 @@ import { List } from '@/components/ui/v2/List';
|
||||
import type { ListItemButtonProps } from '@/components/ui/v2/ListItem';
|
||||
import { ListItem } from '@/components/ui/v2/ListItem';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -61,6 +62,7 @@ export default function SettingsSidebar({
|
||||
className,
|
||||
...props
|
||||
}: SettingsSidebarProps) {
|
||||
const isPlatform = useIsPlatform();
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
|
||||
@@ -95,7 +97,7 @@ export default function SettingsSidebar({
|
||||
<>
|
||||
<Backdrop
|
||||
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"
|
||||
tabIndex={-1}
|
||||
onClick={() => setExpanded(false)}
|
||||
@@ -112,7 +114,7 @@ export default function SettingsSidebar({
|
||||
<Box
|
||||
component="aside"
|
||||
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',
|
||||
className,
|
||||
)}
|
||||
@@ -181,7 +183,12 @@ export default function SettingsSidebar({
|
||||
SMTP
|
||||
</SettingsNavLink>
|
||||
|
||||
<SettingsNavLink href="/git" exact={false} onClick={handleSelect}>
|
||||
<SettingsNavLink
|
||||
href="/git"
|
||||
exact={false}
|
||||
onClick={handleSelect}
|
||||
disabled={!isPlatform}
|
||||
>
|
||||
Git
|
||||
</SettingsNavLink>
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './EmailSetting';
|
||||
export { default as EmailSetting } from './EmailSetting';
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { ControlledSelect } from '@/components/form/ControlledSelect';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { Box } from '@/components/ui/v2/Box';
|
||||
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 { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { Option } from '@/components/ui/v2/Option';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||
import { useAdminApolloClient } from '@/features/projects/common/hooks/useAdminApolloClient';
|
||||
@@ -20,11 +22,18 @@ import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
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({
|
||||
name: Yup.string().required('The name is required.'),
|
||||
schemaName: Yup.string().required('The schema is required'),
|
||||
tableName: Yup.string().required('The table is required'),
|
||||
columnName: Yup.string().required('The column is required'),
|
||||
name: Yup.string().required('The name field is required.'),
|
||||
model: Yup.string().oneOf(AUTO_EMBEDDINGS_MODELS),
|
||||
schemaName: Yup.string().required('The schema field is required'),
|
||||
tableName: Yup.string().required('The table field is required'),
|
||||
columnName: Yup.string().required('The column field is required'),
|
||||
query: Yup.string(),
|
||||
mutation: Yup.string(),
|
||||
});
|
||||
@@ -40,7 +49,7 @@ export interface AutoEmbeddingsFormProps extends DialogFormProps {
|
||||
/**
|
||||
* 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.
|
||||
@@ -74,7 +83,10 @@ export default function AutoEmbeddingsForm({
|
||||
});
|
||||
|
||||
const form = useForm<AutoEmbeddingsFormValues>({
|
||||
defaultValues: initialData,
|
||||
defaultValues: {
|
||||
...initialData,
|
||||
model: initialData?.model ?? 'text-embedding-ada-002',
|
||||
},
|
||||
reValidateMode: 'onSubmit',
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
@@ -106,7 +118,9 @@ export default function AutoEmbeddingsForm({
|
||||
}
|
||||
|
||||
await insertGraphiteAutoEmbeddingsConfiguration({
|
||||
variables: values,
|
||||
variables: {
|
||||
...values,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -129,9 +143,9 @@ export default function AutoEmbeddingsForm({
|
||||
<FormProvider {...form}>
|
||||
<Form
|
||||
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
|
||||
{...register('name')}
|
||||
id="name"
|
||||
@@ -141,7 +155,7 @@ export default function AutoEmbeddingsForm({
|
||||
<Tooltip title="Name of the Auto-Embeddings">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="h-4 w-4"
|
||||
className="w-4 h-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -155,6 +169,36 @@ export default function AutoEmbeddingsForm({
|
||||
autoComplete="off"
|
||||
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
|
||||
{...register('schemaName')}
|
||||
id="schemaName"
|
||||
@@ -164,7 +208,7 @@ export default function AutoEmbeddingsForm({
|
||||
<Tooltip title={<span>Schema where the table belongs to</span>}>
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="h-4 w-4"
|
||||
className="w-4 h-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -186,7 +230,7 @@ export default function AutoEmbeddingsForm({
|
||||
<Tooltip title="Table Name">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="h-4 w-4"
|
||||
className="w-4 h-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -208,7 +252,7 @@ export default function AutoEmbeddingsForm({
|
||||
<Tooltip title="Column name">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="h-4 w-4"
|
||||
className="w-4 h-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -230,7 +274,7 @@ export default function AutoEmbeddingsForm({
|
||||
<Tooltip title="Query">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="h-4 w-4"
|
||||
className="w-4 h-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -254,7 +298,7 @@ export default function AutoEmbeddingsForm({
|
||||
<Tooltip title="Mutation">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="h-4 w-4"
|
||||
className="w-4 h-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -271,7 +315,7 @@ export default function AutoEmbeddingsForm({
|
||||
/>
|
||||
</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}>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
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 { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||
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 { ComputeFormSection } from '@/features/services/components/ServiceForm/components/ComputeFormSection';
|
||||
import {
|
||||
@@ -20,6 +22,7 @@ import {
|
||||
useGetSoftwareVersionsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
@@ -50,9 +53,13 @@ const validationSchema = Yup.object({
|
||||
export type AISettingsFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function AISettings() {
|
||||
const { maintenanceActive } = useUI();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { openDialog } = useDialog();
|
||||
const [updateConfig] = useUpdateConfigMutation();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
|
||||
const [aiServiceEnabled, setAIServiceEnabled] = useState(true);
|
||||
@@ -67,6 +74,7 @@ export default function AISettings() {
|
||||
variables: {
|
||||
appId: currentProject.id,
|
||||
},
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data: graphiteVersionsData, loading: loadingGraphiteVersionsData } =
|
||||
@@ -74,6 +82,7 @@ export default function AISettings() {
|
||||
variables: {
|
||||
software: Software_Type_Enum.Graphite,
|
||||
},
|
||||
skip: !isPlatform,
|
||||
});
|
||||
|
||||
const graphiteVersions = graphiteVersionsData?.softwareVersions || [];
|
||||
@@ -98,8 +107,8 @@ export default function AISettings() {
|
||||
reValidateMode: 'onSubmit',
|
||||
defaultValues: {
|
||||
version: {
|
||||
label: ai?.version ?? availableVersions?.at(0)?.label,
|
||||
value: ai?.version ?? availableVersions?.at(0)?.value,
|
||||
label: ai?.version || availableVersions?.at(0)?.label || '',
|
||||
value: ai?.version || availableVersions?.at(0)?.value || '',
|
||||
},
|
||||
webhookSecret: '',
|
||||
organization: '',
|
||||
@@ -225,6 +234,18 @@ export default function AISettings() {
|
||||
});
|
||||
|
||||
form.reset(formValues);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'AI settings are being updated...',
|
||||
@@ -247,7 +268,7 @@ export default function AISettings() {
|
||||
|
||||
return (
|
||||
<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>
|
||||
<Switch
|
||||
checked={aiServiceEnabled}
|
||||
@@ -270,54 +291,58 @@ export default function AISettings() {
|
||||
className="flex flex-col"
|
||||
>
|
||||
<Box className="space-y-4">
|
||||
{availableVersions.length > 0 && (
|
||||
<Box className="space-y-2">
|
||||
<Box className="flex flex-row items-center space-x-2">
|
||||
<Text className="text-lg font-semibold">Version</Text>
|
||||
<Tooltip title="Version of the service to use.">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="h-4 w-4"
|
||||
color="primary"
|
||||
/>
|
||||
</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 className="space-y-2">
|
||||
<Box className="flex flex-row items-center space-x-2">
|
||||
<Text className="text-lg font-semibold">Version</Text>
|
||||
<Tooltip title="Version of the service to use.">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="w-4 h-4"
|
||||
color="primary"
|
||||
/>
|
||||
</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 ||
|
||||
!!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="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.">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="h-4 w-4"
|
||||
className="w-4 h-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -351,26 +376,28 @@ export default function AISettings() {
|
||||
<Tooltip title="Dedicated resources allocated for the service.">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="h-4 w-4"
|
||||
className="w-4 h-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
|
||||
<Alert
|
||||
severity="info"
|
||||
className="flex items-center justify-between space-x-2"
|
||||
>
|
||||
<span>{getAIResourcesCost()}</span>
|
||||
<b>
|
||||
$
|
||||
{parseFloat(
|
||||
(
|
||||
aiSettingsFormValues.compute.cpu * COST_PER_VCPU
|
||||
).toFixed(2),
|
||||
)}
|
||||
</b>
|
||||
</Alert>
|
||||
{isPlatform ? (
|
||||
<Alert
|
||||
severity="info"
|
||||
className="flex items-center justify-between space-x-2"
|
||||
>
|
||||
<span>{getAIResourcesCost()}</span>
|
||||
<b>
|
||||
$
|
||||
{parseFloat(
|
||||
(
|
||||
aiSettingsFormValues.compute.cpu * COST_PER_VCPU
|
||||
).toFixed(2),
|
||||
)}
|
||||
</b>
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
<ComputeFormSection />
|
||||
</Box>
|
||||
@@ -389,7 +416,7 @@ export default function AISettings() {
|
||||
<Tooltip title="Key to use for authenticating API requests to OpenAI">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="h-4 w-4"
|
||||
className="w-4 h-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -412,7 +439,7 @@ export default function AISettings() {
|
||||
<Tooltip title="Optional. OpenAI organization to use.">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="h-4 w-4"
|
||||
className="w-4 h-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -440,7 +467,7 @@ export default function AISettings() {
|
||||
<Tooltip title="How often to run the job that keeps embeddings up to date.">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="h-4 w-4"
|
||||
className="w-4 h-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { Box } from '@/components/ui/v2/Box';
|
||||
import { Button } from '@/components/ui/v2/Button';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
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 { useUpdateConfigMutation } from '@/utils/__generated__/graphql';
|
||||
import { useState } from 'react';
|
||||
@@ -23,11 +26,14 @@ export default function DisableAIServiceConfirmationDialog({
|
||||
onCancel,
|
||||
onServiceDisabled,
|
||||
}: DisableAIServiceConfirmationDialogProps) {
|
||||
const { closeDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { openDialog, closeDialog } = useDialog();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
|
||||
const [updateConfig] = useUpdateConfigMutation();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
async function handleClick() {
|
||||
setLoading(true);
|
||||
@@ -45,6 +51,18 @@ export default function DisableAIServiceConfirmationDialog({
|
||||
|
||||
onServiceDisabled();
|
||||
closeDialog();
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Disabling the AI service...',
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetAuthenticationSettingsDocument,
|
||||
useGetAuthenticationSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import * as Yup from 'yup';
|
||||
@@ -26,15 +31,19 @@ export type AllowedEmailSettingsFormValues = Yup.InferType<
|
||||
>;
|
||||
|
||||
export default function AllowedEmailDomainsSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetAuthenticationSettingsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { email, emailDomains } = data?.config?.auth?.user || {};
|
||||
@@ -54,6 +63,17 @@ export default function AllowedEmailDomainsSettings() {
|
||||
|
||||
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) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -105,6 +125,18 @@ export default function AllowedEmailDomainsSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
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...',
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetAuthenticationSettingsDocument,
|
||||
useGetAuthenticationSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -23,15 +28,19 @@ export type AllowedRedirectURLFormValues = Yup.InferType<
|
||||
>;
|
||||
|
||||
export default function AllowedRedirectURLsSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { maintenanceActive } = useUI();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetAuthenticationSettingsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { allowedUrls } = data?.config?.auth?.redirections || {};
|
||||
@@ -44,6 +53,14 @@ export default function AllowedRedirectURLsSettings() {
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && allowedUrls) {
|
||||
form.reset({
|
||||
allowedUrls: allowedUrls?.join(', ') || '',
|
||||
});
|
||||
}
|
||||
}, [loading, allowedUrls, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -82,6 +99,18 @@ export default function AllowedRedirectURLsSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
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...',
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetSignInMethodsDocument,
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -20,15 +25,19 @@ const validationSchema = Yup.object({
|
||||
export type AnonymousSignInFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function AnonymousSignInSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetSignInMethodsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { enabled } = data?.config?.auth?.method?.anonymous || {};
|
||||
@@ -41,6 +50,12 @@ export default function AnonymousSignInSettings() {
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
form.reset({ enabled });
|
||||
}
|
||||
}, [loading, enabled, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -75,6 +90,18 @@ export default function AnonymousSignInSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
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...',
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
@@ -7,16 +9,19 @@ import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { InputAdornment } from '@/components/ui/v2/InputAdornment';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||
import {
|
||||
GetSignInMethodsDocument,
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useTheme } from '@mui/material';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import * as Yup from 'yup';
|
||||
@@ -53,15 +58,19 @@ export type AppleProviderFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function AppleProviderSettings() {
|
||||
const theme = useTheme();
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetSignInMethodsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { clientId, enabled, keyId, privateKey, teamId } =
|
||||
@@ -79,6 +88,18 @@ export default function AppleProviderSettings() {
|
||||
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) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -119,6 +140,18 @@ export default function AppleProviderSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
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>
|
||||
</InputAdornment>
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetAuthenticationSettingsDocument,
|
||||
Software_Type_Enum,
|
||||
@@ -11,8 +14,10 @@ import {
|
||||
useGetSoftwareVersionsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -28,21 +33,26 @@ export type AuthServiceVersionFormValues = Yup.InferType<
|
||||
>;
|
||||
|
||||
export default function AuthServiceVersionSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { maintenanceActive } = useUI();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetAuthenticationSettingsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data: authVersionsData } = useGetSoftwareVersionsQuery({
|
||||
variables: {
|
||||
software: Software_Type_Enum.Auth,
|
||||
},
|
||||
skip: !isPlatform,
|
||||
});
|
||||
|
||||
const { version } = data?.config?.auth || {};
|
||||
@@ -59,10 +69,21 @@ export default function AuthServiceVersionSettings() {
|
||||
|
||||
const form = useForm<AuthServiceVersionFormValues>({
|
||||
reValidateMode: 'onSubmit',
|
||||
defaultValues: { version: { label: version, value: version } },
|
||||
defaultValues: { version: { label: '', value: '' } },
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && version) {
|
||||
form.reset({
|
||||
version: {
|
||||
label: version,
|
||||
value: version,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [loading, version, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -97,6 +118,18 @@ export default function AuthServiceVersionSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Auth version is being updated...',
|
||||
@@ -120,12 +153,20 @@ export default function AuthServiceVersionSettings() {
|
||||
}}
|
||||
docsLink="https://github.com/nhost/hasura-auth/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
|
||||
id="version"
|
||||
name="version"
|
||||
autoHighlight
|
||||
freeSolo
|
||||
getOptionLabel={(option) => {
|
||||
if (typeof option === 'string') {
|
||||
return option || '';
|
||||
}
|
||||
|
||||
return option.value;
|
||||
}}
|
||||
isOptionEqualToValue={() => false}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
const inputValueLower = inputValue.toLowerCase();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
@@ -8,15 +10,18 @@ import { Input } from '@/components/ui/v2/Input';
|
||||
import { InputAdornment } from '@/components/ui/v2/InputAdornment';
|
||||
import { BaseProviderSettings } from '@/features/authentication/settings/components/BaseProviderSettings';
|
||||
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 {
|
||||
GetSignInMethodsDocument,
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import * as Yup from 'yup';
|
||||
@@ -46,15 +51,19 @@ const validationSchema = Yup.object({
|
||||
export type AzureADProviderFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function AzureADProviderSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { maintenanceActive } = useUI();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetSignInMethodsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { clientId, clientSecret, tenant, enabled } =
|
||||
@@ -71,6 +80,17 @@ export default function AzureADProviderSettings() {
|
||||
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) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -108,6 +128,18 @@ export default function AzureADProviderSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
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...',
|
||||
@@ -182,7 +214,7 @@ export default function AzureADProviderSettings() {
|
||||
);
|
||||
}}
|
||||
>
|
||||
<CopyIcon className="h-4 w-4" />
|
||||
<CopyIcon className="w-4 h-4" />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
}
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetAuthenticationSettingsDocument,
|
||||
useGetAuthenticationSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import * as Yup from 'yup';
|
||||
@@ -24,15 +29,19 @@ const validationSchema = Yup.object({
|
||||
export type BlockedEmailFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function BlockedEmailSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetAuthenticationSettingsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { email, emailDomains } = data?.config?.auth?.user || {};
|
||||
@@ -51,6 +60,17 @@ export default function BlockedEmailSettings() {
|
||||
const enabled = watch('enabled');
|
||||
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) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -112,6 +132,18 @@ export default function BlockedEmailSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(values);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage:
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetAuthenticationSettingsDocument,
|
||||
useGetAuthenticationSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -21,15 +26,19 @@ const validationSchema = Yup.object({
|
||||
export type ClientURLFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function ClientURLSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetAuthenticationSettingsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { clientUrl, allowedUrls } = data?.config?.auth?.redirections || {};
|
||||
@@ -42,6 +51,12 @@ export default function ClientURLSettings() {
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && clientUrl) {
|
||||
form.reset({ clientUrl });
|
||||
}
|
||||
}, [loading, clientUrl, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -77,6 +92,18 @@ export default function ClientURLSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(values);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Client URL is being updated...',
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { Box } from '@/components/ui/v2/Box';
|
||||
import { Button } from '@/components/ui/v2/Button';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
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 {
|
||||
GetSmtpSettingsDocument,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import { useState } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
function ConfirmDeleteSMTPSettingsModal({
|
||||
close,
|
||||
onDelete,
|
||||
}: {
|
||||
onDelete?: () => Promise<any>;
|
||||
close: () => void;
|
||||
}) {
|
||||
const onClickDelete = async () => {
|
||||
await onDelete();
|
||||
close();
|
||||
};
|
||||
|
||||
return (
|
||||
<Box className={twMerge('w-full rounded-lg p-6 text-left')}>
|
||||
<div className="grid grid-flow-row gap-4">
|
||||
<Text variant="h3" component="h2">
|
||||
Delete SMTP Settings?
|
||||
</Text>
|
||||
|
||||
<Text>This will reset all your SMTP and Postmark settings.</Text>
|
||||
|
||||
<div className="grid grid-flow-row gap-2">
|
||||
<Button color="error" onClick={onClickDelete}>
|
||||
Delete
|
||||
</Button>
|
||||
|
||||
<Button variant="outlined" color="secondary" onClick={close}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DeleteSMTPSettings() {
|
||||
const { openDialog, closeDialog } = useDialog();
|
||||
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { maintenanceActive } = useUI();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetSmtpSettingsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const deleteSMTPSettings = async () => {
|
||||
const updateConfigPromise = updateConfig({
|
||||
variables: {
|
||||
appId: currentProject.id,
|
||||
config: {
|
||||
provider: {
|
||||
smtp: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
setLoading(true);
|
||||
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'SMTP settings are being deleted...',
|
||||
successMessage: 'SMTP settings have been deleted successfully.',
|
||||
errorMessage:
|
||||
'An error occurred while trying to delete the SMTP settings.',
|
||||
},
|
||||
);
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const confirmDeleteSMTPSettings = async () => {
|
||||
openDialog({
|
||||
component: (
|
||||
<ConfirmDeleteSMTPSettingsModal
|
||||
close={closeDialog}
|
||||
onDelete={deleteSMTPSettings}
|
||||
/>
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingsContainer
|
||||
title="Delete SMTP Settings"
|
||||
description="Delete SMTP settings and revert to default values"
|
||||
className="px-0"
|
||||
slotProps={{
|
||||
submitButton: { className: 'hidden' },
|
||||
footer: { className: 'hidden' },
|
||||
}}
|
||||
>
|
||||
<Box className="grid grid-flow-row border-t-1">
|
||||
<Button
|
||||
color="error"
|
||||
className="mx-4 mt-4 justify-self-end"
|
||||
onClick={confirmDeleteSMTPSettings}
|
||||
disabled={loading || maintenanceActive}
|
||||
loading={loading}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</Box>
|
||||
</SettingsContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as DeleteSTMPSettings } from './DeleteSMTPSettings';
|
||||
@@ -1,14 +1,19 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { 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 {
|
||||
GetAuthenticationSettingsDocument,
|
||||
useGetAuthenticationSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -19,15 +24,19 @@ const validationSchema = Yup.object({
|
||||
export type DisableNewUsersFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function DisableNewUsersSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetAuthenticationSettingsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
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) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -73,6 +90,18 @@ export default function DisableNewUsersSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(values);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Disabling new user sign ups...',
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
@@ -12,28 +14,35 @@ import {
|
||||
baseProviderValidationSchema,
|
||||
} from '@/features/authentication/settings/components/BaseProviderSettings';
|
||||
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 {
|
||||
GetSignInMethodsDocument,
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export default function DiscordProviderSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetSignInMethodsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { clientId, clientSecret, enabled } =
|
||||
@@ -49,6 +58,16 @@ export default function DiscordProviderSettings() {
|
||||
resolver: yupResolver(baseProviderValidationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
form.reset({
|
||||
clientId: clientId || '',
|
||||
clientSecret: clientSecret || '',
|
||||
enabled: enabled || false,
|
||||
});
|
||||
}
|
||||
}, [loading, clientId, clientSecret, enabled, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -89,6 +108,18 @@ export default function DiscordProviderSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
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>
|
||||
</InputAdornment>
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { ControlledCheckbox } from '@/components/form/ControlledCheckbox';
|
||||
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 { Text } from '@/components/ui/v2/Text';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetSignInMethodsDocument,
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -29,15 +34,19 @@ const validationSchema = Yup.object({
|
||||
export type EmailAndPasswordFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function EmailAndPasswordSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetSignInMethodsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, error, loading } = useGetSignInMethodsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { hibpEnabled, emailVerificationRequired, passwordMinLength } =
|
||||
@@ -53,6 +62,22 @@ export default function EmailAndPasswordSettings() {
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
form.reset({
|
||||
hibpEnabled,
|
||||
emailVerificationRequired,
|
||||
passwordMinLength,
|
||||
});
|
||||
}
|
||||
}, [
|
||||
loading,
|
||||
hibpEnabled,
|
||||
emailVerificationRequired,
|
||||
passwordMinLength,
|
||||
form,
|
||||
]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -87,6 +112,18 @@ export default function EmailAndPasswordSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
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...`,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
@@ -12,28 +14,35 @@ import {
|
||||
baseProviderValidationSchema,
|
||||
} from '@/features/authentication/settings/components/BaseProviderSettings';
|
||||
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 {
|
||||
GetSignInMethodsDocument,
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export default function FacebookProviderSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetSignInMethodsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { clientId, clientSecret, enabled } =
|
||||
@@ -49,6 +58,16 @@ export default function FacebookProviderSettings() {
|
||||
resolver: yupResolver(baseProviderValidationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
form.reset({
|
||||
clientId: clientId || '',
|
||||
clientSecret: clientSecret || '',
|
||||
enabled: enabled || false,
|
||||
});
|
||||
}
|
||||
}, [loading, clientId, clientSecret, enabled, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -89,6 +108,18 @@ export default function FacebookProviderSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
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>
|
||||
</InputAdornment>
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
@@ -12,30 +14,37 @@ import {
|
||||
baseProviderValidationSchema,
|
||||
} from '@/features/authentication/settings/components/BaseProviderSettings';
|
||||
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 {
|
||||
GetSignInMethodsDocument,
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useTheme } from '@mui/material';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export default function GitHubProviderSettings() {
|
||||
const theme = useTheme();
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetSignInMethodsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { clientId, clientSecret, enabled } =
|
||||
@@ -51,6 +60,16 @@ export default function GitHubProviderSettings() {
|
||||
resolver: yupResolver(baseProviderValidationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
form.reset({
|
||||
clientId: clientId || '',
|
||||
clientSecret: clientSecret || '',
|
||||
enabled: enabled || false,
|
||||
});
|
||||
}
|
||||
}, [loading, clientId, clientSecret, enabled, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -91,6 +110,18 @@ export default function GitHubProviderSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
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>
|
||||
</InputAdornment>
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
@@ -12,28 +14,35 @@ import {
|
||||
baseProviderValidationSchema,
|
||||
} from '@/features/authentication/settings/components/BaseProviderSettings';
|
||||
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 {
|
||||
GetSignInMethodsDocument,
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export default function GoogleProviderSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetSignInMethodsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { clientId, clientSecret, enabled } =
|
||||
@@ -49,6 +58,16 @@ export default function GoogleProviderSettings() {
|
||||
resolver: yupResolver(baseProviderValidationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
form.reset({
|
||||
clientId: clientId || '',
|
||||
clientSecret: clientSecret || '',
|
||||
enabled: enabled || false,
|
||||
});
|
||||
}
|
||||
}, [loading, clientId, clientSecret, enabled, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -89,6 +108,18 @@ export default function GoogleProviderSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
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>
|
||||
</InputAdornment>
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { ControlledSelect } from '@/components/form/ControlledSelect';
|
||||
import { Form } from '@/components/form/Form';
|
||||
@@ -5,17 +7,20 @@ import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { Option } from '@/components/ui/v2/Option';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetAuthenticationSettingsDocument,
|
||||
useGetAuthenticationSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import {
|
||||
AUTH_GRAVATAR_DEFAULT,
|
||||
AUTH_GRAVATAR_RATING,
|
||||
} from '@/utils/constants/settings';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import * as Yup from 'yup';
|
||||
@@ -29,15 +34,19 @@ const validationSchema = Yup.object({
|
||||
export type GravatarFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function GravatarSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetAuthenticationSettingsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const {
|
||||
@@ -56,6 +65,16 @@ export default function GravatarSettings() {
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
form.reset({
|
||||
default: defaultGravatar || '',
|
||||
rating: rating || '',
|
||||
enabled: enabled || false,
|
||||
});
|
||||
}
|
||||
}, [loading, defaultGravatar, rating, enabled, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -91,6 +110,18 @@ export default function GravatarSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(values);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Gravatar settings are being updated...',
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
@@ -12,28 +14,35 @@ import {
|
||||
baseProviderValidationSchema,
|
||||
} from '@/features/authentication/settings/components/BaseProviderSettings';
|
||||
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 {
|
||||
GetSignInMethodsDocument,
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export default function LinkedInProviderSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetSignInMethodsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { clientId, clientSecret, enabled } =
|
||||
@@ -49,6 +58,16 @@ export default function LinkedInProviderSettings() {
|
||||
resolver: yupResolver(baseProviderValidationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
form.reset({
|
||||
clientId: clientId || '',
|
||||
clientSecret: clientSecret || '',
|
||||
enabled: enabled || false,
|
||||
});
|
||||
}
|
||||
}, [loading, clientId, clientSecret, enabled, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -89,6 +108,18 @@ export default function LinkedInProviderSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
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>
|
||||
</InputAdornment>
|
||||
}
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetAuthenticationSettingsDocument,
|
||||
useGetAuthenticationSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import * as Yup from 'yup';
|
||||
@@ -23,15 +28,19 @@ const validationSchema = Yup.object({
|
||||
export type MFASettingsFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function MFASettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetAuthenticationSettingsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { enabled, issuer } = data?.config?.auth?.totp || {};
|
||||
@@ -45,6 +54,15 @@ export default function MFASettings() {
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && issuer && enabled) {
|
||||
form.reset({
|
||||
issuer,
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
}, [loading, issuer, enabled, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -78,6 +96,18 @@ export default function MFASettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(values);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage:
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetSignInMethodsDocument,
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -20,15 +25,19 @@ const validationSchema = Yup.object({
|
||||
export type MagicLinkFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function MagicLinkSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetSignInMethodsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { enabled } = data?.config?.auth?.method?.emailPasswordless || {};
|
||||
@@ -41,6 +50,12 @@ export default function MagicLinkSettings() {
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
form.reset({ enabled });
|
||||
}
|
||||
}, [loading, enabled, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -75,6 +90,18 @@ export default function MagicLinkSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
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...',
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
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 {
|
||||
GetSmtpSettingsDocument,
|
||||
useGetSmtpSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as yup from 'yup';
|
||||
|
||||
const validationSchema = yup
|
||||
.object({
|
||||
sender: yup.string().label('SMTP Sender').email().required(),
|
||||
password: yup.string().label('Password').required(),
|
||||
})
|
||||
.required();
|
||||
|
||||
export type PostmarkFormValues = yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function PostmarkSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
|
||||
const { data } = useGetSmtpSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { sender, password } = data?.config?.provider?.smtp || {};
|
||||
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetSmtpSettingsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const form = useForm<PostmarkFormValues>({
|
||||
reValidateMode: 'onSubmit',
|
||||
resolver: yupResolver(validationSchema),
|
||||
defaultValues: {
|
||||
password: '',
|
||||
sender: '',
|
||||
},
|
||||
values: {
|
||||
password: password || '',
|
||||
sender: sender || '',
|
||||
},
|
||||
mode: 'onSubmit',
|
||||
criteriaMode: 'all',
|
||||
});
|
||||
|
||||
const {
|
||||
register,
|
||||
formState: { errors, isDirty, isSubmitting },
|
||||
} = form;
|
||||
|
||||
const handleEditPostmarkSettings = async (values: PostmarkFormValues) => {
|
||||
const updateConfigPromise = updateConfig({
|
||||
variables: {
|
||||
appId: currentProject.id,
|
||||
config: {
|
||||
provider: {
|
||||
smtp: { method: 'LOGIN', host: 'postmark', ...values },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Postmark settings are being updated...',
|
||||
successMessage: 'Postmark settings have been updated successfully.',
|
||||
errorMessage:
|
||||
'An error occurred while trying to update your Postmark settings.',
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<Form onSubmit={handleEditPostmarkSettings}>
|
||||
<SettingsContainer
|
||||
title="Postmark Settings"
|
||||
description="Configure postmark's native integration to send emails from your email domain."
|
||||
submitButtonText="Save"
|
||||
className="grid grid-cols-9 gap-4"
|
||||
slotProps={{
|
||||
submitButton: {
|
||||
disabled: !isDirty || maintenanceActive,
|
||||
loading: isSubmitting,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
{...register('sender')}
|
||||
id="sender"
|
||||
name="sender"
|
||||
label="From Email"
|
||||
placeholder="noreply@nhost.app"
|
||||
className="lg:col-span-4"
|
||||
hideEmptyHelperText
|
||||
fullWidth
|
||||
error={Boolean(errors.sender)}
|
||||
helperText={errors.sender?.message}
|
||||
/>
|
||||
|
||||
<Input
|
||||
{...register('password')}
|
||||
id="password"
|
||||
label="Password"
|
||||
type="password"
|
||||
placeholder="Enter password"
|
||||
className="lg:col-span-5"
|
||||
hideEmptyHelperText
|
||||
fullWidth
|
||||
error={Boolean(errors.password)}
|
||||
helperText={errors.password?.message}
|
||||
/>
|
||||
</SettingsContainer>
|
||||
</Form>
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './PostmarkSettings';
|
||||
export { default as PostmarkSettings } from './PostmarkSettings';
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
@@ -7,14 +9,17 @@ import { Option } from '@/components/ui/v2/Option';
|
||||
import { Select } from '@/components/ui/v2/Select';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetSignInMethodsDocument,
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import Image from 'next/image';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import * as Yup from 'yup';
|
||||
@@ -44,15 +49,19 @@ const validationSchema = Yup.object({
|
||||
export type SMSSettingsFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function SMSSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetSignInMethodsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, error, loading } = useGetSignInMethodsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { accountSid, authToken, messagingServiceId } =
|
||||
@@ -70,6 +79,17 @@ export default function SMSSettings() {
|
||||
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) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -114,6 +134,18 @@ export default function SMSSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(values);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'SMS settings are being updated...',
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { ControlledCheckbox } from '@/components/form/ControlledCheckbox';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
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 {
|
||||
GetSmtpSettingsDocument,
|
||||
useGetSmtpSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { type Optional } from 'utility-types';
|
||||
import * as yup from 'yup';
|
||||
|
||||
const smtpValidationSchema = yup
|
||||
.object({
|
||||
secure: yup.bool().label('SMTP Secure'),
|
||||
host: yup
|
||||
.string()
|
||||
.label('SMTP Host')
|
||||
.matches(
|
||||
/((https?):\/\/)?(www\.)?[a-z0-9]+(\.[a-z]{2,}){1,3}(#[a-zA-Z0-9#]+?)*\/?(\?[a-zA-Z0-9-_]+=[a-zA-Z0-9-%]+&?)?$/,
|
||||
'SMTP Host must be a valid URL',
|
||||
)
|
||||
.required(),
|
||||
port: yup
|
||||
.number()
|
||||
.typeError('The SMTP port should contain only numbers.')
|
||||
.required(),
|
||||
user: yup.string().label('Username').required(),
|
||||
password: yup.string().label('Password'),
|
||||
method: yup.string().required(),
|
||||
sender: yup.string().label('SMTP Sender').email().required(),
|
||||
})
|
||||
.required();
|
||||
|
||||
export type SmtpFormValues = yup.InferType<typeof smtpValidationSchema>;
|
||||
|
||||
export default function SMTPSettings() {
|
||||
const { maintenanceActive } = useUI();
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
|
||||
const { data } = useGetSmtpSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { secure, host, port, user, method, sender, password } =
|
||||
data?.config?.provider?.smtp || {};
|
||||
|
||||
const form = useForm<Optional<SmtpFormValues, 'password'>>({
|
||||
reValidateMode: 'onSubmit',
|
||||
resolver: yupResolver(smtpValidationSchema),
|
||||
defaultValues: {
|
||||
secure: false,
|
||||
host: '',
|
||||
port: undefined,
|
||||
user: '',
|
||||
password: '',
|
||||
method: '',
|
||||
sender: '',
|
||||
},
|
||||
values: {
|
||||
secure: secure || false,
|
||||
host: host || '',
|
||||
port,
|
||||
user: user || '',
|
||||
password: password || '',
|
||||
method: method || '',
|
||||
sender: sender || '',
|
||||
},
|
||||
mode: 'onSubmit',
|
||||
criteriaMode: 'all',
|
||||
});
|
||||
|
||||
const {
|
||||
register: registerSmtp,
|
||||
formState: { errors, isDirty, isSubmitting },
|
||||
} = form;
|
||||
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetSmtpSettingsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const handleEditSMTPSettings = async (values: SmtpFormValues) => {
|
||||
const { password: newPassword, ...valuesWithoutPassword } = values;
|
||||
|
||||
const updateConfigPromise = updateConfig({
|
||||
variables: {
|
||||
appId: currentProject.id,
|
||||
config: {
|
||||
provider: {
|
||||
smtp: newPassword ? values : valuesWithoutPassword,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'SMTP settings are being updated...',
|
||||
successMessage: 'SMTP settings have been updated successfully.',
|
||||
errorMessage:
|
||||
'An error occurred while trying to update the SMTP settings.',
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<Form onSubmit={handleEditSMTPSettings}>
|
||||
<SettingsContainer
|
||||
title="SMTP Settings"
|
||||
description="Configure your SMTP settings to send emails from your email domain."
|
||||
submitButtonText="Save"
|
||||
className="grid grid-cols-9 gap-4"
|
||||
slotProps={{
|
||||
submitButton: {
|
||||
disabled: !isDirty || maintenanceActive,
|
||||
loading: isSubmitting,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
{...registerSmtp('sender')}
|
||||
id="sender"
|
||||
name="sender"
|
||||
label="From Email"
|
||||
placeholder="noreply@nhost.app"
|
||||
className="lg:col-span-4"
|
||||
hideEmptyHelperText
|
||||
fullWidth
|
||||
error={Boolean(errors.sender)}
|
||||
helperText={errors.sender?.message}
|
||||
/>
|
||||
|
||||
<Input
|
||||
{...registerSmtp('host')}
|
||||
id="host"
|
||||
name="host"
|
||||
label="SMTP Host"
|
||||
className="lg:col-span-4"
|
||||
placeholder="e.g. smtp.sendgrid.net"
|
||||
hideEmptyHelperText
|
||||
fullWidth
|
||||
error={Boolean(errors.host)}
|
||||
helperText={errors.host?.message}
|
||||
/>
|
||||
|
||||
<Input
|
||||
{...registerSmtp('port')}
|
||||
id="port"
|
||||
name="port"
|
||||
label="Port"
|
||||
type="number"
|
||||
placeholder="587"
|
||||
className="lg:col-span-1"
|
||||
hideEmptyHelperText
|
||||
fullWidth
|
||||
error={Boolean(errors.port)}
|
||||
helperText={errors.port?.message}
|
||||
/>
|
||||
|
||||
<Input
|
||||
{...registerSmtp('user')}
|
||||
id="user"
|
||||
label="SMTP Username"
|
||||
placeholder="SMTP Username"
|
||||
className="lg:col-span-4"
|
||||
hideEmptyHelperText
|
||||
fullWidth
|
||||
error={Boolean(errors.user)}
|
||||
helperText={errors.user?.message}
|
||||
/>
|
||||
|
||||
<Input
|
||||
{...registerSmtp('password')}
|
||||
id="password"
|
||||
label="SMTP Password"
|
||||
type="password"
|
||||
placeholder="Enter SMTP password"
|
||||
className="lg:col-span-5"
|
||||
hideEmptyHelperText
|
||||
fullWidth
|
||||
error={Boolean(errors.password)}
|
||||
helperText={errors.password?.message}
|
||||
/>
|
||||
|
||||
<Input
|
||||
{...registerSmtp('method')}
|
||||
id="method"
|
||||
name="method"
|
||||
label="SMTP Auth Method"
|
||||
placeholder="LOGIN"
|
||||
hideEmptyHelperText
|
||||
className="lg:col-span-4"
|
||||
fullWidth
|
||||
error={Boolean(errors.method)}
|
||||
helperText={errors.method?.message}
|
||||
/>
|
||||
|
||||
<ControlledCheckbox
|
||||
name="secure"
|
||||
id="secure"
|
||||
label="Use SSL"
|
||||
className="lg:col-span-9"
|
||||
/>
|
||||
</SettingsContainer>
|
||||
</Form>
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './SMTPSettings';
|
||||
export { default as SMTPSettings } from './SMTPSettings';
|
||||
@@ -1,16 +1,21 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetAuthenticationSettingsDocument,
|
||||
useGetAuthenticationSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -28,15 +33,19 @@ const validationSchema = Yup.object({
|
||||
export type SessionFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function SessionSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetAuthenticationSettingsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { accessToken, refreshToken } = data?.config?.auth?.session || {};
|
||||
@@ -50,6 +59,15 @@ export default function SessionSettings() {
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && accessToken && refreshToken) {
|
||||
form.reset({
|
||||
accessTokenExpiresIn: accessToken?.expiresIn || 900,
|
||||
refreshTokenExpiresIn: refreshToken?.expiresIn || 43200,
|
||||
});
|
||||
}
|
||||
}, [loading, accessToken, refreshToken, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -86,6 +104,18 @@ export default function SessionSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Session settings are being updated...',
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
@@ -12,28 +14,35 @@ import {
|
||||
baseProviderValidationSchema,
|
||||
} from '@/features/authentication/settings/components/BaseProviderSettings';
|
||||
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 {
|
||||
GetSignInMethodsDocument,
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export default function SpotifyProviderSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetSignInMethodsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { clientId, clientSecret, enabled } =
|
||||
@@ -49,6 +58,16 @@ export default function SpotifyProviderSettings() {
|
||||
resolver: yupResolver(baseProviderValidationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
form.reset({
|
||||
clientId: clientId || '',
|
||||
clientSecret: clientSecret || '',
|
||||
enabled: enabled || false,
|
||||
});
|
||||
}
|
||||
}, [loading, clientId, clientSecret, enabled, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -89,6 +108,18 @@ export default function SpotifyProviderSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
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>
|
||||
</InputAdornment>
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
@@ -12,30 +14,37 @@ import {
|
||||
baseProviderValidationSchema,
|
||||
} from '@/features/authentication/settings/components/BaseProviderSettings';
|
||||
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 {
|
||||
GetSignInMethodsDocument,
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useTheme } from '@mui/material';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export default function TwitchProviderSettings() {
|
||||
const theme = useTheme();
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { maintenanceActive } = useUI();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetSignInMethodsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { clientId, clientSecret, enabled } =
|
||||
@@ -51,6 +60,16 @@ export default function TwitchProviderSettings() {
|
||||
resolver: yupResolver(baseProviderValidationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
form.reset({
|
||||
clientId: clientId || '',
|
||||
clientSecret: clientSecret || '',
|
||||
enabled: enabled || false,
|
||||
});
|
||||
}
|
||||
}, [loading, clientId, clientSecret, enabled, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -91,6 +110,18 @@ export default function TwitchProviderSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
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>
|
||||
</InputAdornment>
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
@@ -7,15 +9,18 @@ import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { InputAdornment } from '@/components/ui/v2/InputAdornment';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||
import {
|
||||
GetSignInMethodsDocument,
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import * as Yup from 'yup';
|
||||
@@ -39,15 +44,19 @@ const validationSchema = Yup.object({
|
||||
export type TwitterProviderFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function TwitterProviderSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetSignInMethodsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { consumerKey, consumerSecret, enabled } =
|
||||
@@ -63,6 +72,16 @@ export default function TwitterProviderSettings() {
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
form.reset({
|
||||
consumerSecret: consumerSecret || '',
|
||||
consumerKey: consumerKey || '',
|
||||
enabled: enabled || false,
|
||||
});
|
||||
}
|
||||
}, [loading, consumerKey, consumerSecret, enabled, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -100,6 +119,18 @@ export default function TwitterProviderSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
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>
|
||||
</InputAdornment>
|
||||
}
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetSignInMethodsDocument,
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -20,15 +25,19 @@ const validationSchema = Yup.object({
|
||||
export type WebAuthnFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function WebAuthnSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { maintenanceActive } = useUI();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetSignInMethodsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { enabled } = data?.config?.auth?.method?.webauthn || {};
|
||||
@@ -41,6 +50,12 @@ export default function WebAuthnSettings() {
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
form.reset({ enabled });
|
||||
}
|
||||
}, [loading, enabled, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -80,6 +95,18 @@ export default function WebAuthnSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(values);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'WebAuthn settings are being updated...',
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
@@ -12,28 +14,35 @@ import {
|
||||
baseProviderValidationSchema,
|
||||
} from '@/features/authentication/settings/components/BaseProviderSettings';
|
||||
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 {
|
||||
GetSignInMethodsDocument,
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export default function WindowsLiveProviderSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetSignInMethodsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { clientId, clientSecret, enabled } =
|
||||
@@ -49,6 +58,16 @@ export default function WindowsLiveProviderSettings() {
|
||||
resolver: yupResolver(baseProviderValidationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
form.reset({
|
||||
clientId: clientId || '',
|
||||
clientSecret: clientSecret || '',
|
||||
enabled: enabled || false,
|
||||
});
|
||||
}
|
||||
}, [loading, clientId, clientSecret, enabled, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -89,6 +108,18 @@ export default function WindowsLiveProviderSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
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...',
|
||||
@@ -151,7 +182,7 @@ export default function WindowsLiveProviderSettings() {
|
||||
);
|
||||
}}
|
||||
>
|
||||
<CopyIcon className="h-4 w-4" />
|
||||
<CopyIcon className="w-4 h-4" />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
@@ -8,15 +10,18 @@ import { Input } from '@/components/ui/v2/Input';
|
||||
import { InputAdornment } from '@/components/ui/v2/InputAdornment';
|
||||
import { BaseProviderSettings } from '@/features/authentication/settings/components/BaseProviderSettings';
|
||||
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 {
|
||||
GetSignInMethodsDocument,
|
||||
useGetSignInMethodsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import * as Yup from 'yup';
|
||||
@@ -52,15 +57,19 @@ const validationSchema = Yup.object({
|
||||
export type WorkOsProviderFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function WorkOsProviderSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetSignInMethodsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetSignInMethodsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { clientId, clientSecret, organization, connection, enabled } =
|
||||
@@ -78,6 +87,26 @@ export default function WorkOsProviderSettings() {
|
||||
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) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -115,6 +144,18 @@ export default function WorkOsProviderSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
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>
|
||||
</InputAdornment>
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetPostgresSettingsDocument,
|
||||
Software_Type_Enum,
|
||||
@@ -11,8 +14,10 @@ import {
|
||||
useGetSoftwareVersionsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -30,24 +35,30 @@ export type DatabaseServiceVersionFormValues = Yup.InferType<
|
||||
>;
|
||||
|
||||
export default function DatabaseServiceVersionSettings() {
|
||||
const isPlatform = useIsPlatform();
|
||||
const { openDialog } = useDialog();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetPostgresSettingsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetPostgresSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data: databaseVersionsData } = useGetSoftwareVersionsQuery({
|
||||
variables: {
|
||||
software: Software_Type_Enum.PostgreSql,
|
||||
},
|
||||
skip: !isPlatform,
|
||||
});
|
||||
|
||||
const { version } = data?.config?.postgres || {};
|
||||
|
||||
const databaseVersions = databaseVersionsData?.softwareVersions || [];
|
||||
const availableVersions = Array.from(
|
||||
new Set(databaseVersions.map((el) => el.version)).add(version),
|
||||
@@ -61,10 +72,21 @@ export default function DatabaseServiceVersionSettings() {
|
||||
|
||||
const form = useForm<DatabaseServiceVersionFormValues>({
|
||||
reValidateMode: 'onSubmit',
|
||||
defaultValues: { version: { label: version, value: version } },
|
||||
defaultValues: { version: { label: '', value: '' } },
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && version) {
|
||||
form.reset({
|
||||
version: {
|
||||
label: version,
|
||||
value: version,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [loading, version, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -99,6 +121,18 @@ export default function DatabaseServiceVersionSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Postgres version is being updated...',
|
||||
@@ -123,12 +157,20 @@ export default function DatabaseServiceVersionSettings() {
|
||||
}}
|
||||
docsLink="https://hub.docker.com/r/nhost/postgres/tags"
|
||||
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
|
||||
id="version"
|
||||
name="version"
|
||||
autoHighlight
|
||||
freeSolo
|
||||
getOptionLabel={(option) => {
|
||||
if (typeof option === 'string') {
|
||||
return option || '';
|
||||
}
|
||||
|
||||
return option.value;
|
||||
}}
|
||||
isOptionEqualToValue={() => false}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
const inputValueLower = inputValue.toLowerCase();
|
||||
|
||||
@@ -7,10 +7,12 @@ import { Box } from '@/components/ui/v2/Box';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { UpgradeNotification } from '@/features/projects/common/components/UpgradeNotification';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
useGetPostgresSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
@@ -18,13 +20,15 @@ import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
capacity: Yup.number().required(),
|
||||
capacity: Yup.number().required().min(10),
|
||||
});
|
||||
|
||||
export type AuthDomainFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function AuthDomain() {
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
|
||||
const {
|
||||
@@ -34,14 +38,17 @@ export default function AuthDomain() {
|
||||
refetch: refetchPostgresSettings,
|
||||
} = useGetPostgresSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const capacity =
|
||||
data?.config?.postgres?.resources?.storage?.capacity ??
|
||||
currentProject.plan.featureMaxDbSize;
|
||||
(data?.config?.postgres?.resources?.storage?.capacity ??
|
||||
currentProject?.plan?.featureMaxDbSize) ||
|
||||
0;
|
||||
|
||||
const [updateConfig] = useUpdateConfigMutation();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const form = useForm<Yup.InferType<typeof validationSchema>>({
|
||||
reValidateMode: 'onSubmit',
|
||||
@@ -117,7 +124,7 @@ export default function AuthDomain() {
|
||||
}}
|
||||
className="flex flex-col"
|
||||
>
|
||||
{currentProject.plan.isFree && (
|
||||
{currentProject.plan?.isFree && (
|
||||
<UpgradeNotification message="Unlock by upgrading your project to the Pro plan." />
|
||||
)}
|
||||
<Box className="grid grid-flow-row lg:grid-cols-5">
|
||||
@@ -127,7 +134,7 @@ export default function AuthDomain() {
|
||||
name="capacity"
|
||||
type="number"
|
||||
fullWidth
|
||||
disabled={currentProject.plan.isFree}
|
||||
disabled={currentProject.plan?.isFree}
|
||||
className="lg:col-span-2"
|
||||
error={Boolean(formState.errors.capacity?.message)}
|
||||
helperText={formState.errors.capacity?.message}
|
||||
@@ -138,7 +145,7 @@ export default function AuthDomain() {
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
{!currentProject.plan.isFree && (
|
||||
{!currentProject.plan?.isFree && (
|
||||
<Alert severity="info" className="col-span-6 text-left">
|
||||
Note that volumes can only be increased (not decreased). Also, due
|
||||
to an AWS limitation, the same volume can only be increased once
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetHasuraSettingsDocument,
|
||||
useGetHasuraSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -20,16 +25,20 @@ const validationSchema = Yup.object({
|
||||
export type HasuraAllowListFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function HasuraAllowListSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
||||
useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetHasuraSettingsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetHasuraSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-first',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { enableAllowList } = data?.config?.hasura.settings || {};
|
||||
@@ -42,6 +51,14 @@ export default function HasuraAllowListSettings() {
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
form.reset({
|
||||
enabled: enableAllowList,
|
||||
});
|
||||
}
|
||||
}, [loading, enableAllowList, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -75,6 +92,18 @@ export default function HasuraAllowListSettings() {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
await refetchWorkspaceAndProject();
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Allow list settings are being updated...',
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetHasuraSettingsDocument,
|
||||
useGetHasuraSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -20,16 +25,20 @@ const validationSchema = Yup.object({
|
||||
export type HasuraConsoleFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function HasuraConsoleSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
||||
useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetHasuraSettingsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetHasuraSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-first',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { enableConsole } = data?.config?.hasura.settings || {};
|
||||
@@ -42,6 +51,14 @@ export default function HasuraConsoleSettings() {
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
form.reset({
|
||||
enabled: enableConsole,
|
||||
});
|
||||
}
|
||||
}, [loading, enableConsole, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -75,6 +92,18 @@ export default function HasuraConsoleSettings() {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
await refetchWorkspaceAndProject();
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Hasura Console settings are being updated...',
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetHasuraSettingsDocument,
|
||||
useGetHasuraSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
@@ -28,16 +32,20 @@ const validationSchema = Yup.object({
|
||||
export type HasuraCorsDomainFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function HasuraCorsDomainSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
||||
useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetHasuraSettingsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetHasuraSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-first',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { corsDomain } = data?.config?.hasura.settings || {};
|
||||
@@ -98,6 +106,18 @@ export default function HasuraCorsDomainSettings() {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
await refetchWorkspaceAndProject();
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'CORS domain settings are being updated...',
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetHasuraSettingsDocument,
|
||||
useGetHasuraSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -20,16 +25,20 @@ const validationSchema = Yup.object({
|
||||
export type HasuraDevModeFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function HasuraDevModeSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
||||
useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetHasuraSettingsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetHasuraSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-first',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { devMode } = data?.config?.hasura.settings || {};
|
||||
@@ -42,6 +51,14 @@ export default function HasuraDevModeSettings() {
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
form.reset({
|
||||
enabled: devMode,
|
||||
});
|
||||
}
|
||||
}, [loading, devMode, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -63,7 +80,7 @@ export default function HasuraDevModeSettings() {
|
||||
config: {
|
||||
hasura: {
|
||||
settings: {
|
||||
enableConsole: formValues.enabled,
|
||||
devMode: formValues.enabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -75,6 +92,18 @@ export default function HasuraDevModeSettings() {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
await refetchWorkspaceAndProject();
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Dev Mode settings are being updated...',
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetHasuraSettingsDocument,
|
||||
useGetHasuraSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -30,31 +35,40 @@ export type HasuraEnabledAPIFormValues = Yup.InferType<typeof validationSchema>;
|
||||
const AVAILABLE_HASURA_APIS = ['metadata', 'graphql', 'pgdump', 'config'];
|
||||
|
||||
export default function HasuraEnabledAPISettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
||||
useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetHasuraSettingsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetHasuraSettingsQuery({
|
||||
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>({
|
||||
reValidateMode: 'onSubmit',
|
||||
defaultValues: {
|
||||
enabledAPIs: enabledAPIs.map((api) => ({
|
||||
label: api,
|
||||
value: api,
|
||||
})),
|
||||
enabledAPIs: [],
|
||||
},
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (enabledAPIs && !loading) {
|
||||
form.reset({
|
||||
enabledAPIs: enabledAPIs.map((api) => ({ label: api, value: api })),
|
||||
});
|
||||
}
|
||||
}, [form, enabledAPIs, loading]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -96,6 +110,18 @@ export default function HasuraEnabledAPISettings() {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
await refetchWorkspaceAndProject();
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Enabled APIs are being updated...',
|
||||
@@ -117,7 +143,7 @@ export default function HasuraEnabledAPISettings() {
|
||||
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
|
||||
id="enabledAPIs"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
|
||||
import { Form } from '@/components/form/Form';
|
||||
@@ -5,13 +7,16 @@ import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { HighlightedText } from '@/components/presentational/HighlightedText';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetHasuraSettingsDocument,
|
||||
useGetHasuraSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
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'];
|
||||
|
||||
export default function HasuraLogLevelSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { maintenanceActive } = useUI();
|
||||
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
||||
useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetHasuraSettingsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetHasuraSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-first',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { level } = data?.config?.hasura.logs || {};
|
||||
@@ -56,6 +65,17 @@ export default function HasuraLogLevelSettings() {
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && level) {
|
||||
form.reset({
|
||||
logLevel: {
|
||||
label: level,
|
||||
value: level,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [form, loading, level]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -97,6 +117,18 @@ export default function HasuraLogLevelSettings() {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
await refetchWorkspaceAndProject();
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Log level is being updated...',
|
||||
@@ -128,7 +160,7 @@ export default function HasuraLogLevelSettings() {
|
||||
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
|
||||
id="logLevel"
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetHasuraSettingsDocument,
|
||||
useGetHasuraSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
@@ -26,16 +30,20 @@ const validationSchema = Yup.object({
|
||||
export type HasuraPoolSizeFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function HasuraPoolSizeSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
||||
useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetHasuraSettingsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetHasuraSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-first',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { httpPoolSize } = data?.config?.hasura.events || {};
|
||||
@@ -82,6 +90,18 @@ export default function HasuraPoolSizeSettings() {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
await refetchWorkspaceAndProject();
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Pool size is being updated...',
|
||||
@@ -104,7 +124,7 @@ export default function HasuraPoolSizeSettings() {
|
||||
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
|
||||
{...register('httpPoolSize')}
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetHasuraSettingsDocument,
|
||||
useGetHasuraSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -22,16 +27,20 @@ export type HasuraRemoteSchemaPermissionsFormValues = Yup.InferType<
|
||||
>;
|
||||
|
||||
export default function HasuraRemoteSchemaPermissionsSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
||||
useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetHasuraSettingsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetHasuraSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-first',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { enableRemoteSchemaPermissions } = data?.config?.hasura.settings || {};
|
||||
@@ -44,6 +53,14 @@ export default function HasuraRemoteSchemaPermissionsSettings() {
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
form.reset({
|
||||
enabled: enableRemoteSchemaPermissions,
|
||||
});
|
||||
}
|
||||
}, [loading, enableRemoteSchemaPermissions, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -67,7 +84,7 @@ export default function HasuraRemoteSchemaPermissionsSettings() {
|
||||
config: {
|
||||
hasura: {
|
||||
settings: {
|
||||
enableConsole: formValues.enabled,
|
||||
enableRemoteSchemaPermissions: formValues.enabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -79,6 +96,18 @@ export default function HasuraRemoteSchemaPermissionsSettings() {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
await refetchWorkspaceAndProject();
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage:
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetHasuraSettingsDocument,
|
||||
Software_Type_Enum,
|
||||
@@ -11,8 +14,10 @@ import {
|
||||
useGetSoftwareVersionsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -30,22 +35,27 @@ export type HasuraServiceVersionFormValues = Yup.InferType<
|
||||
>;
|
||||
|
||||
export default function HasuraServiceVersionSettings() {
|
||||
const isPlatform = useIsPlatform();
|
||||
const { openDialog } = useDialog();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
||||
useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetHasuraSettingsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetHasuraSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data: hasuraVersionsData } = useGetSoftwareVersionsQuery({
|
||||
variables: {
|
||||
software: Software_Type_Enum.Hasura,
|
||||
},
|
||||
skip: !isPlatform,
|
||||
});
|
||||
|
||||
const { version } = data?.config?.hasura || {};
|
||||
@@ -53,6 +63,7 @@ export default function HasuraServiceVersionSettings() {
|
||||
const availableVersions = Array.from(
|
||||
new Set(versions.map((el) => el.version)).add(version),
|
||||
)
|
||||
.filter((v) => !!v)
|
||||
.sort()
|
||||
.reverse()
|
||||
.map((availableVersion) => ({
|
||||
@@ -60,12 +71,25 @@ export default function HasuraServiceVersionSettings() {
|
||||
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>({
|
||||
reValidateMode: 'onSubmit',
|
||||
defaultValues: { version: { label: version, value: version } },
|
||||
defaultValues: { version: { label: '', value: '' } },
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && version) {
|
||||
form.reset({
|
||||
version: {
|
||||
label: version,
|
||||
value: version,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [loading, version, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -99,6 +123,18 @@ export default function HasuraServiceVersionSettings() {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
await refetchWorkspaceAndProject();
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Hasura version is being updated...',
|
||||
@@ -123,11 +159,19 @@ export default function HasuraServiceVersionSettings() {
|
||||
}}
|
||||
docsLink="https://hub.docker.com/r/nhost/graphql-engine/tags"
|
||||
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
|
||||
id="version"
|
||||
name="version"
|
||||
freeSolo
|
||||
getOptionLabel={(option) => {
|
||||
if (typeof option === 'string') {
|
||||
return option || '';
|
||||
}
|
||||
|
||||
return option.value;
|
||||
}}
|
||||
autoHighlight
|
||||
isOptionEqualToValue={() => false}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
|
||||
@@ -3,12 +3,12 @@ import { Button } from '@/components/ui/v2/Button';
|
||||
import { Checkbox } from '@/components/ui/v2/Checkbox';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { type RunService } from '@/hooks/useRunServices';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
useDeleteRunServiceConfigMutation,
|
||||
useDeleteRunServiceMutation,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import { type RunService } from 'pages/[workspaceSlug]/[appSlug]/services';
|
||||
import { useState } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ export default function useCurrentWorkspaceAndProject(): UseCurrentWorkspaceAndP
|
||||
// Return a default project if working locally
|
||||
if (!isPlatform) {
|
||||
const localProject: Project = {
|
||||
id: 'local',
|
||||
id: '00000000-0000-0000-0000-000000000000',
|
||||
slug: 'local',
|
||||
name: 'local',
|
||||
appStates: [
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import { useGetPlansQuery } from '@/utils/__generated__/graphql';
|
||||
|
||||
/**
|
||||
* Returns the Pro plan.
|
||||
*/
|
||||
export default function useProPlan() {
|
||||
const isPlatform = useIsPlatform();
|
||||
|
||||
const { data, ...rest } = useGetPlansQuery({
|
||||
variables: {
|
||||
where: {
|
||||
@@ -13,6 +16,7 @@ export default function useProPlan() {
|
||||
},
|
||||
},
|
||||
fetchPolicy: 'cache-first',
|
||||
skip: !isPlatform,
|
||||
});
|
||||
|
||||
return { data: data?.plans?.at(0), ...rest };
|
||||
|
||||
@@ -90,14 +90,6 @@ export default function useProjectRoutes() {
|
||||
icon: <GaugeIcon />,
|
||||
disabled: !isPlatform,
|
||||
},
|
||||
{
|
||||
relativeMainPath: '/settings',
|
||||
relativePath: '/settings/general',
|
||||
exact: false,
|
||||
label: 'Settings',
|
||||
icon: <CogIcon />,
|
||||
disabled: !isPlatform || maintenanceActive,
|
||||
},
|
||||
];
|
||||
|
||||
const allRoutes: ProjectRoute[] = [
|
||||
@@ -143,7 +135,6 @@ export default function useProjectRoutes() {
|
||||
exact: false,
|
||||
label: 'Run',
|
||||
icon: <ServicesIcon />,
|
||||
disabled: !isPlatform,
|
||||
},
|
||||
{
|
||||
relativeMainPath: '/ai',
|
||||
@@ -153,6 +144,14 @@ export default function useProjectRoutes() {
|
||||
icon: <AIIcon />,
|
||||
},
|
||||
...nhostRoutes,
|
||||
{
|
||||
relativeMainPath: '/settings',
|
||||
relativePath: '/settings/general',
|
||||
exact: false,
|
||||
label: 'Settings',
|
||||
icon: <CogIcon />,
|
||||
disabled: maintenanceActive,
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
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 {
|
||||
useGetAuthenticationSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
type ConfigIngressUpdateInput,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -23,12 +27,18 @@ const validationSchema = Yup.object({
|
||||
export type AuthDomainFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function AuthDomain() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const [isVerified, setIsVerified] = useState(false);
|
||||
|
||||
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
||||
useCurrentWorkspaceAndProject();
|
||||
|
||||
const [updateConfig] = useUpdateConfigMutation();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const form = useForm<Yup.InferType<typeof validationSchema>>({
|
||||
reValidateMode: 'onSubmit',
|
||||
@@ -40,6 +50,7 @@ export default function AuthDomain() {
|
||||
variables: {
|
||||
appId: currentProject.id,
|
||||
},
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { networking } = data?.config?.auth?.resources || {};
|
||||
@@ -94,6 +105,18 @@ export default function AuthDomain() {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
await refetchWorkspaceAndProject();
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
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 (
|
||||
<FormProvider {...form}>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
@@ -112,12 +143,11 @@ export default function AuthDomain() {
|
||||
description="Enter below your custom domain for the authentication service."
|
||||
slotProps={{
|
||||
submitButton: {
|
||||
disabled:
|
||||
!isDirty || maintenanceActive || (!isVerified && !initialValue),
|
||||
disabled: isDisabled(),
|
||||
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
|
||||
{...register('auth_fqdn')}
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
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 {
|
||||
useGetHasuraSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
type ConfigIngressUpdateInput,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -23,12 +27,18 @@ const validationSchema = Yup.object({
|
||||
export type HasuraDomainFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function HasuraDomain() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const [isVerified, setIsVerified] = useState(false);
|
||||
|
||||
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
||||
useCurrentWorkspaceAndProject();
|
||||
|
||||
const [updateConfig] = useUpdateConfigMutation();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const form = useForm<Yup.InferType<typeof validationSchema>>({
|
||||
reValidateMode: 'onSubmit',
|
||||
@@ -40,6 +50,7 @@ export default function HasuraDomain() {
|
||||
variables: {
|
||||
appId: currentProject.id,
|
||||
},
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { networking } = data?.config?.hasura?.resources || {};
|
||||
@@ -96,6 +107,18 @@ export default function HasuraDomain() {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
await refetchWorkspaceAndProject();
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
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 (
|
||||
<FormProvider {...form}>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
@@ -114,12 +145,11 @@ export default function HasuraDomain() {
|
||||
description="Enter below your custom domain for the Hasura/GraphQL service."
|
||||
slotProps={{
|
||||
submitButton: {
|
||||
disabled:
|
||||
!isDirty || maintenanceActive || (!isVerified && !initialValue),
|
||||
disabled: isDisabled(),
|
||||
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
|
||||
{...register('hasura_fqdn')}
|
||||
|
||||
@@ -5,29 +5,12 @@ import { Link } from '@/components/ui/v2/Link';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { RunServicePortDomain } from '@/features/projects/custom-domains/settings/components/RunServicePortDomain';
|
||||
import { useGetRunServicesQuery } from '@/utils/__generated__/graphql';
|
||||
import { useMemo } from 'react';
|
||||
import { useRunServices } from '@/hooks/useRunServices';
|
||||
|
||||
export default function RunServiceDomains() {
|
||||
const { currentProject, currentWorkspace } = useCurrentWorkspaceAndProject();
|
||||
|
||||
const {
|
||||
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],
|
||||
);
|
||||
const { services, loading } = useRunServices();
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@@ -45,7 +28,7 @@ export default function RunServiceDomains() {
|
||||
.filter((service) => service.config?.ports?.length > 0)
|
||||
.map((service) => (
|
||||
<SettingsContainer
|
||||
key={service.id}
|
||||
key={service.id ?? service.serviceID}
|
||||
title={
|
||||
<div className="flex flex-row items-center">
|
||||
<Text className="text-lg font-semibold">
|
||||
@@ -58,7 +41,7 @@ export default function RunServiceDomains() {
|
||||
underline="hover"
|
||||
className="font-medium"
|
||||
>
|
||||
<ArrowSquareOutIcon className="mb-1 ml-1 h-4 w-4" />
|
||||
<ArrowSquareOutIcon className="w-4 h-4 mb-1 ml-1" />
|
||||
</Link>
|
||||
</div>
|
||||
}
|
||||
@@ -72,7 +55,7 @@ export default function RunServiceDomains() {
|
||||
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) => (
|
||||
<RunServicePortDomain
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { Button } from '@/components/ui/v2/Button';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
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 { useUpdateRunServiceConfigMutation } from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { type RunService } from '@/hooks/useRunServices';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { type RunService } from 'pages/[workspaceSlug]/[appSlug]/services';
|
||||
import { useState } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
@@ -28,12 +32,17 @@ export default function RunServicePortDomain({
|
||||
service,
|
||||
port,
|
||||
}: RunServicePortProps) {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isVerified, setIsVerified] = useState(false);
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
|
||||
const [updateRunServiceConfig] = useUpdateRunServiceConfigMutation();
|
||||
const [updateRunServiceConfig] = useUpdateRunServiceConfigMutation({
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const runServicePort = service.config.ports.find((p) => p.port === port);
|
||||
const initialValue = runServicePort?.ingresses?.[0]?.fqdn?.[0];
|
||||
@@ -59,7 +68,7 @@ export default function RunServicePortDomain({
|
||||
await updateRunServiceConfig({
|
||||
variables: {
|
||||
appID: currentProject.id,
|
||||
serviceID: service.id,
|
||||
serviceID: service.id ?? service.serviceID,
|
||||
config: {
|
||||
ports: service?.config?.ports?.map((p) => {
|
||||
// exclude the `__typename` because the mutation will fail otherwise
|
||||
@@ -78,7 +87,7 @@ export default function RunServicePortDomain({
|
||||
return {
|
||||
...rest,
|
||||
// exclude the `__typename` because the mutation will fail otherwise
|
||||
ingresses: rest.ingresses.map((item) => ({
|
||||
ingresses: rest?.ingresses?.map((item) => ({
|
||||
fqdn: item.fqdn,
|
||||
})),
|
||||
};
|
||||
@@ -88,6 +97,18 @@ export default function RunServicePortDomain({
|
||||
});
|
||||
|
||||
form.reset(formValues);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: `Port ${port} is being updated...`,
|
||||
@@ -99,6 +120,16 @@ export default function RunServicePortDomain({
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
const isDisabled = () => {
|
||||
if (!isPlatform) {
|
||||
return loading || !isDirty || maintenanceActive;
|
||||
}
|
||||
|
||||
return (
|
||||
loading || !isDirty || maintenanceActive || (!isVerified && !initialValue)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
@@ -121,16 +152,7 @@ export default function RunServicePortDomain({
|
||||
inputRoot: { min: 1, max: 100 },
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="outlined"
|
||||
type="submit"
|
||||
disabled={
|
||||
loading ||
|
||||
!isDirty ||
|
||||
maintenanceActive ||
|
||||
(!isVerified && !initialValue)
|
||||
}
|
||||
>
|
||||
<Button variant="outlined" type="submit" disabled={isDisabled()}>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
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 {
|
||||
useGetServerlessFunctionsSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
type ConfigIngressUpdateInput,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -25,12 +29,17 @@ export type ServerlessFunctionsDomainFormValues = Yup.InferType<
|
||||
>;
|
||||
|
||||
export default function ServerlessFunctionsDomain() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const [isVerified, setIsVerified] = useState(false);
|
||||
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
||||
useCurrentWorkspaceAndProject();
|
||||
|
||||
const [updateConfig] = useUpdateConfigMutation();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const form = useForm<ServerlessFunctionsDomainFormValues>({
|
||||
reValidateMode: 'onSubmit',
|
||||
@@ -42,6 +51,7 @@ export default function ServerlessFunctionsDomain() {
|
||||
variables: {
|
||||
appId: currentProject.id,
|
||||
},
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { networking } = data?.config?.functions?.resources || {};
|
||||
@@ -98,6 +108,18 @@ export default function ServerlessFunctionsDomain() {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
await refetchWorkspaceAndProject();
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
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 (
|
||||
<FormProvider {...form}>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
@@ -117,12 +147,11 @@ export default function ServerlessFunctionsDomain() {
|
||||
description="Enter below your custom domain for Serverless Functions."
|
||||
slotProps={{
|
||||
submitButton: {
|
||||
disabled:
|
||||
!isDirty || maintenanceActive || (!isVerified && !initialValue),
|
||||
disabled: isDisabled(),
|
||||
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
|
||||
{...register('functions_fqdn')}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Button } from '@/components/ui/v2/Button';
|
||||
import { IconButton } from '@/components/ui/v2/IconButton';
|
||||
import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { useDnsLookupCnameLazyQuery } from '@/utils/__generated__/graphql';
|
||||
@@ -21,6 +22,7 @@ export default function VerifyDomain({
|
||||
value,
|
||||
onHostNameVerified,
|
||||
}: VerifyDomainProps) {
|
||||
const isPlatform = useIsPlatform();
|
||||
const [verificationFailed, setVerificationFailed] = useState(false);
|
||||
const [verificationSucceeded, setVerificationSucceeded] = useState(false);
|
||||
|
||||
@@ -72,8 +74,11 @@ export default function VerifyDomain({
|
||||
backgroundColor: 'success.light',
|
||||
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">
|
||||
{!verificationFailed && !verificationSucceeded && (
|
||||
@@ -110,23 +115,29 @@ export default function VerifyDomain({
|
||||
</div>
|
||||
<div className="flex flex-row space-x-2">
|
||||
<Text>Value:</Text>
|
||||
<Text className="font-bold">{value}</Text>
|
||||
<IconButton
|
||||
aria-label="Copy Personal Access Token"
|
||||
variant="borderless"
|
||||
color="secondary"
|
||||
onClick={() => copy(value, 'CNAME Value')}
|
||||
>
|
||||
<CopyIcon className="h-4 w-4" />
|
||||
</IconButton>
|
||||
{isPlatform ? (
|
||||
<>
|
||||
<Text className="font-bold">{value}</Text>
|
||||
<IconButton
|
||||
aria-label="Copy Personal Access Token"
|
||||
variant="borderless"
|
||||
color="secondary"
|
||||
onClick={() => copy(value, 'CNAME Value')}
|
||||
>
|
||||
<CopyIcon className="w-4 h-4" />
|
||||
</IconButton>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
<Button
|
||||
disabled={loading || !hostname}
|
||||
onClick={handleVerifyDomain}
|
||||
className="mt-4 sm:absolute sm:bottom-0 sm:right-0 sm:mt-0"
|
||||
>
|
||||
Verify
|
||||
</Button>
|
||||
{isPlatform ? (
|
||||
<Button
|
||||
disabled={loading || !hostname || isPlatform}
|
||||
onClick={handleVerifyDomain}
|
||||
className="mt-4 sm:absolute sm:bottom-0 sm:right-0 sm:mt-0"
|
||||
>
|
||||
Verify
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import type {
|
||||
BaseEnvironmentVariableFormProps,
|
||||
BaseEnvironmentVariableFormValues,
|
||||
@@ -8,6 +11,7 @@ import {
|
||||
BaseEnvironmentVariableForm,
|
||||
baseEnvironmentVariableFormValidationSchema,
|
||||
} from '@/features/projects/environmentVariables/settings/components/BaseEnvironmentVariableForm';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
GetEnvironmentVariablesDocument,
|
||||
@@ -22,13 +26,17 @@ export interface CreateEnvironmentVariableFormProps
|
||||
/**
|
||||
* Function to be called when the form is submitted.
|
||||
*/
|
||||
onSubmit?: () => Promise<void>;
|
||||
onSubmit?: () => Promise<any>;
|
||||
}
|
||||
|
||||
export default function CreateEnvironmentVariableForm({
|
||||
onSubmit,
|
||||
...props
|
||||
}: CreateEnvironmentVariableFormProps) {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
|
||||
const form = useForm<BaseEnvironmentVariableFormValues>({
|
||||
defaultValues: {
|
||||
name: '',
|
||||
@@ -42,13 +50,14 @@ export default function CreateEnvironmentVariableForm({
|
||||
|
||||
const { data, loading, error } = useGetEnvironmentVariablesQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const availableEnvironmentVariables = data?.config?.global?.environment || [];
|
||||
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetEnvironmentVariablesDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
@@ -103,7 +112,19 @@ export default function CreateEnvironmentVariableForm({
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
onSubmit?.();
|
||||
await onSubmit?.();
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Creating environment variable...',
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import type {
|
||||
BaseEnvironmentVariableFormProps,
|
||||
BaseEnvironmentVariableFormValues,
|
||||
@@ -8,6 +11,7 @@ import {
|
||||
BaseEnvironmentVariableForm,
|
||||
baseEnvironmentVariableFormValidationSchema,
|
||||
} from '@/features/projects/environmentVariables/settings/components/BaseEnvironmentVariableForm';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import type { EnvironmentVariable } from '@/types/application';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
@@ -27,7 +31,7 @@ export interface EditEnvironmentVariableFormProps
|
||||
/**
|
||||
* Function to be called when the form is submitted.
|
||||
*/
|
||||
onSubmit?: () => Promise<void>;
|
||||
onSubmit?: () => Promise<any>;
|
||||
}
|
||||
|
||||
export default function EditEnvironmentVariableForm({
|
||||
@@ -35,6 +39,10 @@ export default function EditEnvironmentVariableForm({
|
||||
onSubmit,
|
||||
...props
|
||||
}: EditEnvironmentVariableFormProps) {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
|
||||
const form = useForm<BaseEnvironmentVariableFormValues>({
|
||||
defaultValues: {
|
||||
id: originalEnvironmentVariable.id || '',
|
||||
@@ -49,13 +57,14 @@ export default function EditEnvironmentVariableForm({
|
||||
|
||||
const { data, loading, error } = useGetEnvironmentVariablesQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const availableEnvironmentVariables = data?.config?.global?.environment || [];
|
||||
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetEnvironmentVariablesDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
@@ -117,7 +126,19 @@ export default function EditEnvironmentVariableForm({
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
onSubmit?.();
|
||||
await onSubmit?.();
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Updating environment variable...',
|
||||
|
||||
@@ -3,6 +3,8 @@ import { Form } from '@/components/form/Form';
|
||||
import { Button } from '@/components/ui/v2/Button';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
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 { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
@@ -63,9 +65,12 @@ export default function EditJwtSecretForm({
|
||||
submitButtonText = 'Save',
|
||||
location,
|
||||
}: EditJwtSecretFormProps) {
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetEnvironmentVariablesDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { onDirtyStateChange } = useDialog();
|
||||
@@ -118,9 +123,9 @@ export default function EditJwtSecretForm({
|
||||
<FormProvider {...form}>
|
||||
<Form
|
||||
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
|
||||
{...register('jwtSecret')}
|
||||
error={Boolean(errors.jwtSecret?.message)}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
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 { Text } from '@/components/ui/v2/Text';
|
||||
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 { EditEnvironmentVariableForm } from '@/features/projects/environmentVariables/settings/components/EditEnvironmentVariableForm';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import type { EnvironmentVariable } from '@/types/application';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
@@ -33,12 +36,14 @@ export interface EnvironmentVariableSettingsFormValues {
|
||||
}
|
||||
|
||||
export default function EnvironmentVariableSettings() {
|
||||
const { openDialog, openAlertDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { openDialog, openAlertDialog } = useDialog();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const { data, loading, error } = useGetEnvironmentVariablesQuery({
|
||||
const { data, loading, error, refetch } = useGetEnvironmentVariablesQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const availableEnvironmentVariables = [
|
||||
@@ -57,6 +62,7 @@ export default function EnvironmentVariableSettings() {
|
||||
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetEnvironmentVariablesDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
@@ -92,6 +98,18 @@ export default function EnvironmentVariableSettings() {
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Deleting environment variable...',
|
||||
@@ -105,7 +123,7 @@ export default function EnvironmentVariableSettings() {
|
||||
function handleOpenCreator() {
|
||||
openDialog({
|
||||
title: 'Create Environment Variable',
|
||||
component: <CreateEnvironmentVariableForm />,
|
||||
component: <CreateEnvironmentVariableForm onSubmit={refetch} />,
|
||||
props: {
|
||||
titleProps: { className: '!pb-0' },
|
||||
PaperProps: { className: 'gap-2 max-w-sm' },
|
||||
@@ -119,6 +137,7 @@ export default function EnvironmentVariableSettings() {
|
||||
component: (
|
||||
<EditEnvironmentVariableForm
|
||||
originalEnvironmentVariable={originalVariable}
|
||||
onSubmit={refetch}
|
||||
/>
|
||||
),
|
||||
props: {
|
||||
@@ -159,7 +178,7 @@ export default function EnvironmentVariableSettings() {
|
||||
)}
|
||||
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>
|
||||
</Box>
|
||||
|
||||
@@ -175,7 +194,7 @@ export default function EnvironmentVariableSettings() {
|
||||
<Dropdown.Trigger
|
||||
asChild
|
||||
hideChevron
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2"
|
||||
className="absolute -translate-y-1/2 right-4 top-1/2"
|
||||
>
|
||||
<IconButton
|
||||
variant="borderless"
|
||||
|
||||
@@ -21,11 +21,15 @@ import {
|
||||
} from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||
import { EditJwtSecretForm } from '@/features/projects/environmentVariables/settings/components/EditJwtSecretForm';
|
||||
import { getJwtSecretsWithoutFalsyValues } from '@/features/projects/environmentVariables/settings/utils/getJwtSecretsWithoutFalsyValues';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { getHasuraConsoleServiceUrl } from '@/utils/env';
|
||||
import { useGetEnvironmentVariablesQuery } from '@/utils/__generated__/graphql';
|
||||
import { Fragment, useState } from 'react';
|
||||
|
||||
export default function SystemEnvironmentVariableSettings() {
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
|
||||
const [showAdminSecret, setShowAdminSecret] = useState(false);
|
||||
const [showWebhookSecret, setShowWebhookSecret] = useState(false);
|
||||
|
||||
@@ -34,7 +38,7 @@ export default function SystemEnvironmentVariableSettings() {
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const { data, loading, error } = useGetEnvironmentVariablesQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { jwtSecrets, webhookSecret, adminSecret } = data?.config?.hasura || {};
|
||||
@@ -46,8 +50,6 @@ export default function SystemEnvironmentVariableSettings() {
|
||||
? JSON.stringify(jwtSecretsWithoutFalsyValues[0], null, 2)
|
||||
: JSON.stringify(jwtSecretsWithoutFalsyValues, null, 2);
|
||||
|
||||
const isPlatform = useIsPlatform();
|
||||
|
||||
const appClient = useAppClient();
|
||||
|
||||
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."
|
||||
docsLink="https://docs.nhost.io/platform/environment-variables#system-environment-variables"
|
||||
rootClassName="gap-0"
|
||||
className="mt-2 mb-2.5 px-0"
|
||||
className="mb-2.5 mt-2 px-0"
|
||||
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 lg:col-span-2">Value</Text>
|
||||
</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.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">
|
||||
{showAdminSecret ? (
|
||||
<InlineCode className="!text-sm font-medium">
|
||||
@@ -156,9 +158,9 @@ export default function SystemEnvironmentVariableSettings() {
|
||||
onClick={() => setShowAdminSecret((show) => !show)}
|
||||
>
|
||||
{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>
|
||||
</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.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">
|
||||
{showWebhookSecret ? (
|
||||
<InlineCode className="!text-sm font-medium">
|
||||
@@ -191,9 +193,9 @@ export default function SystemEnvironmentVariableSettings() {
|
||||
onClick={() => setShowWebhookSecret((show) => !show)}
|
||||
>
|
||||
{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>
|
||||
</div>
|
||||
@@ -217,9 +219,9 @@ export default function SystemEnvironmentVariableSettings() {
|
||||
</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>
|
||||
|
||||
<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">
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import type {
|
||||
BasePermissionVariableFormProps,
|
||||
BasePermissionVariableFormValues,
|
||||
@@ -9,6 +12,7 @@ import {
|
||||
basePermissionVariableValidationSchema,
|
||||
} from '@/features/projects/permissions/settings/components/BasePermissionVariableForm';
|
||||
import { getAllPermissionVariables } from '@/features/projects/permissions/settings/utils/getAllPermissionVariables';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
GetRolesPermissionsDocument,
|
||||
@@ -23,18 +27,20 @@ export interface CreatePermissionVariableFormProps
|
||||
/**
|
||||
* Function to be called when the form is submitted.
|
||||
*/
|
||||
onSubmit?: () => Promise<void>;
|
||||
onSubmit?: () => Promise<any>;
|
||||
}
|
||||
|
||||
export default function CreatePermissionVariableForm({
|
||||
onSubmit,
|
||||
...props
|
||||
}: CreatePermissionVariableFormProps) {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
|
||||
const { data, error, loading } = useGetRolesPermissionsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { customClaims: permissionVariables } =
|
||||
@@ -51,6 +57,7 @@ export default function CreatePermissionVariableForm({
|
||||
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetRolesPermissionsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
@@ -106,6 +113,18 @@ export default function CreatePermissionVariableForm({
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
await onSubmit?.();
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Creating permission variable...',
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import type {
|
||||
BasePermissionVariableFormProps,
|
||||
BasePermissionVariableFormValues,
|
||||
@@ -9,6 +12,7 @@ import {
|
||||
basePermissionVariableValidationSchema,
|
||||
} from '@/features/projects/permissions/settings/components/BasePermissionVariableForm';
|
||||
import { getAllPermissionVariables } from '@/features/projects/permissions/settings/utils/getAllPermissionVariables';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import type { PermissionVariable } from '@/types/application';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
@@ -28,7 +32,7 @@ export interface EditPermissionVariableFormProps
|
||||
/**
|
||||
* Function to be called when the form is submitted.
|
||||
*/
|
||||
onSubmit?: () => Promise<void>;
|
||||
onSubmit?: () => any;
|
||||
}
|
||||
|
||||
export default function EditPermissionVariableForm({
|
||||
@@ -36,11 +40,14 @@ export default function EditPermissionVariableForm({
|
||||
onSubmit,
|
||||
...props
|
||||
}: EditPermissionVariableFormProps) {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
|
||||
const { data, error, loading } = useGetRolesPermissionsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { customClaims: permissionVariables } =
|
||||
@@ -57,6 +64,7 @@ export default function EditPermissionVariableForm({
|
||||
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetRolesPermissionsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
@@ -132,6 +140,19 @@ export default function EditPermissionVariableForm({
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
await onSubmit?.();
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Updating permission variable...',
|
||||
@@ -140,8 +161,6 @@ export default function EditPermissionVariableForm({
|
||||
'An error occurred while trying to update the permission variable.',
|
||||
},
|
||||
);
|
||||
|
||||
await onSubmit?.();
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
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 { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||
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 { EditPermissionVariableForm } from '@/features/projects/permissions/settings/components/EditPermissionVariableForm';
|
||||
import { getAllPermissionVariables } from '@/features/projects/permissions/settings/utils/getAllPermissionVariables';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import type { PermissionVariable } from '@/types/application';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
@@ -29,13 +32,15 @@ import { Fragment } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export default function PermissionVariableSettings() {
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const { openDialog, openAlertDialog } = useDialog();
|
||||
|
||||
const { data, loading, error } = useGetRolesPermissionsQuery({
|
||||
const { data, loading, error, refetch } = useGetRolesPermissionsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { customClaims: permissionVariables } =
|
||||
@@ -43,6 +48,7 @@ export default function PermissionVariableSettings() {
|
||||
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetRolesPermissionsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
@@ -55,6 +61,20 @@ export default function PermissionVariableSettings() {
|
||||
throw error;
|
||||
}
|
||||
|
||||
function showApplyChangesDialog() {
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDeleteVariable({ id }: PermissionVariable) {
|
||||
const updateConfigPromise = updateConfig({
|
||||
variables: {
|
||||
@@ -79,6 +99,7 @@ export default function PermissionVariableSettings() {
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
showApplyChangesDialog();
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Deleting permission variable...',
|
||||
@@ -92,7 +113,7 @@ export default function PermissionVariableSettings() {
|
||||
function handleOpenCreator() {
|
||||
openDialog({
|
||||
title: 'Create Permission Variable',
|
||||
component: <CreatePermissionVariableForm />,
|
||||
component: <CreatePermissionVariableForm onSubmit={refetch} />,
|
||||
props: {
|
||||
titleProps: { className: '!pb-0' },
|
||||
PaperProps: { className: 'max-w-sm' },
|
||||
@@ -104,7 +125,10 @@ export default function PermissionVariableSettings() {
|
||||
openDialog({
|
||||
title: 'Edit Permission Variable',
|
||||
component: (
|
||||
<EditPermissionVariableForm originalVariable={originalVariable} />
|
||||
<EditPermissionVariableForm
|
||||
originalVariable={originalVariable}
|
||||
onSubmit={refetch}
|
||||
/>
|
||||
),
|
||||
props: {
|
||||
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."
|
||||
docsLink="https://docs.nhost.io/guides/api/permissions#permission-variables"
|
||||
rootClassName="gap-0"
|
||||
className="my-2 px-0"
|
||||
className="px-0 my-2"
|
||||
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">Path</Text>
|
||||
</Box>
|
||||
@@ -167,7 +191,7 @@ export default function PermissionVariableSettings() {
|
||||
!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>
|
||||
<IconButton
|
||||
@@ -224,7 +248,7 @@ export default function PermissionVariableSettings() {
|
||||
<>
|
||||
X-Hasura-{permissionVariable.key}{' '}
|
||||
{permissionVariable.isSystemVariable && (
|
||||
<LockIcon className="h-4 w-4" />
|
||||
<LockIcon className="w-4 h-4" />
|
||||
)}
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
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 { Link } from '@/components/ui/v2/Link';
|
||||
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 { ResourcesConfirmationDialog } from '@/features/projects/resources/settings/components/ResourcesConfirmationDialog';
|
||||
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 type { ResourceSettingsFormValues } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
||||
import { resourceSettingsValidationSchema } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import {
|
||||
RESOURCE_VCPU_MULTIPLIER,
|
||||
RESOURCE_VCPU_PRICE,
|
||||
@@ -44,6 +47,8 @@ function getInitialServiceResources(
|
||||
}
|
||||
|
||||
export default function ResourcesForm() {
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { openDialog, closeDialog } = useDialog();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
|
||||
@@ -55,6 +60,7 @@ export default function ResourcesForm() {
|
||||
variables: {
|
||||
appId: currentProject?.id,
|
||||
},
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const {
|
||||
@@ -65,6 +71,7 @@ export default function ResourcesForm() {
|
||||
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetResourcesDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const initialDatabaseResources = getInitialServiceResources(data, 'postgres');
|
||||
@@ -113,7 +120,7 @@ export default function ResourcesForm() {
|
||||
resolver: yupResolver(resourceSettingsValidationSchema),
|
||||
});
|
||||
|
||||
if (!proPlan && !proPlanLoading) {
|
||||
if (isPlatform && !proPlan && !proPlanLoading) {
|
||||
return (
|
||||
<Alert severity="error">
|
||||
Couldn'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 (
|
||||
<ActivityIndicator
|
||||
label="Loading resource settings..."
|
||||
@@ -156,9 +163,10 @@ export default function ResourcesForm() {
|
||||
},
|
||||
);
|
||||
|
||||
const initialPrice =
|
||||
proPlan.price +
|
||||
(billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) * RESOURCE_VCPU_PRICE;
|
||||
const initialPrice = isPlatform
|
||||
? proPlan.price +
|
||||
(billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) * RESOURCE_VCPU_PRICE
|
||||
: 0;
|
||||
|
||||
async function handleSubmit(formValues: ResourceSettingsFormValues) {
|
||||
const updateConfigPromise = updateConfig({
|
||||
@@ -217,6 +225,18 @@ export default function ResourcesForm() {
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
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({
|
||||
title: formValues.enabled
|
||||
? 'Confirm Dedicated Resources'
|
||||
|
||||
@@ -6,6 +6,7 @@ import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
|
||||
import { Link } from '@/components/ui/v2/Link';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import { useProPlan } from '@/features/projects/common/hooks/useProPlan';
|
||||
import { calculateBillableResources } from '@/features/projects/resources/settings/utils/calculateBillableResources';
|
||||
import type { ResourceSettingsFormValues } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
||||
@@ -16,6 +17,8 @@ import {
|
||||
import { useFormState, useWatch } from 'react-hook-form';
|
||||
|
||||
export default function ResourcesFormFooter() {
|
||||
const isPlatform = useIsPlatform();
|
||||
|
||||
const {
|
||||
data: proPlan,
|
||||
loading: proPlanLoading,
|
||||
@@ -63,17 +66,27 @@ export default function ResourcesFormFooter() {
|
||||
},
|
||||
);
|
||||
|
||||
const updatedPrice = enabled
|
||||
? Math.max(
|
||||
priceForTotalAvailableVCPU,
|
||||
(billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) *
|
||||
RESOURCE_VCPU_PRICE,
|
||||
) + proPlan.price
|
||||
: proPlan.price;
|
||||
const computeUpdatedPrice = () => {
|
||||
if (!isPlatform) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
return (
|
||||
Math.max(
|
||||
priceForTotalAvailableVCPU,
|
||||
(billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) *
|
||||
RESOURCE_VCPU_PRICE,
|
||||
) + proPlan.price
|
||||
);
|
||||
}
|
||||
|
||||
return proPlan.price;
|
||||
};
|
||||
|
||||
return (
|
||||
<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"
|
||||
>
|
||||
<Text>
|
||||
@@ -86,20 +99,22 @@ export default function ResourcesFormFooter() {
|
||||
className="font-medium"
|
||||
>
|
||||
Compute Resources
|
||||
<ArrowSquareOutIcon className="ml-1 h-4 w-4" />
|
||||
<ArrowSquareOutIcon className="w-4 h-4 ml-1" />
|
||||
</Link>
|
||||
</Text>
|
||||
|
||||
{(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">
|
||||
<Text>
|
||||
Approximate cost:{' '}
|
||||
<span className="font-medium">${updatedPrice.toFixed(2)}/mo</span>
|
||||
<span className="font-medium">
|
||||
${computeUpdatedPrice().toFixed(2)}/mo
|
||||
</span>
|
||||
</Text>
|
||||
|
||||
<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>
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Box } from '@/components/ui/v2/Box';
|
||||
import { ArrowRightIcon } from '@/components/ui/v2/icons/ArrowRightIcon';
|
||||
import { Slider, sliderClasses } from '@/components/ui/v2/Slider';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import { useProPlan } from '@/features/projects/common/hooks/useProPlan';
|
||||
import { getAllocatedResources } from '@/features/projects/resources/settings/utils/getAllocatedResources';
|
||||
import { prettifyMemory } from '@/features/projects/resources/settings/utils/prettifyMemory';
|
||||
@@ -38,6 +39,8 @@ const StyledAvailableCpuSlider = styled(Slider)(({ theme }) => ({
|
||||
export default function TotalResourcesFormFragment({
|
||||
initialPrice,
|
||||
}: TotalResourcesFormFragmentProps) {
|
||||
const isPlatform = useIsPlatform();
|
||||
|
||||
const {
|
||||
data: proPlan,
|
||||
error: proPlanError,
|
||||
@@ -46,7 +49,7 @@ export default function TotalResourcesFormFragment({
|
||||
const { setValue } = useFormContext<ResourceSettingsFormValues>();
|
||||
const formValues = useWatch<ResourceSettingsFormValues>();
|
||||
|
||||
if (!proPlan && !proPlanLoading) {
|
||||
if (isPlatform && !proPlan && !proPlanLoading) {
|
||||
return (
|
||||
<Alert severity="error">
|
||||
Couldn't load the plan for this projectee. Please try again.
|
||||
@@ -62,7 +65,9 @@ export default function TotalResourcesFormFragment({
|
||||
(formValues.totalAvailableVCPU / RESOURCE_VCPU_MULTIPLIER) *
|
||||
RESOURCE_VCPU_PRICE;
|
||||
|
||||
const updatedPrice = priceForTotalAvailableVCPU + proPlan.price;
|
||||
const updatedPrice = isPlatform
|
||||
? priceForTotalAvailableVCPU + proPlan.price
|
||||
: 0;
|
||||
|
||||
const { vcpu: allocatedVCPU, memory: allocatedMemory } =
|
||||
getAllocatedResources(formValues);
|
||||
@@ -102,8 +107,8 @@ export default function TotalResourcesFormFragment({
|
||||
|
||||
return (
|
||||
<Box className="px-4 pb-4">
|
||||
<Box className="rounded-md border">
|
||||
<Box className="flex flex-col gap-4 bg-transparent p-4">
|
||||
<Box className="border rounded-md">
|
||||
<Box className="flex flex-col gap-4 p-4 bg-transparent">
|
||||
<Box className="flex flex-row items-center justify-between gap-4">
|
||||
<Text color="secondary">
|
||||
Total available compute for your project:
|
||||
@@ -151,7 +156,7 @@ export default function TotalResourcesFormFragment({
|
||||
severity={
|
||||
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 && (
|
||||
<>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import type {
|
||||
BaseRoleFormProps,
|
||||
BaseRoleFormValues,
|
||||
@@ -9,6 +12,7 @@ import {
|
||||
baseRoleFormValidationSchema,
|
||||
} from '@/features/projects/roles/settings/components/BaseRoleForm';
|
||||
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
GetRolesPermissionsDocument,
|
||||
@@ -23,17 +27,20 @@ export interface CreateRoleFormProps
|
||||
/**
|
||||
* Function to be called when the form is submitted.
|
||||
*/
|
||||
onSubmit?: () => Promise<void>;
|
||||
onSubmit?: () => Promise<any>;
|
||||
}
|
||||
|
||||
export default function CreateRoleForm({
|
||||
onSubmit,
|
||||
...props
|
||||
}: CreateRoleFormProps) {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const { data, loading, error } = useGetRolesPermissionsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
const { allowed: allowedRoles } = data?.config?.auth?.user?.roles || {};
|
||||
|
||||
@@ -45,6 +52,7 @@ export default function CreateRoleForm({
|
||||
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetRolesPermissionsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
@@ -85,7 +93,19 @@ export default function CreateRoleForm({
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
onSubmit?.();
|
||||
await onSubmit?.();
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Creating role...',
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import type {
|
||||
BaseRoleFormProps,
|
||||
BaseRoleFormValues,
|
||||
@@ -9,6 +12,7 @@ import {
|
||||
baseRoleFormValidationSchema,
|
||||
} from '@/features/projects/roles/settings/components/BaseRoleForm';
|
||||
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import type { Role } from '@/types/application';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
@@ -28,7 +32,7 @@ export interface EditRoleFormProps
|
||||
/**
|
||||
* Function to be called when the form is submitted.
|
||||
*/
|
||||
onSubmit?: () => Promise<void>;
|
||||
onSubmit?: () => Promise<any>;
|
||||
}
|
||||
|
||||
export default function EditRoleForm({
|
||||
@@ -36,10 +40,13 @@ export default function EditRoleForm({
|
||||
onSubmit,
|
||||
...props
|
||||
}: EditRoleFormProps) {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const { data, loading, error } = useGetRolesPermissionsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { allowed: allowedRoles, default: defaultRole } =
|
||||
@@ -55,6 +62,7 @@ export default function EditRoleForm({
|
||||
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetRolesPermissionsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
@@ -114,7 +122,19 @@ export default function EditRoleForm({
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
onSubmit?.();
|
||||
await onSubmit?.();
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Updating role...',
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
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 { Text } from '@/components/ui/v2/Text';
|
||||
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 { EditRoleForm } from '@/features/projects/roles/settings/components/EditRoleForm';
|
||||
import { getUserRoles } from '@/features/projects/roles/settings/utils/getUserRoles';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import type { Role } from '@/types/application';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
@@ -40,13 +43,15 @@ export interface RoleSettingsFormValues {
|
||||
}
|
||||
|
||||
export default function RoleSettings() {
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const { openDialog, openAlertDialog } = useDialog();
|
||||
|
||||
const { data, loading, error } = useGetRolesPermissionsQuery({
|
||||
const { data, loading, error, refetch } = useGetRolesPermissionsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { allowed: allowedRoles, default: defaultRole } =
|
||||
@@ -54,6 +59,7 @@ export default function RoleSettings() {
|
||||
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetRolesPermissionsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
@@ -64,6 +70,20 @@ export default function RoleSettings() {
|
||||
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) {
|
||||
const updateConfigPromise = updateConfig({
|
||||
variables: {
|
||||
@@ -84,6 +104,7 @@ export default function RoleSettings() {
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
showApplyChangesDialog();
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Updating default role...',
|
||||
@@ -114,6 +135,7 @@ export default function RoleSettings() {
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
showApplyChangesDialog();
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Deleting allowed role...',
|
||||
@@ -127,7 +149,7 @@ export default function RoleSettings() {
|
||||
function handleOpenCreator() {
|
||||
openDialog({
|
||||
title: 'Create Allowed Role',
|
||||
component: <CreateRoleForm />,
|
||||
component: <CreateRoleForm onSubmit={refetch} />,
|
||||
props: {
|
||||
titleProps: { className: '!pb-0' },
|
||||
PaperProps: { className: 'max-w-sm' },
|
||||
@@ -138,7 +160,9 @@ export default function RoleSettings() {
|
||||
function handleOpenEditor(originalRole: Role) {
|
||||
openDialog({
|
||||
title: 'Edit Allowed Role',
|
||||
component: <EditRoleForm originalRole={originalRole} />,
|
||||
component: (
|
||||
<EditRoleForm originalRole={originalRole} onSubmit={refetch} />
|
||||
),
|
||||
props: {
|
||||
titleProps: { className: '!pb-0' },
|
||||
PaperProps: { className: 'max-w-sm' },
|
||||
@@ -177,7 +201,7 @@ export default function RoleSettings() {
|
||||
)}
|
||||
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>
|
||||
</Box>
|
||||
|
||||
@@ -193,7 +217,7 @@ export default function RoleSettings() {
|
||||
<Dropdown.Trigger
|
||||
asChild
|
||||
hideChevron
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2"
|
||||
className="absolute -translate-y-1/2 right-4 top-1/2"
|
||||
>
|
||||
<IconButton
|
||||
variant="borderless"
|
||||
@@ -252,7 +276,7 @@ export default function RoleSettings() {
|
||||
<>
|
||||
{role.name}
|
||||
|
||||
{role.isSystemRole && <LockIcon className="h-4 w-4" />}
|
||||
{role.isSystemRole && <LockIcon className="w-4 h-4" />}
|
||||
|
||||
{defaultRole === role.name && (
|
||||
<Chip
|
||||
|
||||
@@ -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 { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import type {
|
||||
BaseSecretFormProps,
|
||||
BaseSecretFormValues,
|
||||
@@ -7,6 +10,7 @@ import {
|
||||
BaseSecretForm,
|
||||
baseSecretFormValidationSchema,
|
||||
} from '@/features/projects/secrets/settings/components/BaseSecretForm';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
GetSecretsDocument,
|
||||
@@ -20,13 +24,17 @@ export interface CreateSecretFormProps
|
||||
/**
|
||||
* Function to be called when the form is submitted.
|
||||
*/
|
||||
onSubmit?: () => Promise<void>;
|
||||
onSubmit?: () => Promise<any>;
|
||||
}
|
||||
|
||||
export default function CreateSecretForm({
|
||||
onSubmit,
|
||||
...props
|
||||
}: CreateSecretFormProps) {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
|
||||
const form = useForm<BaseSecretFormValues>({
|
||||
defaultValues: {
|
||||
name: '',
|
||||
@@ -39,6 +47,7 @@ export default function CreateSecretForm({
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [insertSecret] = useInsertSecretMutation({
|
||||
refetchQueries: [GetSecretsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
async function handleSubmit({ name, value }: BaseSecretFormValues) {
|
||||
@@ -56,7 +65,19 @@ export default function CreateSecretForm({
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await insertSecretPromise;
|
||||
onSubmit?.();
|
||||
await onSubmit?.();
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Creating secret...',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import type {
|
||||
BaseSecretFormProps,
|
||||
BaseSecretFormValues,
|
||||
@@ -7,6 +8,7 @@ import {
|
||||
BaseSecretForm,
|
||||
baseSecretFormValidationSchema,
|
||||
} from '@/features/projects/secrets/settings/components/BaseSecretForm';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import type { Secret } from '@/types/application';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import {
|
||||
@@ -33,6 +35,9 @@ export default function EditSecretForm({
|
||||
onSubmit,
|
||||
...props
|
||||
}: EditSecretFormProps) {
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
|
||||
const form = useForm<BaseSecretFormValues>({
|
||||
defaultValues: {
|
||||
name: originalSecret.name,
|
||||
@@ -45,6 +50,7 @@ export default function EditSecretForm({
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateSecret] = useUpdateSecretMutation({
|
||||
refetchQueries: [GetSecretsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
async function handleSubmit({ name, value }: BaseSecretFormValues) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
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 { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
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 { COST_PER_VCPU } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
||||
import { ComputeFormSection } from '@/features/services/components/ServiceForm/components/ComputeFormSection';
|
||||
@@ -25,6 +27,7 @@ import {
|
||||
type ServiceFormProps,
|
||||
type ServiceFormValues,
|
||||
} from '@/features/services/components/ServiceForm/ServiceFormTypes';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
@@ -50,11 +53,15 @@ export default function ServiceForm({
|
||||
location,
|
||||
}: ServiceFormProps) {
|
||||
const hostName = useHostName();
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { onDirtyStateChange, openDialog, closeDialog } = useDialog();
|
||||
const [insertRunService] = useInsertRunServiceMutation();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [insertRunServiceConfig] = useInsertRunServiceConfigMutation();
|
||||
const [replaceRunServiceConfig] = useReplaceRunServiceConfigMutation();
|
||||
const [replaceRunServiceConfig] = useReplaceRunServiceConfigMutation({
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
const [detailsServiceId, setDetailsServiceId] = useState('');
|
||||
const [detailsServiceSubdomain, setDetailsServiceSubdomain] = useState(
|
||||
initialData?.subdomain,
|
||||
@@ -145,6 +152,18 @@ export default function ServiceForm({
|
||||
});
|
||||
|
||||
setDetailsServiceId(serviceID);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Insert service config
|
||||
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({
|
||||
title: 'Confirm Resources',
|
||||
component: (
|
||||
@@ -213,26 +237,34 @@ export default function ServiceForm({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (detailsServiceId) {
|
||||
openDialog({
|
||||
title: 'Service Details',
|
||||
component: (
|
||||
<ServiceDetailsDialog
|
||||
serviceID={detailsServiceId}
|
||||
subdomain={detailsServiceSubdomain}
|
||||
ports={formValues.ports}
|
||||
/>
|
||||
),
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
if (!isPlatform) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (detailsServiceId) {
|
||||
openDialog({
|
||||
title: 'Service Details',
|
||||
component: (
|
||||
<ServiceDetailsDialog
|
||||
serviceID={detailsServiceId}
|
||||
subdomain={detailsServiceSubdomain}
|
||||
ports={formValues.ports}
|
||||
/>
|
||||
),
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
});
|
||||
}
|
||||
})();
|
||||
}, [detailsServiceId, detailsServiceSubdomain, formValues, openDialog]);
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [
|
||||
detailsServiceId,
|
||||
detailsServiceSubdomain,
|
||||
formValues,
|
||||
openDialog,
|
||||
isPlatform,
|
||||
]);
|
||||
|
||||
const pricingExplanation = () => {
|
||||
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.">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="h-4 w-4"
|
||||
className="w-4 h-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -311,7 +343,7 @@ export default function ServiceForm({
|
||||
>
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="h-4 w-4"
|
||||
className="w-4 h-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -325,8 +357,8 @@ export default function ServiceForm({
|
||||
autoComplete="off"
|
||||
/>
|
||||
|
||||
{/* This shows only when trying to edit a service */}
|
||||
{serviceID && serviceImage && (
|
||||
{/* This shows only when trying to edit a service and when running against the nhost platform */}
|
||||
{isPlatform && serviceID && serviceImage && (
|
||||
<InfoCard
|
||||
title="Private registry"
|
||||
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.">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="h-4 w-4"
|
||||
className="w-4 h-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -356,22 +388,24 @@ export default function ServiceForm({
|
||||
autoComplete="off"
|
||||
/>
|
||||
|
||||
<Alert
|
||||
severity="info"
|
||||
className="flex items-center justify-between space-x-2"
|
||||
>
|
||||
<span>{pricingExplanation()}</span>
|
||||
<b>
|
||||
$
|
||||
{parseFloat(
|
||||
(
|
||||
formValues.compute.cpu *
|
||||
formValues.replicas *
|
||||
COST_PER_VCPU
|
||||
).toFixed(2),
|
||||
)}
|
||||
</b>
|
||||
</Alert>
|
||||
{isPlatform ? (
|
||||
<Alert
|
||||
severity="info"
|
||||
className="flex items-center justify-between space-x-2"
|
||||
>
|
||||
<span>{pricingExplanation()}</span>
|
||||
<b>
|
||||
$
|
||||
{parseFloat(
|
||||
(
|
||||
formValues.compute.cpu *
|
||||
formValues.replicas *
|
||||
COST_PER_VCPU
|
||||
).toFixed(2),
|
||||
)}
|
||||
</b>
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
<ComputeFormSection showTooltip />
|
||||
|
||||
@@ -388,7 +422,7 @@ export default function ServiceForm({
|
||||
{createServiceFormError && (
|
||||
<Alert
|
||||
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">
|
||||
<strong>Error:</strong> {createServiceFormError.message}
|
||||
|
||||
@@ -11,11 +11,12 @@ import { UserIcon } from '@/components/ui/v2/icons/UserIcon';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||
import { DeleteServiceModal } from '@/features/projects/common/components/DeleteServiceModal';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import { ServiceForm } from '@/features/services/components/ServiceForm';
|
||||
import { type PortTypes } from '@/features/services/components/ServiceForm/components/PortsFormSection/PortsFormSectionTypes';
|
||||
import { type RunService } from '@/hooks/useRunServices';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import type { RunService } from 'pages/[workspaceSlug]/[appSlug]/services';
|
||||
|
||||
interface ServicesListProps {
|
||||
/**
|
||||
@@ -42,19 +43,20 @@ export default function ServicesList({
|
||||
onCreateOrUpdate,
|
||||
onDelete,
|
||||
}: ServicesListProps) {
|
||||
const isPlatform = useIsPlatform();
|
||||
const { openDrawer, openDialog, closeDialog } = useDialog();
|
||||
|
||||
const viewService = async (service: RunService) => {
|
||||
openDrawer({
|
||||
title: (
|
||||
<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>
|
||||
</Box>
|
||||
),
|
||||
component: (
|
||||
<ServiceForm
|
||||
serviceID={service.id}
|
||||
serviceID={service.id ?? service.serviceID}
|
||||
initialData={{
|
||||
...service.config,
|
||||
image: service.config?.image?.image,
|
||||
@@ -94,50 +96,52 @@ export default function ServicesList({
|
||||
<Box className="flex flex-col">
|
||||
{services.map((service) => (
|
||||
<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"
|
||||
sx={{
|
||||
[`&:hover`]: {
|
||||
backgroundColor: 'action.hover',
|
||||
},
|
||||
}}
|
||||
onClick={() => viewService(service)}
|
||||
>
|
||||
<Box
|
||||
onClick={() => viewService(service)}
|
||||
className="flex w-full flex-row justify-between"
|
||||
className="flex flex-row justify-between w-full"
|
||||
sx={{
|
||||
backgroundColor: 'transparent',
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-1 flex-row items-center space-x-4">
|
||||
<CubeIcon className="h-5 w-5" />
|
||||
<div className="flex flex-row items-center flex-1 space-x-4">
|
||||
<CubeIcon className="w-5 h-5" />
|
||||
<div className="flex flex-col">
|
||||
<Text variant="h4" className="font-semibold">
|
||||
{service.config?.name ?? 'unset'}
|
||||
</Text>
|
||||
<Tooltip title={service.updatedAt}>
|
||||
<span className="hidden cursor-pointer text-sm text-slate-500 xs+:flex">
|
||||
Deployed {formatDistanceToNow(new Date(service.updatedAt))}{' '}
|
||||
ago
|
||||
</span>
|
||||
</Tooltip>
|
||||
{isPlatform ? (
|
||||
<Tooltip title={service.updatedAt}>
|
||||
<span className="hidden cursor-pointer text-sm text-slate-500 xs+:flex">
|
||||
Deployed{' '}
|
||||
{formatDistanceToNow(new Date(service.updatedAt))} ago
|
||||
</span>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</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">
|
||||
{service.id}
|
||||
{service.id ?? service.serviceID}
|
||||
</Text>
|
||||
<IconButton
|
||||
variant="borderless"
|
||||
color="secondary"
|
||||
onClick={(event) => {
|
||||
copy(service.id, 'Service Id');
|
||||
copy(service.id ?? service.serviceID, 'Service Id');
|
||||
event.stopPropagation();
|
||||
}}
|
||||
aria-label="Service Id"
|
||||
>
|
||||
<CopyIcon className="h-4 w-4" />
|
||||
<CopyIcon className="w-4 h-4" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</Box>
|
||||
@@ -167,7 +171,7 @@ export default function ServicesList({
|
||||
onClick={() => viewService(service)}
|
||||
className="z-50 grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
|
||||
>
|
||||
<UserIcon className="h-4 w-4" />
|
||||
<UserIcon className="w-4 h-4" />
|
||||
<Text className="font-medium">View Service</Text>
|
||||
</Dropdown.Item>
|
||||
<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"
|
||||
sx={{ color: 'error.main' }}
|
||||
onClick={() => deleteService(service)}
|
||||
disabled={!isPlatform}
|
||||
>
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
<Text className="font-medium" color="error">
|
||||
Delete Service
|
||||
</Text>
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { ControlledAutocomplete } from '@/components/form/ControlledAutocomplete';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetStorageSettingsDocument,
|
||||
Software_Type_Enum,
|
||||
@@ -11,8 +14,10 @@ import {
|
||||
useGetStorageSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -30,21 +35,26 @@ export type StorageServiceVersionFormValues = Yup.InferType<
|
||||
>;
|
||||
|
||||
export default function StorageServiceVersionSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetStorageSettingsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetStorageSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-only',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data: storageVersionsData } = useGetSoftwareVersionsQuery({
|
||||
variables: {
|
||||
software: Software_Type_Enum.Storage,
|
||||
},
|
||||
skip: !isPlatform,
|
||||
});
|
||||
|
||||
const { version } = data?.config?.storage || {};
|
||||
@@ -52,6 +62,7 @@ export default function StorageServiceVersionSettings() {
|
||||
const availableVersions = Array.from(
|
||||
new Set(versions.map((el) => el.version)).add(version),
|
||||
)
|
||||
.filter((v) => !!v)
|
||||
.sort()
|
||||
.reverse()
|
||||
.map((availableVersion) => ({
|
||||
@@ -61,10 +72,21 @@ export default function StorageServiceVersionSettings() {
|
||||
|
||||
const form = useForm<StorageServiceVersionFormValues>({
|
||||
reValidateMode: 'onSubmit',
|
||||
defaultValues: { version: { label: version, value: version } },
|
||||
defaultValues: { version: { label: '', value: '' } },
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
form.reset({
|
||||
version: {
|
||||
label: version,
|
||||
value: version,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [loading, version, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -99,6 +121,18 @@ export default function StorageServiceVersionSettings() {
|
||||
async () => {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Storage version is being updated...',
|
||||
@@ -123,12 +157,20 @@ export default function StorageServiceVersionSettings() {
|
||||
}}
|
||||
docsLink="https://github.com/nhost/hasura-storage/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
|
||||
id="version"
|
||||
name="version"
|
||||
autoHighlight
|
||||
freeSolo
|
||||
getOptionLabel={(option) => {
|
||||
if (typeof option === 'string') {
|
||||
return option || '';
|
||||
}
|
||||
|
||||
return option.value;
|
||||
}}
|
||||
isOptionEqualToValue={() => false}
|
||||
filterOptions={(options, { inputValue }) => {
|
||||
const inputValueLower = inputValue.toLowerCase();
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import { ApplyLocalSettingsDialog } from '@/components/common/ApplyLocalSettingsDialog';
|
||||
import { useDialog } from '@/components/common/DialogProvider';
|
||||
import { useUI } from '@/components/common/UIProvider';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import {
|
||||
GetHasuraSettingsDocument,
|
||||
useGetStorageSettingsQuery,
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { useLocalMimirClient } from '@/hooks/useLocalMimirClient';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
@@ -20,16 +25,20 @@ const validationSchema = Yup.object({
|
||||
export type HasuraStorageAVFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
export default function HasuraStorageAVSettings() {
|
||||
const { openDialog } = useDialog();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { maintenanceActive } = useUI();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject, refetch: refetchWorkspaceAndProject } =
|
||||
useCurrentWorkspaceAndProject();
|
||||
const [updateConfig] = useUpdateConfigMutation({
|
||||
refetchQueries: [GetHasuraSettingsDocument],
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { data, loading, error } = useGetStorageSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
fetchPolicy: 'cache-first',
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
const { server } = data?.config?.storage?.antivirus || {};
|
||||
@@ -42,6 +51,14 @@ export default function HasuraStorageAVSettings() {
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
form.reset({
|
||||
enabled: !!server,
|
||||
});
|
||||
}
|
||||
}, [loading, server, form]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
@@ -81,6 +98,18 @@ export default function HasuraStorageAVSettings() {
|
||||
await updateConfigPromise;
|
||||
form.reset(formValues);
|
||||
await refetchWorkspaceAndProject();
|
||||
|
||||
if (!isPlatform) {
|
||||
openDialog({
|
||||
title: 'Apply your changes',
|
||||
component: <ApplyLocalSettingsDialog />,
|
||||
props: {
|
||||
PaperProps: {
|
||||
className: 'max-w-2xl',
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Antivirus settings are being updated...',
|
||||
|
||||
@@ -12,6 +12,7 @@ query GetSmtpSettings($appId: uuid!) {
|
||||
secure
|
||||
sender
|
||||
user
|
||||
password
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ query getGraphiteAutoEmbeddingsConfigurations($limit: Int!, $offset: Int!) {
|
||||
graphiteAutoEmbeddingsConfigurations(limit: $limit, offset: $offset) {
|
||||
id
|
||||
name
|
||||
model
|
||||
schemaName
|
||||
tableName
|
||||
columnName
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
mutation insertGraphiteAutoEmbeddingsConfiguration(
|
||||
$name: String
|
||||
$model: String
|
||||
$schemaName: String
|
||||
$tableName: String
|
||||
$columnName: String
|
||||
@@ -9,6 +10,7 @@ mutation insertGraphiteAutoEmbeddingsConfiguration(
|
||||
insertGraphiteAutoEmbeddingsConfiguration(
|
||||
object: {
|
||||
name: $name
|
||||
model: $model
|
||||
schemaName: $schemaName
|
||||
tableName: $tableName
|
||||
columnName: $columnName
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
mutation updateGraphiteAutoEmbeddingsConfiguration(
|
||||
$id: uuid!
|
||||
$name: String
|
||||
$model: String
|
||||
$schemaName: String
|
||||
$tableName: String
|
||||
$columnName: String
|
||||
@@ -11,6 +12,7 @@ mutation updateGraphiteAutoEmbeddingsConfiguration(
|
||||
pk_columns: { id: $id }
|
||||
_set: {
|
||||
name: $name
|
||||
model: $model
|
||||
schemaName: $schemaName
|
||||
tableName: $tableName
|
||||
columnName: $columnName
|
||||
@@ -20,6 +22,7 @@ mutation updateGraphiteAutoEmbeddingsConfiguration(
|
||||
) {
|
||||
id
|
||||
name
|
||||
model
|
||||
schemaName
|
||||
tableName
|
||||
columnName
|
||||
|
||||
@@ -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(
|
||||
$appID: uuid!
|
||||
$resolve: Boolean!
|
||||
@@ -11,40 +48,7 @@ query getRunServices(
|
||||
updatedAt
|
||||
subdomain
|
||||
config(resolve: $resolve) {
|
||||
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
|
||||
}
|
||||
...RunServiceConfig
|
||||
}
|
||||
}
|
||||
runServices_aggregate {
|
||||
@@ -54,3 +58,12 @@ query getRunServices(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query getLocalRunServiceConfigs($appID: uuid!, $resolve: Boolean!) {
|
||||
runServiceConfigs(appID: $appID, resolve: $resolve) {
|
||||
serviceID
|
||||
config {
|
||||
...RunServiceConfig
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
dashboard/src/hooks/useLocalMimirClient/index.ts
Normal file
1
dashboard/src/hooks/useLocalMimirClient/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as useLocalMimirClient } from './useLocalMimirClient';
|
||||
@@ -0,0 +1,20 @@
|
||||
import { getConfigServerUrl } from '@/utils/env';
|
||||
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: getConfigServerUrl(),
|
||||
}),
|
||||
}),
|
||||
[],
|
||||
);
|
||||
}
|
||||
2
dashboard/src/hooks/useRunServices/index.ts
Normal file
2
dashboard/src/hooks/useRunServices/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './useRunServices';
|
||||
export { default as useRunServices } from './useRunServices';
|
||||
109
dashboard/src/hooks/useRunServices/useRunServices.ts
Normal file
109
dashboard/src/hooks/useRunServices/useRunServices.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
@@ -9,75 +9,34 @@ import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
|
||||
import { ServicesIcon } from '@/components/ui/v2/icons/ServicesIcon';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
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 { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import { ServiceForm } from '@/features/services/components/ServiceForm';
|
||||
import { type PortTypes } from '@/features/services/components/ServiceForm/components/PortsFormSection/PortsFormSectionTypes';
|
||||
import ServicesList from '@/features/services/components/ServicesList/ServicesList';
|
||||
import { useRunServices, type RunServiceConfig } from '@/hooks/useRunServices';
|
||||
import { useRouter } from 'next/router';
|
||||
import {
|
||||
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'
|
||||
>;
|
||||
import { useCallback, useEffect, type ReactElement } from 'react';
|
||||
|
||||
export default function ServicesPage() {
|
||||
const limit = useRef(25);
|
||||
const router = useRouter();
|
||||
const isPlatform = useIsPlatform();
|
||||
const { openDrawer, openAlertDialog } = useDialog();
|
||||
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 {
|
||||
data,
|
||||
loading,
|
||||
refetch: refetchServices,
|
||||
} = useGetRunServicesQuery({
|
||||
variables: {
|
||||
appID: currentProject.id,
|
||||
resolve: false,
|
||||
limit: limit.current,
|
||||
offset,
|
||||
},
|
||||
});
|
||||
services,
|
||||
totalServicesCount,
|
||||
limit,
|
||||
nrOfPages,
|
||||
currentPage,
|
||||
setCurrentPage,
|
||||
refetch,
|
||||
} = useRunServices();
|
||||
|
||||
useEffect(() => {
|
||||
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 isPlanFree = currentProject?.plan?.isFree;
|
||||
|
||||
const checkConfigFromQuery = useCallback(
|
||||
(base64Config: string) => {
|
||||
@@ -89,7 +48,7 @@ export default function ServicesPage() {
|
||||
openDrawer({
|
||||
title: (
|
||||
<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>
|
||||
</Box>
|
||||
),
|
||||
@@ -111,7 +70,7 @@ export default function ServicesPage() {
|
||||
replicas: parsedConfig?.resources?.replicas,
|
||||
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(() => {
|
||||
@@ -137,18 +96,23 @@ export default function ServicesPage() {
|
||||
}, [checkConfigFromQuery, router.query]);
|
||||
|
||||
const openCreateServiceDialog = () => {
|
||||
// creating services using the local dashboard is not supported
|
||||
if (!isPlatform) {
|
||||
return;
|
||||
}
|
||||
|
||||
openDrawer({
|
||||
title: (
|
||||
<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>
|
||||
</Box>
|
||||
),
|
||||
component: <ServiceForm onSubmit={refetchServices} />,
|
||||
component: <ServiceForm onSubmit={refetch} />,
|
||||
});
|
||||
};
|
||||
|
||||
if (isPlanFree) {
|
||||
if (isPlatform && isPlanFree) {
|
||||
return (
|
||||
<Container>
|
||||
<UpgradeNotification
|
||||
@@ -159,41 +123,44 @@ export default function ServicesPage() {
|
||||
);
|
||||
}
|
||||
|
||||
if (data?.app.runServices.length === 0 && !loading) {
|
||||
if (services.length === 0 && !loading) {
|
||||
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">
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={openCreateServiceDialog}
|
||||
startIcon={<PlusIcon className="h-4 w-4" />}
|
||||
startIcon={<PlusIcon className="w-4 h-4" />}
|
||||
disabled={!isPlatform}
|
||||
>
|
||||
Add service
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Box className="flex flex-col items-center justify-center space-y-5 rounded-lg border px-48 py-12 shadow-sm">
|
||||
<ServicesIcon className="h-10 w-10" />
|
||||
<Box className="flex flex-col items-center justify-center px-48 py-12 space-y-5 border rounded-lg shadow-sm">
|
||||
<ServicesIcon className="w-10 h-10" />
|
||||
<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
|
||||
</Text>
|
||||
<Text variant="subtitle1" className="text-center">
|
||||
All your project’s custom services will be listed here.
|
||||
All your project's custom services will be listed here.
|
||||
</Text>
|
||||
</div>
|
||||
<div className="flex flex-row place-content-between rounded-lg ">
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className="w-full"
|
||||
onClick={openCreateServiceDialog}
|
||||
startIcon={<PlusIcon className="h-4 w-4" />}
|
||||
>
|
||||
Add service
|
||||
</Button>
|
||||
</div>
|
||||
{isPlatform ? (
|
||||
<div className="flex flex-row rounded-lg place-content-between ">
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className="w-full"
|
||||
onClick={openCreateServiceDialog}
|
||||
startIcon={<PlusIcon className="w-4 h-4" />}
|
||||
>
|
||||
Add service
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
@@ -201,12 +168,13 @@ export default function ServicesPage() {
|
||||
|
||||
return (
|
||||
<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
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={openCreateServiceDialog}
|
||||
startIcon={<PlusIcon className="h-4 w-4" />}
|
||||
startIcon={<PlusIcon className="w-4 h-4" />}
|
||||
disabled={!isPlatform}
|
||||
>
|
||||
Add service
|
||||
</Button>
|
||||
@@ -214,42 +182,42 @@ export default function ServicesPage() {
|
||||
<Box className="space-y-4">
|
||||
<ServicesList
|
||||
services={services}
|
||||
onDelete={() => refetchServices()}
|
||||
onCreateOrUpdate={() => refetchServices()}
|
||||
onDelete={() => refetch()}
|
||||
onCreateOrUpdate={() => refetch()}
|
||||
/>
|
||||
<Pagination
|
||||
className="px-2"
|
||||
totalNrOfPages={nrOfPages}
|
||||
currentPageNumber={currentPage}
|
||||
totalNrOfElements={
|
||||
data?.app?.runServices_aggregate.aggregate.count ?? 0
|
||||
}
|
||||
itemsLabel="services"
|
||||
elementsPerPage={limit.current}
|
||||
onPrevPageClick={async () => {
|
||||
setCurrentPage((page) => page - 1);
|
||||
if (currentPage - 1 !== 1) {
|
||||
{isPlatform ? (
|
||||
<Pagination
|
||||
className="px-2"
|
||||
totalNrOfPages={nrOfPages}
|
||||
currentPageNumber={currentPage}
|
||||
totalNrOfElements={totalServicesCount}
|
||||
itemsLabel="services"
|
||||
elementsPerPage={limit.current}
|
||||
onPrevPageClick={async () => {
|
||||
setCurrentPage((page) => page - 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({
|
||||
pathname: router.pathname,
|
||||
query: { ...router.query, page: currentPage - 1 },
|
||||
query: { ...router.query, page: currentPage + 1 },
|
||||
});
|
||||
}
|
||||
}}
|
||||
onNextPageClick={async () => {
|
||||
setCurrentPage((page) => page + 1);
|
||||
await router.push({
|
||||
pathname: router.pathname,
|
||||
query: { ...router.query, page: currentPage + 1 },
|
||||
});
|
||||
}}
|
||||
onPageChange={async (page) => {
|
||||
setCurrentPage(page);
|
||||
await router.push({
|
||||
pathname: router.pathname,
|
||||
query: { ...router.query, page },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}}
|
||||
onPageChange={async (page) => {
|
||||
setCurrentPage(page);
|
||||
await router.push({
|
||||
pathname: router.pathname,
|
||||
query: { ...router.query, page },
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ import type { ReactElement } from 'react';
|
||||
export default function StorageSettingsPage() {
|
||||
const { currentProject, loading, error } = useCurrentWorkspaceAndProject();
|
||||
|
||||
if (currentProject.plan.isFree) {
|
||||
if (currentProject?.plan?.isFree) {
|
||||
return (
|
||||
<Box className="p-4" sx={{ backgroundColor: 'background.default' }}>
|
||||
<UpgradeToProBanner
|
||||
@@ -43,7 +43,7 @@ export default function StorageSettingsPage() {
|
||||
|
||||
return (
|
||||
<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"
|
||||
>
|
||||
<AISettings />
|
||||
|
||||
@@ -11,15 +11,20 @@ import { GravatarSettings } from '@/features/authentication/settings/components/
|
||||
import { MFASettings } from '@/features/authentication/settings/components/MFASettings';
|
||||
import { SessionSettings } from '@/features/authentication/settings/components/SessionSettings';
|
||||
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 type { ReactElement } from 'react';
|
||||
|
||||
export default function SettingsAuthenticationPage() {
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
|
||||
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
skip: !currentProject,
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
if (!data && loading) {
|
||||
@@ -38,7 +43,7 @@ export default function SettingsAuthenticationPage() {
|
||||
|
||||
return (
|
||||
<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"
|
||||
>
|
||||
<AuthServiceVersionSettings />
|
||||
|
||||
@@ -16,7 +16,7 @@ import { type ReactElement } from 'react';
|
||||
export default function CustomDomains() {
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
|
||||
if (currentProject.plan.isFree) {
|
||||
if (currentProject?.plan?.isFree) {
|
||||
return (
|
||||
<Container
|
||||
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"
|
||||
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">
|
||||
<Text className="text-lg font-semibold">Custom Domains</Text>
|
||||
|
||||
@@ -50,7 +50,7 @@ export default function CustomDomains() {
|
||||
className="ml-1 font-medium"
|
||||
>
|
||||
Custom Domains
|
||||
<ArrowSquareOutIcon className="ml-1 h-4 w-4" />
|
||||
<ArrowSquareOutIcon className="w-4 h-4 ml-1" />
|
||||
</Link>
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
@@ -6,15 +6,20 @@ import { DatabaseServiceVersionSettings } from '@/features/database/settings/com
|
||||
import { DatabaseStorageCapacity } from '@/features/database/settings/components/DatabaseStorageCapacity';
|
||||
import { ResetDatabasePasswordSettings } from '@/features/database/settings/components/ResetDatabasePasswordSettings';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import { useGetPostgresSettingsQuery } from '@/generated/graphql';
|
||||
import useLocalMimirClient from '@/hooks/useLocalMimirClient/useLocalMimirClient';
|
||||
import type { ReactElement } from 'react';
|
||||
|
||||
export default function DatabaseSettingsPage() {
|
||||
const isPlatform = useIsPlatform();
|
||||
const localMimirClient = useLocalMimirClient();
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
|
||||
const { loading, error } = useGetPostgresSettingsQuery({
|
||||
variables: { appId: currentProject?.id },
|
||||
skip: !currentProject,
|
||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
@@ -38,8 +43,13 @@ export default function DatabaseSettingsPage() {
|
||||
>
|
||||
<DatabaseServiceVersionSettings />
|
||||
<DatabaseStorageCapacity />
|
||||
<DatabaseConnectionInfo />
|
||||
<ResetDatabasePasswordSettings />
|
||||
|
||||
{isPlatform && (
|
||||
<>
|
||||
<DatabaseConnectionInfo />
|
||||
<ResetDatabasePasswordSettings />
|
||||
</>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user