Compare commits
23 Commits
@nhost/rea
...
@nhost/rea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e3357b7a3 | ||
|
|
4385524311 | ||
|
|
9e404c8fc9 | ||
|
|
f8e6b615dd | ||
|
|
ac4aa01ec9 | ||
|
|
e515e71c8b | ||
|
|
1246e0024a | ||
|
|
81cc9b3810 | ||
|
|
5c6ff6efc8 | ||
|
|
1956ed23f8 | ||
|
|
af34015dbe | ||
|
|
88919a3d99 | ||
|
|
ab26a57d05 | ||
|
|
f1052a8826 | ||
|
|
30daa4146e | ||
|
|
7537237465 | ||
|
|
76e77da5de | ||
|
|
04d2ce110a | ||
|
|
b2755045c9 | ||
|
|
d43931e761 | ||
|
|
44c1e17fd5 | ||
|
|
5df6fa2d0b | ||
|
|
1fa6cc47ec |
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -163,7 +163,7 @@ jobs:
|
|||||||
# * Run this step only if the previous step failed, and Playwright generated a report
|
# * Run this step only if the previous step failed, and Playwright generated a report
|
||||||
- name: Upload Playwright Report
|
- name: Upload Playwright Report
|
||||||
if: ${{ failure() && hashFiles(format('{0}/playwright-report/**', matrix.package.path)) != ''}}
|
if: ${{ failure() && hashFiles(format('{0}/playwright-report/**', matrix.package.path)) != ''}}
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: playwright-${{ steps.file-name.outputs.fileName }}
|
name: playwright-${{ steps.file-name.outputs.fileName }}
|
||||||
path: ${{format('{0}/playwright-report/**', matrix.package.path)}}
|
path: ${{format('{0}/playwright-report/**', matrix.package.path)}}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
// $schema provides code completion hints to IDEs.
|
// $schema provides code completion hints to IDEs.
|
||||||
"$schema": "https://github.com/IBM/audit-ci/raw/main/docs/schema.json",
|
"$schema": "https://github.com/IBM/audit-ci/raw/main/docs/schema.json",
|
||||||
"moderate": true,
|
"moderate": true,
|
||||||
"allowlist": ["vue-template-compiler", "micromatch", "path-to-regexp"]
|
"allowlist": ["vue-template-compiler"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,40 @@
|
|||||||
# @nhost/dashboard
|
# @nhost/dashboard
|
||||||
|
|
||||||
|
## 2.16.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- f8e6b61: fix: can add rule groups in table permissions
|
||||||
|
- 9e404c8: fix: not redirect to 404 page if using local Nhost backend
|
||||||
|
- ac4aa01: fix: can delete column in database page
|
||||||
|
- 4385524: fix: update url to check service health in local dashboard
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react-apollo@16.0.1
|
||||||
|
- @nhost/nextjs@2.2.2
|
||||||
|
|
||||||
|
## 2.15.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- f1052a8: fix: improve stability of the dashboard when pausing projects
|
||||||
|
- 30daa41: fix: update links to docs in overview page
|
||||||
|
- 7537237: feat: add image preview toggle in storage
|
||||||
|
|
||||||
|
## 2.14.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- d43931e: fix: invalid organization slug/project subdomain doesn't open 404 page
|
||||||
|
- 5df6fa2: feat: add unencrypted disk warning in storage capacity settings
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 44c1e17: chore: update `msw` to v1.3.5 to fix vulnerabilities
|
||||||
|
- @nhost/react-apollo@16.0.0
|
||||||
|
- @nhost/nextjs@2.2.1
|
||||||
|
|
||||||
## 2.13.0
|
## 2.13.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/dashboard",
|
"name": "@nhost/dashboard",
|
||||||
"version": "2.13.0",
|
"version": "2.16.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
@@ -177,7 +177,7 @@
|
|||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"jsdom": "^22.1.0",
|
"jsdom": "^22.1.0",
|
||||||
"lint-staged": "^15.2.2",
|
"lint-staged": "^15.2.2",
|
||||||
"msw": "^1.3.3",
|
"msw": "^1.3.5",
|
||||||
"msw-storybook-addon": "^1.10.0",
|
"msw-storybook-addon": "^1.10.0",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.38",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/v3/dialog';
|
} from '@/components/ui/v3/dialog';
|
||||||
|
|
||||||
|
import { LoadingScreen } from '@/components/presentational/LoadingScreen';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
@@ -53,8 +54,8 @@ export default function TransferProjectDialog({
|
|||||||
}: TransferProjectDialogProps) {
|
}: TransferProjectDialogProps) {
|
||||||
const { push } = useRouter();
|
const { push } = useRouter();
|
||||||
const currentUserId = useUserId();
|
const currentUserId = useUserId();
|
||||||
const { project } = useProject();
|
const { project, loading: projectLoading } = useProject();
|
||||||
const { orgs, currentOrg } = useOrgs();
|
const { orgs, currentOrg, loading: orgsLoading } = useOrgs();
|
||||||
const [transferProject] = useBillingTransferAppMutation();
|
const [transferProject] = useBillingTransferAppMutation();
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof transferProjectFormSchema>>({
|
const form = useForm<z.infer<typeof transferProjectFormSchema>>({
|
||||||
@@ -96,6 +97,10 @@ export default function TransferProjectDialog({
|
|||||||
member.user.id === userId,
|
member.user.id === userId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (projectLoading || orgsLoading) {
|
||||||
|
return <LoadingScreen />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
open={open}
|
open={open}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export default function AISettings() {
|
|||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
const { project } = useProject();
|
const { project, loading: loadingProject } = useProject();
|
||||||
|
|
||||||
const [aiServiceEnabled, setAIServiceEnabled] = useState(true);
|
const [aiServiceEnabled, setAIServiceEnabled] = useState(true);
|
||||||
|
|
||||||
@@ -73,9 +73,10 @@ export default function AISettings() {
|
|||||||
error: errorGettingAiSettings,
|
error: errorGettingAiSettings,
|
||||||
} = useGetAiSettingsQuery({
|
} = useGetAiSettingsQuery({
|
||||||
variables: {
|
variables: {
|
||||||
appId: project.id,
|
appId: project?.id,
|
||||||
},
|
},
|
||||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
|
skip: !project?.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: graphiteVersionsData, loading: loadingGraphiteVersionsData } =
|
const { data: graphiteVersionsData, loading: loadingGraphiteVersionsData } =
|
||||||
@@ -192,11 +193,11 @@ export default function AISettings() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loadingAiSettings || loadingGraphiteVersionsData) {
|
if (loadingProject || loadingAiSettings || loadingGraphiteVersionsData) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
delay={1000}
|
delay={1000}
|
||||||
label="Loading Postgres version..."
|
label="Loading AI settings..."
|
||||||
className="justify-center"
|
className="justify-center"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -269,7 +270,7 @@ export default function AISettings() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className="space-y-4" sx={{ backgroundColor: 'background.default' }}>
|
<Box className="space-y-4" sx={{ backgroundColor: 'background.default' }}>
|
||||||
<Box className="flex flex-row items-center justify-between p-4 rounded-lg border-1">
|
<Box className="flex flex-row items-center justify-between rounded-lg border-1 p-4">
|
||||||
<Text className="text-lg font-semibold">Enable AI service</Text>
|
<Text className="text-lg font-semibold">Enable AI service</Text>
|
||||||
<Switch
|
<Switch
|
||||||
checked={aiServiceEnabled}
|
checked={aiServiceEnabled}
|
||||||
@@ -298,7 +299,7 @@ export default function AISettings() {
|
|||||||
<Tooltip title="Version of the service to use.">
|
<Tooltip title="Version of the service to use.">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="w-4 h-4"
|
className="h-4 w-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -353,7 +354,7 @@ export default function AISettings() {
|
|||||||
<Tooltip title="Used to validate requests between postgres and the AI service. The AI service will also include the header X-Graphite-Webhook-Secret with this value set when calling external webhooks so the source of the request can be validated.">
|
<Tooltip title="Used to validate requests between postgres and the AI service. The AI service will also include the header X-Graphite-Webhook-Secret with this value set when calling external webhooks so the source of the request can be validated.">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="w-4 h-4"
|
className="h-4 w-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -377,7 +378,7 @@ export default function AISettings() {
|
|||||||
<Tooltip title="Dedicated resources allocated for the service.">
|
<Tooltip title="Dedicated resources allocated for the service.">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="w-4 h-4"
|
className="h-4 w-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -417,7 +418,7 @@ export default function AISettings() {
|
|||||||
<Tooltip title="Key to use for authenticating API requests to OpenAI">
|
<Tooltip title="Key to use for authenticating API requests to OpenAI">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="w-4 h-4"
|
className="h-4 w-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -440,7 +441,7 @@ export default function AISettings() {
|
|||||||
<Tooltip title="Optional. OpenAI organization to use.">
|
<Tooltip title="Optional. OpenAI organization to use.">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="w-4 h-4"
|
className="h-4 w-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -468,7 +469,7 @@ export default function AISettings() {
|
|||||||
<Tooltip title="How often to run the job that keeps embeddings up to date.">
|
<Tooltip title="How often to run the job that keeps embeddings up to date.">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="w-4 h-4"
|
className="h-4 w-4"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ContactUs } from '@/components/common/ContactUs';
|
import { ContactUs } from '@/components/common/ContactUs';
|
||||||
|
import { LoadingScreen } from '@/components/presentational/LoadingScreen';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { Button } from '@/components/ui/v2/Button';
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
import { Dropdown } from '@/components/ui/v2/Dropdown';
|
import { Dropdown } from '@/components/ui/v2/Dropdown';
|
||||||
@@ -6,7 +7,7 @@ import { Text } from '@/components/ui/v2/Text';
|
|||||||
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||||
import { useInterval } from '@/hooks/useInterval';
|
import { useInterval } from '@/hooks/useInterval';
|
||||||
import { getRelativeDateByApplicationState } from '@/utils/helpers';
|
import { getRelativeDateByApplicationState } from '@/utils/helpers';
|
||||||
import { useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
export interface AppLoaderProps {
|
export interface AppLoaderProps {
|
||||||
/**
|
/**
|
||||||
@@ -33,25 +34,31 @@ export default function AppLoader({
|
|||||||
date,
|
date,
|
||||||
restoring,
|
restoring,
|
||||||
}: AppLoaderProps) {
|
}: AppLoaderProps) {
|
||||||
const { project } = useProject();
|
const { project, loading } = useProject();
|
||||||
|
const [timeElapsed, setTimeElapsed] = useState<number>(0);
|
||||||
|
|
||||||
let timeElapsedSinceEventCreation: number;
|
useEffect(() => {
|
||||||
|
if (!project || loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (date) {
|
let timeElapsedSinceEventCreation: number;
|
||||||
timeElapsedSinceEventCreation = getRelativeDateByApplicationState(date);
|
|
||||||
} else if (unpause) {
|
|
||||||
timeElapsedSinceEventCreation = getRelativeDateByApplicationState(
|
|
||||||
project.appStates[0].createdAt,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
timeElapsedSinceEventCreation = getRelativeDateByApplicationState(
|
|
||||||
project.createdAt,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [timeElapsed, setTimeElapsed] = useState(timeElapsedSinceEventCreation);
|
if (date) {
|
||||||
|
timeElapsedSinceEventCreation = getRelativeDateByApplicationState(date);
|
||||||
|
} else if (unpause) {
|
||||||
|
timeElapsedSinceEventCreation = getRelativeDateByApplicationState(
|
||||||
|
project.appStates[0].createdAt,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
timeElapsedSinceEventCreation = getRelativeDateByApplicationState(
|
||||||
|
project.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeElapsed(timeElapsedSinceEventCreation);
|
||||||
|
}, [project, date, unpause, loading]);
|
||||||
|
|
||||||
// Would be also valuable to check the appCreatedTime so this doesn't ever appear if not created under a time limit. @GC
|
|
||||||
useInterval(
|
useInterval(
|
||||||
() => {
|
() => {
|
||||||
setTimeElapsed(timeElapsed + 1);
|
setTimeElapsed(timeElapsed + 1);
|
||||||
@@ -59,6 +66,10 @@ export default function AppLoader({
|
|||||||
startLoader ? 1000 : null,
|
startLoader ? 1000 : null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <LoadingScreen />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-flow-row gap-2">
|
<div className="grid grid-flow-row gap-2">
|
||||||
<div className="grid grid-flow-row gap-1">
|
<div className="grid grid-flow-row gap-1">
|
||||||
@@ -86,9 +97,9 @@ export default function AppLoader({
|
|||||||
<ActivityIndicator className="mx-auto" />
|
<ActivityIndicator className="mx-auto" />
|
||||||
|
|
||||||
{timeElapsed > 180 && (
|
{timeElapsed > 180 && (
|
||||||
<Dropdown.Root className="flex flex-col mx-auto">
|
<Dropdown.Root className="mx-auto flex flex-col">
|
||||||
<Dropdown.Trigger
|
<Dropdown.Trigger
|
||||||
className="flex mx-auto font-medium"
|
className="mx-auto flex font-medium"
|
||||||
hideChevron
|
hideChevron
|
||||||
asChild
|
asChild
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ export default function useIsHealthy() {
|
|||||||
const appUrl = generateAppServiceUrl(
|
const appUrl = generateAppServiceUrl(
|
||||||
project?.subdomain,
|
project?.subdomain,
|
||||||
project?.region,
|
project?.region,
|
||||||
'auth',
|
'hasura',
|
||||||
);
|
);
|
||||||
|
|
||||||
const { failureCount, status } = useQuery(
|
const { failureCount, status } = useQuery(
|
||||||
['/healthz'],
|
['/v1/version'],
|
||||||
() => fetch(`${appUrl}/healthz`),
|
() => fetch(`${appUrl}/v1/version`),
|
||||||
{
|
{
|
||||||
enabled: !isPlatform && !!project,
|
enabled: !isPlatform && !!project,
|
||||||
retry: true,
|
retry: true,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { ProjectFragment } from '@/utils/__generated__/graphql';
|
||||||
import {
|
import {
|
||||||
getAuthServiceUrl,
|
getAuthServiceUrl,
|
||||||
getDatabaseServiceUrl,
|
getDatabaseServiceUrl,
|
||||||
@@ -7,7 +8,6 @@ import {
|
|||||||
getStorageServiceUrl,
|
getStorageServiceUrl,
|
||||||
isPlatform,
|
isPlatform,
|
||||||
} from '@/utils/env';
|
} from '@/utils/env';
|
||||||
import type { ProjectFragment } from '@/utils/__generated__/graphql';
|
|
||||||
|
|
||||||
export type NhostService =
|
export type NhostService =
|
||||||
| 'auth'
|
| 'auth'
|
||||||
@@ -18,21 +18,6 @@ export type NhostService =
|
|||||||
| 'hasura'
|
| 'hasura'
|
||||||
| 'grafana';
|
| 'grafana';
|
||||||
|
|
||||||
/**
|
|
||||||
* The default slugs that are used when running the dashboard locally. These
|
|
||||||
* values are used both in local mode and when running the dashboard locally
|
|
||||||
* against the remote (either staging or production) backend.
|
|
||||||
*/
|
|
||||||
export const defaultLocalBackendSlugs: Record<NhostService, string> = {
|
|
||||||
auth: '/v1/auth',
|
|
||||||
db: '',
|
|
||||||
graphql: '/v1/graphql',
|
|
||||||
functions: '/v1/functions',
|
|
||||||
storage: '/v1/files',
|
|
||||||
hasura: '',
|
|
||||||
grafana: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default slugs that are used when running the dashboard against the
|
* The default slugs that are used when running the dashboard against the
|
||||||
* remote (either staging or production) backend in a cloud environment.
|
* remote (either staging or production) backend in a cloud environment.
|
||||||
|
|||||||
@@ -34,7 +34,11 @@ export default function AuthDomain() {
|
|||||||
const localMimirClient = useLocalMimirClient();
|
const localMimirClient = useLocalMimirClient();
|
||||||
const [isVerified, setIsVerified] = useState(false);
|
const [isVerified, setIsVerified] = useState(false);
|
||||||
|
|
||||||
const { project, refetch: refetchProject } = useProject();
|
const {
|
||||||
|
project,
|
||||||
|
refetch: refetchProject,
|
||||||
|
loading: loadingProject,
|
||||||
|
} = useProject();
|
||||||
|
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
@@ -48,7 +52,7 @@ export default function AuthDomain() {
|
|||||||
|
|
||||||
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
||||||
variables: {
|
variables: {
|
||||||
appId: project.id,
|
appId: project?.id,
|
||||||
},
|
},
|
||||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
@@ -62,7 +66,7 @@ export default function AuthDomain() {
|
|||||||
}
|
}
|
||||||
}, [data, loading, form, initialValue]);
|
}, [data, loading, form, initialValue]);
|
||||||
|
|
||||||
if (loading) {
|
if (loadingProject || loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
delay={1000}
|
delay={1000}
|
||||||
@@ -147,7 +151,7 @@ export default function AuthDomain() {
|
|||||||
loading: formState.isSubmitting,
|
loading: formState.isSubmitting,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
className="grid grid-flow-row px-4 gap-x-4 gap-y-4 lg:grid-cols-5"
|
className="grid grid-flow-row gap-x-4 gap-y-4 px-4 lg:grid-cols-5"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
{...register('auth_fqdn')}
|
{...register('auth_fqdn')}
|
||||||
|
|||||||
@@ -14,10 +14,14 @@ const validationSchema = Yup.object({
|
|||||||
export type DatabaseDomainFormValues = Yup.InferType<typeof validationSchema>;
|
export type DatabaseDomainFormValues = Yup.InferType<typeof validationSchema>;
|
||||||
|
|
||||||
export default function DatabaseDomain() {
|
export default function DatabaseDomain() {
|
||||||
const { project } = useProject();
|
const { project, loading } = useProject();
|
||||||
|
|
||||||
const [dbFQDN, setDbFQDN] = useState('');
|
const [dbFQDN, setDbFQDN] = useState('');
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const postgresHost = generateAppServiceUrl(
|
const postgresHost = generateAppServiceUrl(
|
||||||
project.subdomain,
|
project.subdomain,
|
||||||
project.region,
|
project.region,
|
||||||
@@ -36,7 +40,7 @@ export default function DatabaseDomain() {
|
|||||||
className: 'hidden',
|
className: 'hidden',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
className="grid grid-flow-row px-4 gap-x-4 gap-y-4 lg:grid-cols-5"
|
className="grid grid-flow-row gap-x-4 gap-y-4 px-4 lg:grid-cols-5"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
id="database_fqdn"
|
id="database_fqdn"
|
||||||
|
|||||||
@@ -33,7 +33,11 @@ export default function HasuraDomain() {
|
|||||||
const localMimirClient = useLocalMimirClient();
|
const localMimirClient = useLocalMimirClient();
|
||||||
const [isVerified, setIsVerified] = useState(false);
|
const [isVerified, setIsVerified] = useState(false);
|
||||||
|
|
||||||
const { project, refetch: refetchProject } = useProject();
|
const {
|
||||||
|
project,
|
||||||
|
refetch: refetchProject,
|
||||||
|
loading: loadingProject,
|
||||||
|
} = useProject();
|
||||||
|
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
@@ -47,7 +51,7 @@ export default function HasuraDomain() {
|
|||||||
|
|
||||||
const { data, loading, error } = useGetHasuraSettingsQuery({
|
const { data, loading, error } = useGetHasuraSettingsQuery({
|
||||||
variables: {
|
variables: {
|
||||||
appId: project.id,
|
appId: project?.id,
|
||||||
},
|
},
|
||||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
@@ -61,7 +65,7 @@ export default function HasuraDomain() {
|
|||||||
}
|
}
|
||||||
}, [data, loading, form, initialValue]);
|
}, [data, loading, form, initialValue]);
|
||||||
|
|
||||||
if (loading) {
|
if (loadingProject || loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
delay={0}
|
delay={0}
|
||||||
@@ -148,7 +152,7 @@ export default function HasuraDomain() {
|
|||||||
loading: formState.isSubmitting,
|
loading: formState.isSubmitting,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
className="grid grid-flow-row px-4 gap-x-4 gap-y-4 lg:grid-cols-5"
|
className="grid grid-flow-row gap-x-4 gap-y-4 px-4 lg:grid-cols-5"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
{...register('hasura_fqdn')}
|
{...register('hasura_fqdn')}
|
||||||
|
|||||||
@@ -35,7 +35,11 @@ export default function ServerlessFunctionsDomain() {
|
|||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
const localMimirClient = useLocalMimirClient();
|
const localMimirClient = useLocalMimirClient();
|
||||||
const [isVerified, setIsVerified] = useState(false);
|
const [isVerified, setIsVerified] = useState(false);
|
||||||
const { project, refetch: refetchProject } = useProject();
|
const {
|
||||||
|
project,
|
||||||
|
refetch: refetchProject,
|
||||||
|
loading: loadingProject,
|
||||||
|
} = useProject();
|
||||||
|
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
@@ -49,7 +53,7 @@ export default function ServerlessFunctionsDomain() {
|
|||||||
|
|
||||||
const { data, loading, error } = useGetServerlessFunctionsSettingsQuery({
|
const { data, loading, error } = useGetServerlessFunctionsSettingsQuery({
|
||||||
variables: {
|
variables: {
|
||||||
appId: project.id,
|
appId: project?.id,
|
||||||
},
|
},
|
||||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
@@ -63,7 +67,7 @@ export default function ServerlessFunctionsDomain() {
|
|||||||
}
|
}
|
||||||
}, [data, loading, form, initialValue]);
|
}, [data, loading, form, initialValue]);
|
||||||
|
|
||||||
if (loading) {
|
if (loadingProject || loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
delay={1000}
|
delay={1000}
|
||||||
@@ -151,7 +155,7 @@ export default function ServerlessFunctionsDomain() {
|
|||||||
loading: formState.isSubmitting,
|
loading: formState.isSubmitting,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
className="grid grid-flow-row px-4 gap-x-4 gap-y-4 lg:grid-cols-5"
|
className="grid grid-flow-row gap-x-4 gap-y-4 px-4 lg:grid-cols-5"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
{...register('functions_fqdn')}
|
{...register('functions_fqdn')}
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useDataGridConfig } from '@/components/dataGrid/DataGridConfigProvider';
|
|
||||||
import type { DataGridPaginationProps } from '@/components/dataGrid/DataGridPagination';
|
|
||||||
import { DataGridPagination } from '@/components/dataGrid/DataGridPagination';
|
|
||||||
import type { BoxProps } from '@/components/ui/v2/Box';
|
import type { BoxProps } from '@/components/ui/v2/Box';
|
||||||
import { Box } from '@/components/ui/v2/Box';
|
import { Box } from '@/components/ui/v2/Box';
|
||||||
import { Button } from '@/components/ui/v2/Button';
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
@@ -13,6 +10,9 @@ import { RowIcon } from '@/components/ui/v2/icons/RowIcon';
|
|||||||
import { useDeleteRecordMutation } from '@/features/orgs/projects/database/dataGrid/hooks/useDeleteRecordMutation';
|
import { useDeleteRecordMutation } from '@/features/orgs/projects/database/dataGrid/hooks/useDeleteRecordMutation';
|
||||||
import type { DataBrowserGridColumn } from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
|
import type { DataBrowserGridColumn } from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
|
||||||
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||||
|
import { useDataGridConfig } from '@/features/orgs/projects/storage/dataGrid/components/DataGridConfigProvider';
|
||||||
|
import type { DataGridPaginationProps } from '@/features/orgs/projects/storage/dataGrid/components/DataGridPagination';
|
||||||
|
import { DataGridPagination } from '@/features/orgs/projects/storage/dataGrid/components/DataGridPagination';
|
||||||
import { triggerToast } from '@/utils/toast';
|
import { triggerToast } from '@/utils/toast';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -120,7 +120,7 @@ export default function DataBrowserGridControls({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{numberOfSelectedRows > 0 && (
|
{numberOfSelectedRows > 0 && (
|
||||||
<div className="grid items-center grid-flow-col gap-2 place-content-start">
|
<div className="grid grid-flow-col place-content-start items-center gap-2">
|
||||||
<Chip
|
<Chip
|
||||||
size="small"
|
size="small"
|
||||||
color="info"
|
color="info"
|
||||||
@@ -161,7 +161,7 @@ export default function DataBrowserGridControls({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{numberOfSelectedRows === 0 && (
|
{numberOfSelectedRows === 0 && (
|
||||||
<div className="grid items-center grid-flow-col col-span-6 gap-2">
|
<div className="col-span-6 grid grid-flow-col items-center gap-2">
|
||||||
{columns.length > 0 && (
|
{columns.length > 0 && (
|
||||||
<DataGridPagination
|
<DataGridPagination
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
@@ -177,7 +177,7 @@ export default function DataBrowserGridControls({
|
|||||||
<Dropdown.Root>
|
<Dropdown.Root>
|
||||||
<Dropdown.Trigger asChild hideChevron>
|
<Dropdown.Trigger asChild hideChevron>
|
||||||
<Button
|
<Button
|
||||||
startIcon={<PlusIcon className="w-4 h-4" />}
|
startIcon={<PlusIcon className="h-4 w-4" />}
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
Insert
|
Insert
|
||||||
|
|||||||
@@ -18,21 +18,7 @@ const ruleSchema = Yup.object().shape({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const ruleGroupSchema = Yup.object().shape({
|
const ruleGroupSchema = Yup.object().shape({
|
||||||
operator: Yup.string().test(
|
operator: Yup.string().required('Please select an operator.'),
|
||||||
'operator',
|
|
||||||
'Please select an operator.',
|
|
||||||
(selectedOperator, ctx) => {
|
|
||||||
// `from` is part of the Yup API, but it's not typed.
|
|
||||||
// @ts-ignore
|
|
||||||
const [, { value }] = ctx.from;
|
|
||||||
|
|
||||||
if (Object.keys(value.filter).length > 0 && !selectedOperator) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
rules: Yup.array().of(ruleSchema),
|
rules: Yup.array().of(ruleSchema),
|
||||||
groups: Yup.array().of(Yup.lazy(() => ruleGroupSchema) as any),
|
groups: Yup.array().of(Yup.lazy(() => ruleGroupSchema) as any),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import { Button } from '@/components/ui/v2/Button';
|
|||||||
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
|
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
|
||||||
import { Link } from '@/components/ui/v2/Link';
|
import { Link } from '@/components/ui/v2/Link';
|
||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
|
import { generateAppServiceUrl } from '@/features/orgs/projects/common/utils/generateAppServiceUrl';
|
||||||
import type {
|
import type {
|
||||||
Rule,
|
Rule,
|
||||||
RuleGroup,
|
RuleGroup,
|
||||||
} from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
|
} from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
@@ -68,7 +68,7 @@ export default function RuleGroupEditor({
|
|||||||
sx,
|
sx,
|
||||||
...props
|
...props
|
||||||
}: RuleGroupEditorProps) {
|
}: RuleGroupEditorProps) {
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { project } = useProject();
|
||||||
const form = useFormContext();
|
const form = useFormContext();
|
||||||
|
|
||||||
const { control, getValues } = form;
|
const { control, getValues } = form;
|
||||||
@@ -185,13 +185,13 @@ export default function RuleGroupEditor({
|
|||||||
<Text>
|
<Text>
|
||||||
This rule group contains one or more objects (e.g: _exists) that
|
This rule group contains one or more objects (e.g: _exists) that
|
||||||
are not supported by our dashboard yet.{' '}
|
are not supported by our dashboard yet.{' '}
|
||||||
{currentProject && (
|
{project && (
|
||||||
<span>
|
<span>
|
||||||
Please{' '}
|
Please{' '}
|
||||||
<Link
|
<Link
|
||||||
href={`${generateAppServiceUrl(
|
href={`${generateAppServiceUrl(
|
||||||
currentProject.subdomain,
|
project.subdomain,
|
||||||
currentProject.region,
|
project.region,
|
||||||
'hasura',
|
'hasura',
|
||||||
)}/console/data/default/schema/${schema}/tables/${table}/permissions`}
|
)}/console/data/default/schema/${schema}/tables/${table}/permissions`}
|
||||||
underline="hover"
|
underline="hover"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
|
||||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||||
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
|
||||||
import { getHasuraAdminSecret } from '@/utils/env';
|
import { getHasuraAdminSecret } from '@/utils/env';
|
||||||
import type { MutationOptions } from '@tanstack/react-query';
|
import type { MutationOptions } from '@tanstack/react-query';
|
||||||
@@ -39,10 +39,10 @@ export default function useDeleteColumnMutation({
|
|||||||
const {
|
const {
|
||||||
query: { dataSourceSlug, schemaSlug, tableSlug },
|
query: { dataSourceSlug, schemaSlug, tableSlug },
|
||||||
} = useRouter();
|
} = useRouter();
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
const { project } = useProject();
|
||||||
const appUrl = generateAppServiceUrl(
|
const appUrl = generateAppServiceUrl(
|
||||||
currentProject?.subdomain,
|
project?.subdomain,
|
||||||
currentProject?.region,
|
project?.region,
|
||||||
'hasura',
|
'hasura',
|
||||||
);
|
);
|
||||||
const mutationFn = isPlatform ? deleteColumn : deleteColumnMigration;
|
const mutationFn = isPlatform ? deleteColumn : deleteColumnMigration;
|
||||||
@@ -55,7 +55,7 @@ export default function useDeleteColumnMutation({
|
|||||||
adminSecret:
|
adminSecret:
|
||||||
process.env.NEXT_PUBLIC_ENV === 'dev'
|
process.env.NEXT_PUBLIC_ENV === 'dev'
|
||||||
? getHasuraAdminSecret()
|
? getHasuraAdminSecret()
|
||||||
: customAdminSecret || currentProject?.config?.hasura.adminSecret,
|
: customAdminSecret || project?.config?.hasura.adminSecret,
|
||||||
dataSource: customDataSource || (dataSourceSlug as string),
|
dataSource: customDataSource || (dataSourceSlug as string),
|
||||||
schema: customSchema || (schemaSlug as string),
|
schema: customSchema || (schemaSlug as string),
|
||||||
table: customTable || (tableSlug as string),
|
table: customTable || (tableSlug as string),
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ import { useUI } from '@/components/common/UIProvider';
|
|||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
|
import { Alert } from '@/components/ui/v2/Alert';
|
||||||
import { Box } from '@/components/ui/v2/Box';
|
import { Box } from '@/components/ui/v2/Box';
|
||||||
import { Input } from '@/components/ui/v2/Input';
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
import { InputAdornment } from '@/components/ui/v2/InputAdornment';
|
import { InputAdornment } from '@/components/ui/v2/InputAdornment';
|
||||||
|
import { Link } from '@/components/ui/v2/Link';
|
||||||
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
import { UpgradeNotification } from '@/features/orgs/projects/common/components/UpgradeNotification';
|
import { UpgradeNotification } from '@/features/orgs/projects/common/components/UpgradeNotification';
|
||||||
import { useAppState } from '@/features/orgs/projects/common/hooks/useAppState';
|
import { useAppState } from '@/features/orgs/projects/common/hooks/useAppState';
|
||||||
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
|
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
|
||||||
@@ -14,6 +17,7 @@ import { useLocalMimirClient } from '@/features/orgs/projects/hooks/useLocalMimi
|
|||||||
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||||
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
|
import { execPromiseWithErrorToast } from '@/features/orgs/utils/execPromiseWithErrorToast';
|
||||||
import {
|
import {
|
||||||
|
useGetPersistentVolumesEncryptedQuery,
|
||||||
useGetPostgresSettingsQuery,
|
useGetPostgresSettingsQuery,
|
||||||
useUpdateConfigMutation,
|
useUpdateConfigMutation,
|
||||||
} from '@/generated/graphql';
|
} from '@/generated/graphql';
|
||||||
@@ -57,6 +61,15 @@ export default function DatabaseStorageCapacity() {
|
|||||||
org?.plan?.featureMaxDbSize) ||
|
org?.plan?.featureMaxDbSize) ||
|
||||||
0;
|
0;
|
||||||
|
|
||||||
|
const { data: encryptedVolumesData } = useGetPersistentVolumesEncryptedQuery({
|
||||||
|
variables: { appId: project?.id },
|
||||||
|
skip: !isPlatform,
|
||||||
|
});
|
||||||
|
|
||||||
|
const showEncryptionWarning = encryptedVolumesData
|
||||||
|
? !encryptedVolumesData?.systemConfig?.persistentVolumesEncrypted
|
||||||
|
: false;
|
||||||
|
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
@@ -187,6 +200,28 @@ export default function DatabaseStorageCapacity() {
|
|||||||
isDirty={isDirty}
|
isDirty={isDirty}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{showEncryptionWarning ? (
|
||||||
|
<Alert severity="warning" className="flex flex-col gap-3 text-left">
|
||||||
|
<div className="flex flex-col gap-2 lg:flex-row lg:justify-between">
|
||||||
|
<Text className="flex items-start gap-1 font-semibold">
|
||||||
|
Disk encryption is now available!
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Text>
|
||||||
|
To enable encryption in this project all you have to do is
|
||||||
|
pause & unpause it in{' '}
|
||||||
|
<Link
|
||||||
|
href={`/orgs/${org?.slug}/projects/${project?.subdomain}/settings`}
|
||||||
|
underline="hover"
|
||||||
|
>
|
||||||
|
General Settings
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Alert>
|
||||||
|
) : null}
|
||||||
</SettingsContainer>
|
</SettingsContainer>
|
||||||
</Form>
|
</Form>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
query GetPersistentVolumesEncrypted($appId: uuid!) {
|
||||||
|
systemConfig(appID: $appId) {
|
||||||
|
persistentVolumesEncrypted
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,8 +13,8 @@ import { Tooltip } from '@/components/ui/v2/Tooltip';
|
|||||||
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||||
import { LogsRangeSelector } from '@/features/orgs/projects/logs/components/LogsRangeSelector';
|
import { LogsRangeSelector } from '@/features/orgs/projects/logs/components/LogsRangeSelector';
|
||||||
import { AvailableLogsService } from '@/features/orgs/projects/logs/utils/constants/services';
|
import { AvailableLogsService } from '@/features/orgs/projects/logs/utils/constants/services';
|
||||||
import { MINUTES_TO_DECREASE_FROM_CURRENT_DATE } from '@/utils/constants/common';
|
|
||||||
import { useGetServiceLabelValuesQuery } from '@/utils/__generated__/graphql';
|
import { useGetServiceLabelValuesQuery } from '@/utils/__generated__/graphql';
|
||||||
|
import { MINUTES_TO_DECREASE_FROM_CURRENT_DATE } from '@/utils/constants/common';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { subMinutes } from 'date-fns';
|
import { subMinutes } from 'date-fns';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -55,7 +55,7 @@ export default function LogsHeader({
|
|||||||
|
|
||||||
const { data, loading: loadingServiceLabelValues } =
|
const { data, loading: loadingServiceLabelValues } =
|
||||||
useGetServiceLabelValuesQuery({
|
useGetServiceLabelValuesQuery({
|
||||||
variables: { appID: project.id },
|
variables: { appID: project?.id },
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -121,7 +121,7 @@ export default function LogsHeader({
|
|||||||
<Box className="flex flex-row space-x-2">
|
<Box className="flex flex-row space-x-2">
|
||||||
<ControlledSelect
|
<ControlledSelect
|
||||||
{...register('service')}
|
{...register('service')}
|
||||||
className="w-full text-sm font-normal min-w-fit"
|
className="w-full min-w-fit text-sm font-normal"
|
||||||
placeholder="All Services"
|
placeholder="All Services"
|
||||||
aria-label="Select service"
|
aria-label="Select service"
|
||||||
hideEmptyHelperText
|
hideEmptyHelperText
|
||||||
@@ -165,12 +165,12 @@ export default function LogsHeader({
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
title={
|
title={
|
||||||
<div className="p-2 space-y-4">
|
<div className="space-y-4 p-2">
|
||||||
<h2>Here are some useful regular expressions:</h2>
|
<h2>Here are some useful regular expressions:</h2>
|
||||||
<ul className="pl-3 space-y-2 list-disc">
|
<ul className="list-disc space-y-2 pl-3">
|
||||||
<li>
|
<li>
|
||||||
use
|
use
|
||||||
<code className="px-1 py-px mx-1 rounded-md bg-slate-500 text-slate-100">
|
<code className="mx-1 rounded-md bg-slate-500 px-1 py-px text-slate-100">
|
||||||
(?i)error
|
(?i)error
|
||||||
</code>
|
</code>
|
||||||
to search for lines with the word <b>error</b> (case
|
to search for lines with the word <b>error</b> (case
|
||||||
@@ -178,7 +178,7 @@ export default function LogsHeader({
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
use
|
use
|
||||||
<code className="px-1 py-px mx-1 rounded-md bg-slate-500 text-slate-100">
|
<code className="mx-1 rounded-md bg-slate-500 px-1 py-px text-slate-100">
|
||||||
error
|
error
|
||||||
</code>
|
</code>
|
||||||
to search for lines with the word <b>error</b> (case
|
to search for lines with the word <b>error</b> (case
|
||||||
@@ -186,7 +186,7 @@ export default function LogsHeader({
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
use
|
use
|
||||||
<code className="px-1 py-px mx-1 rounded-md bg-slate-500 text-slate-100">
|
<code className="mx-1 rounded-md bg-slate-500 px-1 py-px text-slate-100">
|
||||||
/metadata.*error
|
/metadata.*error
|
||||||
</code>
|
</code>
|
||||||
to search for errors in hasura's metadata endpoint
|
to search for errors in hasura's metadata endpoint
|
||||||
@@ -208,10 +208,10 @@ export default function LogsHeader({
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Box className="ml-2 rounded-full cursor-pointer">
|
<Box className="ml-2 cursor-pointer rounded-full">
|
||||||
<InfoIcon
|
<InfoIcon
|
||||||
aria-label="Info"
|
aria-label="Info"
|
||||||
className="w-5 h-5"
|
className="h-5 w-5"
|
||||||
color="info"
|
color="info"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -224,7 +224,7 @@ export default function LogsHeader({
|
|||||||
className="h-10"
|
className="h-10"
|
||||||
startIcon={
|
startIcon={
|
||||||
loading ? (
|
loading ? (
|
||||||
<ActivityIndicator className="w-4 h-4" />
|
<ActivityIndicator className="h-4 w-4" />
|
||||||
) : (
|
) : (
|
||||||
<SearchIcon />
|
<SearchIcon />
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -33,15 +33,24 @@ export default function MetricsSettings() {
|
|||||||
const isPlatform = useIsPlatform();
|
const isPlatform = useIsPlatform();
|
||||||
const { maintenanceActive } = useUI();
|
const { maintenanceActive } = useUI();
|
||||||
const localMimirClient = useLocalMimirClient();
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { project, refetch: refetchProject } = useProject();
|
const {
|
||||||
|
project,
|
||||||
|
refetch: refetchProject,
|
||||||
|
loading: loadingProject,
|
||||||
|
} = useProject();
|
||||||
const [updateConfig] = useUpdateConfigMutation({
|
const [updateConfig] = useUpdateConfigMutation({
|
||||||
refetchQueries: [GetObservabilitySettingsDocument],
|
refetchQueries: [GetObservabilitySettingsDocument],
|
||||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data, loading, error } = useGetObservabilitySettingsQuery({
|
const {
|
||||||
|
data,
|
||||||
|
loading: loadingObservabilitySettings,
|
||||||
|
error,
|
||||||
|
} = useGetObservabilitySettingsQuery({
|
||||||
variables: { appId: project?.id },
|
variables: { appId: project?.id },
|
||||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
|
skip: !project?.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { enabled: alertingEnabled } =
|
const { enabled: alertingEnabled } =
|
||||||
@@ -59,14 +68,14 @@ export default function MetricsSettings() {
|
|||||||
const alerting = watch('enabled');
|
const alerting = watch('enabled');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!loading) {
|
if (!loadingObservabilitySettings) {
|
||||||
alertingForm.reset({
|
alertingForm.reset({
|
||||||
enabled: alertingEnabled,
|
enabled: alertingEnabled,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [loading, alertingEnabled, alertingForm]);
|
}, [loadingObservabilitySettings, alertingEnabled, alertingForm]);
|
||||||
|
|
||||||
if (loading) {
|
if (loadingProject || loadingObservabilitySettings) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
delay={1000}
|
delay={1000}
|
||||||
@@ -124,7 +133,7 @@ export default function MetricsSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid max-w-5xl grid-flow-row bg-transparent gap-y-6">
|
<div className="grid max-w-5xl grid-flow-row gap-y-6 bg-transparent">
|
||||||
<FormProvider {...alertingForm}>
|
<FormProvider {...alertingForm}>
|
||||||
<Form onSubmit={handleSubmit}>
|
<Form onSubmit={handleSubmit}>
|
||||||
<SettingsContainer
|
<SettingsContainer
|
||||||
|
|||||||
@@ -10,28 +10,28 @@ const features: CardProps[] = [
|
|||||||
description: 'Learn how to use Postgres with Nhost',
|
description: 'Learn how to use Postgres with Nhost',
|
||||||
icon: <DatabaseIcon className="h-8 w-8" sx={{ color: 'text.secondary' }} />,
|
icon: <DatabaseIcon className="h-8 w-8" sx={{ color: 'text.secondary' }} />,
|
||||||
disableIconBackground: true,
|
disableIconBackground: true,
|
||||||
link: 'https://docs.nhost.io/platform/database',
|
link: 'https://docs.nhost.io/product/database',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'GraphQL API',
|
title: 'GraphQL API',
|
||||||
description: 'Learn how to interact with the GraphQL API',
|
description: 'Learn how to interact with the GraphQL API',
|
||||||
icon: <GraphQLIcon className="h-8 w-8" sx={{ color: 'text.secondary' }} />,
|
icon: <GraphQLIcon className="h-8 w-8" sx={{ color: 'text.secondary' }} />,
|
||||||
disableIconBackground: true,
|
disableIconBackground: true,
|
||||||
link: 'https://docs.nhost.io/platform/graphql',
|
link: 'https://docs.nhost.io/product/graphql',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Authentication',
|
title: 'Authentication',
|
||||||
description: 'Learn how to authenticate users with Nhost',
|
description: 'Learn how to authenticate users with Nhost',
|
||||||
icon: <UserIcon className="h-8 w-8" sx={{ color: 'text.secondary' }} />,
|
icon: <UserIcon className="h-8 w-8" sx={{ color: 'text.secondary' }} />,
|
||||||
disableIconBackground: true,
|
disableIconBackground: true,
|
||||||
link: 'https://docs.nhost.io/platform/authentication',
|
link: 'https://docs.nhost.io/product/authentication',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Storage',
|
title: 'Storage',
|
||||||
description: 'Learn how to use Storage with Nhost',
|
description: 'Learn how to use Storage with Nhost',
|
||||||
icon: <StorageIcon className="h-8 w-8" sx={{ color: 'text.secondary' }} />,
|
icon: <StorageIcon className="h-8 w-8" sx={{ color: 'text.secondary' }} />,
|
||||||
disableIconBackground: true,
|
disableIconBackground: true,
|
||||||
link: 'https://docs.nhost.io/platform/storage',
|
link: 'https://docs.nhost.io/product/storage',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export default function useRunServices() {
|
|||||||
refetch: refetchPlatformServices,
|
refetch: refetchPlatformServices,
|
||||||
} = useGetRunServicesQuery({
|
} = useGetRunServicesQuery({
|
||||||
variables: {
|
variables: {
|
||||||
appID: project.id,
|
appID: project?.id,
|
||||||
resolve: false,
|
resolve: false,
|
||||||
limit: limit.current,
|
limit: limit.current,
|
||||||
offset,
|
offset,
|
||||||
@@ -59,7 +59,7 @@ export default function useRunServices() {
|
|||||||
data: localServicesData,
|
data: localServicesData,
|
||||||
refetch: refetchLocalServices,
|
refetch: refetchLocalServices,
|
||||||
} = useGetLocalRunServiceConfigsQuery({
|
} = useGetLocalRunServiceConfigsQuery({
|
||||||
variables: { appID: project.id as any, resolve: false },
|
variables: { appID: project?.id as any, resolve: false },
|
||||||
skip: isPlatform,
|
skip: isPlatform,
|
||||||
client: localMimirClient,
|
client: localMimirClient,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import type { UseDataGridOptions } from '@/components/dataGrid/DataGrid/useDataGrid';
|
|
||||||
import { DataGridBody } from '@/components/dataGrid/DataGridBody';
|
|
||||||
import { DataGridConfigProvider } from '@/components/dataGrid/DataGridConfigProvider';
|
|
||||||
import { DataGridFrame } from '@/components/dataGrid/DataGridFrame';
|
|
||||||
import type { DataGridHeaderProps } from '@/components/dataGrid/DataGridHeader';
|
|
||||||
import { DataGridHeader } from '@/components/dataGrid/DataGridHeader';
|
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { Box } from '@/components/ui/v2/Box';
|
import { Box } from '@/components/ui/v2/Box';
|
||||||
import { DataBrowserEmptyState } from '@/features/database/dataGrid/components/DataBrowserEmptyState';
|
import { DataBrowserEmptyState } from '@/features/orgs/projects/database/dataGrid/components/DataBrowserEmptyState';
|
||||||
import type { DataBrowserGridColumn } from '@/features/database/dataGrid/types/dataBrowser';
|
import type { DataBrowserGridColumn } from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
|
||||||
|
import type { UseDataGridOptions } from '@/features/orgs/projects/storage/dataGrid/components/DataGrid/useDataGrid';
|
||||||
|
import { DataGridBody } from '@/features/orgs/projects/storage/dataGrid/components/DataGridBody';
|
||||||
|
import { DataGridConfigProvider } from '@/features/orgs/projects/storage/dataGrid/components/DataGridConfigProvider';
|
||||||
|
import { DataGridFrame } from '@/features/orgs/projects/storage/dataGrid/components/DataGridFrame';
|
||||||
|
import type { DataGridHeaderProps } from '@/features/orgs/projects/storage/dataGrid/components/DataGridHeader';
|
||||||
|
import { DataGridHeader } from '@/features/orgs/projects/storage/dataGrid/components/DataGridHeader';
|
||||||
import type { ForwardedRef } from 'react';
|
import type { ForwardedRef } from 'react';
|
||||||
import { forwardRef, useEffect, useRef } from 'react';
|
import { forwardRef, useEffect, useRef } from 'react';
|
||||||
import mergeRefs from 'react-merge-refs';
|
import mergeRefs from 'react-merge-refs';
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import type { DataGridProps } from '@/components/dataGrid/DataGrid';
|
|
||||||
import { DataGridCell } from '@/components/dataGrid/DataGridCell';
|
|
||||||
import { useDataGridConfig } from '@/components/dataGrid/DataGridConfigProvider';
|
|
||||||
import type { BoxProps } from '@/components/ui/v2/Box';
|
import type { BoxProps } from '@/components/ui/v2/Box';
|
||||||
import { Box } from '@/components/ui/v2/Box';
|
import { Box } from '@/components/ui/v2/Box';
|
||||||
import { Button } from '@/components/ui/v2/Button';
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
|
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
|
||||||
import type { DataBrowserGridColumn } from '@/features/database/dataGrid/types/dataBrowser';
|
import type { DataBrowserGridColumn } from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
|
||||||
|
import type { DataGridProps } from '@/features/orgs/projects/storage/dataGrid/components/DataGrid/DataGrid';
|
||||||
|
import { DataGridCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridCell';
|
||||||
|
import { useDataGridConfig } from '@/features/orgs/projects/storage/dataGrid/components/DataGridConfigProvider';
|
||||||
import type { DetailedHTMLProps, HTMLProps, KeyboardEvent } from 'react';
|
import type { DetailedHTMLProps, HTMLProps, KeyboardEvent } from 'react';
|
||||||
import { Fragment, useMemo, useRef } from 'react';
|
import { Fragment, useMemo, useRef } from 'react';
|
||||||
import type { Row } from 'react-table';
|
import type { Row } from 'react-table';
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { CommonDataGridCellProps } from '@/components/dataGrid/DataGridCell';
|
|
||||||
import { useDataGridCell } from '@/components/dataGrid/DataGridCell';
|
|
||||||
import { ReadOnlyToggle } from '@/components/presentational/ReadOnlyToggle';
|
import { ReadOnlyToggle } from '@/components/presentational/ReadOnlyToggle';
|
||||||
import { Dropdown } from '@/components/ui/v2/Dropdown';
|
import { Dropdown } from '@/components/ui/v2/Dropdown';
|
||||||
import type { KeyboardEvent as ReactKeyboardEvent, MouseEvent } from 'react';
|
import type { CommonDataGridCellProps } from '@/features/orgs/projects/storage/dataGrid/components/DataGridCell';
|
||||||
|
import { useDataGridCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridCell';
|
||||||
|
import type { MouseEvent, KeyboardEvent as ReactKeyboardEvent } from 'react';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export type DataGridBooleanCellProps<TData extends object> =
|
export type DataGridBooleanCellProps<TData extends object> =
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import type {
|
|||||||
ColumnType,
|
ColumnType,
|
||||||
DataBrowserGridCell,
|
DataBrowserGridCell,
|
||||||
DataBrowserGridCellProps,
|
DataBrowserGridCellProps,
|
||||||
} from '@/features/database/dataGrid/types/dataBrowser';
|
} from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
|
||||||
import { triggerToast } from '@/utils/toast';
|
import { triggerToast } from '@/utils/toast';
|
||||||
import type {
|
import type {
|
||||||
FocusEvent,
|
FocusEvent,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { UseDataGridReturn } from '@/components/dataGrid/DataGrid';
|
import type { UseDataGridReturn } from '@/features/orgs/projects/storage/dataGrid/components/DataGrid/useDataGrid';
|
||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
|
|
||||||
const DataGridConfigContext = createContext<Partial<UseDataGridReturn>>(null);
|
const DataGridConfigContext = createContext<Partial<UseDataGridReturn>>(null);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { UseDataGridReturn } from '@/components/dataGrid/DataGrid';
|
import type { UseDataGridReturn } from '@/features/orgs/projects/storage/dataGrid/components/DataGrid/useDataGrid';
|
||||||
import type { PropsWithChildren } from 'react';
|
import type { PropsWithChildren } from 'react';
|
||||||
import DataGridConfigContext from './DataGridConfigContext';
|
import DataGridConfigContext from './DataGridConfigContext';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { UseDataGridReturn } from '@/components/dataGrid/DataGrid';
|
import type { UseDataGridReturn } from '@/features/orgs/projects/storage/dataGrid/components/DataGrid';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import DataGridConfigContext from './DataGridConfigContext';
|
import DataGridConfigContext from './DataGridConfigContext';
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { CommonDataGridCellProps } from '@/components/dataGrid/DataGridCell';
|
|
||||||
import { useDataGridCell } from '@/components/dataGrid/DataGridCell';
|
|
||||||
import { Input, inputClasses } from '@/components/ui/v2/Input';
|
import { Input, inputClasses } from '@/components/ui/v2/Input';
|
||||||
import type { TextProps } from '@/components/ui/v2/Text';
|
import type { TextProps } from '@/components/ui/v2/Text';
|
||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
|
import type { CommonDataGridCellProps } from '@/features/orgs/projects/storage/dataGrid/components/DataGridCell';
|
||||||
|
import { useDataGridCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridCell';
|
||||||
import { getDateComponents } from '@/utils/getDateComponents';
|
import { getDateComponents } from '@/utils/getDateComponents';
|
||||||
import type { ChangeEvent, KeyboardEvent } from 'react';
|
import type { ChangeEvent, KeyboardEvent } from 'react';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { CommonDataGridCellProps } from '@/components/dataGrid/DataGridCell';
|
|
||||||
import { useDataGridCell } from '@/components/dataGrid/DataGridCell';
|
|
||||||
import { Input, inputClasses } from '@/components/ui/v2/Input';
|
import { Input, inputClasses } from '@/components/ui/v2/Input';
|
||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
|
import type { CommonDataGridCellProps } from '@/features/orgs/projects/storage/dataGrid/components/DataGridCell';
|
||||||
|
import { useDataGridCell } from '@/features/orgs/projects/storage/dataGrid/components/DataGridCell';
|
||||||
import type { ChangeEvent, KeyboardEvent } from 'react';
|
import type { ChangeEvent, KeyboardEvent } from 'react';
|
||||||
|
|
||||||
export type DataGridDecimalCellProps<TData extends object> =
|
export type DataGridDecimalCellProps<TData extends object> =
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useDataGridConfig } from '@/components/dataGrid/DataGridConfigProvider';
|
import { useDataGridConfig } from '@/features/orgs/projects/storage/dataGrid/components/DataGridConfigProvider';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import type { DetailedHTMLProps, HTMLProps } from 'react';
|
import type { DetailedHTMLProps, HTMLProps } from 'react';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import type { DataGridProps } from '@/components/dataGrid/DataGrid';
|
|
||||||
import { useDataGridConfig } from '@/components/dataGrid/DataGridConfigProvider';
|
|
||||||
import { Box } from '@/components/ui/v2/Box';
|
import { Box } from '@/components/ui/v2/Box';
|
||||||
import { Button } from '@/components/ui/v2/Button';
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
import { Divider } from '@/components/ui/v2/Divider';
|
import { Divider } from '@/components/ui/v2/Divider';
|
||||||
@@ -9,7 +7,10 @@ import { ArrowUpIcon } from '@/components/ui/v2/icons/ArrowUpIcon';
|
|||||||
import { PencilIcon } from '@/components/ui/v2/icons/PencilIcon';
|
import { PencilIcon } from '@/components/ui/v2/icons/PencilIcon';
|
||||||
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
|
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
|
||||||
import { TrashIcon } from '@/components/ui/v2/icons/TrashIcon';
|
import { TrashIcon } from '@/components/ui/v2/icons/TrashIcon';
|
||||||
import type { DataBrowserGridColumn } from '@/features/database/dataGrid/types/dataBrowser';
|
import type { DataBrowserGridColumn } from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
|
||||||
|
import type { DataGridProps } from '@/features/orgs/projects/storage/dataGrid/components/DataGrid';
|
||||||
|
import { useDataGridConfig } from '@/features/orgs/projects/storage/dataGrid/components/DataGridConfigProvider';
|
||||||
|
import { DataGridHeaderButton } from '@/features/orgs/projects/storage/dataGrid/components/DataGridHeaderButton';
|
||||||
import type { DetailedHTMLProps, HTMLProps } from 'react';
|
import type { DetailedHTMLProps, HTMLProps } from 'react';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
@@ -54,7 +55,7 @@ export default function DataGridHeader<T extends object>({
|
|||||||
componentsProps,
|
componentsProps,
|
||||||
...props
|
...props
|
||||||
}: DataGridHeaderProps<T>) {
|
}: DataGridHeaderProps<T>) {
|
||||||
const { flatHeaders, allowSort, allowResize } = useDataGridConfig<T>();
|
const { flatHeaders } = useDataGridConfig<T>();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -96,53 +97,11 @@ export default function DataGridHeader<T extends object>({
|
|||||||
}}
|
}}
|
||||||
key={column.id}
|
key={column.id}
|
||||||
>
|
>
|
||||||
{column.id === 'selection' ? (
|
<DataGridHeaderButton
|
||||||
<span
|
column={column}
|
||||||
{...headerProps}
|
headerProps={headerProps}
|
||||||
className="relative grid w-full grid-flow-col items-center justify-between p-2"
|
onRemoveColumn={onRemoveColumn}
|
||||||
>
|
/>
|
||||||
{column.render('Header')}
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<Dropdown.Trigger
|
|
||||||
className={twMerge(
|
|
||||||
'focus:outline-none motion-safe:transition-colors',
|
|
||||||
)}
|
|
||||||
disabled={
|
|
||||||
column.isDisabled || (column.disableSortBy && !onRemoveColumn)
|
|
||||||
}
|
|
||||||
hideChevron
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
{...headerProps}
|
|
||||||
className="relative grid w-full grid-flow-col items-center justify-between p-2"
|
|
||||||
>
|
|
||||||
{column.render('Header')}
|
|
||||||
|
|
||||||
{allowSort && (
|
|
||||||
<Box component="span" sx={{ color: 'text.primary' }}>
|
|
||||||
{column.isSorted && !column.isSortedDesc && (
|
|
||||||
<ArrowUpIcon className="h-3 w-3" />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{column.isSorted && column.isSortedDesc && (
|
|
||||||
<ArrowDownIcon className="h-3 w-3" />
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{allowResize && !column.disableResizing && (
|
|
||||||
<span
|
|
||||||
{...column.getResizerProps({
|
|
||||||
onClick: (event: Event) => event.stopPropagation(),
|
|
||||||
})}
|
|
||||||
className="absolute -right-0.5 bottom-0 top-0 z-10 h-full w-1.5 group-hover:bg-slate-900 group-hover:bg-opacity-20 group-active:bg-slate-900 group-active:bg-opacity-20 motion-safe:transition-colors"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Dropdown.Trigger>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Dropdown.Content
|
<Dropdown.Content
|
||||||
menu
|
menu
|
||||||
PaperProps={{ className: 'w-52 mt-1' }}
|
PaperProps={{ className: 'w-52 mt-1' }}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import { Box } from '@/components/ui/v2/Box';
|
||||||
|
import { Dropdown } from '@/components/ui/v2/Dropdown';
|
||||||
|
import { ArrowDownIcon } from '@/components/ui/v2/icons/ArrowDownIcon';
|
||||||
|
import { ArrowUpIcon } from '@/components/ui/v2/icons/ArrowUpIcon';
|
||||||
|
import type { DataBrowserGridColumn } from '@/features/orgs/projects/database/dataGrid/types/dataBrowser';
|
||||||
|
import { useDataGridConfig } from '@/features/orgs/projects/storage/dataGrid/components/DataGridConfigProvider';
|
||||||
|
import type { TableHeaderProps } from 'react-table';
|
||||||
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
interface DataGridHeaderButtonProps<T extends object> {
|
||||||
|
column: DataBrowserGridColumn<T>;
|
||||||
|
headerProps: TableHeaderProps;
|
||||||
|
onRemoveColumn: (column: DataBrowserGridColumn<T>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DataGridHeaderButton<T extends object>({
|
||||||
|
column,
|
||||||
|
headerProps,
|
||||||
|
onRemoveColumn,
|
||||||
|
}: DataGridHeaderButtonProps<T>) {
|
||||||
|
const { allowSort, allowResize } = useDataGridConfig();
|
||||||
|
|
||||||
|
if (column.id === 'selection') {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
{...headerProps}
|
||||||
|
className="relative grid w-full grid-flow-col items-center justify-between p-2"
|
||||||
|
>
|
||||||
|
{column.render('Header')}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (column.id === 'preview') {
|
||||||
|
return (
|
||||||
|
<div className="focus:outline-none motion-safe:transition-colors">
|
||||||
|
{column.render('Header')}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown.Trigger
|
||||||
|
className={twMerge('focus:outline-none motion-safe:transition-colors')}
|
||||||
|
disabled={column.isDisabled || (column.disableSortBy && !onRemoveColumn)}
|
||||||
|
hideChevron
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
{...headerProps}
|
||||||
|
className="relative grid w-full grid-flow-col items-center justify-between p-2"
|
||||||
|
>
|
||||||
|
{column.render('Header')}
|
||||||
|
|
||||||
|
{allowSort && (
|
||||||
|
<Box component="span" sx={{ color: 'text.primary' }}>
|
||||||
|
{column.isSorted && !column.isSortedDesc && (
|
||||||
|
<ArrowUpIcon className="h-3 w-3" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{column.isSorted && column.isSortedDesc && (
|
||||||
|
<ArrowDownIcon className="h-3 w-3" />
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{allowResize && !column.disableResizing && (
|
||||||
|
<span
|
||||||
|
{...column.getResizerProps({
|
||||||
|
onClick: (event: Event) => event.stopPropagation(),
|
||||||
|
})}
|
||||||
|
className="absolute -right-0.5 bottom-0 top-0 z-10 h-full w-1.5 group-hover:bg-slate-900 group-hover:bg-opacity-20 group-active:bg-slate-900 group-active:bg-opacity-20 motion-safe:transition-colors"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Dropdown.Trigger>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as DataGridHeaderButton } from './DataGridHeaderButton';
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import type { CommonDataGridCellProps } from '@/components/dataGrid/DataGridCell';
|
|
||||||
import { useDataGridCell } from '@/components/dataGrid/DataGridCell';
|
|
||||||
import { Input, inputClasses } from '@/components/ui/v2/Input';
|
import { Input, inputClasses } from '@/components/ui/v2/Input';
|
||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
|
import {
|
||||||
|
useDataGridCell,
|
||||||
|
type CommonDataGridCellProps,
|
||||||
|
} from '@/features/orgs/projects/storage/dataGrid/components/DataGridCell';
|
||||||
import type { ChangeEvent, KeyboardEvent } from 'react';
|
import type { ChangeEvent, KeyboardEvent } from 'react';
|
||||||
|
|
||||||
export type DataGridIntegerCellProps<TData extends object> =
|
export type DataGridIntegerCellProps<TData extends object> =
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import { VideoPreviewIcon } from '@/components/ui/v2/icons/VideoPreviewIcon';
|
|||||||
import { XIcon } from '@/components/ui/v2/icons/XIcon';
|
import { XIcon } from '@/components/ui/v2/icons/XIcon';
|
||||||
import { useAppClient } from '@/features/orgs/projects/hooks/useAppClient';
|
import { useAppClient } from '@/features/orgs/projects/hooks/useAppClient';
|
||||||
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||||
|
import { usePreviewToggle } from '@/features/orgs/projects/storage/dataGrid/hooks/usePreviewToggle';
|
||||||
|
import { useSSRLocalStorage } from '@/hooks/useSSRLocalStorage';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { useEffect, useReducer, useState } from 'react';
|
import { useEffect, useReducer, useState } from 'react';
|
||||||
@@ -46,11 +48,16 @@ function useBlob({
|
|||||||
const [objectUrl, setObjectUrl] = useState<string>();
|
const [objectUrl, setObjectUrl] = useState<string>();
|
||||||
const [error, setError] = useState<Error>();
|
const [error, setError] = useState<Error>();
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [preview] = useSSRLocalStorage('preview', true);
|
||||||
|
|
||||||
// This side-effect fetches the blob of the file from the server and sets the
|
// This side-effect fetches the blob of the file from the server and sets the
|
||||||
// relevant `objectUrl` state. Abort controller is reponsible for cancelling
|
// relevant `objectUrl` state. Abort controller is reponsible for cancelling
|
||||||
// the fetch if the component is unmounted.
|
// the fetch if the component is unmounted.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!preview) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
|
|
||||||
async function generateOptimizedObjectUrl() {
|
async function generateOptimizedObjectUrl() {
|
||||||
@@ -104,7 +111,7 @@ function useBlob({
|
|||||||
generateObjectUrl();
|
generateObjectUrl();
|
||||||
|
|
||||||
return () => abortController.abort();
|
return () => abortController.abort();
|
||||||
}, [blob, fetchBlob, objectUrl, mimeType]);
|
}, [blob, fetchBlob, objectUrl, mimeType, preview]);
|
||||||
|
|
||||||
return { objectUrl, error, loading };
|
return { objectUrl, error, loading };
|
||||||
}
|
}
|
||||||
@@ -168,8 +175,13 @@ export default function DataGridPreviewCell<TData extends object>({
|
|||||||
}: DataGridPreviewCellProps<TData>) {
|
}: DataGridPreviewCellProps<TData>) {
|
||||||
const { project } = useProject();
|
const { project } = useProject();
|
||||||
const appClient = useAppClient();
|
const appClient = useAppClient();
|
||||||
const { objectUrl, loading, error } = useBlob({ fetchBlob, blob, mimeType });
|
const { objectUrl, loading, error } = useBlob({
|
||||||
|
fetchBlob,
|
||||||
|
blob,
|
||||||
|
mimeType,
|
||||||
|
});
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
const { previewEnabled } = usePreviewToggle();
|
||||||
|
|
||||||
const [
|
const [
|
||||||
{ loading: previewLoading, error: previewError, data: previewUrl },
|
{ loading: previewLoading, error: previewError, data: previewUrl },
|
||||||
@@ -365,7 +377,7 @@ export default function DataGridPreviewCell<TData extends object>({
|
|||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<div className="flex h-full w-full justify-center">
|
<div className="flex h-full w-full justify-center">
|
||||||
{previewableImages.includes(mimeType) && objectUrl && (
|
{previewEnabled && previewableImages.includes(mimeType) && objectUrl ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
aria-label={alt}
|
aria-label={alt}
|
||||||
@@ -381,9 +393,11 @@ export default function DataGridPreviewCell<TData extends object>({
|
|||||||
/>
|
/>
|
||||||
</picture>
|
</picture>
|
||||||
</button>
|
</button>
|
||||||
)}
|
) : null}
|
||||||
|
|
||||||
{(!previewableImages.includes(mimeType) || !objectUrl) && (
|
{(!previewableImages.includes(mimeType) ||
|
||||||
|
!objectUrl ||
|
||||||
|
!previewEnabled) && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleOpenPreview}
|
onClick={handleOpenPreview}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import type { CommonDataGridCellProps } from '@/components/dataGrid/DataGridCell';
|
|
||||||
import { useDataGridCell } from '@/components/dataGrid/DataGridCell';
|
|
||||||
import { Button } from '@/components/ui/v2/Button';
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
|
import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
|
||||||
import { Input, inputClasses } from '@/components/ui/v2/Input';
|
import { Input, inputClasses } from '@/components/ui/v2/Input';
|
||||||
import { Text } from '@/components/ui/v2/Text';
|
import { Text } from '@/components/ui/v2/Text';
|
||||||
|
import {
|
||||||
|
useDataGridCell,
|
||||||
|
type CommonDataGridCellProps,
|
||||||
|
} from '@/features/orgs/projects/storage/dataGrid/components/DataGridCell';
|
||||||
import { copy } from '@/utils/copy';
|
import { copy } from '@/utils/copy';
|
||||||
import type { ChangeEvent, KeyboardEvent, Ref } from 'react';
|
import type { ChangeEvent, KeyboardEvent, Ref } from 'react';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { FilePreviewIcon } from '@/components/ui/v2/icons/FilePreviewIcon';
|
|||||||
import { useAppClient } from '@/features/orgs/projects/hooks/useAppClient';
|
import { useAppClient } from '@/features/orgs/projects/hooks/useAppClient';
|
||||||
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||||
import { FilesDataGridControls } from '@/features/orgs/projects/storage/dataGrid/components/FilesDataGridControls';
|
import { FilesDataGridControls } from '@/features/orgs/projects/storage/dataGrid/components/FilesDataGridControls';
|
||||||
|
import { PreviewHeader } from '@/features/orgs/projects/storage/dataGrid/components/PreviewHeader';
|
||||||
import { useBuckets } from '@/features/orgs/projects/storage/dataGrid/hooks/useBuckets';
|
import { useBuckets } from '@/features/orgs/projects/storage/dataGrid/hooks/useBuckets';
|
||||||
import { useFiles } from '@/features/orgs/projects/storage/dataGrid/hooks/useFiles';
|
import { useFiles } from '@/features/orgs/projects/storage/dataGrid/hooks/useFiles';
|
||||||
import { useFilesAggregate } from '@/features/orgs/projects/storage/dataGrid/hooks/useFilesAggregate';
|
import { useFilesAggregate } from '@/features/orgs/projects/storage/dataGrid/hooks/useFilesAggregate';
|
||||||
@@ -112,7 +113,8 @@ export default function FilesDataGrid(props: FilesDataGridProps) {
|
|||||||
const memoizedColumns: Column<StoredFile>[] = useMemo(
|
const memoizedColumns: Column<StoredFile>[] = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
Header: 'Preview',
|
id: 'preview',
|
||||||
|
Header: PreviewHeader,
|
||||||
accessor: 'preview',
|
accessor: 'preview',
|
||||||
Cell: (cellProps) =>
|
Cell: (cellProps) =>
|
||||||
DataGridPreviewCell({
|
DataGridPreviewCell({
|
||||||
@@ -121,8 +123,8 @@ export default function FilesDataGrid(props: FilesDataGridProps) {
|
|||||||
<FilePreviewIcon className="h-5 w-5 fill-current" />
|
<FilePreviewIcon className="h-5 w-5 fill-current" />
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
minWidth: 80,
|
minWidth: 120,
|
||||||
width: 80,
|
width: 120,
|
||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
disableResizing: true,
|
disableResizing: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
import { useDialog } from '@/components/common/DialogProvider';
|
import { useDialog } from '@/components/common/DialogProvider';
|
||||||
import { useDataGridConfig } from '@/components/dataGrid/DataGridConfigProvider';
|
|
||||||
import type { DataGridPaginationProps } from '@/components/dataGrid/DataGridPagination';
|
|
||||||
import { DataGridPagination } from '@/components/dataGrid/DataGridPagination';
|
|
||||||
import type { BoxProps } from '@/components/ui/v2/Box';
|
import type { BoxProps } from '@/components/ui/v2/Box';
|
||||||
import { Box } from '@/components/ui/v2/Box';
|
import { Box } from '@/components/ui/v2/Box';
|
||||||
import { Button } from '@/components/ui/v2/Button';
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
@@ -10,6 +7,9 @@ import type { InputProps } from '@/components/ui/v2/Input';
|
|||||||
import { Input } from '@/components/ui/v2/Input';
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
import { useAppClient } from '@/features/orgs/projects/hooks/useAppClient';
|
import { useAppClient } from '@/features/orgs/projects/hooks/useAppClient';
|
||||||
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||||
|
import { useDataGridConfig } from '@/features/orgs/projects/storage/dataGrid/components/DataGridConfigProvider';
|
||||||
|
import type { DataGridPaginationProps } from '@/features/orgs/projects/storage/dataGrid/components/DataGridPagination';
|
||||||
|
import { DataGridPagination } from '@/features/orgs/projects/storage/dataGrid/components/DataGridPagination';
|
||||||
import type { FileUploadButtonProps } from '@/features/orgs/projects/storage/dataGrid/components/FileUploadButton';
|
import type { FileUploadButtonProps } from '@/features/orgs/projects/storage/dataGrid/components/FileUploadButton';
|
||||||
import { FileUploadButton } from '@/features/orgs/projects/storage/dataGrid/components/FileUploadButton';
|
import { FileUploadButton } from '@/features/orgs/projects/storage/dataGrid/components/FileUploadButton';
|
||||||
import type { Files } from '@/utils/__generated__/graphql';
|
import type { Files } from '@/utils/__generated__/graphql';
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Switch } from '@/components/ui/v2/Switch';
|
||||||
|
import { usePreviewToggle } from '@/features/orgs/projects/storage/dataGrid/hooks/usePreviewToggle';
|
||||||
|
import { type ChangeEvent } from 'react';
|
||||||
|
|
||||||
|
export default function PreviewHeader() {
|
||||||
|
const { previewEnabled, setPreviewEnabled } = usePreviewToggle();
|
||||||
|
|
||||||
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setPreviewEnabled(e.target.checked);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row items-center gap-2 p-2">
|
||||||
|
Preview
|
||||||
|
<Switch
|
||||||
|
className="self-center"
|
||||||
|
checked={previewEnabled}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as PreviewHeader } from './PreviewHeader';
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as usePreviewToggle } from './usePreviewToggle';
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||||
|
import { useSSRLocalStorage } from '@/hooks/useSSRLocalStorage';
|
||||||
|
|
||||||
|
// Store {projectId: previewEnabled, projectId2: previewEnabled, ...}
|
||||||
|
type PreviewLocalStorage = {
|
||||||
|
[key: string]: boolean | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function usePreviewToggle() {
|
||||||
|
const [preview, setPreview] = useSSRLocalStorage<PreviewLocalStorage>(
|
||||||
|
'preview',
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
const { project } = useProject();
|
||||||
|
|
||||||
|
// Default to previewEnabled true if not set
|
||||||
|
const previewEnabled = preview[project?.id] ?? true;
|
||||||
|
|
||||||
|
const setPreviewEnabled = (value: boolean) => {
|
||||||
|
const newPreview = { ...preview };
|
||||||
|
newPreview[project?.id] = value;
|
||||||
|
setPreview(newPreview);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
previewEnabled,
|
||||||
|
setPreviewEnabled,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { default as useIsHealthy } from './useIsHealthy';
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
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 { useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether or not the app is healthy.
|
|
||||||
*/
|
|
||||||
export default function useIsHealthy() {
|
|
||||||
const isPlatform = useIsPlatform();
|
|
||||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
|
||||||
|
|
||||||
const appUrl = generateAppServiceUrl(
|
|
||||||
currentProject?.subdomain,
|
|
||||||
currentProject?.region,
|
|
||||||
'auth',
|
|
||||||
);
|
|
||||||
|
|
||||||
const { failureCount, status } = useQuery(
|
|
||||||
['/healthz'],
|
|
||||||
() => fetch(`${appUrl}/healthz`),
|
|
||||||
{
|
|
||||||
enabled: !isPlatform && !!currentProject,
|
|
||||||
retry: true,
|
|
||||||
retryDelay: 5000,
|
|
||||||
cacheTime: 0,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return isPlatform || (status === 'success' && failureCount === 0);
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,35 @@
|
|||||||
|
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
|
||||||
|
import { useCurrentOrg } from '@/features/orgs/projects/hooks/useCurrentOrg';
|
||||||
|
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redirects to 404 page if either currentWorkspace/currentProject resolves to undefined.
|
* Redirects to 404 page if either currentWorkspace/currentProject resolves to undefined
|
||||||
|
* or if the current pathname is not a valid organization/project.
|
||||||
|
* Not applicable if running dashboard with local Nhost backend.
|
||||||
*/
|
*/
|
||||||
export default function useNotFoundRedirect() {
|
export default function useNotFoundRedirect() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const {
|
const {
|
||||||
query: { orgSlug, workspaceSlug, appSubdomain, updating, appSlug },
|
query: {
|
||||||
|
orgSlug: urlOrgSlug,
|
||||||
|
workspaceSlug: urlWorkspaceSlug,
|
||||||
|
appSubdomain: urlAppSubdomain,
|
||||||
|
updating,
|
||||||
|
appSlug: urlAppSlug,
|
||||||
|
},
|
||||||
isReady,
|
isReady,
|
||||||
} = router;
|
} = router;
|
||||||
|
|
||||||
|
const { project, loading: projectLoading } = useProject();
|
||||||
|
const isPlatform = useIsPlatform();
|
||||||
|
const { org, loading: orgLoading } = useCurrentOrg();
|
||||||
|
|
||||||
|
const { subdomain: projectSubdomain } = project || {};
|
||||||
|
const { slug: currentOrgSlug } = org || {};
|
||||||
|
|
||||||
const { currentProject, currentWorkspace, loading } =
|
const { currentProject, currentWorkspace, loading } =
|
||||||
useCurrentWorkspaceAndProject();
|
useCurrentWorkspaceAndProject();
|
||||||
|
|
||||||
@@ -23,6 +41,10 @@ export default function useNotFoundRedirect() {
|
|||||||
!isReady ||
|
!isReady ||
|
||||||
// If the current workspace and project are not loaded, we don't want to redirect to 404
|
// If the current workspace and project are not loaded, we don't want to redirect to 404
|
||||||
loading ||
|
loading ||
|
||||||
|
// If the project is loading, we don't want to redirect to 404
|
||||||
|
projectLoading ||
|
||||||
|
// If the org is loading, we don't want to redirect to 404
|
||||||
|
orgLoading ||
|
||||||
// If we're already on the 404 page, we don't want to redirect to 404
|
// If we're already on the 404 page, we don't want to redirect to 404
|
||||||
router.pathname === '/404' ||
|
router.pathname === '/404' ||
|
||||||
router.pathname === '/' ||
|
router.pathname === '/' ||
|
||||||
@@ -31,12 +53,14 @@ export default function useNotFoundRedirect() {
|
|||||||
router.pathname === '/run-one-click-install' ||
|
router.pathname === '/run-one-click-install' ||
|
||||||
router.pathname.includes('/orgs/_') ||
|
router.pathname.includes('/orgs/_') ||
|
||||||
router.pathname.includes('/orgs/_/projects/_') ||
|
router.pathname.includes('/orgs/_/projects/_') ||
|
||||||
orgSlug ||
|
(!isPlatform &&
|
||||||
(orgSlug && appSubdomain) ||
|
router.pathname.includes('/orgs/[orgSlug]/projects/[appSubdomain]')) ||
|
||||||
|
(urlOrgSlug === currentOrgSlug && !urlAppSubdomain) ||
|
||||||
|
(urlOrgSlug === currentOrgSlug && urlAppSubdomain === projectSubdomain) ||
|
||||||
// If we are on a valid workspace and project, we don't want to redirect to 404
|
// If we are on a valid workspace and project, we don't want to redirect to 404
|
||||||
(workspaceSlug && currentWorkspace && appSlug && currentProject) ||
|
(urlWorkspaceSlug && currentWorkspace && urlAppSlug && currentProject) ||
|
||||||
// If we are on a valid workspace and no project is selected, we don't want to redirect to 404
|
// If we are on a valid workspace and no project is selected, we don't want to redirect to 404
|
||||||
(workspaceSlug && currentWorkspace && !appSlug && !currentProject)
|
(urlWorkspaceSlug && currentWorkspace && !urlAppSlug && !currentProject)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -47,11 +71,16 @@ export default function useNotFoundRedirect() {
|
|||||||
currentWorkspace,
|
currentWorkspace,
|
||||||
isReady,
|
isReady,
|
||||||
loading,
|
loading,
|
||||||
appSubdomain,
|
urlAppSubdomain,
|
||||||
appSlug,
|
urlAppSlug,
|
||||||
router,
|
router,
|
||||||
updating,
|
updating,
|
||||||
workspaceSlug,
|
projectLoading,
|
||||||
orgSlug,
|
orgLoading,
|
||||||
|
currentOrgSlug,
|
||||||
|
projectSubdomain,
|
||||||
|
urlWorkspaceSlug,
|
||||||
|
urlOrgSlug,
|
||||||
|
isPlatform,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import type { ReactElement } from 'react';
|
|||||||
|
|
||||||
function BackupsContent() {
|
function BackupsContent() {
|
||||||
return (
|
return (
|
||||||
<div className="grid w-full grid-flow-row gap-6 mt-6">
|
<div className="mt-6 grid w-full grid-flow-row gap-6">
|
||||||
<div>
|
<div>
|
||||||
<Text className="font-medium">Database</Text>
|
<Text className="font-medium">Database</Text>
|
||||||
<Text color="secondary">
|
<Text color="secondary">
|
||||||
@@ -27,12 +27,13 @@ function BackupsContent() {
|
|||||||
|
|
||||||
export default function BackupsPage() {
|
export default function BackupsPage() {
|
||||||
const { currentOrg: org, loading } = useOrgs();
|
const { currentOrg: org, loading } = useOrgs();
|
||||||
const isPlanFree = org.plan.isFree;
|
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <ActivityIndicator label="Loading project..." delay={1000} />;
|
return <ActivityIndicator label="Loading project..." delay={1000} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isPlanFree = org.plan.isFree;
|
||||||
|
|
||||||
if (isPlanFree) {
|
if (isPlanFree) {
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
@@ -44,20 +45,19 @@ export default function BackupsPage() {
|
|||||||
description=""
|
description=""
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className="grid max-w-5xl grid-flow-row bg-transparent gap-y-6">
|
<Container className="grid max-w-5xl grid-flow-row gap-y-6 bg-transparent">
|
||||||
<div className="grid justify-between grid-flow-col gap-2">
|
<div className="grid grid-flow-col justify-between gap-2">
|
||||||
<Text className="text-2xl font-medium" variant="h1">
|
<Text className="text-2xl font-medium" variant="h1">
|
||||||
Backups
|
Backups
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Chip
|
<Chip
|
||||||
color={org?.plan.isFree ? 'default' : 'success'}
|
color={isPlanFree ? 'default' : 'success'}
|
||||||
label={org?.plan.isFree ? 'Off' : 'Live'}
|
label={isPlanFree ? 'Off' : 'Live'}
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import {
|
|||||||
} from '@/features/orgs/projects/logs/components/LogsHeader';
|
} from '@/features/orgs/projects/logs/components/LogsHeader';
|
||||||
import { AvailableLogsService } from '@/features/orgs/projects/logs/utils/constants/services';
|
import { AvailableLogsService } from '@/features/orgs/projects/logs/utils/constants/services';
|
||||||
import { useRemoteApplicationGQLClientWithSubscriptions } from '@/hooks/useRemoteApplicationGQLClientWithSubscriptions';
|
import { useRemoteApplicationGQLClientWithSubscriptions } from '@/hooks/useRemoteApplicationGQLClientWithSubscriptions';
|
||||||
import { MINUTES_TO_DECREASE_FROM_CURRENT_DATE } from '@/utils/constants/common';
|
|
||||||
import {
|
import {
|
||||||
GetLogsSubscriptionDocument,
|
GetLogsSubscriptionDocument,
|
||||||
useGetProjectLogsQuery,
|
useGetProjectLogsQuery,
|
||||||
} from '@/utils/__generated__/graphql';
|
} from '@/utils/__generated__/graphql';
|
||||||
|
import { MINUTES_TO_DECREASE_FROM_CURRENT_DATE } from '@/utils/constants/common';
|
||||||
import { subMinutes } from 'date-fns';
|
import { subMinutes } from 'date-fns';
|
||||||
import {
|
import {
|
||||||
useCallback,
|
useCallback,
|
||||||
@@ -45,7 +45,7 @@ export default function LogsPage() {
|
|||||||
|
|
||||||
const { data, error, subscribeToMore, client, loading, refetch } =
|
const { data, error, subscribeToMore, client, loading, refetch } =
|
||||||
useGetProjectLogsQuery({
|
useGetProjectLogsQuery({
|
||||||
variables: { appID: project.id, ...filters },
|
variables: { appID: project?.id, ...filters },
|
||||||
client: clientWithSplit,
|
client: clientWithSplit,
|
||||||
fetchPolicy: 'cache-and-network',
|
fetchPolicy: 'cache-and-network',
|
||||||
notifyOnNetworkStatusChange: true,
|
notifyOnNetworkStatusChange: true,
|
||||||
@@ -56,7 +56,7 @@ export default function LogsPage() {
|
|||||||
subscribeToMore({
|
subscribeToMore({
|
||||||
document: GetLogsSubscriptionDocument,
|
document: GetLogsSubscriptionDocument,
|
||||||
variables: {
|
variables: {
|
||||||
appID: project.id,
|
appID: project?.id,
|
||||||
service: filters.service,
|
service: filters.service,
|
||||||
from: filters.from,
|
from: filters.from,
|
||||||
regexFilter: filters.regexFilter,
|
regexFilter: filters.regexFilter,
|
||||||
@@ -98,7 +98,7 @@ export default function LogsPage() {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[subscribeToMore, project.id, filters],
|
[subscribeToMore, project?.id, filters],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -133,7 +133,7 @@ export default function LogsPage() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full h-full">
|
<div className="flex h-full w-full flex-col">
|
||||||
<RetryableErrorBoundary>
|
<RetryableErrorBoundary>
|
||||||
<LogsHeader
|
<LogsHeader
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
|||||||
@@ -22,18 +22,18 @@ import { useLocalMimirClient } from '@/features/orgs/projects/hooks/useLocalMimi
|
|||||||
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
import { useProject } from '@/features/orgs/projects/hooks/useProject';
|
||||||
|
|
||||||
export default function SettingsAuthenticationPage() {
|
export default function SettingsAuthenticationPage() {
|
||||||
const { project } = useProject();
|
const { project, loading: loadingProject } = useProject();
|
||||||
const isPlatform = useIsPlatform();
|
const isPlatform = useIsPlatform();
|
||||||
const localMimirClient = useLocalMimirClient();
|
const localMimirClient = useLocalMimirClient();
|
||||||
|
|
||||||
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
const { data, loading, error } = useGetAuthenticationSettingsQuery({
|
||||||
variables: { appId: project?.id },
|
variables: { appId: project?.id },
|
||||||
fetchPolicy: 'cache-and-network',
|
fetchPolicy: 'cache-and-network',
|
||||||
skip: !project,
|
skip: !project?.id,
|
||||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!data && loading) {
|
if (!data || loadingProject || loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
delay={1000}
|
delay={1000}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { UpgradeToProBanner } from '@/components/common/UpgradeToProBanner';
|
import { UpgradeToProBanner } from '@/components/common/UpgradeToProBanner';
|
||||||
import { Container } from '@/components/layout/Container';
|
import { Container } from '@/components/layout/Container';
|
||||||
|
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||||
import { Box } from '@/components/ui/v2/Box';
|
import { Box } from '@/components/ui/v2/Box';
|
||||||
import { ArrowSquareOutIcon } from '@/components/ui/v2/icons/ArrowSquareOutIcon';
|
import { ArrowSquareOutIcon } from '@/components/ui/v2/icons/ArrowSquareOutIcon';
|
||||||
import { Link } from '@/components/ui/v2/Link';
|
import { Link } from '@/components/ui/v2/Link';
|
||||||
@@ -15,7 +16,11 @@ import { useCurrentOrg } from '@/features/orgs/projects/hooks/useCurrentOrg';
|
|||||||
import { type ReactElement } from 'react';
|
import { type ReactElement } from 'react';
|
||||||
|
|
||||||
export default function CustomDomains() {
|
export default function CustomDomains() {
|
||||||
const { org } = useCurrentOrg();
|
const { org, loading: loadingOrg } = useCurrentOrg();
|
||||||
|
|
||||||
|
if (loadingOrg) {
|
||||||
|
return <ActivityIndicator delay={1000} label="Loading project..." />;
|
||||||
|
}
|
||||||
|
|
||||||
if (org?.plan?.isFree) {
|
if (org?.plan?.isFree) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -15,15 +15,15 @@ import type { ReactElement } from 'react';
|
|||||||
export default function DatabaseSettingsPage() {
|
export default function DatabaseSettingsPage() {
|
||||||
const isPlatform = useIsPlatform();
|
const isPlatform = useIsPlatform();
|
||||||
const localMimirClient = useLocalMimirClient();
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { project } = useProject();
|
const { project, loading: loadingProject } = useProject();
|
||||||
|
|
||||||
const { loading, error } = useGetPostgresSettingsQuery({
|
const { loading, error } = useGetPostgresSettingsQuery({
|
||||||
variables: { appId: project?.id },
|
variables: { appId: project?.id },
|
||||||
skip: !project,
|
skip: !project?.id,
|
||||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (loading) {
|
if (loadingProject || loading) {
|
||||||
return (
|
return (
|
||||||
<ActivityIndicator
|
<ActivityIndicator
|
||||||
delay={1000}
|
delay={1000}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useUI } from '@/components/common/UIProvider';
|
|||||||
import { Form } from '@/components/form/Form';
|
import { Form } from '@/components/form/Form';
|
||||||
import { Container } from '@/components/layout/Container';
|
import { Container } from '@/components/layout/Container';
|
||||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { LoadingScreen } from '@/components/presentational/LoadingScreen';
|
||||||
import { Alert } from '@/components/ui/v2/Alert';
|
import { Alert } from '@/components/ui/v2/Alert';
|
||||||
import { Input } from '@/components/ui/v2/Input';
|
import { Input } from '@/components/ui/v2/Input';
|
||||||
import { Link } from '@/components/ui/v2/Link';
|
import { Link } from '@/components/ui/v2/Link';
|
||||||
@@ -29,7 +29,7 @@ import { ApplicationStatus } from '@/types/application';
|
|||||||
import { slugifyString } from '@/utils/helpers';
|
import { slugifyString } from '@/utils/helpers';
|
||||||
import { yupResolver } from '@hookform/resolvers/yup';
|
import { yupResolver } from '@hookform/resolvers/yup';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useMemo, type ReactElement } from 'react';
|
import { useEffect, useMemo, type ReactElement } from 'react';
|
||||||
import { FormProvider, useForm } from 'react-hook-form';
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
@@ -96,6 +96,14 @@ export default function SettingsGeneralPage() {
|
|||||||
|
|
||||||
const { register, formState } = form;
|
const { register, formState } = form;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading) {
|
||||||
|
form.reset({
|
||||||
|
name: project?.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [loading, project?.name, form]);
|
||||||
|
|
||||||
async function handleProjectNameChange(data: ProjectNameValidationSchema) {
|
async function handleProjectNameChange(data: ProjectNameValidationSchema) {
|
||||||
const newProjectSlug = slugifyString(data.name);
|
const newProjectSlug = slugifyString(data.name);
|
||||||
|
|
||||||
@@ -186,7 +194,7 @@ export default function SettingsGeneralPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <ActivityIndicator label="Loading project..." />;
|
return <LoadingScreen />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Container } from '@/components/layout/Container';
|
import { Container } from '@/components/layout/Container';
|
||||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
import { LoadingScreen } from '@/components/presentational/LoadingScreen';
|
||||||
import { ProjectLayout } from '@/features/orgs/layout/ProjectLayout';
|
import { ProjectLayout } from '@/features/orgs/layout/ProjectLayout';
|
||||||
import { SettingsLayout } from '@/features/orgs/layout/SettingsLayout';
|
import { SettingsLayout } from '@/features/orgs/layout/SettingsLayout';
|
||||||
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
|
import { useIsPlatform } from '@/features/orgs/projects/common/hooks/useIsPlatform';
|
||||||
@@ -12,21 +12,17 @@ import type { ReactElement } from 'react';
|
|||||||
export default function MetricsSettingsPage() {
|
export default function MetricsSettingsPage() {
|
||||||
const isPlatform = useIsPlatform();
|
const isPlatform = useIsPlatform();
|
||||||
const localMimirClient = useLocalMimirClient();
|
const localMimirClient = useLocalMimirClient();
|
||||||
const { project } = useProject();
|
const { project, loading: loadingProject } = useProject();
|
||||||
|
|
||||||
const { loading, error } = useGetObservabilitySettingsQuery({
|
const { loading: loadingObservabilitySettings, error } =
|
||||||
variables: { appId: project?.id },
|
useGetObservabilitySettingsQuery({
|
||||||
...(!isPlatform ? { client: localMimirClient } : {}),
|
variables: { appId: project?.id },
|
||||||
});
|
...(!isPlatform ? { client: localMimirClient } : {}),
|
||||||
|
skip: !project?.id,
|
||||||
|
});
|
||||||
|
|
||||||
if (loading) {
|
if (loadingProject || loadingObservabilitySettings) {
|
||||||
return (
|
return <LoadingScreen />;
|
||||||
<ActivityIndicator
|
|
||||||
delay={1000}
|
|
||||||
label="Loading Observability settings..."
|
|
||||||
className="justify-center"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -35,7 +31,7 @@ export default function MetricsSettingsPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
className="grid max-w-5xl grid-flow-row bg-transparent gap-y-6"
|
className="grid max-w-5xl grid-flow-row gap-y-6 bg-transparent"
|
||||||
rootClassName="bg-transparent"
|
rootClassName="bg-transparent"
|
||||||
>
|
>
|
||||||
<MetricsSettings />
|
<MetricsSettings />
|
||||||
|
|||||||
45
dashboard/src/utils/__generated__/graphql.ts
generated
45
dashboard/src/utils/__generated__/graphql.ts
generated
@@ -27682,6 +27682,13 @@ export type GetBackupPresignedUrlQueryVariables = Exact<{
|
|||||||
|
|
||||||
export type GetBackupPresignedUrlQuery = { __typename?: 'query_root', getBackupPresignedUrl: { __typename?: 'BackupPresignedURL', url: string, expiresAt: any } };
|
export type GetBackupPresignedUrlQuery = { __typename?: 'query_root', getBackupPresignedUrl: { __typename?: 'BackupPresignedURL', url: string, expiresAt: any } };
|
||||||
|
|
||||||
|
export type GetPersistentVolumesEncryptedQueryVariables = Exact<{
|
||||||
|
appId: Scalars['uuid'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type GetPersistentVolumesEncryptedQuery = { __typename?: 'query_root', systemConfig?: { __typename?: 'ConfigSystemConfig', persistentVolumesEncrypted?: boolean | null } | null };
|
||||||
|
|
||||||
export type GetJwtSecretsQueryVariables = Exact<{
|
export type GetJwtSecretsQueryVariables = Exact<{
|
||||||
appId: Scalars['uuid'];
|
appId: Scalars['uuid'];
|
||||||
}>;
|
}>;
|
||||||
@@ -29656,6 +29663,44 @@ export type GetBackupPresignedUrlQueryResult = Apollo.QueryResult<GetBackupPresi
|
|||||||
export function refetchGetBackupPresignedUrlQuery(variables: GetBackupPresignedUrlQueryVariables) {
|
export function refetchGetBackupPresignedUrlQuery(variables: GetBackupPresignedUrlQueryVariables) {
|
||||||
return { query: GetBackupPresignedUrlDocument, variables: variables }
|
return { query: GetBackupPresignedUrlDocument, variables: variables }
|
||||||
}
|
}
|
||||||
|
export const GetPersistentVolumesEncryptedDocument = gql`
|
||||||
|
query GetPersistentVolumesEncrypted($appId: uuid!) {
|
||||||
|
systemConfig(appID: $appId) {
|
||||||
|
persistentVolumesEncrypted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useGetPersistentVolumesEncryptedQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useGetPersistentVolumesEncryptedQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useGetPersistentVolumesEncryptedQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { data, loading, error } = useGetPersistentVolumesEncryptedQuery({
|
||||||
|
* variables: {
|
||||||
|
* appId: // value for 'appId'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useGetPersistentVolumesEncryptedQuery(baseOptions: Apollo.QueryHookOptions<GetPersistentVolumesEncryptedQuery, GetPersistentVolumesEncryptedQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<GetPersistentVolumesEncryptedQuery, GetPersistentVolumesEncryptedQueryVariables>(GetPersistentVolumesEncryptedDocument, options);
|
||||||
|
}
|
||||||
|
export function useGetPersistentVolumesEncryptedLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetPersistentVolumesEncryptedQuery, GetPersistentVolumesEncryptedQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<GetPersistentVolumesEncryptedQuery, GetPersistentVolumesEncryptedQueryVariables>(GetPersistentVolumesEncryptedDocument, options);
|
||||||
|
}
|
||||||
|
export type GetPersistentVolumesEncryptedQueryHookResult = ReturnType<typeof useGetPersistentVolumesEncryptedQuery>;
|
||||||
|
export type GetPersistentVolumesEncryptedLazyQueryHookResult = ReturnType<typeof useGetPersistentVolumesEncryptedLazyQuery>;
|
||||||
|
export type GetPersistentVolumesEncryptedQueryResult = Apollo.QueryResult<GetPersistentVolumesEncryptedQuery, GetPersistentVolumesEncryptedQueryVariables>;
|
||||||
|
export function refetchGetPersistentVolumesEncryptedQuery(variables: GetPersistentVolumesEncryptedQueryVariables) {
|
||||||
|
return { query: GetPersistentVolumesEncryptedDocument, variables: variables }
|
||||||
|
}
|
||||||
export const GetJwtSecretsDocument = gql`
|
export const GetJwtSecretsDocument = gql`
|
||||||
query GetJWTSecrets($appId: uuid!) {
|
query GetJWTSecrets($appId: uuid!) {
|
||||||
config(appID: $appId, resolve: false) {
|
config(appID: $appId, resolve: false) {
|
||||||
|
|||||||
@@ -1,5 +1,27 @@
|
|||||||
# @nhost/docs
|
# @nhost/docs
|
||||||
|
|
||||||
|
## 2.27.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 81cc9b3: chore: add missing images to permissions API
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- af34015: chore: add note about encryption at rest
|
||||||
|
- 1956ed2: chore: added pgmq extension to postgres docs
|
||||||
|
- 88919a3: chore: added support for nodejs22 to functions
|
||||||
|
|
||||||
|
## 2.26.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 04d2ce1: feat: add reference documentation for signin security key
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 1fa6cc4: chore: added docs for pg_jsonschema
|
||||||
|
|
||||||
## 2.25.0
|
## 2.25.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -113,22 +113,22 @@ GraphQL requests from unauthenticated users resolve permissions using the `publi
|
|||||||
|
|
||||||
## Insert Permissions
|
## Insert Permissions
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Here is a popular approach for insert permission for authenticated users.
|
Here is a popular approach for insert permission for authenticated users.
|
||||||
|
|
||||||
1. At the top of the page, click **"insert"** on the **"user"** role.
|
1. At the top of the page, click **"insert"** on the **"user"** role.
|
||||||
1. Select **"Without any checks"**.
|
1. Select **"Without any checks"**.
|
||||||
1. Select the columns you want to allow users to insert. In our example, we only mark `title`, because that's the only column that should be inserted by the user. The `id` is automatically generated by the database and `user_id` is set using a column preset.
|
1. Select the columns you want to allow users to insert. In our example, we do not mark `id` nor `user_id`, because they should not be inserted by the user. The `id` is automatically generated by the database and `user_id` is set using a column preset.
|
||||||
1. Under **Column presets**, set `user_id` to `x-hasura-user-id`. This way, every new record's `user_id` value is set to the ID of the user making the request.
|
1. Under **Column presets**, set `user_id` to `x-hasura-user-id`. This way, every new record's `user_id` value is set to the ID of the user making the request.
|
||||||
|
|
||||||
Now, authenticated users are allowed to insert posts. Users are allowed to add a title when inserting a post. The post's `id` is automatically generated by the database and the `user_id` is automatically set to the user's id using the `user_id = x-hasura-user-id` column preset.
|
Now, authenticated users are allowed to insert todos. Users are allowed to add a title when inserting a todo. The todo's `id` is automatically generated by the database and the `user_id` is automatically set to the user's id using the `user_id = x-hasura-user-id` column preset.
|
||||||
|
|
||||||
## Select, Update and Delete Permissions
|
## Select, Update and Delete Permissions
|
||||||
|
|
||||||
Select, update, and delete permissions usually follow the same pattern. Here's an example of how to add select permissions:
|
Select, update, and delete permissions usually follow the same pattern. Here's an example of how to add select permissions:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
One of the most common permission requirements is that authenticated users should only be able to read their own data. This is how to do that:
|
One of the most common permission requirements is that authenticated users should only be able to read their own data. This is how to do that:
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ In the table below you can find a list of available extensions with Nhost Postgr
|
|||||||
| pg_freespacemap | 1.2 | examine the free space map (FSM) |
|
| pg_freespacemap | 1.2 | examine the free space map (FSM) |
|
||||||
| pg_hashids | 1.3 | pg_hashids |
|
| pg_hashids | 1.3 | pg_hashids |
|
||||||
| pg_ivm | 1.9 | incremental view maintenance on PostgreSQL |
|
| pg_ivm | 1.9 | incremental view maintenance on PostgreSQL |
|
||||||
|
| pg_jsonschema | 0.3.3 | pg_jsonschema |
|
||||||
| pg_prewarm | 1.2 | prewarm relation data |
|
| pg_prewarm | 1.2 | prewarm relation data |
|
||||||
| pg_repack | 1.5.1 | Reorganize tables in PostgreSQL databases with minimal locks |
|
| pg_repack | 1.5.1 | Reorganize tables in PostgreSQL databases with minimal locks |
|
||||||
| pg_squeeze | 1.7 | A tool to remove unused space from a relation. |
|
| pg_squeeze | 1.7 | A tool to remove unused space from a relation. |
|
||||||
@@ -52,6 +53,7 @@ In the table below you can find a list of available extensions with Nhost Postgr
|
|||||||
| pg_trgm | 1.6 | text similarity measurement and index searching based on trigrams |
|
| pg_trgm | 1.6 | text similarity measurement and index searching based on trigrams |
|
||||||
| pg_visibility | 1.2 | examine the visibility map (VM) and page-level visibility info |
|
| pg_visibility | 1.2 | examine the visibility map (VM) and page-level visibility info |
|
||||||
| pgcrypto | 1.3 | cryptographic functions |
|
| pgcrypto | 1.3 | cryptographic functions |
|
||||||
|
| pgmq | 1.4.5 | A lightweight message queue. Like AWS SQS and RSMQ but on Postgres. |
|
||||||
| pgrowlocks | 1.2 | show row-level locking information |
|
| pgrowlocks | 1.2 | show row-level locking information |
|
||||||
| pgstattuple | 1.5 | show tuple-level statistics |
|
| pgstattuple | 1.5 | show tuple-level statistics |
|
||||||
| plpgsql | 1.0 | PL/pgSQL procedural language |
|
| plpgsql | 1.0 | PL/pgSQL procedural language |
|
||||||
@@ -75,7 +77,6 @@ In the table below you can find a list of available extensions with Nhost Postgr
|
|||||||
|
|
||||||
In addition, you can find more information about some of the extensions below
|
In addition, you can find more information about some of the extensions below
|
||||||
|
|
||||||
|
|
||||||
## hypopg
|
## hypopg
|
||||||
|
|
||||||
HypoPG is a PostgreSQL extension adding support for hypothetical indexes.
|
HypoPG is a PostgreSQL extension adding support for hypothetical indexes.
|
||||||
@@ -147,6 +148,30 @@ DROP EXTENSION ip4r;
|
|||||||
|
|
||||||
- [GitHub](https://github.com/RhodiumToad/ip4r)
|
- [GitHub](https://github.com/RhodiumToad/ip4r)
|
||||||
|
|
||||||
|
## pgmq
|
||||||
|
|
||||||
|
A lightweight message queue. Like AWS SQS and RSMQ but on Postgres.
|
||||||
|
|
||||||
|
### Managing
|
||||||
|
|
||||||
|
To install the extension you can create a migration with the following contents:
|
||||||
|
|
||||||
|
```sql SQL
|
||||||
|
SET ROLE postgres;
|
||||||
|
CREATE EXTENSION pgmq;
|
||||||
|
```
|
||||||
|
|
||||||
|
To uninstall it, you can use the following migration:
|
||||||
|
|
||||||
|
```sql SQL
|
||||||
|
SET ROLE postgres;
|
||||||
|
DROP EXTENSION pgmq;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Resources
|
||||||
|
|
||||||
|
- [GitHub](https://github.com/tembo-io/pgmq)
|
||||||
|
|
||||||
## postgis
|
## postgis
|
||||||
|
|
||||||
PostGIS extends the capabilities of the PostgreSQL relational database by adding support storing, indexing and querying geographic data.
|
PostGIS extends the capabilities of the PostgreSQL relational database by adding support storing, indexing and querying geographic data.
|
||||||
@@ -277,6 +302,30 @@ DROP EXTENSION pg_ivm;
|
|||||||
|
|
||||||
- [GitHub](https://github.com/sraoss/pg_ivm)
|
- [GitHub](https://github.com/sraoss/pg_ivm)
|
||||||
|
|
||||||
|
## pg_jsonschema
|
||||||
|
|
||||||
|
pg_jsonschema is a PostgreSQL extension adding support for JSON schema validation on json and jsonb data types.
|
||||||
|
|
||||||
|
### Managing
|
||||||
|
|
||||||
|
To install the extension you can create a migration with the following contents:
|
||||||
|
|
||||||
|
```sql SQL
|
||||||
|
SET ROLE postgres;
|
||||||
|
CREATE EXTENSION pg_jsonschema;
|
||||||
|
```
|
||||||
|
|
||||||
|
To uninstall it, you can use the following migration:
|
||||||
|
|
||||||
|
```sql SQL
|
||||||
|
SET ROLE postgres;
|
||||||
|
DROP EXTENSION pg_jsonschema;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Resources
|
||||||
|
|
||||||
|
- [GitHub](https://github.com/supabase/pg_jsonschema)
|
||||||
|
|
||||||
## pg_repack
|
## pg_repack
|
||||||
|
|
||||||
pg_repack is a PostgreSQL extension which lets you remove bloat from tables and indexes, and optionally restore the physical order of clustered indexes. Unlike CLUSTER and VACUUM FULL it works online, without holding an exclusive lock on the processed tables during processing. pg_repack is efficient to boot, with performance comparable to using CLUSTER directly.
|
pg_repack is a PostgreSQL extension which lets you remove bloat from tables and indexes, and optionally restore the physical order of clustered indexes. Unlike CLUSTER and VACUUM FULL it works online, without holding an exclusive lock on the processed tables during processing. pg_repack is efficient to boot, with performance comparable to using CLUSTER directly.
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ The following runtimes are supported:
|
|||||||
|
|
||||||
- [Node.js 18](https://nodejs.org)
|
- [Node.js 18](https://nodejs.org)
|
||||||
- [Node.js 20](https://nodejs.org)
|
- [Node.js 20](https://nodejs.org)
|
||||||
|
- [Node.js 22](https://nodejs.org)
|
||||||
|
|
||||||
To select your preferred runtime ensure the following configuration is present in your `nhost.toml` file:
|
To select your preferred runtime ensure the following configuration is present in your `nhost.toml` file:
|
||||||
|
|
||||||
@@ -27,6 +28,14 @@ version = 18
|
|||||||
```toml
|
```toml
|
||||||
[functions.node]
|
[functions.node]
|
||||||
version = 20
|
version = 20
|
||||||
|
```
|
||||||
|
|
||||||
|
</Tab>
|
||||||
|
<Tab title="Node.js 22">
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[functions.node]
|
||||||
|
version = 22
|
||||||
```
|
```
|
||||||
|
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|||||||
@@ -354,7 +354,8 @@
|
|||||||
"reference/javascript/auth/sign-in-email-otp",
|
"reference/javascript/auth/sign-in-email-otp",
|
||||||
"reference/javascript/auth/verify-email-otp",
|
"reference/javascript/auth/verify-email-otp",
|
||||||
"reference/javascript/auth/sign-in-id-token",
|
"reference/javascript/auth/sign-in-id-token",
|
||||||
"reference/javascript/auth/link-id-token"
|
"reference/javascript/auth/link-id-token",
|
||||||
|
"reference/javascript/auth/sign-in-security-key"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -440,7 +441,8 @@
|
|||||||
"reference/react/use-user-roles",
|
"reference/react/use-user-roles",
|
||||||
"reference/react/use-sign-in-email-otp",
|
"reference/react/use-sign-in-email-otp",
|
||||||
"reference/react/use-sign-in-id-token",
|
"reference/react/use-sign-in-id-token",
|
||||||
"reference/react/use-link-id-token"
|
"reference/react/use-link-id-token",
|
||||||
|
"reference/react/use-sign-in-security-key"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -490,7 +492,8 @@
|
|||||||
"reference/nextjs/use-user-roles",
|
"reference/nextjs/use-user-roles",
|
||||||
"reference/nextjs/use-sign-in-email-otp",
|
"reference/nextjs/use-sign-in-email-otp",
|
||||||
"reference/nextjs/use-sign-in-id-token",
|
"reference/nextjs/use-sign-in-id-token",
|
||||||
"reference/nextjs/use-link-id-token"
|
"reference/nextjs/use-link-id-token",
|
||||||
|
"reference/nextjs/use-sign-in-security-key"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -535,7 +538,8 @@
|
|||||||
"reference/vue/use-sign-up-email-security-key",
|
"reference/vue/use-sign-up-email-security-key",
|
||||||
"reference/vue/use-sign-in-email-otp",
|
"reference/vue/use-sign-in-email-otp",
|
||||||
"reference/vue/use-sign-in-id-token",
|
"reference/vue/use-sign-in-id-token",
|
||||||
"reference/vue/use-link-id-token"
|
"reference/vue/use-link-id-token",
|
||||||
|
"reference/vue/use-sign-in-security-key"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost/docs",
|
"name": "@nhost/docs",
|
||||||
"version": "2.25.0",
|
"version": "2.27.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "mintlify dev"
|
"start": "mintlify dev"
|
||||||
|
|||||||
@@ -97,3 +97,11 @@ This borrowing of resources is convenient in case of short and unexpected bursts
|
|||||||
## Disk Performance
|
## Disk Performance
|
||||||
|
|
||||||
By default disks are provisioned with a capacity for 3000 IOPS and 125 Mbps of throughput. If you need higher performance don't hesitate to contact us.
|
By default disks are provisioned with a capacity for 3000 IOPS and 125 Mbps of throughput. If you need higher performance don't hesitate to contact us.
|
||||||
|
|
||||||
|
## Encryption at Rest
|
||||||
|
|
||||||
|
All files uploaded to the [storage](/product/storage) service are encrypted at rest using AES-256 encryption. Similarly, any volumes provisioned for your [database](/product/database) and [Run services](/product/run) are also encrypted using AES-256.
|
||||||
|
|
||||||
|
<Warning>
|
||||||
|
Only volumes provisioned after December 2024 are encrypted with AES-256. You can verify your volume's encryption status by navigating to your project's settings -> Database -> Storage capacity. If your volume is not encrypted, you'll see a warning message indicating this. To enable encryption on your volume, you can pause and then unpause your project. This action will provision new volumes with encryption enabled.
|
||||||
|
</Warning>
|
||||||
|
|||||||
10
docs/reference/javascript/auth/sign-in-security-key.mdx
Normal file
10
docs/reference/javascript/auth/sign-in-security-key.mdx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
title: signInSecurityKey()
|
||||||
|
sidebarTitle: signInSecurityKey()
|
||||||
|
---
|
||||||
|
|
||||||
|
Use `nhost.auth.signInSecurityKey` to sign in a user with a security key using the WebAuthn API
|
||||||
|
|
||||||
|
```ts
|
||||||
|
nhost.auth.signInSecurityKey()
|
||||||
|
```
|
||||||
10
docs/reference/javascript/nhost-js/sign-in-security-key.mdx
Normal file
10
docs/reference/javascript/nhost-js/sign-in-security-key.mdx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
title: signInSecurityKey()
|
||||||
|
sidebarTitle: signInSecurityKey()
|
||||||
|
---
|
||||||
|
|
||||||
|
Use `nhost.auth.signInSecurityKey` to sign in a user with a security key using the WebAuthn API
|
||||||
|
|
||||||
|
```ts
|
||||||
|
nhost.auth.signInSecurityKey()
|
||||||
|
```
|
||||||
25
docs/reference/nextjs/use-sign-in-security-key.mdx
Normal file
25
docs/reference/nextjs/use-sign-in-security-key.mdx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
title: useSignInSecurityKey()
|
||||||
|
sidebarTitle: useSignInSecurityKey()
|
||||||
|
---
|
||||||
|
|
||||||
|
Use the hook `useSignInSecurityKey` to sign in a user with a security key using the WebAuthn API.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const {
|
||||||
|
signInSecurityKey,
|
||||||
|
needsEmailVerification,
|
||||||
|
isLoading,
|
||||||
|
isSuccess,
|
||||||
|
isError,
|
||||||
|
error
|
||||||
|
} = useSignInSecurityKey()
|
||||||
|
|
||||||
|
console.log({ needsEmailVerification, isLoading, isSuccess, isError, error })
|
||||||
|
|
||||||
|
const handleFormSubmit = async (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
await signInSecurityKey()
|
||||||
|
}
|
||||||
|
```
|
||||||
25
docs/reference/react/use-sign-in-security-key.mdx
Normal file
25
docs/reference/react/use-sign-in-security-key.mdx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
title: useSignInSecurityKey()
|
||||||
|
sidebarTitle: useSignInSecurityKey()
|
||||||
|
---
|
||||||
|
|
||||||
|
Use the hook `useSignInSecurityKey` to sign in a user with a security key using the WebAuthn API.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const {
|
||||||
|
signInSecurityKey,
|
||||||
|
needsEmailVerification,
|
||||||
|
isLoading,
|
||||||
|
isSuccess,
|
||||||
|
isError,
|
||||||
|
error
|
||||||
|
} = useSignInSecurityKey()
|
||||||
|
|
||||||
|
console.log({ needsEmailVerification, isLoading, isSuccess, isError, error })
|
||||||
|
|
||||||
|
const handleFormSubmit = async (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
await signInSecurityKey()
|
||||||
|
}
|
||||||
|
```
|
||||||
25
docs/reference/vue/use-sign-in-security-key.mdx
Normal file
25
docs/reference/vue/use-sign-in-security-key.mdx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
title: useSignInSecurityKey()
|
||||||
|
sidebarTitle: useSignInSecurityKey()
|
||||||
|
---
|
||||||
|
|
||||||
|
Use the composable `useSignInSecurityKey` to sign in a user with a security key using the WebAuthn API
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const {
|
||||||
|
signInSecurityKey,
|
||||||
|
needsEmailVerification,
|
||||||
|
isLoading,
|
||||||
|
isSuccess,
|
||||||
|
isError,
|
||||||
|
error
|
||||||
|
} = useSignInSecurityKey()
|
||||||
|
|
||||||
|
console.log({ needsEmailVerification, isLoading, isSuccess, isError, error })
|
||||||
|
|
||||||
|
const handleFormSubmit = async (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
await signInSecurityKey()
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -1,5 +1,17 @@
|
|||||||
# @nhost-examples/cli
|
# @nhost-examples/cli
|
||||||
|
|
||||||
|
## 0.3.17
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/nhost-js@3.2.4
|
||||||
|
|
||||||
|
## 0.3.16
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/nhost-js@3.2.3
|
||||||
|
|
||||||
## 0.3.15
|
## 0.3.15
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/cli",
|
"name": "@nhost-examples/cli",
|
||||||
"version": "0.3.15",
|
"version": "0.3.17",
|
||||||
"main": "src/index.mjs",
|
"main": "src/index.mjs",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,5 +1,20 @@
|
|||||||
# @nhost-examples/codegen-react-apollo
|
# @nhost-examples/codegen-react-apollo
|
||||||
|
|
||||||
|
## 0.4.18
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react@3.9.1
|
||||||
|
- @nhost/react-apollo@16.0.1
|
||||||
|
|
||||||
|
## 0.4.17
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [04d2ce1]
|
||||||
|
- @nhost/react@3.9.0
|
||||||
|
- @nhost/react-apollo@16.0.0
|
||||||
|
|
||||||
## 0.4.16
|
## 0.4.16
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/codegen-react-apollo",
|
"name": "@nhost-examples/codegen-react-apollo",
|
||||||
"version": "0.4.16",
|
"version": "0.4.18",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"codegen": "graphql-codegen",
|
"codegen": "graphql-codegen",
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
# @nhost-examples/codegen-react-query
|
# @nhost-examples/codegen-react-query
|
||||||
|
|
||||||
|
## 0.4.18
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react@3.9.1
|
||||||
|
|
||||||
|
## 0.4.17
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [04d2ce1]
|
||||||
|
- @nhost/react@3.9.0
|
||||||
|
|
||||||
## 0.4.16
|
## 0.4.16
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/codegen-react-query",
|
"name": "@nhost-examples/codegen-react-query",
|
||||||
"version": "0.4.16",
|
"version": "0.4.18",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"codegen": "graphql-codegen",
|
"codegen": "graphql-codegen",
|
||||||
|
|||||||
@@ -1,5 +1,20 @@
|
|||||||
# @nhost-examples/react-urql
|
# @nhost-examples/react-urql
|
||||||
|
|
||||||
|
## 0.3.18
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react@3.9.1
|
||||||
|
- @nhost/react-urql@13.0.1
|
||||||
|
|
||||||
|
## 0.3.17
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [04d2ce1]
|
||||||
|
- @nhost/react@3.9.0
|
||||||
|
- @nhost/react-urql@13.0.0
|
||||||
|
|
||||||
## 0.3.16
|
## 0.3.16
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/codegen-react-urql",
|
"name": "@nhost-examples/codegen-react-urql",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.3.16",
|
"version": "0.3.18",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
# @nhost-examples/multi-tenant-one-to-many
|
# @nhost-examples/multi-tenant-one-to-many
|
||||||
|
|
||||||
|
## 2.2.18
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/nhost-js@3.2.4
|
||||||
|
|
||||||
|
## 2.2.17
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/nhost-js@3.2.3
|
||||||
|
|
||||||
## 2.2.16
|
## 2.2.16
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/multi-tenant-one-to-many",
|
"name": "@nhost-examples/multi-tenant-one-to-many",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.2.16",
|
"version": "2.2.18",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
|
|||||||
@@ -1,5 +1,22 @@
|
|||||||
# @nhost-examples/nextjs
|
# @nhost-examples/nextjs
|
||||||
|
|
||||||
|
## 0.4.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react@3.9.1
|
||||||
|
- @nhost/react-apollo@16.0.1
|
||||||
|
- @nhost/nextjs@2.2.2
|
||||||
|
|
||||||
|
## 0.4.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [04d2ce1]
|
||||||
|
- @nhost/react@3.9.0
|
||||||
|
- @nhost/react-apollo@16.0.0
|
||||||
|
- @nhost/nextjs@2.2.1
|
||||||
|
|
||||||
## 0.4.0
|
## 0.4.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/nextjs",
|
"name": "@nhost-examples/nextjs",
|
||||||
"version": "0.4.0",
|
"version": "0.4.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
# @nhost-examples/node-storage
|
# @nhost-examples/node-storage
|
||||||
|
|
||||||
|
## 0.2.17
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/nhost-js@3.2.4
|
||||||
|
|
||||||
|
## 0.2.16
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/nhost-js@3.2.3
|
||||||
|
|
||||||
## 0.2.15
|
## 0.2.15
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/node-storage",
|
"name": "@nhost-examples/node-storage",
|
||||||
"version": "0.2.15",
|
"version": "0.2.17",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "This is an example of how to use the Storage with Node.js",
|
"description": "This is an example of how to use the Storage with Node.js",
|
||||||
"main": "src/index.mjs",
|
"main": "src/index.mjs",
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
# @nhost-examples/nextjs-server-components
|
# @nhost-examples/nextjs-server-components
|
||||||
|
|
||||||
|
## 0.5.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/nhost-js@3.2.4
|
||||||
|
|
||||||
|
## 0.5.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/nhost-js@3.2.3
|
||||||
|
|
||||||
## 0.5.0
|
## 0.5.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/nextjs-server-components",
|
"name": "@nhost-examples/nextjs-server-components",
|
||||||
"version": "0.5.0",
|
"version": "0.5.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
|||||||
@@ -1,5 +1,24 @@
|
|||||||
# @nhost-examples/react-apollo
|
# @nhost-examples/react-apollo
|
||||||
|
|
||||||
|
## 1.2.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react@3.9.1
|
||||||
|
- @nhost/react-apollo@16.0.1
|
||||||
|
|
||||||
|
## 1.2.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 04d2ce1: feat: update signin components to use `useSignInSecuritykey` with user handle
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [04d2ce1]
|
||||||
|
- @nhost/react@3.9.0
|
||||||
|
- @nhost/react-apollo@16.0.0
|
||||||
|
|
||||||
## 1.1.2
|
## 1.1.2
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ httpPoolSize = 100
|
|||||||
version = 18
|
version = 18
|
||||||
|
|
||||||
[auth]
|
[auth]
|
||||||
version = '0.32.1'
|
version = '0.37.0-beta1'
|
||||||
|
|
||||||
[auth.elevatedPrivileges]
|
[auth.elevatedPrivileges]
|
||||||
mode = 'required'
|
mode = 'required'
|
||||||
@@ -47,6 +47,27 @@ disableNewUsers = false
|
|||||||
default = 'user'
|
default = 'user'
|
||||||
allowed = ['user', 'me']
|
allowed = ['user', 'me']
|
||||||
|
|
||||||
|
[auth.rateLimit]
|
||||||
|
[auth.rateLimit.emails]
|
||||||
|
limit = 100
|
||||||
|
interval = '1h'
|
||||||
|
|
||||||
|
[auth.rateLimit.sms]
|
||||||
|
limit = 100
|
||||||
|
interval = '1h'
|
||||||
|
|
||||||
|
[auth.rateLimit.bruteForce]
|
||||||
|
limit = 100
|
||||||
|
interval = '5m'
|
||||||
|
|
||||||
|
[auth.rateLimit.signups]
|
||||||
|
limit = 100
|
||||||
|
interval = '5m'
|
||||||
|
|
||||||
|
[auth.rateLimit.global]
|
||||||
|
limit = 1000
|
||||||
|
interval = '1m'
|
||||||
|
|
||||||
[auth.user.locale]
|
[auth.user.locale]
|
||||||
default = 'en'
|
default = 'en'
|
||||||
allowed = ['en']
|
allowed = ['en']
|
||||||
@@ -158,6 +179,14 @@ issuer = 'nhost'
|
|||||||
version = '16.2-20240718-1'
|
version = '16.2-20240718-1'
|
||||||
|
|
||||||
[provider]
|
[provider]
|
||||||
|
[provider.smtp]
|
||||||
|
host = "smtp.test.com"
|
||||||
|
method = "LOGIN"
|
||||||
|
password = "test123123"
|
||||||
|
port = 587
|
||||||
|
secure = false
|
||||||
|
sender = "test@nhost.io"
|
||||||
|
user = "test"
|
||||||
|
|
||||||
[storage]
|
[storage]
|
||||||
version = '0.6.1'
|
version = '0.6.1'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/react-apollo",
|
"name": "@nhost-examples/react-apollo",
|
||||||
"version": "1.1.2",
|
"version": "1.2.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,41 +1,21 @@
|
|||||||
import { zodResolver } from '@hookform/resolvers/zod'
|
|
||||||
import { useSignInEmailSecurityKey } from '@nhost/react'
|
|
||||||
import { ArrowLeft } from 'lucide-react'
|
|
||||||
import { useState } from 'react'
|
|
||||||
import { useForm } from 'react-hook-form'
|
|
||||||
import { Link, useNavigate } from 'react-router-dom'
|
|
||||||
import { toast } from 'sonner'
|
|
||||||
import { z } from 'zod'
|
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
import SignInFooter from '@/components/auth/sign-in-footer'
|
import SignInFooter from '@/components/auth/sign-in-footer'
|
||||||
import { Button, buttonVariants } from '@/components/ui/button'
|
import { Button, buttonVariants } from '@/components/ui/button'
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||||
import { Form, FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form'
|
|
||||||
import { Input } from '@/components/ui/input'
|
|
||||||
import { Separator } from '@/components/ui/separator'
|
import { Separator } from '@/components/ui/separator'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
const formSchema = z.object({
|
import { useSignInSecurityKey } from '@nhost/react'
|
||||||
email: z.string().email()
|
import { ArrowLeft } from 'lucide-react'
|
||||||
})
|
import { useState } from 'react'
|
||||||
|
import { Link, useNavigate } from 'react-router-dom'
|
||||||
|
import { toast } from 'sonner'
|
||||||
|
|
||||||
export default function SignInSecurityKey() {
|
export default function SignInSecurityKey() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { signInEmailSecurityKey } = useSignInEmailSecurityKey()
|
const { signInSecurityKey } = useSignInSecurityKey()
|
||||||
const [showEmailVerificationDialog, setShowEmailVerificationDialog] = useState(false)
|
const [showEmailVerificationDialog, setShowEmailVerificationDialog] = useState(false)
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const handleSignInSecurityKey = async () => {
|
||||||
resolver: zodResolver(formSchema),
|
const { isError, isSuccess, needsEmailVerification, error } = await signInSecurityKey()
|
||||||
defaultValues: {
|
|
||||||
email: ''
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const onSubmit = async (values: z.infer<typeof formSchema>) => {
|
|
||||||
const { email } = values
|
|
||||||
|
|
||||||
const { isError, isSuccess, needsEmailVerification, error } = await signInEmailSecurityKey(
|
|
||||||
email
|
|
||||||
)
|
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
toast.error(error?.message)
|
toast.error(error?.message)
|
||||||
@@ -51,24 +31,9 @@ export default function SignInSecurityKey() {
|
|||||||
<div className="flex flex-col items-center justify-center w-full max-w-md p-8 bg-white rounded-md shadow">
|
<div className="flex flex-col items-center justify-center w-full max-w-md p-8 bg-white rounded-md shadow">
|
||||||
<h1 className="mb-8 text-3xl">Sign in with a security key</h1>
|
<h1 className="mb-8 text-3xl">Sign in with a security key</h1>
|
||||||
|
|
||||||
<Form {...form}>
|
<Button onClick={handleSignInSecurityKey} className="w-full">
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col w-full space-y-4">
|
Sign In
|
||||||
<FormField
|
</Button>
|
||||||
control={form.control}
|
|
||||||
name="email"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="email" type="email" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage className="text-xs" />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button type="submit">Sign In</Button>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
|
|
||||||
<Link to="/sign-in" className={cn(buttonVariants({ variant: 'link' }), 'my-2')}>
|
<Link to="/sign-in" className={cn(buttonVariants({ variant: 'link' }), 'my-2')}>
|
||||||
<ArrowLeft className="w-4 h-4" />
|
<ArrowLeft className="w-4 h-4" />
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
# @nhost-examples/react-gqty
|
# @nhost-examples/react-gqty
|
||||||
|
|
||||||
|
## 1.2.18
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react@3.9.1
|
||||||
|
|
||||||
|
## 1.2.17
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [04d2ce1]
|
||||||
|
- @nhost/react@3.9.0
|
||||||
|
|
||||||
## 1.2.16
|
## 1.2.16
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/react-gqty",
|
"name": "@nhost-examples/react-gqty",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.2.16",
|
"version": "1.2.18",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -1,5 +1,20 @@
|
|||||||
# @nhost-examples/react-native
|
# @nhost-examples/react-native
|
||||||
|
|
||||||
|
## 0.1.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/react@3.9.1
|
||||||
|
- @nhost/react-apollo@16.0.1
|
||||||
|
|
||||||
|
## 0.1.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [04d2ce1]
|
||||||
|
- @nhost/react@3.9.0
|
||||||
|
- @nhost/react-apollo@16.0.0
|
||||||
|
|
||||||
## 0.1.1
|
## 0.1.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/react-native",
|
"name": "@nhost-examples/react-native",
|
||||||
"version": "0.1.1",
|
"version": "0.1.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"android": "react-native run-android",
|
"android": "react-native run-android",
|
||||||
|
|||||||
@@ -1,5 +1,26 @@
|
|||||||
# @nhost-examples/vue-apollo
|
# @nhost-examples/vue-apollo
|
||||||
|
|
||||||
|
## 0.8.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/nhost-js@3.2.4
|
||||||
|
- @nhost/apollo@8.0.4
|
||||||
|
- @nhost/vue@2.9.1
|
||||||
|
|
||||||
|
## 0.8.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 04d2ce1: feat: update signin components to use `useSignInSecuritykey` with user handle
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [04d2ce1]
|
||||||
|
- @nhost/vue@2.9.0
|
||||||
|
- @nhost/nhost-js@3.2.3
|
||||||
|
- @nhost/apollo@8.0.3
|
||||||
|
|
||||||
## 0.7.2
|
## 0.7.2
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ httpPoolSize = 100
|
|||||||
version = 18
|
version = 18
|
||||||
|
|
||||||
[auth]
|
[auth]
|
||||||
version = '0.27.0-beta13'
|
version = '0.37.0-beta1'
|
||||||
|
|
||||||
[auth.elevatedPrivileges]
|
[auth.elevatedPrivileges]
|
||||||
mode = 'required'
|
mode = 'required'
|
||||||
|
|||||||
60
examples/vue-apollo/nhost/overlays/local.json
Normal file
60
examples/vue-apollo/nhost/overlays/local.json
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"value": "disabled",
|
||||||
|
"op": "replace",
|
||||||
|
"path": "/auth/elevatedPrivileges/mode"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "localhost",
|
||||||
|
"op": "replace",
|
||||||
|
"path": "/auth/method/webauthn/relyingParty/id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "http://localhost:5173",
|
||||||
|
"op": "replace",
|
||||||
|
"path": "/auth/method/webauthn/relyingParty/origins/0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "http://localhost:5173",
|
||||||
|
"op": "replace",
|
||||||
|
"path": "/auth/redirections/allowedUrls/0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "http://localhost:5173/profile",
|
||||||
|
"op": "replace",
|
||||||
|
"path": "/auth/redirections/allowedUrls/1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "remove",
|
||||||
|
"path": "/auth/redirections/allowedUrls/2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "remove",
|
||||||
|
"path": "/auth/redirections/allowedUrls/2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "remove",
|
||||||
|
"path": "/auth/redirections/allowedUrls/2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "remove",
|
||||||
|
"path": "/auth/redirections/allowedUrls/2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "remove",
|
||||||
|
"path": "/auth/redirections/allowedUrls/2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "remove",
|
||||||
|
"path": "/auth/redirections/allowedUrls/2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "remove",
|
||||||
|
"path": "/auth/redirections/allowedUrls/2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "http://localhost:5173",
|
||||||
|
"op": "replace",
|
||||||
|
"path": "/auth/redirections/clientUrl"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/vue-apollo",
|
"name": "@nhost-examples/vue-apollo",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.7.2",
|
"version": "0.8.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<form @submit="handleSignIn">
|
<form @submit="handleSignIn">
|
||||||
<v-text-field v-model="email" label="Email" />
|
|
||||||
<v-btn
|
<v-btn
|
||||||
block
|
block
|
||||||
color="primary"
|
color="primary"
|
||||||
@@ -23,17 +22,15 @@
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
import { useSignInEmailSecurityKey } from '@nhost/vue'
|
import { useSignInSecurityKey } from '@nhost/vue'
|
||||||
|
|
||||||
const email = ref('')
|
|
||||||
const emailVerificationDialog = ref(false)
|
const emailVerificationDialog = ref(false)
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { signInEmailSecurityKey, error, isLoading } = useSignInEmailSecurityKey()
|
const { signInSecurityKey, error, isLoading } = useSignInSecurityKey()
|
||||||
|
|
||||||
const handleSignIn = async (e: Event) => {
|
const handleSignIn = async (e: Event) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
const { isSuccess, needsEmailVerification } = await signInEmailSecurityKey(email)
|
const { isSuccess, needsEmailVerification } = await signInSecurityKey()
|
||||||
if (isSuccess) {
|
if (isSuccess) {
|
||||||
router.replace('/')
|
router.replace('/')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
// "extends": "../../config/tsconfig.base.json",
|
"extends": "../../config/tsconfig.base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
|
|||||||
@@ -1,5 +1,20 @@
|
|||||||
# @nhost-examples/vue-quickstart
|
# @nhost-examples/vue-quickstart
|
||||||
|
|
||||||
|
## 0.2.18
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- @nhost/apollo@8.0.4
|
||||||
|
- @nhost/vue@2.9.1
|
||||||
|
|
||||||
|
## 0.2.17
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [04d2ce1]
|
||||||
|
- @nhost/vue@2.9.0
|
||||||
|
- @nhost/apollo@8.0.3
|
||||||
|
|
||||||
## 0.2.16
|
## 0.2.16
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nhost-examples/vue-quickstart",
|
"name": "@nhost-examples/vue-quickstart",
|
||||||
"version": "0.2.16",
|
"version": "0.2.18",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user