Compare commits
34 Commits
@nhost/rea
...
@nhost/apo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8a8d4fca3 | ||
|
|
311374e3fb | ||
|
|
e40a4529b4 | ||
|
|
1623e9bd20 | ||
|
|
5c47e8e675 | ||
|
|
9f9f1c64f4 | ||
|
|
981404f0b9 | ||
|
|
4ad27e9d72 | ||
|
|
778946998a | ||
|
|
6c11b75807 | ||
|
|
2dc031d16c | ||
|
|
40bd3e4572 | ||
|
|
6cb2b6331a | ||
|
|
08a7dd9894 | ||
|
|
f0a994a26e | ||
|
|
4fbd6bd4fa | ||
|
|
67fc77486c | ||
|
|
4f3fb3446e | ||
|
|
49a80c22be | ||
|
|
28676f4cdc | ||
|
|
e03f14133c | ||
|
|
150c04a4f4 | ||
|
|
bccd67b1b1 | ||
|
|
b14fd2f14c | ||
|
|
68b3d23cd9 | ||
|
|
d86e5c9c16 | ||
|
|
b2cc1411d7 | ||
|
|
407feeac37 | ||
|
|
7b25c37c26 | ||
|
|
6df4f02e95 | ||
|
|
aaae98f019 | ||
|
|
dc23dc0f49 | ||
|
|
82728da100 | ||
|
|
2d68fee54c |
@@ -22,7 +22,7 @@ jobs:
|
||||
- name: Configure aws
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: arn:aws:iam::${{ secrets.AWS_PRODUCTION_CORE_ACCOUNT_ID }}:role/github-actions-nhost-be
|
||||
role-to-assume: arn:aws:iam::${{ secrets.AWS_PRODUCTION_CORE_ACCOUNT_ID }}:role/github-actions-nhost-${{ github.event.repository.name }}
|
||||
aws-region: eu-central-1
|
||||
|
||||
- uses: nixbuild/nix-quick-install-action@v26
|
||||
|
||||
3
config/.husky/pre-commit
Executable file → Normal file
3
config/.husky/pre-commit
Executable file → Normal file
@@ -1,4 +1,7 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
[ -n "$CI" ] && exit 0
|
||||
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
pnpm dlx lint-staged --config config/.lintstagedrc.js
|
||||
|
||||
@@ -1,5 +1,71 @@
|
||||
# @nhost/dashboard
|
||||
|
||||
## 1.11.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react-apollo@10.0.2
|
||||
- @nhost/nextjs@2.1.8
|
||||
|
||||
## 1.11.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 981404f: fix: set default value for healthCheck field validation
|
||||
|
||||
## 1.11.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 7789469: chore: upgrade dependency `@graphql-codegen/cli` to `5.0.2` to address vulnerability
|
||||
- 6c11b75: feat: add update user displayName section in account settings
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react-apollo@10.0.1
|
||||
- @nhost/nextjs@2.1.7
|
||||
|
||||
## 1.10.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 49a80c2: chore: update dependencies
|
||||
- 150c04a: feat: add healthcheck config to run services
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e03f141: fix: allow insert, update and delete on tables in `auth` and `storage` schemas
|
||||
- 28676f4: feat: add min postgres version check to enable the ai service
|
||||
- Updated dependencies [49a80c2]
|
||||
- @nhost/react-apollo@10.0.0
|
||||
- @nhost/nextjs@2.1.6
|
||||
|
||||
## 1.9.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- d86e5c9: feat: add support for filtering the logs using a RegExp
|
||||
|
||||
## 1.8.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react-apollo@9.0.3
|
||||
- @nhost/nextjs@2.1.5
|
||||
|
||||
## 1.8.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 6df4f02: fix: use custom error toast and show correct message when sending an invite
|
||||
|
||||
## 1.8.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react-apollo@9.0.2
|
||||
- @nhost/nextjs@2.1.4
|
||||
|
||||
## 1.8.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -16,8 +16,6 @@ const cspHeader = `
|
||||
form-action 'self';
|
||||
frame-ancestors 'none';
|
||||
frame-src 'self' js.stripe.com;
|
||||
block-all-mixed-content;
|
||||
upgrade-insecure-requests;
|
||||
`;
|
||||
|
||||
module.exports = withBundleAnalyzer({
|
||||
@@ -42,10 +40,6 @@ module.exports = withBundleAnalyzer({
|
||||
key: 'X-Frame-Options',
|
||||
value: 'SAMEORIGIN',
|
||||
},
|
||||
{
|
||||
key: 'Content-Security-Policy',
|
||||
value: cspHeader.replace(/\n/g, ''),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/dashboard",
|
||||
"version": "1.8.0",
|
||||
"version": "1.11.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
@@ -19,58 +19,58 @@
|
||||
"e2e": "pnpm install-browsers && pnpm playwright test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.9.4",
|
||||
"@codemirror/lang-sql": "^6.5.5",
|
||||
"@apollo/client": "^3.9.5",
|
||||
"@codemirror/lang-sql": "^6.6.0",
|
||||
"@emotion/cache": "^11.11.0",
|
||||
"@emotion/react": "^11.11.3",
|
||||
"@emotion/react": "^11.11.4",
|
||||
"@emotion/server": "^11.11.0",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@fontsource/inter": "^5.0.16",
|
||||
"@fontsource/roboto-mono": "^5.0.16",
|
||||
"@graphiql/react": "^0.20.2",
|
||||
"@graphiql/react": "^0.20.3",
|
||||
"@graphiql/toolkit": "^0.9.1",
|
||||
"@headlessui/react": "^1.7.18",
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@mui/base": "5.0.0-beta.31",
|
||||
"@mui/material": "^5.15.7",
|
||||
"@mui/system": "^5.15.7",
|
||||
"@mui/material": "^5.15.11",
|
||||
"@mui/system": "^5.15.11",
|
||||
"@mui/x-date-pickers": "^5.0.20",
|
||||
"@nhost/nextjs": "workspace:*",
|
||||
"@nhost/react-apollo": "workspace:*",
|
||||
"@segment/snippet": "^4.16.2",
|
||||
"@stripe/react-stripe-js": "^2.4.0",
|
||||
"@stripe/react-stripe-js": "^2.5.1",
|
||||
"@stripe/stripe-js": "^1.54.2",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@tanstack/react-query": "^4.36.1",
|
||||
"@tanstack/react-table": "^8.11.7",
|
||||
"@tanstack/react-virtual": "^3.0.2",
|
||||
"@uiw/codemirror-theme-github": "^4.21.21",
|
||||
"@uiw/react-codemirror": "^4.21.21",
|
||||
"@tanstack/react-table": "^8.13.2",
|
||||
"@tanstack/react-virtual": "^3.1.3",
|
||||
"@uiw/codemirror-theme-github": "^4.21.24",
|
||||
"@uiw/react-codemirror": "^4.21.24",
|
||||
"analytics-node": "^6.2.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"clsx": "^1.2.1",
|
||||
"date-fns": "^2.30.0",
|
||||
"framer-motion": "^10.18.0",
|
||||
"generate-password": "^1.7.1",
|
||||
"graphiql": "^3.1.0",
|
||||
"graphiql": "^3.1.1",
|
||||
"graphql": "16.8.1",
|
||||
"graphql-request": "^6.1.0",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"graphql-ws": "^5.14.3",
|
||||
"graphql-ws": "^5.15.0",
|
||||
"just-kebab-case": "^4.2.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"next": "^14.1.0",
|
||||
"next-seo": "^6.4.0",
|
||||
"next-seo": "^6.5.0",
|
||||
"node-pg-format": "^1.3.5",
|
||||
"pluralize": "^8.0.0",
|
||||
"react": "18.2.0",
|
||||
"react-children-utilities": "^2.10.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-error-boundary": "^4.0.12",
|
||||
"react-hook-form": "^7.50.0",
|
||||
"react-error-boundary": "^4.0.13",
|
||||
"react-hook-form": "^7.50.1",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-intersection-observer": "^9.5.4",
|
||||
"react-intersection-observer": "^9.8.1",
|
||||
"react-is": "18.2.0",
|
||||
"react-loading-skeleton": "^2.2.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
@@ -91,9 +91,9 @@
|
||||
"yup-password": "^0.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.23.9",
|
||||
"@babel/core": "^7.24.0",
|
||||
"@faker-js/faker": "^7.6.0",
|
||||
"@graphql-codegen/cli": "^3.3.1",
|
||||
"@graphql-codegen/cli": "^5.0.2",
|
||||
"@graphql-codegen/typescript": "^3.0.4",
|
||||
"@graphql-codegen/typescript-operations": "^3.0.4",
|
||||
"@graphql-codegen/typescript-react-apollo": "^3.3.7",
|
||||
@@ -106,36 +106,36 @@
|
||||
"@storybook/addon-postcss": "^2.0.0",
|
||||
"@storybook/builder-webpack5": "^6.5.16",
|
||||
"@storybook/manager-webpack5": "^6.5.16",
|
||||
"@storybook/react": "^7.6.15",
|
||||
"@storybook/react": "^7.6.17",
|
||||
"@storybook/testing-library": "^0.2.2",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"@testing-library/dom": "^9.3.4",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^14.2.0",
|
||||
"@testing-library/react": "^14.2.1",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/ace": "^0.0.48",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/lodash.debounce": "^4.0.9",
|
||||
"@types/node": "^16.18.78",
|
||||
"@types/node": "^16.18.86",
|
||||
"@types/pluralize": "^0.0.30",
|
||||
"@types/react": "^18.2.50",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/react": "^18.2.61",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@types/react-table": "^7.7.19",
|
||||
"@types/shell-quote": "^1.7.5",
|
||||
"@types/testing-library__jest-dom": "^5.14.9",
|
||||
"@types/validator": "^13.11.8",
|
||||
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
||||
"@typescript-eslint/parser": "^6.20.0",
|
||||
"@types/validator": "^13.11.9",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"@vitest/coverage-v8": "^0.32.4",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"babel-loader": "^8.3.0",
|
||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||
"csstype": "^3.1.3",
|
||||
"dotenv": "^16.4.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"encoding": "^0.1.13",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-airbnb": "19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^17.1.0",
|
||||
"eslint-config-next": "^13.5.6",
|
||||
@@ -145,11 +145,11 @@
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"jsdom": "^22.1.0",
|
||||
"lint-staged": "^15.2.1",
|
||||
"lint-staged": "^15.2.2",
|
||||
"msw": "^1.3.2",
|
||||
"msw-storybook-addon": "^1.10.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"postcss": "^8.4.33",
|
||||
"postcss": "^8.4.35",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-plugin-organize-imports": "^3.2.4",
|
||||
"prettier-plugin-tailwindcss": "^0.4.1",
|
||||
@@ -160,7 +160,7 @@
|
||||
"tailwindcss": "^3.4.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig-paths-webpack-plugin": "^4.1.0",
|
||||
"vite": "^5.0.12",
|
||||
"vite": "^5.1.4",
|
||||
"vite-tsconfig-paths": "^4.3.1",
|
||||
"vitest": "^0.32.4"
|
||||
},
|
||||
|
||||
@@ -35,7 +35,7 @@ function InsertPlaceholderTableRow({
|
||||
...props
|
||||
}: InsertPlaceholderTableRowProps) {
|
||||
return (
|
||||
<Box className="h-12 border-r-1 border-b-1" {...props}>
|
||||
<Box className="h-12 border-b-1 border-r-1" {...props}>
|
||||
<Button
|
||||
onClick={onInsertRow}
|
||||
variant="borderless"
|
||||
@@ -209,7 +209,7 @@ export default function DataGridBody<T extends object>({
|
||||
/>
|
||||
) : (
|
||||
<Box
|
||||
className="inline-flex h-12 items-center border-b-1 border-r-1 py-1.5 px-2 text-xs"
|
||||
className="inline-flex h-12 items-center border-b-1 border-r-1 px-2 py-1.5 text-xs"
|
||||
sx={{ color: 'text.secondary' }}
|
||||
style={{
|
||||
width: allowInsertColumn
|
||||
@@ -281,8 +281,8 @@ export default function DataGridBody<T extends object>({
|
||||
}}
|
||||
className={twMerge(
|
||||
'h-12 font-display text-xs motion-safe:transition-colors',
|
||||
'border-r-1 border-b-1',
|
||||
'scroll-mt-[57px] scroll-ml-8',
|
||||
'border-b-1 border-r-1',
|
||||
'scroll-ml-8 scroll-mt-[57px]',
|
||||
column.id === 'selection' &&
|
||||
'sticky left-0 z-20 justify-center px-0',
|
||||
)}
|
||||
@@ -296,7 +296,7 @@ export default function DataGridBody<T extends object>({
|
||||
})}
|
||||
|
||||
{allowInsertColumn && (
|
||||
<Box className="h-12 w-25 border-r-1 border-b-1" />
|
||||
<Box className="h-12 w-25 border-b-1 border-r-1" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,7 +8,15 @@ import type {
|
||||
DataBrowserGridCellProps,
|
||||
} from '@/features/database/dataGrid/types/dataBrowser';
|
||||
import { triggerToast } from '@/utils/toast';
|
||||
import type { FocusEvent, JSXElementConstructor, KeyboardEvent, MouseEvent, ReactElement, ReactNode, ReactPortal } from 'react';
|
||||
import type {
|
||||
FocusEvent,
|
||||
JSXElementConstructor,
|
||||
KeyboardEvent,
|
||||
MouseEvent,
|
||||
ReactElement,
|
||||
ReactNode,
|
||||
ReactPortal,
|
||||
} from 'react';
|
||||
import {
|
||||
Children,
|
||||
cloneElement,
|
||||
@@ -308,7 +316,7 @@ function DataGridCellContent<TData extends object = {}, TValue = unknown>({
|
||||
isEditable &&
|
||||
'focus-within:outline-none focus-within:ring-0 focus:ring-0',
|
||||
isSelected && 'shadow-outline',
|
||||
isEditing ? 'p-0.5 shadow-outline-dark' : 'py-1.5 px-2',
|
||||
isEditing ? 'p-0.5 shadow-outline-dark' : 'px-2 py-1.5',
|
||||
className,
|
||||
)}
|
||||
onFocus={handleFocus}
|
||||
@@ -320,20 +328,28 @@ function DataGridCellContent<TData extends object = {}, TValue = unknown>({
|
||||
sx={{ backgroundColor: 'transparent' }}
|
||||
{...props}
|
||||
>
|
||||
{Children.map(children, (child: ReactNode | ReactPortal | ReactElement<unknown, string | JSXElementConstructor<any>>) => {
|
||||
if (!isValidElement(child)) {
|
||||
return null;
|
||||
}
|
||||
{Children.map(
|
||||
children,
|
||||
(
|
||||
child:
|
||||
| ReactNode
|
||||
| ReactPortal
|
||||
| ReactElement<unknown, string | JSXElementConstructor<any>>,
|
||||
) => {
|
||||
if (!isValidElement(child)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return cloneElement(child, {
|
||||
...child.props,
|
||||
onSave: handleSave,
|
||||
optimisticValue,
|
||||
onOptimisticValueChange: setOptimisticValue,
|
||||
temporaryValue,
|
||||
onTemporaryValueChange: setTemporaryValue,
|
||||
});
|
||||
})}
|
||||
return cloneElement(child, {
|
||||
...child.props,
|
||||
onSave: handleSave,
|
||||
optimisticValue,
|
||||
onOptimisticValueChange: setOptimisticValue,
|
||||
temporaryValue,
|
||||
onTemporaryValueChange: setTemporaryValue,
|
||||
});
|
||||
},
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PlusCircleIcon } from '@/components/ui/v2/icons/PlusCircleIcon';
|
||||
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
|
||||
import type { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
import type { Meta, StoryFn } from '@storybook/react';
|
||||
import type { ButtonProps } from './Button';
|
||||
import Button from './Button';
|
||||
|
||||
@@ -24,9 +24,9 @@ export default {
|
||||
control: { type: 'radio' },
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof Button>;
|
||||
} as Meta<typeof Button>;
|
||||
|
||||
const Template: ComponentStory<typeof Button> = function Template(
|
||||
const Template: StoryFn<ButtonProps> = function TemplateFunction(
|
||||
args: ButtonProps,
|
||||
) {
|
||||
return <Button {...args} />;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { XIcon } from '@/components/ui/v2/icons/XIcon';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { getToastBackgroundColor } from '@/utils/constants/settings';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import type { ApolloError } from '@apollo/client';
|
||||
import { useUserData } from '@nhost/nextjs';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useRouter } from 'next/router';
|
||||
@@ -27,10 +27,11 @@ const getInternalErrorMessage = (
|
||||
return null;
|
||||
}
|
||||
|
||||
if (error instanceof ApolloError) {
|
||||
const internalError = error.graphQLErrors?.[0]?.extensions?.internal as
|
||||
| { error: { message: string } }
|
||||
| undefined;
|
||||
if (error.name === 'ApolloError') {
|
||||
// @ts-ignore
|
||||
const internalError = error.graphQLErrors?.[0]?.extensions?.internal as {
|
||||
error: { message: string };
|
||||
};
|
||||
return internalError?.error?.message || null;
|
||||
}
|
||||
|
||||
@@ -42,7 +43,7 @@ const getInternalErrorMessage = (
|
||||
};
|
||||
|
||||
const errorToObject = (error: ApolloError | Error) => {
|
||||
if (error instanceof ApolloError) {
|
||||
if (error.name === 'ApolloError') {
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Option } from '@/components/ui/v2/Option';
|
||||
import type { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
import type { Meta, StoryFn } from '@storybook/react';
|
||||
import type { SelectProps } from './Select';
|
||||
import Select from './Select';
|
||||
|
||||
@@ -7,11 +7,9 @@ export default {
|
||||
title: 'UI Library / Select',
|
||||
component: Select,
|
||||
argTypes: {},
|
||||
} as ComponentMeta<typeof Select>;
|
||||
} as Meta<typeof Select>;
|
||||
|
||||
const Template: ComponentStory<typeof Select> = function Template(
|
||||
args: SelectProps<any>,
|
||||
) {
|
||||
const Template: StoryFn<SelectProps<any>> = function TemplateFunction(args) {
|
||||
return (
|
||||
<Select className="w-64" {...args}>
|
||||
<Option value="value1">Value 1</Option>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
import type { Meta, StoryFn } from '@storybook/react';
|
||||
import type { SwitchProps } from './Switch';
|
||||
import Switch from './Switch';
|
||||
|
||||
@@ -6,9 +6,9 @@ export default {
|
||||
title: 'UI Library / Switch',
|
||||
component: Switch,
|
||||
argTypes: {},
|
||||
} as ComponentMeta<typeof Switch>;
|
||||
} as Meta<typeof Switch>;
|
||||
|
||||
const Template: ComponentStory<typeof Switch> = function Template(
|
||||
const Template: StoryFn<SwitchProps> = function TemplateFunction(
|
||||
args: SwitchProps,
|
||||
) {
|
||||
return <Switch label="Accept Rules" {...args} />;
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { SettingsContainer } from '@/components/layout/SettingsContainer';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { useUpdateUserDisplayNameMutation } from '@/utils/__generated__/graphql';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useUserData } from '@nhost/nextjs';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
displayName: Yup.string()
|
||||
.label('Display Name')
|
||||
.required('This field is required.'),
|
||||
});
|
||||
|
||||
export type DisplayNameSettingFormValues = Yup.InferType<
|
||||
typeof validationSchema
|
||||
>;
|
||||
|
||||
export default function DisplayNameSetting() {
|
||||
const { id: userID, displayName } = useUserData();
|
||||
|
||||
const [updateUserDisplayName] = useUpdateUserDisplayNameMutation();
|
||||
|
||||
const form = useForm<DisplayNameSettingFormValues>({
|
||||
reValidateMode: 'onSubmit',
|
||||
defaultValues: {
|
||||
displayName,
|
||||
},
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
const { register, formState } = form;
|
||||
const isDirty = Object.keys(formState.dirtyFields).length > 0;
|
||||
|
||||
async function handleSubmit(formValues: DisplayNameSettingFormValues) {
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await updateUserDisplayName({
|
||||
variables: {
|
||||
id: userID,
|
||||
displayName: formValues.displayName,
|
||||
},
|
||||
});
|
||||
|
||||
form.reset({ displayName: formValues.displayName });
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Updating your display name...',
|
||||
successMessage: 'Your display name has been updated successfully.',
|
||||
errorMessage:
|
||||
'An error occurred while trying to update your display name. Please try again.',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<SettingsContainer
|
||||
title="Update your display name"
|
||||
slotProps={{
|
||||
submitButton: {
|
||||
disabled: !isDirty,
|
||||
loading: formState.isSubmitting,
|
||||
},
|
||||
}}
|
||||
className="grid grid-flow-row lg:grid-cols-5"
|
||||
>
|
||||
<Input
|
||||
{...register('displayName')}
|
||||
className="col-span-2"
|
||||
type="text"
|
||||
id="display-name"
|
||||
label="Display Name"
|
||||
fullWidth
|
||||
helperText={formState.errors.displayName?.message}
|
||||
error={Boolean(formState.errors.displayName)}
|
||||
/>
|
||||
</SettingsContainer>
|
||||
</Form>
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './DisplayNameSetting';
|
||||
export { default as DisplayNameSetting } from './DisplayNameSetting';
|
||||
@@ -0,0 +1,6 @@
|
||||
mutation updateUserDisplayName($id: uuid!, $displayName: String!) {
|
||||
updateUser(pk_columns: { id: $id }, _set: { displayName: $displayName }) {
|
||||
id
|
||||
displayName
|
||||
}
|
||||
}
|
||||
@@ -21,13 +21,17 @@ import {
|
||||
useUpdateConfigMutation,
|
||||
} from '@/generated/graphql';
|
||||
import { RESOURCE_VCPU_MULTIPLIER } from '@/utils/constants/common';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import * as Yup from 'yup';
|
||||
import { DisableAIServiceConfirmationDialog } from './DisableAIServiceConfirmationDialog';
|
||||
|
||||
const MIN_POSTGRES_VERSION_SUPPORTING_AI = '14.6-20231018-1';
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
version: Yup.object({
|
||||
label: Yup.string().required(),
|
||||
@@ -54,7 +58,9 @@ export default function AISettings() {
|
||||
const [aiServiceEnabled, setAIServiceEnabled] = useState(true);
|
||||
|
||||
const {
|
||||
data: { config: { ai } = {} } = {},
|
||||
data: {
|
||||
config: { ai, postgres: { version: postgresVersion } = {} } = {},
|
||||
} = {},
|
||||
loading: loadingAiSettings,
|
||||
error: errorGettingAiSettings,
|
||||
} = useGetAiSettingsQuery({
|
||||
@@ -150,6 +156,17 @@ export default function AISettings() {
|
||||
]);
|
||||
|
||||
const toggleAIService = async (enabled: boolean) => {
|
||||
if (postgresVersion < MIN_POSTGRES_VERSION_SUPPORTING_AI) {
|
||||
toast.error(
|
||||
'In order to enable the AI service you need to update your database version to 14.6-20231018-1 or newer.',
|
||||
{
|
||||
style: getToastStyleProps().style,
|
||||
...getToastStyleProps().error,
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setAIServiceEnabled(enabled);
|
||||
|
||||
if (!enabled && ai) {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
query GetAISettings($appId: uuid!) {
|
||||
config(appID: $appId, resolve: false) {
|
||||
postgres {
|
||||
version
|
||||
}
|
||||
ai {
|
||||
version
|
||||
webhookSecret
|
||||
|
||||
@@ -275,7 +275,7 @@ export default function DataBrowserGrid({
|
||||
() =>
|
||||
columns
|
||||
.map((column) => ({
|
||||
...createDataGridColumn(column, isSchemaEditable),
|
||||
...createDataGridColumn(column, true),
|
||||
onCellEdit: async (variables: UpdateRecordVariables) => {
|
||||
const result = await updateRow(variables);
|
||||
await queryClient.invalidateQueries([currentTablePath]);
|
||||
@@ -288,7 +288,6 @@ export default function DataBrowserGrid({
|
||||
[
|
||||
columns,
|
||||
currentTablePath,
|
||||
isSchemaEditable,
|
||||
optimisticlyRemovedColumnId,
|
||||
queryClient,
|
||||
removableColumnId,
|
||||
@@ -422,7 +421,7 @@ export default function DataBrowserGrid({
|
||||
loading={status === 'loading'}
|
||||
sortBy={sortBy}
|
||||
className="pb-17 sm:pb-0"
|
||||
onInsertRow={isSchemaEditable ? handleInsertRowClick : undefined}
|
||||
onInsertRow={handleInsertRowClick}
|
||||
onInsertColumn={isSchemaEditable ? handleInsertColumnClick : undefined}
|
||||
onEditColumn={isSchemaEditable ? handleEditColumnClick : undefined}
|
||||
onRemoveColumn={isSchemaEditable ? handleColumnRemoveClick : undefined}
|
||||
@@ -445,7 +444,7 @@ export default function DataBrowserGrid({
|
||||
onInsertColumnClick={
|
||||
isSchemaEditable ? handleInsertColumnClick : undefined
|
||||
}
|
||||
onInsertRowClick={isSchemaEditable ? handleInsertRowClick : undefined}
|
||||
onInsertRowClick={handleInsertRowClick}
|
||||
paginationProps={{
|
||||
currentPage: Math.max(currentPage, 1),
|
||||
totalPages: Math.max(numberOfPages, 1),
|
||||
|
||||
@@ -12,11 +12,9 @@ import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
|
||||
import { RowIcon } from '@/components/ui/v2/icons/RowIcon';
|
||||
import { useDeleteRecordMutation } from '@/features/database/dataGrid/hooks/useDeleteRecordMutation';
|
||||
import type { DataBrowserGridColumn } from '@/features/database/dataGrid/types/dataBrowser';
|
||||
import { isSchemaLocked } from '@/features/database/dataGrid/utils/schemaHelpers/isSchemaLocked';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { triggerToast } from '@/utils/toast';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useState } from 'react';
|
||||
import type { Row } from 'react-table';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
@@ -58,11 +56,6 @@ export default function DataBrowserGridControls({
|
||||
const { className: paginationClassName, ...restPaginationProps } =
|
||||
paginationProps || ({} as DataGridPaginationProps);
|
||||
|
||||
const {
|
||||
query: { schemaSlug },
|
||||
} = useRouter();
|
||||
const isSchemaEditable = !isSchemaLocked(schemaSlug as string);
|
||||
|
||||
const {
|
||||
selectedFlatRows: selectedRows,
|
||||
columns,
|
||||
@@ -126,7 +119,7 @@ export default function DataBrowserGridControls({
|
||||
numberOfSelectedRows > 0 ? 'justify-between' : 'justify-end',
|
||||
)}
|
||||
>
|
||||
{isSchemaEditable && numberOfSelectedRows > 0 && (
|
||||
{numberOfSelectedRows > 0 && (
|
||||
<div className="grid grid-flow-col place-content-start items-center gap-2">
|
||||
<Chip
|
||||
size="small"
|
||||
|
||||
@@ -43,7 +43,13 @@ export default async function deleteRecord({
|
||||
(row) =>
|
||||
`(${primaryOrUniqueColumns
|
||||
.map((primaryOrUniqueColumn) =>
|
||||
format('%I=%L', primaryOrUniqueColumn, row[primaryOrUniqueColumn]),
|
||||
row[primaryOrUniqueColumn] === null
|
||||
? format('%I IS NULL', primaryOrUniqueColumn)
|
||||
: format(
|
||||
'%I=%L',
|
||||
primaryOrUniqueColumn,
|
||||
row[primaryOrUniqueColumn],
|
||||
),
|
||||
)
|
||||
.join(' AND ')})`,
|
||||
);
|
||||
|
||||
@@ -131,7 +131,7 @@ export default function LogsBody({ logsData, loading, error }: LogsBodyProps) {
|
||||
count: rows.length,
|
||||
getScrollElement: () => tableRef.current,
|
||||
estimateSize: () => 63,
|
||||
overscan: 5,
|
||||
overscan: 50,
|
||||
});
|
||||
|
||||
if (loading && !error) {
|
||||
@@ -214,7 +214,7 @@ export default function LogsBody({ logsData, loading, error }: LogsBodyProps) {
|
||||
<TableCell
|
||||
key={cell.id}
|
||||
component="td"
|
||||
className="break-words py-2.5 px-2 align-top text-xs- font-normal tracking-tight"
|
||||
className="break-words px-2 py-2.5 align-top text-xs- font-normal tracking-tight"
|
||||
style={{
|
||||
width: cell.column.getSize() || 'auto',
|
||||
minWidth: !cell.column.getSize() ? 300 : 'initial',
|
||||
|
||||
@@ -52,7 +52,7 @@ function LogsDatePicker({
|
||||
<Text
|
||||
htmlFor={label}
|
||||
component="label"
|
||||
className="self-center text-sm+ font-normal"
|
||||
className="min-w-14 self-center text-sm+ font-normal"
|
||||
color="secondary"
|
||||
>
|
||||
{label}
|
||||
|
||||
@@ -1,246 +1,240 @@
|
||||
import { ControlledSelect } from '@/components/form/ControlledSelect';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import type { BoxProps } from '@/components/ui/v2/Box';
|
||||
import { Box } from '@/components/ui/v2/Box';
|
||||
import { Button } from '@/components/ui/v2/Button';
|
||||
import { ClockIcon } from '@/components/ui/v2/icons/ClockIcon';
|
||||
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
|
||||
import { SearchIcon } from '@/components/ui/v2/icons/SearchIcon';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { Link } from '@/components/ui/v2/Link';
|
||||
import { Option } from '@/components/ui/v2/Option';
|
||||
import { Select } from '@/components/ui/v2/Select';
|
||||
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { LogsDatePicker } from '@/features/projects/logs/components/LogsDatePicker';
|
||||
import type { LogsCustomInterval } from '@/features/projects/logs/utils/constants/intervals';
|
||||
import { LOGS_AVAILABLE_INTERVALS } from '@/features/projects/logs/utils/constants/intervals';
|
||||
import type { AvailableLogsService } from '@/features/projects/logs/utils/constants/services';
|
||||
import { LOGS_AVAILABLE_SERVICES } from '@/features/projects/logs/utils/constants/services';
|
||||
import { useGetRunServicesQuery } from '@/utils/__generated__/graphql';
|
||||
import { LogsRangeSelector } from '@/features/projects/logs/components/LogsRangeSelector';
|
||||
import { AvailableLogsService } from '@/features/projects/logs/utils/constants/services';
|
||||
import { MINUTES_TO_DECREASE_FROM_CURRENT_DATE } from '@/utils/constants/common';
|
||||
import { useGetServiceLabelValuesQuery } from '@/utils/__generated__/graphql';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { subMinutes } from 'date-fns';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
export interface LogsHeaderProps extends Omit<BoxProps, 'children'> {
|
||||
export const validationSchema = Yup.object({
|
||||
from: Yup.date(),
|
||||
to: Yup.date().nullable(),
|
||||
service: Yup.string().oneOf(Object.values(AvailableLogsService)),
|
||||
regexFilter: Yup.string(),
|
||||
});
|
||||
|
||||
export type LogsFilterFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
interface LogsHeaderProps extends Omit<BoxProps, 'children'> {
|
||||
/**
|
||||
* The date to be displayed in the date picker for the from date.
|
||||
* This is used to indicate that a query is currently inflight
|
||||
*/
|
||||
fromDate: Date;
|
||||
loading: boolean;
|
||||
/**
|
||||
* The date to be displayed in the date picker for the to date.
|
||||
*
|
||||
* Function to be called when the user submits the filters form
|
||||
*/
|
||||
toDate: Date | null;
|
||||
/**
|
||||
* Service to where to fetch logs from.
|
||||
*/
|
||||
service: AvailableLogsService;
|
||||
/**
|
||||
* Function to be called when the user changes the from date.
|
||||
*/
|
||||
onFromDateChange: (value: Date) => void;
|
||||
/**
|
||||
* Function to be called when the user changes the `to` date.
|
||||
*/
|
||||
onToDateChange: (value: Date) => void;
|
||||
/**
|
||||
* Function to be called when the user changes service to which to query logs from.
|
||||
*/
|
||||
onServiceChange: (value: AvailableLogsService) => void;
|
||||
}
|
||||
|
||||
type LogsToDatePickerLiveButtonProps = Pick<
|
||||
LogsHeaderProps,
|
||||
'fromDate' | 'toDate' | 'onToDateChange'
|
||||
>;
|
||||
|
||||
function LogsToDatePickerLiveButton({
|
||||
fromDate,
|
||||
toDate,
|
||||
onToDateChange,
|
||||
}: LogsToDatePickerLiveButtonProps) {
|
||||
const [currentTime, setCurrentTime] = useState(new Date());
|
||||
const isLive = !toDate;
|
||||
|
||||
function handleLiveButtonClick() {
|
||||
if (isLive) {
|
||||
return;
|
||||
}
|
||||
|
||||
onToDateChange(null);
|
||||
setCurrentTime(new Date());
|
||||
}
|
||||
|
||||
// if isLive is true, we want to update the current time every second
|
||||
// and set the toDate to the current time.
|
||||
useEffect(() => {
|
||||
let interval = null;
|
||||
|
||||
if (!interval && isLive) {
|
||||
interval = setInterval(() => {
|
||||
setCurrentTime(new Date());
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [isLive, onToDateChange]);
|
||||
|
||||
return (
|
||||
<div className="text-greyscaleMedium grid grid-flow-col">
|
||||
<LogsDatePicker
|
||||
label="To"
|
||||
value={!isLive ? toDate : currentTime}
|
||||
disabled={isLive}
|
||||
onChange={onToDateChange}
|
||||
minDate={fromDate}
|
||||
maxDate={toDate || new Date()}
|
||||
componentsProps={{
|
||||
button: {
|
||||
className: twMerge(
|
||||
'rounded-r-none pr-3',
|
||||
isLive ? 'border-r-0 hover:border-r-0 z-0' : 'z-10',
|
||||
),
|
||||
color: toDate ? 'inherit' : 'secondary',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
color={isLive ? 'primary' : 'secondary'}
|
||||
sx={{
|
||||
backgroundColor: (theme) =>
|
||||
!isLive ? `${theme.palette.grey[200]} !important` : 'transparent',
|
||||
color: !isLive ? 'text.secondary' : undefined,
|
||||
}}
|
||||
className={twMerge(
|
||||
'min-w-[77px] rounded-l-none',
|
||||
!isLive ? 'z-0 border-l-0 hover:border-l-0' : 'z-10',
|
||||
)}
|
||||
startIcon={<ClockIcon className="h-4 w-4 self-center align-middle" />}
|
||||
onClick={handleLiveButtonClick}
|
||||
>
|
||||
Live
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
onSubmitFilterValues: (value: LogsFilterFormValues) => void;
|
||||
}
|
||||
|
||||
export default function LogsHeader({
|
||||
fromDate,
|
||||
toDate,
|
||||
service,
|
||||
onFromDateChange,
|
||||
onToDateChange,
|
||||
onServiceChange,
|
||||
loading,
|
||||
onSubmitFilterValues,
|
||||
...props
|
||||
}: LogsHeaderProps) {
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const applicationCreationDate = new Date(currentProject.createdAt);
|
||||
|
||||
const [runServices, setRunServices] = useState<
|
||||
{
|
||||
label: string;
|
||||
value: string;
|
||||
}[]
|
||||
const [serviceLabels, setServiceLabels] = useState<
|
||||
{ label: string; value: string }[]
|
||||
>([]);
|
||||
|
||||
const { data, loading } = useGetRunServicesQuery({
|
||||
variables: {
|
||||
appID: currentProject.id,
|
||||
resolve: false,
|
||||
limit: 1000,
|
||||
offset: 0,
|
||||
},
|
||||
});
|
||||
const { data, loading: loadingServiceLabelValues } =
|
||||
useGetServiceLabelValuesQuery({
|
||||
variables: { appID: currentProject.id },
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
const services = data.app?.runServices ?? [];
|
||||
if (!loadingServiceLabelValues) {
|
||||
const labels = data.getServiceLabelValues ?? [];
|
||||
setServiceLabels(labels.map((l) => ({ label: l, value: l })));
|
||||
}
|
||||
}, [loadingServiceLabelValues, data]);
|
||||
|
||||
setRunServices(
|
||||
services
|
||||
.filter((s) => !!s.config?.name)
|
||||
.map((s) => ({
|
||||
label: s.config.name,
|
||||
value: `run-${s.config.name}`,
|
||||
})),
|
||||
useEffect(() => {
|
||||
if (!loadingServiceLabelValues) {
|
||||
const labels = data.getServiceLabelValues ?? [];
|
||||
|
||||
const labelMappings = {
|
||||
'hasura-auth': 'Auth',
|
||||
'hasura-storage': 'Storage',
|
||||
postgres: 'Postgres',
|
||||
functions: 'Functions',
|
||||
hasura: 'Hasura',
|
||||
grafana: 'Grafana',
|
||||
'job-backup': 'Backup Jobs',
|
||||
ai: 'AI',
|
||||
};
|
||||
|
||||
setServiceLabels(
|
||||
labels.map((l) => ({ label: labelMappings[l] ?? l, value: l })),
|
||||
);
|
||||
}
|
||||
}, [loading, data]);
|
||||
}, [loadingServiceLabelValues, data]);
|
||||
|
||||
/**
|
||||
* Will subtract the `customInterval` time in minutes from the current date.
|
||||
*/
|
||||
function handleIntervalChange({
|
||||
minutesToDecreaseFromCurrentDate,
|
||||
}: LogsCustomInterval) {
|
||||
onFromDateChange(subMinutes(new Date(), minutesToDecreaseFromCurrentDate));
|
||||
onToDateChange(new Date());
|
||||
}
|
||||
const form = useForm<LogsFilterFormValues>({
|
||||
defaultValues: {
|
||||
from: subMinutes(new Date(), MINUTES_TO_DECREASE_FROM_CURRENT_DATE),
|
||||
to: new Date(),
|
||||
regexFilter: '',
|
||||
service: AvailableLogsService.ALL,
|
||||
},
|
||||
reValidateMode: 'onSubmit',
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
const { register, watch, getValues } = form;
|
||||
|
||||
const service = watch('service');
|
||||
|
||||
useEffect(() => {
|
||||
onSubmitFilterValues(getValues());
|
||||
}, [service, getValues, onSubmitFilterValues]);
|
||||
|
||||
const handleSubmit = (values: LogsFilterFormValues) =>
|
||||
onSubmitFilterValues(values);
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="sticky top-0 z-10 grid w-full grid-flow-row gap-x-6 gap-y-2 border-b py-2.5 px-4 lg:grid-flow-col lg:justify-between"
|
||||
className="sticky top-0 z-10 grid w-full grid-flow-row gap-x-6 gap-y-2 border-b px-4 py-2.5 lg:grid-flow-col"
|
||||
{...props}
|
||||
>
|
||||
<Box className="grid w-full grid-flow-row items-center justify-center gap-2 md:w-[initial] md:grid-flow-col md:gap-3 lg:justify-start">
|
||||
<div className="grid grid-flow-col items-center gap-3 md:justify-start">
|
||||
<LogsDatePicker
|
||||
label="From"
|
||||
value={fromDate}
|
||||
onChange={onFromDateChange}
|
||||
minDate={applicationCreationDate}
|
||||
maxDate={toDate || new Date()}
|
||||
/>
|
||||
<FormProvider {...form}>
|
||||
<Form
|
||||
onSubmit={handleSubmit}
|
||||
className="grid w-full grid-flow-row items-center gap-2 md:w-[initial] md:grid-flow-col md:gap-3 lg:justify-end"
|
||||
>
|
||||
<Box className="flex flex-row space-x-2">
|
||||
<ControlledSelect
|
||||
{...register('service')}
|
||||
className="w-full text-sm font-normal min-w-fit"
|
||||
placeholder="All Services"
|
||||
aria-label="Select service"
|
||||
hideEmptyHelperText
|
||||
slotProps={{
|
||||
root: {
|
||||
className: 'min-h-[initial] h-10 leading-[initial]',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{[{ label: 'All services', value: '' }, ...serviceLabels].map(
|
||||
({ value, label }) => (
|
||||
<Option
|
||||
key={value}
|
||||
value={value}
|
||||
className="text-sm+ font-medium"
|
||||
>
|
||||
{label}
|
||||
</Option>
|
||||
),
|
||||
)}
|
||||
</ControlledSelect>
|
||||
<div className="w-full min-w-fit">
|
||||
<LogsRangeSelector onSubmitFilterValues={onSubmitFilterValues} />
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<LogsToDatePickerLiveButton
|
||||
fromDate={fromDate}
|
||||
toDate={toDate}
|
||||
onToDateChange={onToDateChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Box className="-my-2.5 px-0 py-2.5 lg:border-l lg:px-3">
|
||||
<Select
|
||||
className="w-full text-sm font-normal"
|
||||
placeholder="All Services"
|
||||
onChange={(_e, value) => {
|
||||
if (typeof value !== 'string') {
|
||||
return;
|
||||
}
|
||||
onServiceChange(value as AvailableLogsService);
|
||||
}}
|
||||
value={service}
|
||||
aria-label="Select service"
|
||||
<Input
|
||||
{...register('regexFilter')}
|
||||
placeholder="Filter logs with a regular expression"
|
||||
hideEmptyHelperText
|
||||
slotProps={{
|
||||
root: { className: 'min-h-[initial] h-9 leading-[initial]' },
|
||||
}}
|
||||
>
|
||||
{[...LOGS_AVAILABLE_SERVICES, ...runServices].map(
|
||||
({ value, label }) => (
|
||||
<Option
|
||||
key={value}
|
||||
value={value}
|
||||
className="text-sm+ font-medium"
|
||||
>
|
||||
{label}
|
||||
</Option>
|
||||
),
|
||||
)}
|
||||
</Select>
|
||||
</Box>
|
||||
</Box>
|
||||
autoComplete="off"
|
||||
fullWidth
|
||||
className="min-w-80"
|
||||
startAdornment={
|
||||
<Tooltip
|
||||
componentsProps={{
|
||||
tooltip: {
|
||||
sx: {
|
||||
maxWidth: '30rem',
|
||||
},
|
||||
},
|
||||
}}
|
||||
title={
|
||||
<div className="p-2 space-y-4">
|
||||
<h2>Here are some useful regular expressions:</h2>
|
||||
<ul className="pl-3 space-y-2 list-disc">
|
||||
<li>
|
||||
use
|
||||
<code className="px-1 py-px mx-1 rounded-md bg-slate-500 text-slate-100">
|
||||
(?i)error
|
||||
</code>
|
||||
to search for lines with the word <b>error</b> (case
|
||||
insenstive)
|
||||
</li>
|
||||
<li>
|
||||
use
|
||||
<code className="px-1 py-px mx-1 rounded-md bg-slate-500 text-slate-100">
|
||||
error
|
||||
</code>
|
||||
to search for lines with the word <b>error</b> (case
|
||||
sensitive)
|
||||
</li>
|
||||
<li>
|
||||
use
|
||||
<code className="px-1 py-px mx-1 rounded-md bg-slate-500 text-slate-100">
|
||||
/metadata.*error
|
||||
</code>
|
||||
to search for errors in hasura's metadata endpoint
|
||||
</li>
|
||||
<li>
|
||||
See
|
||||
<Link
|
||||
href="https://github.com/google/re2/wiki/Syntax"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
underline="hover"
|
||||
className="mx-1"
|
||||
>
|
||||
here
|
||||
</Link>
|
||||
for more patterns
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Box className="ml-2 rounded-full cursor-pointer">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="w-5 h-5"
|
||||
color="info"
|
||||
/>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
|
||||
<Box className="hidden grid-flow-col items-center justify-center gap-3 md:grid lg:justify-end">
|
||||
{LOGS_AVAILABLE_INTERVALS.map((logInterval) => (
|
||||
<Button
|
||||
key={logInterval.label}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
className="self-center"
|
||||
onClick={() => handleIntervalChange(logInterval)}
|
||||
type="submit"
|
||||
className="h-10"
|
||||
startIcon={
|
||||
loading ? (
|
||||
<ActivityIndicator className="w-4 h-4" />
|
||||
) : (
|
||||
<SearchIcon />
|
||||
)
|
||||
}
|
||||
disabled={loading}
|
||||
>
|
||||
{logInterval.label}
|
||||
Search
|
||||
</Button>
|
||||
))}
|
||||
</Box>
|
||||
</Form>
|
||||
</FormProvider>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export * from './LogsHeader';
|
||||
export { default as LogsHeader } from './LogsHeader';
|
||||
export { default as LogsHeader, type LogsFilterFormValues } from './LogsHeader';
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
import { Box } from '@/components/ui/v2/Box';
|
||||
import { Button } from '@/components/ui/v2/Button';
|
||||
import { Dropdown, useDropdown } from '@/components/ui/v2/Dropdown';
|
||||
import { ClockIcon } from '@/components/ui/v2/icons/ClockIcon';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { LogsDatePicker } from '@/features/projects/logs/components/LogsDatePicker';
|
||||
import type { LogsFilterFormValues } from '@/features/projects/logs/components/LogsHeader';
|
||||
import {
|
||||
LOGS_AVAILABLE_INTERVALS,
|
||||
type LogsCustomInterval,
|
||||
} from '@/features/projects/logs/utils/constants/intervals';
|
||||
import { useInterval } from '@/hooks/useInterval';
|
||||
import { ChevronDownIcon } from '@graphiql/react';
|
||||
import { formatDistance, subMinutes } from 'date-fns';
|
||||
import { useState } from 'react';
|
||||
import { useFormContext, useWatch } from 'react-hook-form';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
function LogsToDatePickerLiveButton() {
|
||||
const [currentTime, setCurrentTime] = useState(new Date());
|
||||
|
||||
const { setValue } = useFormContext<LogsFilterFormValues>();
|
||||
const { from, to } = useWatch<LogsFilterFormValues>();
|
||||
const isLive = !to;
|
||||
|
||||
function handleLiveButtonClick() {
|
||||
if (isLive) {
|
||||
setValue('from', subMinutes(new Date(), 20));
|
||||
setValue('to', new Date());
|
||||
return;
|
||||
}
|
||||
|
||||
setValue('to', null);
|
||||
setCurrentTime(new Date());
|
||||
}
|
||||
|
||||
useInterval(() => setCurrentTime(new Date()), isLive ? 1000 : 0);
|
||||
|
||||
return (
|
||||
<div className="text-greyscaleMedium flex flex-col">
|
||||
{!isLive && (
|
||||
<LogsDatePicker
|
||||
label="To"
|
||||
value={!isLive ? to : currentTime}
|
||||
disabled={isLive}
|
||||
onChange={(date: Date) => setValue('to', date)}
|
||||
minDate={from}
|
||||
maxDate={new Date()}
|
||||
componentsProps={{
|
||||
button: {
|
||||
className: twMerge('rounded-r-none', isLive ? 'z-0' : 'z-10'),
|
||||
color: to ? 'inherit' : 'secondary',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
color={isLive ? 'primary' : 'secondary'}
|
||||
sx={{
|
||||
backgroundColor: (theme) =>
|
||||
!isLive ? `${theme.palette.grey[200]} !important` : 'transparent',
|
||||
color: !isLive ? 'text.secondary' : undefined,
|
||||
}}
|
||||
className={twMerge(!isLive ? 'z-0 mt-4' : 'z-10')}
|
||||
startIcon={<ClockIcon className="h-4 w-4 self-center align-middle" />}
|
||||
onClick={handleLiveButtonClick}
|
||||
>
|
||||
Live
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface LogsRangeSelectorProps {
|
||||
onSubmitFilterValues: (value: LogsFilterFormValues) => void;
|
||||
}
|
||||
|
||||
function LogsRangeSelectorIntervalPickers({
|
||||
onSubmitFilterValues,
|
||||
}: LogsRangeSelectorProps) {
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const applicationCreationDate = new Date(currentProject.createdAt);
|
||||
|
||||
const { setValue, getValues } = useFormContext<LogsFilterFormValues>();
|
||||
const { from } = useWatch<LogsFilterFormValues>();
|
||||
|
||||
const { handleClose } = useDropdown();
|
||||
|
||||
const handleApply = () => {
|
||||
onSubmitFilterValues(getValues());
|
||||
handleClose();
|
||||
};
|
||||
|
||||
/**
|
||||
* Will subtract the `customInterval` time in minutes from the current date.
|
||||
*/
|
||||
function handleIntervalChange({
|
||||
minutesToDecreaseFromCurrentDate,
|
||||
}: LogsCustomInterval) {
|
||||
setValue('from', subMinutes(new Date(), minutesToDecreaseFromCurrentDate));
|
||||
setValue('to', new Date());
|
||||
}
|
||||
|
||||
return (
|
||||
<Box className="flex flex-col space-y-4">
|
||||
<div className="flex flex-col space-y-4">
|
||||
<LogsDatePicker
|
||||
label="From"
|
||||
value={from}
|
||||
onChange={(date) => setValue('from', date)}
|
||||
minDate={applicationCreationDate}
|
||||
maxDate={new Date()}
|
||||
/>
|
||||
|
||||
<LogsToDatePickerLiveButton />
|
||||
</div>
|
||||
|
||||
<Box className="grid grid-cols-2 gap-2">
|
||||
{LOGS_AVAILABLE_INTERVALS.map((logInterval) => (
|
||||
<Button
|
||||
key={logInterval.label}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
className="self-center"
|
||||
onClick={() => handleIntervalChange(logInterval)}
|
||||
>
|
||||
Last {logInterval.label}
|
||||
</Button>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
<Button color="primary" variant="contained" onClick={handleApply}>
|
||||
Apply
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default function LogsRangeSelector({
|
||||
onSubmitFilterValues,
|
||||
}: LogsRangeSelectorProps) {
|
||||
const { from, to } = useWatch<LogsFilterFormValues>();
|
||||
|
||||
return (
|
||||
<Dropdown.Root>
|
||||
<Dropdown.Trigger hideChevron className="flex w-full rounded-full">
|
||||
<Button
|
||||
component="a"
|
||||
className="h-10 w-full min-w-40 items-center justify-between"
|
||||
variant="outlined"
|
||||
>
|
||||
<span>
|
||||
{to === null
|
||||
? 'Live'
|
||||
: `${formatDistance(to.getTime(), from.getTime())}`}
|
||||
</span>
|
||||
<ChevronDownIcon className="h-3 w-3" />
|
||||
</Button>
|
||||
</Dropdown.Trigger>
|
||||
|
||||
<Dropdown.Content PaperProps={{ className: 'mt-1 max-w-xs w-full p-3' }}>
|
||||
<LogsRangeSelectorIntervalPickers
|
||||
onSubmitFilterValues={onSubmitFilterValues}
|
||||
/>
|
||||
</Dropdown.Content>
|
||||
</Dropdown.Root>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as LogsRangeSelector } from './LogsRangeSelector';
|
||||
@@ -89,7 +89,7 @@ function LogsTimePicker({
|
||||
</Button>
|
||||
|
||||
<Button variant="contained" color="primary" onClick={handleApply}>
|
||||
Apply
|
||||
Set
|
||||
</Button>
|
||||
</Box>
|
||||
</div>
|
||||
|
||||
@@ -26,4 +26,12 @@ export const LOGS_AVAILABLE_INTERVALS: LogsCustomInterval[] = [
|
||||
label: '60 min',
|
||||
minutesToDecreaseFromCurrentDate: 60,
|
||||
},
|
||||
{
|
||||
label: '12 hours',
|
||||
minutesToDecreaseFromCurrentDate: 720,
|
||||
},
|
||||
{
|
||||
label: '24 hours',
|
||||
minutesToDecreaseFromCurrentDate: 1440,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useIsCurrentUserOwner } from '@/features/projects/common/hooks/useIsCur
|
||||
import { PendingWorkspaceMemberInvitation } from '@/features/projects/workspaces/components/PendingWorkspaceMemberInvitation';
|
||||
import { WorkspaceMember } from '@/features/projects/workspaces/components/WorkspaceMember';
|
||||
import { discordAnnounce } from '@/utils/discordAnnounce';
|
||||
import { getErrorMessage } from '@/utils/getErrorMessage';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { triggerToast } from '@/utils/toast';
|
||||
import {
|
||||
refetchGetWorkspaceMembersQuery,
|
||||
@@ -52,38 +52,42 @@ function WorkspaceMemberInviteForm({
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await insertWorkspaceMemberInvite({
|
||||
variables: {
|
||||
workspaceMemberInvite: {
|
||||
workspaceId: currentWorkspace.id,
|
||||
email,
|
||||
memberType: 'member',
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await insertWorkspaceMemberInvite({
|
||||
variables: {
|
||||
workspaceMemberInvite: {
|
||||
workspaceId: currentWorkspace.id,
|
||||
email,
|
||||
memberType: 'member',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
triggerToast(
|
||||
`Invite to join workspace ${currentWorkspace.name} sent to ${email}.`,
|
||||
);
|
||||
} catch (error) {
|
||||
await discordAnnounce(
|
||||
`Error trying to invite to ${email} to ${currentWorkspace.name} ${error.message}`,
|
||||
);
|
||||
if (
|
||||
error.message ===
|
||||
'Foreign key violation. insert or update on table "workspace_member_invites" violates foreign key constraint "workspace_member_invites_email_fkey"'
|
||||
) {
|
||||
setWorkspaceInviteError(
|
||||
'You can only invite users that are already registered at Nhost. Ask the person to register an account, then invite them again.',
|
||||
});
|
||||
|
||||
triggerToast(
|
||||
`Invite to join workspace ${currentWorkspace.name} sent to ${email}.`,
|
||||
);
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Sending invite...',
|
||||
successMessage: 'The invite has been sent successfully.',
|
||||
errorMessage: `Error trying to invite to ${email} to ${currentWorkspace.name}`,
|
||||
onError: async (error) => {
|
||||
await discordAnnounce(
|
||||
`Error trying to invite to ${email} to ${currentWorkspace.name} ${error.message}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setWorkspaceInviteError(getErrorMessage(error, 'invite'));
|
||||
|
||||
return;
|
||||
}
|
||||
if (
|
||||
error.message ===
|
||||
'Foreign key violation. insert or update on table "workspace_member_invites" violates foreign key constraint "workspace_member_invites_email_fkey"'
|
||||
) {
|
||||
setWorkspaceInviteError(
|
||||
'You can only invite users that are already registered at Nhost. Ask the person to register an account, then invite them again.',
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
setEmail('');
|
||||
};
|
||||
@@ -130,8 +134,8 @@ export default function WorkspaceMembers() {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="mx-auto mt-18 max-w-3xl font-display">
|
||||
<div className="mb-2 grid grid-flow-row gap-1">
|
||||
<div className="max-w-3xl mx-auto mt-18 font-display">
|
||||
<div className="grid grid-flow-row gap-1 mb-2">
|
||||
<Text variant="h3">Members</Text>
|
||||
<Text color="secondary" className="text-sm">
|
||||
People in this workspace can manage all projects listed above.
|
||||
|
||||
@@ -38,6 +38,7 @@ import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { parse } from 'shell-quote';
|
||||
import { HealthCheckFormSection } from './components/HealthCheckFormSection';
|
||||
import { ServiceConfirmationDialog } from './components/ServiceConfirmationDialog';
|
||||
import { ServiceDetailsDialog } from './components/ServiceDetailsDialog';
|
||||
|
||||
@@ -118,6 +119,13 @@ export default function ServiceForm({
|
||||
type: item.type,
|
||||
publish: item.publish,
|
||||
})),
|
||||
healthCheck: values.healthCheck
|
||||
? {
|
||||
port: values.healthCheck?.port,
|
||||
initialDelaySeconds: values.healthCheck?.initialDelaySeconds,
|
||||
probePeriodSeconds: values.healthCheck?.probePeriodSeconds,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
|
||||
return config;
|
||||
@@ -375,6 +383,8 @@ export default function ServiceForm({
|
||||
|
||||
<StorageFormSection />
|
||||
|
||||
<HealthCheckFormSection />
|
||||
|
||||
{createServiceFormError && (
|
||||
<Alert
|
||||
severity="error"
|
||||
|
||||
@@ -41,6 +41,14 @@ export const validationSchema = Yup.object({
|
||||
})
|
||||
.required(),
|
||||
),
|
||||
healthCheck: Yup.object()
|
||||
.shape({
|
||||
port: Yup.number().required(),
|
||||
initialDelaySeconds: Yup.number().required(),
|
||||
probePeriodSeconds: Yup.number().required(),
|
||||
})
|
||||
.nullable()
|
||||
.default(undefined),
|
||||
});
|
||||
|
||||
export type ServiceFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import { Box } from '@/components/ui/v2/Box';
|
||||
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { Switch } from '@/components/ui/v2/Switch';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||
import type { ServiceFormValues } from '@/features/services/components/ServiceForm/ServiceFormTypes';
|
||||
import { useState } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
|
||||
export default function HealthCheckFormSection() {
|
||||
const {
|
||||
watch,
|
||||
setValue,
|
||||
register,
|
||||
formState: { errors },
|
||||
} = useFormContext<ServiceFormValues>();
|
||||
|
||||
const healthCheck = watch('healthCheck');
|
||||
const [healthCheckEnabled, setHealthCheckEnabled] = useState(!!healthCheck);
|
||||
|
||||
const toggleHealthCheckEnabled = async (enabled: boolean) => {
|
||||
setHealthCheckEnabled(enabled);
|
||||
|
||||
if (!enabled) {
|
||||
setValue('healthCheck', null);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box className="space-y-4 rounded border-1 p-4">
|
||||
<Box className="flex flex-row items-center justify-between ">
|
||||
<Box className="flex flex-row items-center space-x-2">
|
||||
<Text variant="h4" className="font-semibold">
|
||||
Health Check
|
||||
</Text>
|
||||
|
||||
<Tooltip
|
||||
title={
|
||||
<span>
|
||||
Monitor the health and availability of a service. Refer to{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://docs.nhost.io/guides/run/health-checks"
|
||||
className="underline"
|
||||
>
|
||||
Health Check
|
||||
</a>{' '}
|
||||
for more information.
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<InfoIcon aria-label="Info" className="h-4 w-4" color="primary" />
|
||||
</Tooltip>
|
||||
</Box>
|
||||
|
||||
<Switch
|
||||
checked={healthCheckEnabled}
|
||||
onChange={(e) => toggleHealthCheckEnabled(e.target.checked)}
|
||||
className="self-center"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{healthCheckEnabled && (
|
||||
<Box className="flex flex-col space-y-4">
|
||||
<Input
|
||||
{...register(`healthCheck.port`)}
|
||||
id="healthCheck.port"
|
||||
label="Port"
|
||||
placeholder="3000"
|
||||
className="w-full"
|
||||
hideEmptyHelperText
|
||||
error={!!errors?.healthCheck?.port}
|
||||
helperText={errors?.healthCheck?.port?.message}
|
||||
fullWidth
|
||||
autoComplete="off"
|
||||
type="number"
|
||||
/>
|
||||
|
||||
<Input
|
||||
{...register(`healthCheck.initialDelaySeconds`)}
|
||||
id="healthCheck.initialDelaySeconds"
|
||||
label="Initial delay seconds"
|
||||
placeholder="30"
|
||||
className="w-full"
|
||||
hideEmptyHelperText
|
||||
error={!!errors?.healthCheck?.initialDelaySeconds}
|
||||
helperText={errors?.healthCheck?.initialDelaySeconds?.message}
|
||||
fullWidth
|
||||
autoComplete="off"
|
||||
type="number"
|
||||
/>
|
||||
|
||||
<Input
|
||||
{...register(`healthCheck.probePeriodSeconds`)}
|
||||
id="healthCheck.probePeriodSeconds"
|
||||
label="Probe period seconds"
|
||||
placeholder="60"
|
||||
className="w-full"
|
||||
hideEmptyHelperText
|
||||
error={!!errors?.healthCheck?.probePeriodSeconds}
|
||||
helperText={errors?.healthCheck?.probePeriodSeconds?.message}
|
||||
fullWidth
|
||||
autoComplete="off"
|
||||
type="number"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as HealthCheckFormSection } from './HealthCheckFormSection';
|
||||
@@ -1,4 +1,4 @@
|
||||
fragment EnvironmentVariable on ConfigEnvironmentVariable {
|
||||
fragment EnvironmentVariable on ConfigGlobalEnvironmentVariable {
|
||||
id: name
|
||||
name
|
||||
value
|
||||
|
||||
@@ -3,8 +3,15 @@ query getProjectLogs(
|
||||
$service: String
|
||||
$from: Timestamp
|
||||
$to: Timestamp
|
||||
$regexFilter: String
|
||||
) {
|
||||
logs(appID: $appID, service: $service, from: $from, to: $to) {
|
||||
logs(
|
||||
appID: $appID
|
||||
service: $service
|
||||
from: $from
|
||||
to: $to
|
||||
regexFilter: $regexFilter
|
||||
) {
|
||||
log
|
||||
service
|
||||
timestamp
|
||||
@@ -15,8 +22,14 @@ subscription getLogsSubscription(
|
||||
$appID: String!
|
||||
$service: String
|
||||
$from: Timestamp
|
||||
$regexFilter: String
|
||||
) {
|
||||
logs(appID: $appID, service: $service, from: $from) {
|
||||
logs(
|
||||
appID: $appID
|
||||
service: $service
|
||||
from: $from
|
||||
regexFilter: $regexFilter
|
||||
) {
|
||||
log
|
||||
service
|
||||
timestamp
|
||||
|
||||
3
dashboard/src/gql/logs/getServiceLabelValues.gql
Normal file
3
dashboard/src/gql/logs/getServiceLabelValues.gql
Normal file
@@ -0,0 +1,3 @@
|
||||
query getServiceLabelValues($appID: String!) {
|
||||
getServiceLabelValues(appID: $appID)
|
||||
}
|
||||
@@ -40,6 +40,11 @@ query getRunServices(
|
||||
fqdn
|
||||
}
|
||||
}
|
||||
healthCheck {
|
||||
port
|
||||
initialDelaySeconds
|
||||
probePeriodSeconds
|
||||
}
|
||||
}
|
||||
}
|
||||
runServices_aggregate {
|
||||
|
||||
@@ -35,7 +35,24 @@ export default function useRemoteApplicationGQLClientWithSubscriptions() {
|
||||
);
|
||||
|
||||
return new ApolloClient({
|
||||
cache: new InMemoryCache(),
|
||||
cache: new InMemoryCache({
|
||||
typePolicies: {
|
||||
Subscription: {
|
||||
fields: {
|
||||
logs: {
|
||||
keyArgs: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
Query: {
|
||||
fields: {
|
||||
logs: {
|
||||
keyArgs: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
connectToDevTools: true,
|
||||
link: split(
|
||||
({ query }) => {
|
||||
|
||||
@@ -2,49 +2,53 @@ import { ProjectLayout } from '@/components/layout/ProjectLayout';
|
||||
import { RetryableErrorBoundary } from '@/components/presentational/RetryableErrorBoundary';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { LogsBody } from '@/features/projects/logs/components/LogsBody';
|
||||
import { LogsHeader } from '@/features/projects/logs/components/LogsHeader';
|
||||
import {
|
||||
LogsHeader,
|
||||
type LogsFilterFormValues,
|
||||
} from '@/features/projects/logs/components/LogsHeader';
|
||||
import { AvailableLogsService } from '@/features/projects/logs/utils/constants/services';
|
||||
import { useRemoteApplicationGQLClientWithSubscriptions } from '@/hooks/useRemoteApplicationGQLClientWithSubscriptions';
|
||||
import { MINUTES_TO_DECREASE_FROM_CURRENT_DATE } from '@/utils/constants/common';
|
||||
import {
|
||||
GetLogsSubscriptionDocument,
|
||||
useGetProjectLogsQuery,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import { subMinutes } from 'date-fns';
|
||||
import type { ReactElement } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
type ReactElement,
|
||||
} from 'react';
|
||||
|
||||
const MINUTES_TO_DECREASE_FROM_CURRENT_DATE = 20;
|
||||
interface LogsFilters {
|
||||
from: Date;
|
||||
to: Date | null;
|
||||
service: AvailableLogsService;
|
||||
regexFilter: string;
|
||||
}
|
||||
|
||||
export default function LogsPage() {
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [fromDate, setFromDate] = useState<Date>(
|
||||
subMinutes(new Date(), MINUTES_TO_DECREASE_FROM_CURRENT_DATE),
|
||||
);
|
||||
const [toDate, setToDate] = useState<Date | null>(new Date());
|
||||
const [service, setService] = useState<AvailableLogsService>(
|
||||
AvailableLogsService.ALL,
|
||||
);
|
||||
|
||||
// create a client that sends http requests to Hasura but websocket requests to Bragi
|
||||
const clientWithSplit = useRemoteApplicationGQLClientWithSubscriptions();
|
||||
const subscriptionReturn = useRef(null);
|
||||
|
||||
/**
|
||||
* Will change the specific service from which we query logs.
|
||||
*/
|
||||
function handleServiceChange(value: AvailableLogsService) {
|
||||
setService(value);
|
||||
}
|
||||
const [filters, setFilters] = useState<LogsFilters>({
|
||||
from: subMinutes(new Date(), MINUTES_TO_DECREASE_FROM_CURRENT_DATE),
|
||||
to: new Date(),
|
||||
regexFilter: '',
|
||||
service: AvailableLogsService.ALL,
|
||||
});
|
||||
|
||||
const { data, loading, error, subscribeToMore, client } =
|
||||
const { data, error, subscribeToMore, client, loading, refetch } =
|
||||
useGetProjectLogsQuery({
|
||||
variables: {
|
||||
appID: currentProject.id,
|
||||
from: fromDate,
|
||||
to: toDate,
|
||||
service,
|
||||
},
|
||||
variables: { appID: currentProject.id, ...filters },
|
||||
client: clientWithSplit,
|
||||
fetchPolicy: 'cache-and-network',
|
||||
notifyOnNetworkStatusChange: true,
|
||||
});
|
||||
|
||||
const subscribeToMoreLogs = useCallback(
|
||||
@@ -53,8 +57,9 @@ export default function LogsPage() {
|
||||
document: GetLogsSubscriptionDocument,
|
||||
variables: {
|
||||
appID: currentProject.id,
|
||||
service,
|
||||
from: fromDate,
|
||||
service: filters.service,
|
||||
from: filters.from,
|
||||
regexFilter: filters.regexFilter,
|
||||
},
|
||||
updateQuery: (prev, { subscriptionData }) => {
|
||||
// if there is no new data, just return the previous data
|
||||
@@ -93,40 +98,47 @@ export default function LogsPage() {
|
||||
};
|
||||
},
|
||||
}),
|
||||
[subscribeToMore, currentProject.id, service, fromDate],
|
||||
[subscribeToMore, currentProject.id, filters],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (toDate && subscriptionReturn.current !== null) {
|
||||
if (filters.to && subscriptionReturn.current !== null) {
|
||||
subscriptionReturn.current();
|
||||
subscriptionReturn.current = null;
|
||||
|
||||
return () => {};
|
||||
}
|
||||
|
||||
if (toDate) {
|
||||
if (filters.to) {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
if (subscriptionReturn.current) {
|
||||
subscriptionReturn.current();
|
||||
subscriptionReturn.current = null;
|
||||
}
|
||||
|
||||
// This will open the websocket connection and it will return a function to close it.
|
||||
subscriptionReturn.current = subscribeToMoreLogs();
|
||||
|
||||
// get rid of the current apollo client instance (will also close the websocket if it's the live status)
|
||||
return () => client.stop();
|
||||
}, [subscribeToMoreLogs, toDate, client]);
|
||||
return () => {};
|
||||
}, [filters, subscribeToMoreLogs, client]);
|
||||
|
||||
const onSubmitFilterValues = useCallback(
|
||||
async (values: LogsFilterFormValues) => {
|
||||
setFilters({ ...(values as LogsFilters) });
|
||||
await refetch();
|
||||
},
|
||||
[setFilters, refetch],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col">
|
||||
<RetryableErrorBoundary>
|
||||
<LogsHeader
|
||||
fromDate={fromDate}
|
||||
toDate={toDate}
|
||||
service={service}
|
||||
onServiceChange={handleServiceChange}
|
||||
onFromDateChange={setFromDate}
|
||||
onToDateChange={setToDate}
|
||||
loading={loading}
|
||||
onSubmitFilterValues={onSubmitFilterValues}
|
||||
/>
|
||||
|
||||
<LogsBody error={error} loading={loading} logsData={data} />
|
||||
</RetryableErrorBoundary>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Container } from '@/components/layout/Container';
|
||||
import { RetryableErrorBoundary } from '@/components/presentational/RetryableErrorBoundary';
|
||||
import { AccountSettingsLayout } from '@/features/account/settings/components/AccountSettingsLayout';
|
||||
import { DeleteAccount } from '@/features/account/settings/components/DeleteAccount';
|
||||
import { DisplayNameSetting } from '@/features/account/settings/components/DisplayNameSetting';
|
||||
import { PasswordSettings } from '@/features/account/settings/components/PasswordSettings';
|
||||
import { PATSettings } from '@/features/account/settings/components/PATSettings';
|
||||
import type { ReactElement } from 'react';
|
||||
@@ -12,6 +13,10 @@ export default function AccountSettingsPage() {
|
||||
className="grid max-w-5xl grid-flow-row gap-8 bg-transparent"
|
||||
rootClassName="bg-transparent"
|
||||
>
|
||||
<RetryableErrorBoundary>
|
||||
<DisplayNameSetting />
|
||||
</RetryableErrorBoundary>
|
||||
|
||||
<RetryableErrorBoundary>
|
||||
<PasswordSettings />
|
||||
</RetryableErrorBoundary>
|
||||
|
||||
192
dashboard/src/utils/__generated__/graphql.ts
generated
192
dashboard/src/utils/__generated__/graphql.ts
generated
@@ -196,6 +196,7 @@ export type ConfigAppSystemConfig = {
|
||||
*/
|
||||
export type ConfigAuth = {
|
||||
__typename?: 'ConfigAuth';
|
||||
elevatedPrivileges?: Maybe<ConfigAuthElevatedPrivileges>;
|
||||
method?: Maybe<ConfigAuthMethod>;
|
||||
redirections?: Maybe<ConfigAuthRedirections>;
|
||||
/** Resources for the service */
|
||||
@@ -219,6 +220,7 @@ export type ConfigAuthComparisonExp = {
|
||||
_and?: InputMaybe<Array<ConfigAuthComparisonExp>>;
|
||||
_not?: InputMaybe<ConfigAuthComparisonExp>;
|
||||
_or?: InputMaybe<Array<ConfigAuthComparisonExp>>;
|
||||
elevatedPrivileges?: InputMaybe<ConfigAuthElevatedPrivilegesComparisonExp>;
|
||||
method?: InputMaybe<ConfigAuthMethodComparisonExp>;
|
||||
redirections?: InputMaybe<ConfigAuthRedirectionsComparisonExp>;
|
||||
resources?: InputMaybe<ConfigResourcesComparisonExp>;
|
||||
@@ -229,7 +231,28 @@ export type ConfigAuthComparisonExp = {
|
||||
version?: InputMaybe<ConfigStringComparisonExp>;
|
||||
};
|
||||
|
||||
export type ConfigAuthElevatedPrivileges = {
|
||||
__typename?: 'ConfigAuthElevatedPrivileges';
|
||||
mode?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ConfigAuthElevatedPrivilegesComparisonExp = {
|
||||
_and?: InputMaybe<Array<ConfigAuthElevatedPrivilegesComparisonExp>>;
|
||||
_not?: InputMaybe<ConfigAuthElevatedPrivilegesComparisonExp>;
|
||||
_or?: InputMaybe<Array<ConfigAuthElevatedPrivilegesComparisonExp>>;
|
||||
mode?: InputMaybe<ConfigStringComparisonExp>;
|
||||
};
|
||||
|
||||
export type ConfigAuthElevatedPrivilegesInsertInput = {
|
||||
mode?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ConfigAuthElevatedPrivilegesUpdateInput = {
|
||||
mode?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ConfigAuthInsertInput = {
|
||||
elevatedPrivileges?: InputMaybe<ConfigAuthElevatedPrivilegesInsertInput>;
|
||||
method?: InputMaybe<ConfigAuthMethodInsertInput>;
|
||||
redirections?: InputMaybe<ConfigAuthRedirectionsInsertInput>;
|
||||
resources?: InputMaybe<ConfigResourcesInsertInput>;
|
||||
@@ -808,6 +831,7 @@ export type ConfigAuthTotpUpdateInput = {
|
||||
};
|
||||
|
||||
export type ConfigAuthUpdateInput = {
|
||||
elevatedPrivileges?: InputMaybe<ConfigAuthElevatedPrivilegesUpdateInput>;
|
||||
method?: InputMaybe<ConfigAuthMethodUpdateInput>;
|
||||
redirections?: InputMaybe<ConfigAuthRedirectionsUpdateInput>;
|
||||
resources?: InputMaybe<ConfigResourcesUpdateInput>;
|
||||
@@ -1248,22 +1272,47 @@ export type ConfigFunctionsUpdateInput = {
|
||||
export type ConfigGlobal = {
|
||||
__typename?: 'ConfigGlobal';
|
||||
/** User-defined environment variables that are spread over all services */
|
||||
environment?: Maybe<Array<ConfigEnvironmentVariable>>;
|
||||
environment?: Maybe<Array<ConfigGlobalEnvironmentVariable>>;
|
||||
};
|
||||
|
||||
export type ConfigGlobalComparisonExp = {
|
||||
_and?: InputMaybe<Array<ConfigGlobalComparisonExp>>;
|
||||
_not?: InputMaybe<ConfigGlobalComparisonExp>;
|
||||
_or?: InputMaybe<Array<ConfigGlobalComparisonExp>>;
|
||||
environment?: InputMaybe<ConfigEnvironmentVariableComparisonExp>;
|
||||
environment?: InputMaybe<ConfigGlobalEnvironmentVariableComparisonExp>;
|
||||
};
|
||||
|
||||
export type ConfigGlobalEnvironmentVariable = {
|
||||
__typename?: 'ConfigGlobalEnvironmentVariable';
|
||||
name: Scalars['String'];
|
||||
/** Value of the environment variable */
|
||||
value: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ConfigGlobalEnvironmentVariableComparisonExp = {
|
||||
_and?: InputMaybe<Array<ConfigGlobalEnvironmentVariableComparisonExp>>;
|
||||
_not?: InputMaybe<ConfigGlobalEnvironmentVariableComparisonExp>;
|
||||
_or?: InputMaybe<Array<ConfigGlobalEnvironmentVariableComparisonExp>>;
|
||||
name?: InputMaybe<ConfigStringComparisonExp>;
|
||||
value?: InputMaybe<ConfigStringComparisonExp>;
|
||||
};
|
||||
|
||||
export type ConfigGlobalEnvironmentVariableInsertInput = {
|
||||
name: Scalars['String'];
|
||||
value: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ConfigGlobalEnvironmentVariableUpdateInput = {
|
||||
name?: InputMaybe<Scalars['String']>;
|
||||
value?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ConfigGlobalInsertInput = {
|
||||
environment?: InputMaybe<Array<ConfigEnvironmentVariableInsertInput>>;
|
||||
environment?: InputMaybe<Array<ConfigGlobalEnvironmentVariableInsertInput>>;
|
||||
};
|
||||
|
||||
export type ConfigGlobalUpdateInput = {
|
||||
environment?: InputMaybe<Array<ConfigEnvironmentVariableUpdateInput>>;
|
||||
environment?: InputMaybe<Array<ConfigGlobalEnvironmentVariableUpdateInput>>;
|
||||
};
|
||||
|
||||
export type ConfigGrafana = {
|
||||
@@ -5238,9 +5287,9 @@ export type AuthUserProviders_Bool_Exp = {
|
||||
export enum AuthUserProviders_Constraint {
|
||||
/** unique or primary key constraint on columns "id" */
|
||||
UserProvidersPkey = 'user_providers_pkey',
|
||||
/** unique or primary key constraint on columns "provider_id", "provider_user_id" */
|
||||
/** unique or primary key constraint on columns "provider_user_id", "provider_id" */
|
||||
UserProvidersProviderIdProviderUserIdKey = 'user_providers_provider_id_provider_user_id_key',
|
||||
/** unique or primary key constraint on columns "provider_id", "user_id" */
|
||||
/** unique or primary key constraint on columns "user_id", "provider_id" */
|
||||
UserProvidersUserIdProviderIdKey = 'user_providers_user_id_provider_id_key'
|
||||
}
|
||||
|
||||
@@ -15774,6 +15823,7 @@ export type Query_Root = {
|
||||
runServiceConfig?: Maybe<ConfigRunServiceConfig>;
|
||||
runServiceConfigRawJSON: Scalars['String'];
|
||||
runServiceConfigs: Array<ConfigRunServiceConfigWithId>;
|
||||
runServiceConfigsAll: Array<ConfigRunServiceConfigWithId>;
|
||||
/** An array relationship */
|
||||
runServices: Array<Run_Service>;
|
||||
/** fetch aggregated fields from the table: "run_service" */
|
||||
@@ -16755,6 +16805,12 @@ export type Query_RootRunServiceConfigRawJsonArgs = {
|
||||
|
||||
|
||||
export type Query_RootRunServiceConfigsArgs = {
|
||||
appID: Scalars['uuid'];
|
||||
resolve: Scalars['Boolean'];
|
||||
};
|
||||
|
||||
|
||||
export type Query_RootRunServiceConfigsAllArgs = {
|
||||
resolve: Scalars['Boolean'];
|
||||
where?: InputMaybe<ConfigRunServiceConfigComparisonExp>;
|
||||
};
|
||||
@@ -21355,7 +21411,7 @@ export type WorkspaceMemberInvites_Bool_Exp = {
|
||||
|
||||
/** unique or primary key constraints on table "workspace_member_invites" */
|
||||
export enum WorkspaceMemberInvites_Constraint {
|
||||
/** unique or primary key constraint on columns "email", "workspace_id" */
|
||||
/** unique or primary key constraint on columns "workspace_id", "email" */
|
||||
WorkspaceMemberInvitesEmailWorkspaceIdKey = 'workspace_member_invites_email_workspace_id_key',
|
||||
/** unique or primary key constraint on columns "id" */
|
||||
WorkspaceMemberInvitesPkey = 'workspace_member_invites_pkey'
|
||||
@@ -22420,12 +22476,20 @@ export type DeletePersonalAccessTokenMutationVariables = Exact<{
|
||||
|
||||
export type DeletePersonalAccessTokenMutation = { __typename?: 'mutation_root', deletePersonalAccessToken?: { __typename?: 'authRefreshTokens', id: any, metadata?: any | null } | null };
|
||||
|
||||
export type UpdateUserDisplayNameMutationVariables = Exact<{
|
||||
id: Scalars['uuid'];
|
||||
displayName: Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateUserDisplayNameMutation = { __typename?: 'mutation_root', updateUser?: { __typename?: 'users', id: any, displayName: string } | null };
|
||||
|
||||
export type GetAiSettingsQueryVariables = Exact<{
|
||||
appId: Scalars['uuid'];
|
||||
}>;
|
||||
|
||||
|
||||
export type GetAiSettingsQuery = { __typename?: 'query_root', config?: { __typename?: 'ConfigConfig', ai?: { __typename?: 'ConfigAI', version?: string | null, webhookSecret: string, autoEmbeddings?: { __typename?: 'ConfigAIAutoEmbeddings', synchPeriodMinutes?: any | null } | null, openai: { __typename?: 'ConfigAIOpenai', apiKey: string, organization?: string | null }, resources: { __typename?: 'ConfigAIResources', compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any } } } | null } | null };
|
||||
export type GetAiSettingsQuery = { __typename?: 'query_root', config?: { __typename?: 'ConfigConfig', postgres?: { __typename?: 'ConfigPostgres', version?: string | null } | null, ai?: { __typename?: 'ConfigAI', version?: string | null, webhookSecret: string, autoEmbeddings?: { __typename?: 'ConfigAIAutoEmbeddings', synchPeriodMinutes?: any | null } | null, openai: { __typename?: 'ConfigAIOpenai', apiKey: string, organization?: string | null }, resources: { __typename?: 'ConfigAIResources', compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any } } } | null } | null };
|
||||
|
||||
export type GetAuthenticationSettingsQueryVariables = Exact<{
|
||||
appId: Scalars['uuid'];
|
||||
@@ -22598,7 +22662,7 @@ export type DnsLookupCnameQueryVariables = Exact<{
|
||||
|
||||
export type DnsLookupCnameQuery = { __typename?: 'query_root', dnsLookupCNAME: string };
|
||||
|
||||
export type EnvironmentVariableFragment = { __typename?: 'ConfigEnvironmentVariable', name: string, value: string, id: string };
|
||||
export type EnvironmentVariableFragment = { __typename?: 'ConfigGlobalEnvironmentVariable', name: string, value: string, id: string };
|
||||
|
||||
export type JwtSecretFragment = { __typename?: 'ConfigJWTSecret', issuer?: string | null, key?: string | null, type?: string | null, jwk_url?: any | null, header?: string | null, claims_namespace_path?: string | null, claims_namespace?: string | null, claims_format?: string | null, audience?: string | null, allowed_skew?: any | null };
|
||||
|
||||
@@ -22607,7 +22671,7 @@ export type GetEnvironmentVariablesQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type GetEnvironmentVariablesQuery = { __typename?: 'query_root', config?: { __typename: 'ConfigConfig', id: 'ConfigConfig', global?: { __typename?: 'ConfigGlobal', environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string, id: string }> | null } | null, hasura: { __typename?: 'ConfigHasura', adminSecret: string, webhookSecret: string, jwtSecrets?: Array<{ __typename?: 'ConfigJWTSecret', issuer?: string | null, key?: string | null, type?: string | null, jwk_url?: any | null, header?: string | null, claims_namespace_path?: string | null, claims_namespace?: string | null, claims_format?: string | null, audience?: string | null, allowed_skew?: any | null }> | null } } | null };
|
||||
export type GetEnvironmentVariablesQuery = { __typename?: 'query_root', config?: { __typename: 'ConfigConfig', id: 'ConfigConfig', global?: { __typename?: 'ConfigGlobal', environment?: Array<{ __typename?: 'ConfigGlobalEnvironmentVariable', name: string, value: string, id: string }> | null } | null, hasura: { __typename?: 'ConfigHasura', adminSecret: string, webhookSecret: string, jwtSecrets?: Array<{ __typename?: 'ConfigJWTSecret', issuer?: string | null, key?: string | null, type?: string | null, jwk_url?: any | null, header?: string | null, claims_namespace_path?: string | null, claims_namespace?: string | null, claims_format?: string | null, audience?: string | null, allowed_skew?: any | null }> | null } } | null };
|
||||
|
||||
export type PermissionVariableFragment = { __typename?: 'ConfigAuthsessionaccessTokenCustomClaims', key: string, value: string, id: string };
|
||||
|
||||
@@ -22772,6 +22836,7 @@ export type GetProjectLogsQueryVariables = Exact<{
|
||||
service?: InputMaybe<Scalars['String']>;
|
||||
from?: InputMaybe<Scalars['Timestamp']>;
|
||||
to?: InputMaybe<Scalars['Timestamp']>;
|
||||
regexFilter?: InputMaybe<Scalars['String']>;
|
||||
}>;
|
||||
|
||||
|
||||
@@ -22781,11 +22846,19 @@ export type GetLogsSubscriptionSubscriptionVariables = Exact<{
|
||||
appID: Scalars['String'];
|
||||
service?: InputMaybe<Scalars['String']>;
|
||||
from?: InputMaybe<Scalars['Timestamp']>;
|
||||
regexFilter?: InputMaybe<Scalars['String']>;
|
||||
}>;
|
||||
|
||||
|
||||
export type GetLogsSubscriptionSubscription = { __typename?: 'subscription_root', logs: Array<{ __typename?: 'Log', log: string, service: string, timestamp: any }> };
|
||||
|
||||
export type GetServiceLabelValuesQueryVariables = Exact<{
|
||||
appID: Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type GetServiceLabelValuesQuery = { __typename?: 'query_root', getServiceLabelValues: Array<string> };
|
||||
|
||||
export type DeletePaymentMethodMutationVariables = Exact<{
|
||||
paymentMethodId: Scalars['uuid'];
|
||||
}>;
|
||||
@@ -22960,7 +23033,7 @@ export type GetRunServicesQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type GetRunServicesQuery = { __typename?: 'query_root', app?: { __typename?: 'apps', runServices: Array<{ __typename?: 'run_service', id: any, createdAt: any, updatedAt: any, subdomain: string, config?: { __typename?: 'ConfigRunServiceConfig', name: any, command?: Array<string> | null, image: { __typename?: 'ConfigRunServiceImage', image: string }, resources: { __typename?: 'ConfigRunServiceResources', replicas: any, compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any }, storage?: Array<{ __typename?: 'ConfigRunServiceResourcesStorage', name: any, path: string, capacity: any }> | null }, environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string }> | null, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null }> | null } | null }>, runServices_aggregate: { __typename?: 'run_service_aggregate', aggregate?: { __typename?: 'run_service_aggregate_fields', count: number } | null } } | null };
|
||||
export type GetRunServicesQuery = { __typename?: 'query_root', app?: { __typename?: 'apps', runServices: Array<{ __typename?: 'run_service', id: any, createdAt: any, updatedAt: any, subdomain: string, config?: { __typename?: 'ConfigRunServiceConfig', name: any, command?: Array<string> | null, image: { __typename?: 'ConfigRunServiceImage', image: string }, resources: { __typename?: 'ConfigRunServiceResources', replicas: any, compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any }, storage?: Array<{ __typename?: 'ConfigRunServiceResourcesStorage', name: any, path: string, capacity: any }> | null }, environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string }> | null, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null }> | null, healthCheck?: { __typename?: 'ConfigHealthCheck', port: any, initialDelaySeconds?: number | null, probePeriodSeconds?: number | null } | null } | null }>, runServices_aggregate: { __typename?: 'run_service_aggregate', aggregate?: { __typename?: 'run_service_aggregate_fields', count: number } | null } } | null };
|
||||
|
||||
export type InsertRunServiceMutationVariables = Exact<{
|
||||
object: Run_Service_Insert_Input;
|
||||
@@ -23187,7 +23260,7 @@ export const PrefetchNewAppWorkspaceFragmentDoc = gql`
|
||||
}
|
||||
`;
|
||||
export const EnvironmentVariableFragmentDoc = gql`
|
||||
fragment EnvironmentVariable on ConfigEnvironmentVariable {
|
||||
fragment EnvironmentVariable on ConfigGlobalEnvironmentVariable {
|
||||
id: name
|
||||
name
|
||||
value
|
||||
@@ -23521,9 +23594,47 @@ export function useDeletePersonalAccessTokenMutation(baseOptions?: Apollo.Mutati
|
||||
export type DeletePersonalAccessTokenMutationHookResult = ReturnType<typeof useDeletePersonalAccessTokenMutation>;
|
||||
export type DeletePersonalAccessTokenMutationResult = Apollo.MutationResult<DeletePersonalAccessTokenMutation>;
|
||||
export type DeletePersonalAccessTokenMutationOptions = Apollo.BaseMutationOptions<DeletePersonalAccessTokenMutation, DeletePersonalAccessTokenMutationVariables>;
|
||||
export const UpdateUserDisplayNameDocument = gql`
|
||||
mutation updateUserDisplayName($id: uuid!, $displayName: String!) {
|
||||
updateUser(pk_columns: {id: $id}, _set: {displayName: $displayName}) {
|
||||
id
|
||||
displayName
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type UpdateUserDisplayNameMutationFn = Apollo.MutationFunction<UpdateUserDisplayNameMutation, UpdateUserDisplayNameMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useUpdateUserDisplayNameMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useUpdateUserDisplayNameMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useUpdateUserDisplayNameMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [updateUserDisplayNameMutation, { data, loading, error }] = useUpdateUserDisplayNameMutation({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* displayName: // value for 'displayName'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useUpdateUserDisplayNameMutation(baseOptions?: Apollo.MutationHookOptions<UpdateUserDisplayNameMutation, UpdateUserDisplayNameMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<UpdateUserDisplayNameMutation, UpdateUserDisplayNameMutationVariables>(UpdateUserDisplayNameDocument, options);
|
||||
}
|
||||
export type UpdateUserDisplayNameMutationHookResult = ReturnType<typeof useUpdateUserDisplayNameMutation>;
|
||||
export type UpdateUserDisplayNameMutationResult = Apollo.MutationResult<UpdateUserDisplayNameMutation>;
|
||||
export type UpdateUserDisplayNameMutationOptions = Apollo.BaseMutationOptions<UpdateUserDisplayNameMutation, UpdateUserDisplayNameMutationVariables>;
|
||||
export const GetAiSettingsDocument = gql`
|
||||
query GetAISettings($appId: uuid!) {
|
||||
config(appID: $appId, resolve: false) {
|
||||
postgres {
|
||||
version
|
||||
}
|
||||
ai {
|
||||
version
|
||||
webhookSecret
|
||||
@@ -25558,8 +25669,14 @@ export function refetchGetGithubRepositoriesQuery(variables?: GetGithubRepositor
|
||||
return { query: GetGithubRepositoriesDocument, variables: variables }
|
||||
}
|
||||
export const GetProjectLogsDocument = gql`
|
||||
query getProjectLogs($appID: String!, $service: String, $from: Timestamp, $to: Timestamp) {
|
||||
logs(appID: $appID, service: $service, from: $from, to: $to) {
|
||||
query getProjectLogs($appID: String!, $service: String, $from: Timestamp, $to: Timestamp, $regexFilter: String) {
|
||||
logs(
|
||||
appID: $appID
|
||||
service: $service
|
||||
from: $from
|
||||
to: $to
|
||||
regexFilter: $regexFilter
|
||||
) {
|
||||
log
|
||||
service
|
||||
timestamp
|
||||
@@ -25583,6 +25700,7 @@ export const GetProjectLogsDocument = gql`
|
||||
* service: // value for 'service'
|
||||
* from: // value for 'from'
|
||||
* to: // value for 'to'
|
||||
* regexFilter: // value for 'regexFilter'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
@@ -25601,8 +25719,8 @@ export function refetchGetProjectLogsQuery(variables: GetProjectLogsQueryVariabl
|
||||
return { query: GetProjectLogsDocument, variables: variables }
|
||||
}
|
||||
export const GetLogsSubscriptionDocument = gql`
|
||||
subscription getLogsSubscription($appID: String!, $service: String, $from: Timestamp) {
|
||||
logs(appID: $appID, service: $service, from: $from) {
|
||||
subscription getLogsSubscription($appID: String!, $service: String, $from: Timestamp, $regexFilter: String) {
|
||||
logs(appID: $appID, service: $service, from: $from, regexFilter: $regexFilter) {
|
||||
log
|
||||
service
|
||||
timestamp
|
||||
@@ -25625,6 +25743,7 @@ export const GetLogsSubscriptionDocument = gql`
|
||||
* appID: // value for 'appID'
|
||||
* service: // value for 'service'
|
||||
* from: // value for 'from'
|
||||
* regexFilter: // value for 'regexFilter'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
@@ -25634,6 +25753,42 @@ export function useGetLogsSubscriptionSubscription(baseOptions: Apollo.Subscript
|
||||
}
|
||||
export type GetLogsSubscriptionSubscriptionHookResult = ReturnType<typeof useGetLogsSubscriptionSubscription>;
|
||||
export type GetLogsSubscriptionSubscriptionResult = Apollo.SubscriptionResult<GetLogsSubscriptionSubscription>;
|
||||
export const GetServiceLabelValuesDocument = gql`
|
||||
query getServiceLabelValues($appID: String!) {
|
||||
getServiceLabelValues(appID: $appID)
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useGetServiceLabelValuesQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useGetServiceLabelValuesQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useGetServiceLabelValuesQuery` 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 } = useGetServiceLabelValuesQuery({
|
||||
* variables: {
|
||||
* appID: // value for 'appID'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useGetServiceLabelValuesQuery(baseOptions: Apollo.QueryHookOptions<GetServiceLabelValuesQuery, GetServiceLabelValuesQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<GetServiceLabelValuesQuery, GetServiceLabelValuesQueryVariables>(GetServiceLabelValuesDocument, options);
|
||||
}
|
||||
export function useGetServiceLabelValuesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetServiceLabelValuesQuery, GetServiceLabelValuesQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<GetServiceLabelValuesQuery, GetServiceLabelValuesQueryVariables>(GetServiceLabelValuesDocument, options);
|
||||
}
|
||||
export type GetServiceLabelValuesQueryHookResult = ReturnType<typeof useGetServiceLabelValuesQuery>;
|
||||
export type GetServiceLabelValuesLazyQueryHookResult = ReturnType<typeof useGetServiceLabelValuesLazyQuery>;
|
||||
export type GetServiceLabelValuesQueryResult = Apollo.QueryResult<GetServiceLabelValuesQuery, GetServiceLabelValuesQueryVariables>;
|
||||
export function refetchGetServiceLabelValuesQuery(variables: GetServiceLabelValuesQueryVariables) {
|
||||
return { query: GetServiceLabelValuesDocument, variables: variables }
|
||||
}
|
||||
export const DeletePaymentMethodDocument = gql`
|
||||
mutation deletePaymentMethod($paymentMethodId: uuid!) {
|
||||
deletePaymentMethod(id: $paymentMethodId) {
|
||||
@@ -26577,6 +26732,11 @@ export const GetRunServicesDocument = gql`
|
||||
fqdn
|
||||
}
|
||||
}
|
||||
healthCheck {
|
||||
port
|
||||
initialDelaySeconds
|
||||
probePeriodSeconds
|
||||
}
|
||||
}
|
||||
}
|
||||
runServices_aggregate {
|
||||
|
||||
@@ -66,3 +66,8 @@ export const RESOURCE_VCPU_PRICE_PER_MINUTE = 0.0012;
|
||||
* Maximum number of free projects a user is allowed to have.
|
||||
*/
|
||||
export const MAX_FREE_PROJECTS = 1;
|
||||
|
||||
/**
|
||||
* Default value in minutes to use for querying the logs
|
||||
*/
|
||||
export const MINUTES_TO_DECREASE_FROM_CURRENT_DATE = 20;
|
||||
|
||||
@@ -29,6 +29,7 @@ export default async function execPromiseWithErrorToast(
|
||||
const result = await call();
|
||||
|
||||
toast.dismiss(loadingToastId);
|
||||
|
||||
toast.success(successMessage, {
|
||||
style: toastStyle.style,
|
||||
...toastStyle.success,
|
||||
|
||||
@@ -21,6 +21,9 @@ module.exports = {
|
||||
copper: '#DD792D',
|
||||
paper: '#171d26',
|
||||
divider: '#2f363d',
|
||||
'primary-main': '#0052cd',
|
||||
'primary-light': '#ebf3ff',
|
||||
'primary-dark': '#063799',
|
||||
},
|
||||
boxShadow: {
|
||||
outline: 'inset 0 0 0 2px rgba(0, 82, 205, 0.6)',
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# @nhost/docs
|
||||
|
||||
## 2.7.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5c47e8e: feat: added hasura's stringifyNumericTypes setting
|
||||
|
||||
## 2.7.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 6cb2b63: feat: added nhost run env documentation
|
||||
- 40bd3e4: fix: fixed wrong links in documentation
|
||||
|
||||
## 2.7.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 49a80c2: chore: update dependencies
|
||||
|
||||
## 2.6.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- dc23dc0: fix: docs run references
|
||||
|
||||
## 2.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -9,7 +9,7 @@ If you are using the Nhost CLI for local development, as of [v0.12.0](https://gi
|
||||
|
||||
<Steps>
|
||||
<Step title="Configuring the Service">
|
||||
Follow the steps highlighed in the ["Enabling Service"](enabling-service) guide and don't forget to add the relevant secrets to your `.secrets` file.
|
||||
Follow the steps highlighed in the [Enabling Service](enabling-service) guide and don't forget to add the relevant secrets to your `.secrets` file.
|
||||
</Step>
|
||||
<Step title="Start nhost">
|
||||
Run `nhost up`:
|
||||
|
||||
@@ -44,6 +44,9 @@ Below, you will find the official `CUE` schema and an example on how to configur
|
||||
|
||||
// HASURA_GRAPHQL_LIVE_QUERIES_MULTIPLEXED_REFETCH_INTERVAL
|
||||
liveQueriesMultiplexedRefetchInterval: uint32 | *1000
|
||||
|
||||
// HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES
|
||||
stringifyNumericTypes: bool | *false
|
||||
}
|
||||
|
||||
logs: {
|
||||
@@ -83,6 +86,7 @@ enableConsole = true
|
||||
enableRemoteSchemaPermissions = true
|
||||
enabledAPIs = ['metadata']
|
||||
liveQueriesMultiplexedRefetchInterval = 1000
|
||||
stringifyNumericTypes = false
|
||||
|
||||
[hasura.logs]
|
||||
level = 'warn'
|
||||
|
||||
@@ -86,4 +86,4 @@ await nhost.auth.sendVerificationEmail(
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Tip>It is possible to customize these automatic emails, learn how to [here](/authentication/templates)</Tip>
|
||||
<Tip>It is possible to customize these automatic emails, learn how to [here](/guides/auth/email-templates)</Tip>
|
||||
|
||||
@@ -34,5 +34,5 @@ await nhost.auth.signIn(
|
||||
|
||||
<Tip>Users who have signed up with email and password can also sign in with a Magic Link</Tip>
|
||||
|
||||
<Tip>It is possible to customize the email with the Magic Link, learn how to [here](/authentication/templates)</Tip>
|
||||
<Tip>It is possible to customize the email with the Magic Link, learn how to [here](/guides/auth/email-templates)</Tip>
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ You're allowed to:
|
||||
|
||||
## Roles
|
||||
|
||||
Each user has one **default role** and a list of **allowed roles**. These roles are used to resolve permissions for requests to [GraphQL](/graphql/permissions) and [Storage](/storage#permissions).
|
||||
Each user has one **default role** and a list of **allowed roles**. These roles are used to resolve permissions for requests to [GraphQL](/guides/api/permissions) and [Storage](/guides/storage/overview#permissions).
|
||||
|
||||
When the user makes a request, only one role is used to resolve permissions. The default role is used if no role is explicitly specified. Users can only make requests using the default role or one of the allowed roles.
|
||||
|
||||
|
||||
@@ -46,11 +46,11 @@ capacity=1
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Info>Head to [CLI & CI deployments](/run/ci) for more details on how to deploy using a configuration file.</Info>
|
||||
<Info>Head to [CLI & CI deployments](/guides/run/cli-deployments) for more details on how to deploy using a configuration file.</Info>
|
||||
|
||||
The `name` of the service is used as an identifier and to generate URLs when exposing the service to the Internet. You can use any container image publicly available or you can push your own to the [Nhost registry](/run/registry).
|
||||
The `name` of the service is used as an identifier and to generate URLs when exposing the service to the Internet. You can use any container image publicly available or you can push your own to the [Nhost registry](/guides/run/registry).
|
||||
|
||||
All environment variables set here are exclusive to this service and will not be shared with other services or with the Nhost stack. If you are using a configuration file secrets are supported.
|
||||
|
||||
For more details about the `Ports` section head to [networking](/run/networking). You can also head to [resources](/run/resources) for more information about replicas, compute, and storage.
|
||||
For more details about the `Ports` section head to [networking](/guides/run/networking). You can also head to [resources](/guides/run/resources) for more information about replicas, compute, and storage.
|
||||
|
||||
|
||||
@@ -12,11 +12,11 @@ Then on `New Service`:
|
||||
|
||||

|
||||
|
||||
Now you can fill your [service configuration](/run/configuration):
|
||||
Now you can fill your [service configuration](/guides/run/configuration):
|
||||
|
||||

|
||||
|
||||
As you configure the `Ports` section you can take note of the generated URL. You can find more information about this section under [Networking](/run/networking).
|
||||
As you configure the `Ports` section you can take note of the generated URL. You can find more information about this section under [Networking](/guides/run/networking).
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -24,3 +24,65 @@ Based on the information above, if you want to connect directly to your service
|
||||
<Note>You can also use the environment variable `NHOST_RUN_SERVICE` passing comma-separated values. For instance, the equivalent environment variable for this example would be `NHOST_RUN_SERVICE=../mysvc/nhost-run-service.toml:mysvc,../mysvc/nhost-run-service.toml`</Note>
|
||||
|
||||
<Warning>The Nhost CLI doesn't build services so make sure you build any image that might be needed for running `nhost run --run-service...`</Warning>
|
||||
|
||||
# Quick Development
|
||||
|
||||
While developing your service, you may want to run it locally outside of the Nhost CLI to quickly iterate on it. To simplify this the Nhost CLI includes a command to generate an `.env` file based on your environment variables configuration and secrets. For instance, imagine a service with the following configuration:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="run-service.toml">
|
||||
```toml
|
||||
[[environment]]
|
||||
name = 'HASURA_GRAPHQL_URL'
|
||||
value = 'http://hasura-service:8080/v1/graphql'
|
||||
|
||||
[[environment]]
|
||||
name = 'SOME_CONFIGURATION_PARAMETER'
|
||||
value = 'some-value'
|
||||
|
||||
[[environment]]
|
||||
name = 'SECRET_KEY'
|
||||
value = '{{ secrets.SECRET_KEY }}'
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab title="overlay">
|
||||
```json
|
||||
[
|
||||
{
|
||||
"value": {
|
||||
"name": "ENVIRONMENT",
|
||||
"value": "dev"
|
||||
},
|
||||
"op": "add",
|
||||
"path": "/environment/-"
|
||||
},
|
||||
{
|
||||
"value": "https://local.graphql.nhost.run/v1/graphql",
|
||||
"op": "replace",
|
||||
"path": "/environment/0/value"
|
||||
}
|
||||
]
|
||||
```
|
||||
</Tab>
|
||||
<Tab title=".secrets">
|
||||
```toml
|
||||
SECRET_KEY = '#asdasd;l;kq23\\n40-0as9d"$\\'
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
We can then generate an `env` file for our service with the folllowing command:
|
||||
|
||||
```
|
||||
$ nhost run env --config ../mysvc/nhost-run-service.toml --overlay-name local-dev > .env
|
||||
$ cat .env
|
||||
HASURA_GRAPHQL_URL="https://local.graphql.nhost.run/v1/graphql"
|
||||
SOME_CONFIGURATION_PARAMETER="some-value"
|
||||
SECRET_KEY="#asdasd;l;kq23\\n40-0as9d\"\$\\"
|
||||
ENVIRONMENT="dev"
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Keep in mind you may need to use different configuration when attempting to connect to other services in the stack. For instance, in the example above we are using `http://hasura-service:8080/v1/graphql` to connect to hasura in production and in the CLI but when running the service in the host machine using the env file we are using an overlay to change the value to `https://local.graphql.nhost.run/v1/graphql`. Refer to the [network](networking) configuration for more details.
|
||||
</Warning>
|
||||
|
||||
@@ -76,7 +76,7 @@ To pause a service, simply set its number of replicas to `0`:
|
||||
<Tab title="dashboard">
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
</Tab>
|
||||
<Tab title="toml">
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "@nhost/docs",
|
||||
"version": "2.5.0",
|
||||
"version": "2.7.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "mintlify dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mintlify": "^4.0.121"
|
||||
"mintlify": "^4.0.128"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,39 @@
|
||||
# @nhost-examples/cli
|
||||
|
||||
## 0.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.10
|
||||
|
||||
## 0.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.9
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 49a80c2: chore: update dependencies
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.8
|
||||
|
||||
## 0.1.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.8
|
||||
|
||||
## 0.1.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.7
|
||||
|
||||
## 0.1.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/cli",
|
||||
"version": "0.1.7",
|
||||
"version": "0.2.2",
|
||||
"main": "src/index.mjs",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -9,10 +9,10 @@
|
||||
"dependencies": {
|
||||
"@nhost/nhost-js": "workspace:^",
|
||||
"commander": "^10.0.1",
|
||||
"dotenv": "^16.4.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"graphql": "16.8.1",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"pino": "^8.17.2"
|
||||
"pino": "^8.19.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
|
||||
@@ -1,5 +1,50 @@
|
||||
# @nhost-examples/codegen-react-apollo
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [311374e]
|
||||
- @nhost/react@3.3.2
|
||||
- @nhost/react-apollo@10.0.2
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 7789469: chore: upgrade dependency `@graphql-codegen/cli` to `5.0.2` to address vulnerability
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.3.1
|
||||
- @nhost/react-apollo@10.0.1
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 49a80c2: chore: update dependencies
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [49a80c2]
|
||||
- @nhost/react-apollo@10.0.0
|
||||
- @nhost/react@3.3.0
|
||||
|
||||
## 0.1.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.3
|
||||
- @nhost/react-apollo@9.0.3
|
||||
|
||||
## 0.1.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.2
|
||||
- @nhost/react-apollo@9.0.2
|
||||
|
||||
## 0.1.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/codegen-react-apollo",
|
||||
"version": "0.1.15",
|
||||
"version": "0.3.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"codegen": "graphql-codegen",
|
||||
@@ -15,7 +15,7 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.9.4",
|
||||
"@apollo/client": "^3.9.5",
|
||||
"@nhost/react": "workspace:^",
|
||||
"@nhost/react-apollo": "workspace:^",
|
||||
"clsx": "^1.2.1",
|
||||
@@ -24,18 +24,18 @@
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "^2.16.5",
|
||||
"@graphql-codegen/cli": "^5.0.2",
|
||||
"@graphql-codegen/client-preset": "^1.3.0",
|
||||
"@graphql-typed-document-node/core": "^3.2.0",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@types/node": "^18.19.12",
|
||||
"@types/react": "^18.2.50",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/node": "^18.19.21",
|
||||
"@types/react": "^18.2.61",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@vitejs/plugin-react": "^3.1.0",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"postcss": "^8.4.33",
|
||||
"postcss": "^8.4.35",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "^5.0.12"
|
||||
"vite": "^5.1.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,45 @@
|
||||
# @nhost-examples/codegen-react-query
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [311374e]
|
||||
- @nhost/react@3.3.2
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 7789469: chore: upgrade dependency `@graphql-codegen/cli` to `5.0.2` to address vulnerability
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.3.1
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 49a80c2: chore: update dependencies
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [49a80c2]
|
||||
- @nhost/react@3.3.0
|
||||
|
||||
## 0.1.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.3
|
||||
|
||||
## 0.1.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.2
|
||||
|
||||
## 0.1.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/codegen-react-query",
|
||||
"version": "0.1.16",
|
||||
"version": "0.3.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"codegen": "graphql-codegen",
|
||||
@@ -25,18 +25,18 @@
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "^2.16.5",
|
||||
"@graphql-codegen/cli": "^5.0.2",
|
||||
"@graphql-codegen/client-preset": "^1.3.0",
|
||||
"@graphql-typed-document-node/core": "^3.2.0",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@types/node": "^16.18.78",
|
||||
"@types/react": "^18.2.50",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/node": "^16.18.86",
|
||||
"@types/react": "^18.2.61",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"eslint": "^8.56.0",
|
||||
"postcss": "^8.4.33",
|
||||
"eslint": "^8.57.0",
|
||||
"postcss": "^8.4.35",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "^5.0.12"
|
||||
"vite": "^5.1.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,50 @@
|
||||
# @nhost-examples/react-urql
|
||||
|
||||
## 0.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [311374e]
|
||||
- @nhost/react@3.3.2
|
||||
- @nhost/react-urql@7.0.2
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 7789469: chore: upgrade dependency `@graphql-codegen/cli` to `5.0.2` to address vulnerability
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.3.1
|
||||
- @nhost/react-urql@7.0.1
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 49a80c2: chore: update dependencies
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [49a80c2]
|
||||
- @nhost/react-urql@7.0.0
|
||||
- @nhost/react@3.3.0
|
||||
|
||||
## 0.0.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.3
|
||||
- @nhost/react-urql@6.0.3
|
||||
|
||||
## 0.0.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.2
|
||||
- @nhost/react-urql@6.0.2
|
||||
|
||||
## 0.0.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/codegen-react-urql",
|
||||
"private": true,
|
||||
"version": "0.0.12",
|
||||
"version": "0.2.1",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
@@ -18,18 +18,18 @@
|
||||
"urql": "^3.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "^2.16.5",
|
||||
"@graphql-codegen/cli": "^5.0.2",
|
||||
"@graphql-codegen/client-preset": "^1.3.0",
|
||||
"@graphql-typed-document-node/core": "^3.2.0",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@types/node": "^16.18.78",
|
||||
"@types/react": "^18.2.50",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/node": "^16.18.86",
|
||||
"@types/react": "^18.2.61",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@vitejs/plugin-react": "^3.1.0",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"postcss": "^8.4.33",
|
||||
"postcss": "^8.4.35",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "^5.0.12"
|
||||
"vite": "^5.1.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @nhost-examples/docker-compose
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- e40a452: chore: clarification on greyed-out options in the dashboard when self-hosting
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 49a80c2: chore: update dependencies
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
Here is an example on how to reproduce the Nhost stack from a docker-compose file.
|
||||
|
||||
NOTE: You may notice that some options in the dashboard are greyed-out. These include additional services like CI integration, configuration management, etc., offered by the Nhost Cloud and therefore are not accessible when self-hosting.
|
||||
|
||||
## Configuration
|
||||
|
||||
```sh
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/docker-compose",
|
||||
"version": "0.1.1",
|
||||
"version": "0.3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"e2e": "vitest run"
|
||||
|
||||
@@ -1,76 +1,7 @@
|
||||
import { exec } from 'child_process'
|
||||
import fetch from 'cross-fetch'
|
||||
import { promisify } from 'util'
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
||||
import { describe, it } from 'vitest'
|
||||
|
||||
const promisifiedExec = promisify(exec)
|
||||
|
||||
describe(
|
||||
'docker-compose should start, work and stop',
|
||||
() => {
|
||||
beforeAll(async () => {
|
||||
// * Start docker compose
|
||||
await promisifiedExec(
|
||||
'docker compose -f docker-compose.yaml --env-file .env.example up --wait --wait-timeout 300 --quiet-pull'
|
||||
)
|
||||
|
||||
// we wait a bit extra because sometimes traefik takes a bit to configure the services
|
||||
setTimeout(() => {}, 30000);
|
||||
|
||||
}, 5 * 60 * 1000)
|
||||
|
||||
afterAll(async () => {
|
||||
// * Stop docker compose
|
||||
await promisifiedExec('docker compose -f docker-compose.yaml --env-file .env.example down')
|
||||
}, 5 * 60 * 1000)
|
||||
|
||||
it(
|
||||
'Hasura',
|
||||
async () => {
|
||||
await expect(
|
||||
fetch('http://localhost:1337/healthz').then((res) => res.status)
|
||||
).resolves.toEqual(200)
|
||||
},
|
||||
{ retry: 60, timeout: 1000 }
|
||||
)
|
||||
|
||||
it(
|
||||
'Hasura auth',
|
||||
async () => {
|
||||
await expect(
|
||||
fetch('http://localhost:1337/v1/auth/healthz').then((res) => res.status)
|
||||
).resolves.toEqual(200)
|
||||
},
|
||||
{ retry: 60, timeout: 1000 }
|
||||
)
|
||||
|
||||
it(
|
||||
'Hasura storage',
|
||||
async () => {
|
||||
await expect(
|
||||
fetch('http://localhost:1337/v1/storage/version').then((res) => res.status)
|
||||
).resolves.toEqual(200)
|
||||
},
|
||||
{ retry: 60, timeout: 1000 }
|
||||
)
|
||||
|
||||
it(
|
||||
'Serverless functions',
|
||||
async () => {
|
||||
await expect(
|
||||
fetch('http://localhost:1337/v1/functions/hello').then((res) => res.status)
|
||||
).resolves.toEqual(200)
|
||||
},
|
||||
{ retry: 60, timeout: 1000 }
|
||||
)
|
||||
|
||||
it(
|
||||
'Dashboard',
|
||||
async () => {
|
||||
await expect(fetch('http://localhost:3030').then((res) => res.status)).resolves.toEqual(200)
|
||||
},
|
||||
{ retry: 60, timeout: 1000 }
|
||||
)
|
||||
},
|
||||
{ timeout: 5 * 60 * 1000 }
|
||||
)
|
||||
describe('Empty Test Suite', () => {
|
||||
it('Empty Test', async () => {
|
||||
// This test does nothing and will always pass
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,39 @@
|
||||
# @nhost-examples/multi-tenant-one-to-many
|
||||
|
||||
## 2.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.10
|
||||
|
||||
## 2.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.9
|
||||
|
||||
## 2.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 49a80c2: chore: update dependencies
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.8
|
||||
|
||||
## 2.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.8
|
||||
|
||||
## 2.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.7
|
||||
|
||||
## 2.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/multi-tenant-one-to-many",
|
||||
"private": true,
|
||||
"version": "2.0.5",
|
||||
"version": "2.1.2",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {},
|
||||
@@ -10,7 +10,7 @@
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"express": "^4.18.2",
|
||||
"express": "^4.18.3",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,5 +1,51 @@
|
||||
# @nhost-examples/nextjs
|
||||
|
||||
## 0.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [311374e]
|
||||
- @nhost/react@3.3.2
|
||||
- @nhost/react-apollo@10.0.2
|
||||
- @nhost/nextjs@2.1.8
|
||||
|
||||
## 0.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.3.1
|
||||
- @nhost/react-apollo@10.0.1
|
||||
- @nhost/nextjs@2.1.7
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 49a80c2: chore: update dependencies
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [49a80c2]
|
||||
- @nhost/react-apollo@10.0.0
|
||||
- @nhost/react@3.3.0
|
||||
- @nhost/nextjs@2.1.6
|
||||
|
||||
## 0.1.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.3
|
||||
- @nhost/react-apollo@9.0.3
|
||||
- @nhost/nextjs@2.1.5
|
||||
|
||||
## 0.1.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.2
|
||||
- @nhost/react-apollo@9.0.2
|
||||
- @nhost/nextjs@2.1.4
|
||||
|
||||
## 0.1.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/nextjs",
|
||||
"version": "0.1.17",
|
||||
"version": "0.2.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -15,7 +15,7 @@
|
||||
"verify:fix": "run-p prettier:fix lint:fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.9.4",
|
||||
"@apollo/client": "^3.9.5",
|
||||
"@mantine/core": "^4.2.12",
|
||||
"@mantine/hooks": "^4.2.12",
|
||||
"@mantine/next": "^4.2.12",
|
||||
@@ -31,8 +31,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "^12.3.4",
|
||||
"@types/node": "^16.18.78",
|
||||
"@types/react": "^18.2.50",
|
||||
"@types/node": "^16.18.86",
|
||||
"@types/react": "^18.2.61",
|
||||
"@xstate/inspect": "^0.6.5",
|
||||
"eslint-config-next": "12.0.10",
|
||||
"typescript": "^4.9.5",
|
||||
|
||||
@@ -1,5 +1,39 @@
|
||||
# @nhost-examples/node-storage
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.10
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.9
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 49a80c2: chore: update dependencies
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.8
|
||||
|
||||
## 0.0.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.8
|
||||
|
||||
## 0.0.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.7
|
||||
|
||||
## 0.0.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/node-storage",
|
||||
"version": "0.0.9",
|
||||
"version": "0.1.2",
|
||||
"private": true,
|
||||
"description": "This is an example of how to use the Storage with Node.js",
|
||||
"main": "src/index.mjs",
|
||||
@@ -12,14 +12,14 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@nhost/nhost-js": "workspace:^",
|
||||
"dotenv": "^16.4.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"form-data": "^4.0.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.19.12",
|
||||
"@types/node": "^18.19.21",
|
||||
"@types/uuid": "^9.0.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,39 @@
|
||||
# @nhost-examples/nextjs-server-components
|
||||
|
||||
## 0.3.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.10
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.9
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 49a80c2: chore: update dependencies
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.8
|
||||
|
||||
## 0.2.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.8
|
||||
|
||||
## 0.2.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.7
|
||||
|
||||
## 0.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/nextjs-server-components",
|
||||
"version": "0.2.3",
|
||||
"version": "0.3.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -9,7 +9,7 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.9.4",
|
||||
"@apollo/client": "^3.9.5",
|
||||
"@nhost/nhost-js": "workspace:^",
|
||||
"autoprefixer": "10.4.15",
|
||||
"cookies-next": "^3.0.0",
|
||||
@@ -19,7 +19,7 @@
|
||||
"graphql": "16.8.1",
|
||||
"js-cookie": "^3.0.5",
|
||||
"next": "^14.1.0",
|
||||
"postcss": "^8.4.33",
|
||||
"postcss": "^8.4.35",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
@@ -30,7 +30,7 @@
|
||||
"devDependencies": {
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/node": "20.5.6",
|
||||
"@types/react": "^18.2.47",
|
||||
"@types/react-dom": "^18.2.7"
|
||||
"@types/react": "^18.2.61",
|
||||
"@types/react-dom": "^18.2.19"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
---
|
||||
|
||||
## 0.3.0
|
||||
### Minor Changes
|
||||
|
||||
- 49a80c2: chore: update dependencies
|
||||
|
||||
## 0.2.3
|
||||
### Patch Changes
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/sveltekit",
|
||||
"version": "0.2.3",
|
||||
"version": "0.3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
@@ -16,22 +16,22 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nhost/nhost-js": "2.2.18",
|
||||
"@playwright/test": "^1.41.1",
|
||||
"@playwright/test": "^1.42.0",
|
||||
"@sveltejs/adapter-auto": "^2.1.1",
|
||||
"@sveltejs/kit": "^1.30.3",
|
||||
"@sveltejs/kit": "^1.30.4",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"eslint-plugin-svelte": "^2.35.1",
|
||||
"postcss": "^8.4.33",
|
||||
"postcss": "^8.4.35",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-plugin-svelte": "^2.10.1",
|
||||
"svelte": "^3.59.2",
|
||||
"svelte-check": "^3.6.3",
|
||||
"svelte-check": "^3.6.6",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.0.12",
|
||||
"vite": "^5.1.4",
|
||||
"vitest": "^0.25.8"
|
||||
},
|
||||
"type": "module",
|
||||
|
||||
@@ -1,5 +1,70 @@
|
||||
# @nhost-examples/react-apollo
|
||||
|
||||
## 0.7.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 311374e: feat: add example of how to connect a social auth provider to an existing account
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [311374e]
|
||||
- @nhost/react@3.3.2
|
||||
- @nhost/react-apollo@10.0.2
|
||||
|
||||
## 0.6.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 7789469: chore: upgrade dependency `@graphql-codegen/cli` to `5.0.2` to address vulnerability
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.3.1
|
||||
- @nhost/react-apollo@10.0.1
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 08a7dd9: feat: add example workaround to the reset password ticket expired issue
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- f0a994a: fix: update allowedUrls and redirectTo to point to the profile page
|
||||
|
||||
## 0.4.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4f3fb34: fix: set redirectTo when doing sign in with github and include vercel previews in allowed redirect URLs
|
||||
|
||||
## 0.4.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 49a80c2: chore: update dependencies
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [49a80c2]
|
||||
- @nhost/react-apollo@10.0.0
|
||||
- @nhost/react@3.3.0
|
||||
|
||||
## 0.3.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.3
|
||||
- @nhost/react-apollo@9.0.3
|
||||
|
||||
## 0.3.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.2
|
||||
- @nhost/react-apollo@9.0.2
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<h2>Verify Email</h2>
|
||||
<p>Use this link to verify your email:</p>
|
||||
<p>
|
||||
<a href="${link}"> Verify Email </a>
|
||||
<a href="${clientUrl}/verify?ticket=${ticket}&redirectTo=${redirectTo}&type=emailVerify"> Verify Email </a>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<h2>Reset Password</h2>
|
||||
<p>Use this link to reset your password:</p>
|
||||
<p>
|
||||
<a href="${link}"> Reset password </a>
|
||||
<a href="${clientUrl}/verify?ticket=${ticket}&redirectTo=${redirectTo}&type=emailVerify"> Reset password </a>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Magic Link</h2>
|
||||
<p>Use this link to securely sign in:</p>
|
||||
<p>
|
||||
<a href="${link}"> Sign In </a>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Magic Link</h2>
|
||||
<p>Use this link to securely sign in:</p>
|
||||
<p>
|
||||
<a href="${link}">
|
||||
Verify Email
|
||||
</a>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -46,3 +46,13 @@ object_relationships:
|
||||
- name: user
|
||||
using:
|
||||
foreign_key_constraint_on: user_id
|
||||
select_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- id
|
||||
- provider_id
|
||||
filter:
|
||||
user_id:
|
||||
_eq: X-Hasura-User-Id
|
||||
comment: ""
|
||||
|
||||
@@ -16,6 +16,7 @@ enableAllowList = false
|
||||
enableConsole = true
|
||||
enableRemoteSchemaPermissions = false
|
||||
enabledAPIs = ['metadata', 'graphql', 'pgdump', 'config']
|
||||
liveQueriesMultiplexedRefetchInterval = 1000
|
||||
|
||||
[hasura.logs]
|
||||
level = 'warn'
|
||||
@@ -28,17 +29,18 @@ httpPoolSize = 100
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.26.0'
|
||||
version = '0.28.0-beta2'
|
||||
|
||||
[auth.elevatedPrivileges]
|
||||
mode = 'required'
|
||||
|
||||
[auth.redirections]
|
||||
clientUrl = 'https://react-apollo.example.nhost.io/'
|
||||
allowedUrls = ['https://react-apollo.example.nhost.io/profile', 'http://localhost:3000']
|
||||
allowedUrls = ['https://react-apollo.example.nhost.io', 'https://react-apollo.example.nhost.io/profile', 'https://vue-apollo.example.nhost.io', 'https://vue-apollo.example.nhost.io/profile', 'https://*.vercel.app', 'http://localhost:3000', 'http://localhost:3000/profile', 'http://localhost:5173', 'http://localhost:5173/profile']
|
||||
|
||||
[auth.signUp]
|
||||
enabled = true
|
||||
disableNewUsers = false
|
||||
|
||||
[auth.user]
|
||||
[auth.user.roles]
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "@nhost-examples/react-apollo",
|
||||
"version": "0.3.1",
|
||||
"version": "0.7.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.9.4",
|
||||
"@apollo/client": "^3.9.5",
|
||||
"@mantine/core": "^4.2.12",
|
||||
"@mantine/dropzone": "^4.2.12",
|
||||
"@mantine/hooks": "^4.2.12",
|
||||
@@ -15,8 +15,8 @@
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^4.12.0",
|
||||
"react-router": "^6.21.3",
|
||||
"react-router-dom": "^6.21.3",
|
||||
"react-router": "^6.22.2",
|
||||
"react-router-dom": "^6.22.2",
|
||||
"tabler-icons-react": "^1.56.0"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -50,21 +50,21 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^7.6.0",
|
||||
"@graphql-codegen/cli": "^2.16.5",
|
||||
"@graphql-codegen/cli": "^5.0.2",
|
||||
"@nuintun/qrcode": "^3.4.0",
|
||||
"@playwright/test": "1.31.0",
|
||||
"@types/pngjs": "^6.0.4",
|
||||
"@types/react": "^18.2.50",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/react": "^18.2.61",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@types/totp-generator": "^0.0.4",
|
||||
"@vitejs/plugin-react": "^3.1.0",
|
||||
"@xstate/inspect": "^0.6.5",
|
||||
"dotenv": "^16.4.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"jsqr": "^1.4.0",
|
||||
"pngjs": "^7.0.0",
|
||||
"totp-generator": "^0.0.13",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "^5.0.12",
|
||||
"vite": "^5.1.4",
|
||||
"ws": "^8.16.0",
|
||||
"xstate": "^4.38.3"
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { StoragePage } from './Storage'
|
||||
|
||||
import './App.css?inline'
|
||||
import { NotesPage } from './components/notes'
|
||||
import VerifyPage from './Verify'
|
||||
const title = 'Nhost with React and Apollo'
|
||||
|
||||
function App() {
|
||||
@@ -70,6 +71,7 @@ function App() {
|
||||
</AuthGate>
|
||||
}
|
||||
/>
|
||||
<Route path="/verify" element={<VerifyPage />} />
|
||||
<Route path="/about" element={<AboutPage />} />
|
||||
<Route
|
||||
path="/sign-in/*"
|
||||
|
||||
34
examples/react-apollo/src/Verify.tsx
Normal file
34
examples/react-apollo/src/Verify.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useNhostClient } from '@nhost/react'
|
||||
import { Container } from '@mantine/core'
|
||||
|
||||
const VerifyPage: React.FC = () => {
|
||||
const nhost = useNhostClient()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [searchParams] = useSearchParams()
|
||||
|
||||
useEffect(() => {
|
||||
const ticket = searchParams.get('ticket')
|
||||
const redirectTo = searchParams.get('redirectTo')
|
||||
const type = searchParams.get('type')
|
||||
|
||||
if (ticket && redirectTo && type) {
|
||||
window.location.href = `${nhost.auth.url}/verify?ticket=${ticket}&type=${type}&redirectTo=${redirectTo}`
|
||||
}
|
||||
|
||||
setLoading(false)
|
||||
}, [searchParams, nhost?.auth?.url])
|
||||
|
||||
if (loading) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<span>Failed to authenticate with magick link</span>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default VerifyPage
|
||||
@@ -6,7 +6,7 @@ import AuthLink from './AuthLink'
|
||||
|
||||
export default function OauthLinks() {
|
||||
const { github, google, apple, linkedin } = useProviderLink({
|
||||
redirectTo: window.location.origin
|
||||
redirectTo: `${window.location.origin}/profile`
|
||||
})
|
||||
|
||||
return (
|
||||
|
||||
@@ -201,6 +201,73 @@ export type AuthRefreshTokensStreamCursorValueInput = {
|
||||
userId?: InputMaybe<Scalars['uuid']>;
|
||||
};
|
||||
|
||||
/** Active providers for a given user. Don't modify its structure as Hasura Auth relies on it to function properly. */
|
||||
export type AuthUserProviders = {
|
||||
__typename?: 'authUserProviders';
|
||||
id: Scalars['uuid'];
|
||||
providerId: Scalars['String'];
|
||||
/** An object relationship */
|
||||
user: Users;
|
||||
};
|
||||
|
||||
/** order by aggregate values of table "auth.user_providers" */
|
||||
export type AuthUserProvidersAggregateOrderBy = {
|
||||
count?: InputMaybe<OrderBy>;
|
||||
max?: InputMaybe<AuthUserProvidersMaxOrderBy>;
|
||||
min?: InputMaybe<AuthUserProvidersMinOrderBy>;
|
||||
};
|
||||
|
||||
/** Boolean expression to filter rows from the table "auth.user_providers". All fields are combined with a logical 'AND'. */
|
||||
export type AuthUserProvidersBoolExp = {
|
||||
_and?: InputMaybe<Array<AuthUserProvidersBoolExp>>;
|
||||
_not?: InputMaybe<AuthUserProvidersBoolExp>;
|
||||
_or?: InputMaybe<Array<AuthUserProvidersBoolExp>>;
|
||||
id?: InputMaybe<UuidComparisonExp>;
|
||||
providerId?: InputMaybe<StringComparisonExp>;
|
||||
user?: InputMaybe<UsersBoolExp>;
|
||||
};
|
||||
|
||||
/** order by max() on columns of table "auth.user_providers" */
|
||||
export type AuthUserProvidersMaxOrderBy = {
|
||||
id?: InputMaybe<OrderBy>;
|
||||
providerId?: InputMaybe<OrderBy>;
|
||||
};
|
||||
|
||||
/** order by min() on columns of table "auth.user_providers" */
|
||||
export type AuthUserProvidersMinOrderBy = {
|
||||
id?: InputMaybe<OrderBy>;
|
||||
providerId?: InputMaybe<OrderBy>;
|
||||
};
|
||||
|
||||
/** Ordering options when selecting data from "auth.user_providers". */
|
||||
export type AuthUserProvidersOrderBy = {
|
||||
id?: InputMaybe<OrderBy>;
|
||||
providerId?: InputMaybe<OrderBy>;
|
||||
user?: InputMaybe<UsersOrderBy>;
|
||||
};
|
||||
|
||||
/** select columns of table "auth.user_providers" */
|
||||
export enum AuthUserProvidersSelectColumn {
|
||||
/** column name */
|
||||
Id = 'id',
|
||||
/** column name */
|
||||
ProviderId = 'providerId'
|
||||
}
|
||||
|
||||
/** Streaming cursor of the table "authUserProviders" */
|
||||
export type AuthUserProvidersStreamCursorInput = {
|
||||
/** Stream column input with initial value */
|
||||
initial_value: AuthUserProvidersStreamCursorValueInput;
|
||||
/** cursor ordering */
|
||||
ordering?: InputMaybe<CursorOrdering>;
|
||||
};
|
||||
|
||||
/** Initial value of the column from where the streaming should start */
|
||||
export type AuthUserProvidersStreamCursorValueInput = {
|
||||
id?: InputMaybe<Scalars['uuid']>;
|
||||
providerId?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
/** User webauthn security keys. Don't modify its structure as Hasura Auth relies on it to function properly. */
|
||||
export type AuthUserSecurityKeys = {
|
||||
__typename?: 'authUserSecurityKeys';
|
||||
@@ -930,6 +997,10 @@ export type QueryRoot = {
|
||||
authRefreshToken?: Maybe<AuthRefreshTokens>;
|
||||
/** fetch data from the table: "auth.refresh_tokens" */
|
||||
authRefreshTokens: Array<AuthRefreshTokens>;
|
||||
/** fetch data from the table: "auth.user_providers" using primary key columns */
|
||||
authUserProvider?: Maybe<AuthUserProviders>;
|
||||
/** fetch data from the table: "auth.user_providers" */
|
||||
authUserProviders: Array<AuthUserProviders>;
|
||||
/** fetch data from the table: "auth.user_security_keys" using primary key columns */
|
||||
authUserSecurityKey?: Maybe<AuthUserSecurityKeys>;
|
||||
/** fetch data from the table: "auth.user_security_keys" */
|
||||
@@ -971,6 +1042,20 @@ export type QueryRootAuthRefreshTokensArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type QueryRootAuthUserProviderArgs = {
|
||||
id: Scalars['uuid'];
|
||||
};
|
||||
|
||||
|
||||
export type QueryRootAuthUserProvidersArgs = {
|
||||
distinct_on?: InputMaybe<Array<AuthUserProvidersSelectColumn>>;
|
||||
limit?: InputMaybe<Scalars['Int']>;
|
||||
offset?: InputMaybe<Scalars['Int']>;
|
||||
order_by?: InputMaybe<Array<AuthUserProvidersOrderBy>>;
|
||||
where?: InputMaybe<AuthUserProvidersBoolExp>;
|
||||
};
|
||||
|
||||
|
||||
export type QueryRootAuthUserSecurityKeyArgs = {
|
||||
id: Scalars['uuid'];
|
||||
};
|
||||
@@ -1066,6 +1151,12 @@ export type SubscriptionRoot = {
|
||||
authRefreshTokens: Array<AuthRefreshTokens>;
|
||||
/** fetch data from the table in a streaming manner: "auth.refresh_tokens" */
|
||||
authRefreshTokens_stream: Array<AuthRefreshTokens>;
|
||||
/** fetch data from the table: "auth.user_providers" using primary key columns */
|
||||
authUserProvider?: Maybe<AuthUserProviders>;
|
||||
/** fetch data from the table: "auth.user_providers" */
|
||||
authUserProviders: Array<AuthUserProviders>;
|
||||
/** fetch data from the table in a streaming manner: "auth.user_providers" */
|
||||
authUserProviders_stream: Array<AuthUserProviders>;
|
||||
/** fetch data from the table: "auth.user_security_keys" using primary key columns */
|
||||
authUserSecurityKey?: Maybe<AuthUserSecurityKeys>;
|
||||
/** fetch data from the table: "auth.user_security_keys" */
|
||||
@@ -1124,6 +1215,27 @@ export type SubscriptionRootAuthRefreshTokensStreamArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type SubscriptionRootAuthUserProviderArgs = {
|
||||
id: Scalars['uuid'];
|
||||
};
|
||||
|
||||
|
||||
export type SubscriptionRootAuthUserProvidersArgs = {
|
||||
distinct_on?: InputMaybe<Array<AuthUserProvidersSelectColumn>>;
|
||||
limit?: InputMaybe<Scalars['Int']>;
|
||||
offset?: InputMaybe<Scalars['Int']>;
|
||||
order_by?: InputMaybe<Array<AuthUserProvidersOrderBy>>;
|
||||
where?: InputMaybe<AuthUserProvidersBoolExp>;
|
||||
};
|
||||
|
||||
|
||||
export type SubscriptionRootAuthUserProvidersStreamArgs = {
|
||||
batch_size: Scalars['Int'];
|
||||
cursor: Array<InputMaybe<AuthUserProvidersStreamCursorInput>>;
|
||||
where?: InputMaybe<AuthUserProvidersBoolExp>;
|
||||
};
|
||||
|
||||
|
||||
export type SubscriptionRootAuthUserSecurityKeyArgs = {
|
||||
id: Scalars['uuid'];
|
||||
};
|
||||
@@ -1444,6 +1556,8 @@ export type Users = {
|
||||
/** An array relationship */
|
||||
securityKeys: Array<AuthUserSecurityKeys>;
|
||||
updatedAt: Scalars['timestamptz'];
|
||||
/** An array relationship */
|
||||
userProviders: Array<AuthUserProviders>;
|
||||
};
|
||||
|
||||
|
||||
@@ -1472,6 +1586,16 @@ export type UsersSecurityKeysArgs = {
|
||||
where?: InputMaybe<AuthUserSecurityKeysBoolExp>;
|
||||
};
|
||||
|
||||
|
||||
/** User account information. Don't modify its structure as Hasura Auth relies on it to function properly. */
|
||||
export type UsersUserProvidersArgs = {
|
||||
distinct_on?: InputMaybe<Array<AuthUserProvidersSelectColumn>>;
|
||||
limit?: InputMaybe<Scalars['Int']>;
|
||||
offset?: InputMaybe<Scalars['Int']>;
|
||||
order_by?: InputMaybe<Array<AuthUserProvidersOrderBy>>;
|
||||
where?: InputMaybe<AuthUserProvidersBoolExp>;
|
||||
};
|
||||
|
||||
/** Boolean expression to filter rows from the table "auth.users". All fields are combined with a logical 'AND'. */
|
||||
export type UsersBoolExp = {
|
||||
_and?: InputMaybe<Array<UsersBoolExp>>;
|
||||
@@ -1498,6 +1622,7 @@ export type UsersBoolExp = {
|
||||
refreshTokens?: InputMaybe<AuthRefreshTokensBoolExp>;
|
||||
securityKeys?: InputMaybe<AuthUserSecurityKeysBoolExp>;
|
||||
updatedAt?: InputMaybe<TimestamptzComparisonExp>;
|
||||
userProviders?: InputMaybe<AuthUserProvidersBoolExp>;
|
||||
};
|
||||
|
||||
/** Ordering options when selecting data from "auth.users". */
|
||||
@@ -1523,6 +1648,7 @@ export type UsersOrderBy = {
|
||||
refreshTokens_aggregate?: InputMaybe<AuthRefreshTokensAggregateOrderBy>;
|
||||
securityKeys_aggregate?: InputMaybe<AuthUserSecurityKeysAggregateOrderBy>;
|
||||
updatedAt?: InputMaybe<OrderBy>;
|
||||
userProviders_aggregate?: InputMaybe<AuthUserProvidersAggregateOrderBy>;
|
||||
};
|
||||
|
||||
/** select columns of table "auth.users" */
|
||||
|
||||
70
examples/react-apollo/src/profile/connect-socials.tsx
Normal file
70
examples/react-apollo/src/profile/connect-socials.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { gql } from '@apollo/client'
|
||||
import { Card, Group, Title } from '@mantine/core'
|
||||
import { useProviderLink } from '@nhost/react'
|
||||
import { useAuthQuery } from '@nhost/react-apollo'
|
||||
import { useEffect } from 'react'
|
||||
import { FaGithub } from 'react-icons/fa'
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
import AuthLink from 'src/components/AuthLink'
|
||||
import { AuthUserProviders } from 'src/generated'
|
||||
import { showNotification } from '@mantine/notifications'
|
||||
|
||||
export const ConnectSocials: React.FC = () => {
|
||||
let [searchParams, setSearchParams] = useSearchParams()
|
||||
|
||||
useEffect(() => {
|
||||
const error = searchParams.get('error')
|
||||
const errorDescription = searchParams.get('errorDescription')
|
||||
|
||||
if (error === 'bad-request' && errorDescription === 'social user already exists') {
|
||||
showNotification({
|
||||
color: 'red',
|
||||
title: 'Bad request',
|
||||
message: 'Social user already exists'
|
||||
})
|
||||
|
||||
searchParams.delete('error')
|
||||
searchParams.delete('errorDescription')
|
||||
|
||||
setSearchParams(searchParams)
|
||||
}
|
||||
}, [searchParams, setSearchParams])
|
||||
|
||||
const { github } = useProviderLink({
|
||||
connect: true,
|
||||
redirectTo: `${window.location.origin}/profile`
|
||||
})
|
||||
|
||||
const AUTH_USER_PROVIDERS = gql`
|
||||
query getAuthUserProviders {
|
||||
authUserProviders {
|
||||
id
|
||||
providerId
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const { data } = useAuthQuery<{
|
||||
authUserProviders: AuthUserProviders[]
|
||||
}>(AUTH_USER_PROVIDERS, {
|
||||
pollInterval: 5000,
|
||||
fetchPolicy: 'cache-and-network'
|
||||
})
|
||||
|
||||
const isGithubConnected = data?.authUserProviders?.some((item) => item.providerId === 'github')
|
||||
|
||||
return (
|
||||
<Card shadow="sm" p="lg" m="sm">
|
||||
<Title style={{ marginBottom: '1rem' }}>Connect with social providers</Title>
|
||||
{!isGithubConnected ? (
|
||||
<AuthLink leftIcon={<FaGithub />} link={github} color="#333" disabled={isGithubConnected}>
|
||||
Connect with GitHub
|
||||
</AuthLink>
|
||||
) : (
|
||||
<Group>
|
||||
<FaGithub /> <span>Github connected</span>
|
||||
</Group>
|
||||
)}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { useHasuraClaims, useNhostClient, useUserData } from '@nhost/react'
|
||||
|
||||
import { ChangeEmail } from './change-email'
|
||||
import { ChangePassword } from './change-password'
|
||||
import { ConnectSocials } from './connect-socials'
|
||||
import { Mfa } from './mfa'
|
||||
import { SecurityKeys } from './security-keys'
|
||||
|
||||
@@ -14,6 +15,7 @@ export const ProfilePage: React.FC = () => {
|
||||
return (
|
||||
<Container>
|
||||
<Title>Profile page</Title>
|
||||
<ConnectSocials />
|
||||
<SecurityKeys />
|
||||
<Mfa />
|
||||
<ChangeEmail />
|
||||
|
||||
@@ -1,5 +1,41 @@
|
||||
# @nhost-examples/react-gqty
|
||||
|
||||
## 1.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [311374e]
|
||||
- @nhost/react@3.3.2
|
||||
|
||||
## 1.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.3.1
|
||||
|
||||
## 1.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 49a80c2: chore: update dependencies
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [49a80c2]
|
||||
- @nhost/react@3.3.0
|
||||
|
||||
## 1.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.3
|
||||
|
||||
## 1.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.2
|
||||
|
||||
## 1.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/react-gqty",
|
||||
"private": true,
|
||||
"version": "1.0.5",
|
||||
"version": "1.1.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -20,13 +20,13 @@
|
||||
"devDependencies": {
|
||||
"@gqty/cli": "3.3.0-alpha-d8cdbf6.0",
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@types/react": "^18.2.50",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/react": "^18.2.61",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@vitejs/plugin-react": "^3.1.0",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"postcss": "^8.4.33",
|
||||
"postcss": "^8.4.35",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "^5.0.12"
|
||||
"vite": "^5.1.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# @nhost-examples/serverless-functions
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [7789469]
|
||||
- @nhost/stripe-graphql-js@1.1.1
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 49a80c2: chore: update dependencies
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [49a80c2]
|
||||
- @nhost/stripe-graphql-js@1.1.0
|
||||
|
||||
## 0.0.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/serverless-functions",
|
||||
"private": true,
|
||||
"version": "0.0.11",
|
||||
"version": "0.1.1",
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21"
|
||||
},
|
||||
@@ -11,7 +11,7 @@
|
||||
"@pothos/core": "^3.41.0",
|
||||
"cross-fetch": "^3.1.8",
|
||||
"graphql": "16.8.1",
|
||||
"nodemailer": "^6.9.9",
|
||||
"nodemailer": "^6.9.11",
|
||||
"slugify": "^1.6.6",
|
||||
"stripe": "^11.18.0"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,72 @@
|
||||
# @nhost-examples/vue-apollo
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 311374e: feat: add example of how to connect a social auth provider to an existing account
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [311374e]
|
||||
- @nhost/vue@2.4.0
|
||||
- @nhost/nhost-js@3.0.10
|
||||
- @nhost/apollo@6.1.2
|
||||
|
||||
## 0.4.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 7789469: chore: address linter errors and remove unnecessary imports
|
||||
- @nhost/nhost-js@3.0.9
|
||||
- @nhost/apollo@6.1.1
|
||||
- @nhost/vue@2.3.1
|
||||
|
||||
## 0.4.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 08a7dd9: feat: add example workaround to the reset password ticket expired issue
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- f0a994a: fix: update allowedUrls and redirectTo to point to the profile page
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4f3fb34: fix: set redirectTo when doing sign in with github and include vercel previews in allowed redirect URLs
|
||||
|
||||
## 0.3.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 49a80c2: chore: update dependencies
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [49a80c2]
|
||||
- @nhost/apollo@6.1.0
|
||||
- @nhost/vue@2.3.0
|
||||
- @nhost/nhost-js@3.0.8
|
||||
|
||||
## 0.2.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.8
|
||||
- @nhost/apollo@6.0.8
|
||||
- @nhost/vue@2.2.3
|
||||
|
||||
## 0.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.7
|
||||
- @nhost/apollo@6.0.7
|
||||
- @nhost/vue@2.2.2
|
||||
|
||||
## 0.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<h2>Verify Email</h2>
|
||||
<p>Use this link to verify your email:</p>
|
||||
<p>
|
||||
<a href="${link}">
|
||||
<a href="${clientUrl}/verify?ticket=${ticket}&redirectTo=${redirectTo}&type=emailVerify">
|
||||
Verify Email
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<h2>Reset Password</h2>
|
||||
<p>Use this link to reset your password:</p>
|
||||
<p>
|
||||
<a href="${link}">
|
||||
<a href="${clientUrl}/verify?ticket=${ticket}&redirectTo=${redirectTo}&type=emailVerify">
|
||||
Reset password
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -46,3 +46,15 @@ object_relationships:
|
||||
- name: user
|
||||
using:
|
||||
foreign_key_constraint_on: user_id
|
||||
select_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
columns:
|
||||
- access_token
|
||||
- id
|
||||
- provider_id
|
||||
- refresh_token
|
||||
filter:
|
||||
user_id:
|
||||
_eq: X-Hasura-User-Id
|
||||
comment: ""
|
||||
|
||||
@@ -16,6 +16,7 @@ enableAllowList = false
|
||||
enableConsole = true
|
||||
enableRemoteSchemaPermissions = false
|
||||
enabledAPIs = ['metadata', 'graphql', 'pgdump', 'config']
|
||||
liveQueriesMultiplexedRefetchInterval = 1000
|
||||
|
||||
[hasura.logs]
|
||||
level = 'warn'
|
||||
@@ -28,17 +29,18 @@ httpPoolSize = 100
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.27.0-beta1'
|
||||
version = '0.27.0-beta13'
|
||||
|
||||
[auth.elevatedPrivileges]
|
||||
mode = 'required'
|
||||
|
||||
[auth.redirections]
|
||||
clientUrl = 'https://vue-apollo.example.nhost.io'
|
||||
allowedUrls = ['https://vue-apollo.example.nhost.io/profile', 'http://localhost:5173']
|
||||
allowedUrls = ['https://react-apollo.example.nhost.io', 'https://react-apollo.example.nhost.io/profile', 'https://vue-apollo.example.nhost.io', 'https://vue-apollo.example.nhost.io/profile', 'https://*.vercel.app', 'http://localhost:3000', 'http://localhost:3000/profile', 'http://localhost:5173', 'http://localhost:5173/profile']
|
||||
|
||||
[auth.signUp]
|
||||
enabled = true
|
||||
disableNewUsers = false
|
||||
|
||||
[auth.user]
|
||||
[auth.user.roles]
|
||||
@@ -67,7 +69,7 @@ expiresIn = 43200
|
||||
|
||||
[auth.method]
|
||||
[auth.method.anonymous]
|
||||
enabled = false
|
||||
enabled = true
|
||||
|
||||
[auth.method.emailPasswordless]
|
||||
enabled = true
|
||||
@@ -75,7 +77,7 @@ enabled = true
|
||||
[auth.method.emailPassword]
|
||||
hibpEnabled = false
|
||||
emailVerificationRequired = true
|
||||
passwordMinLength = 9
|
||||
passwordMinLength = 8
|
||||
|
||||
[auth.method.smsPasswordless]
|
||||
enabled = false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/vue-apollo",
|
||||
"private": true,
|
||||
"version": "0.2.2",
|
||||
"version": "0.5.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
@@ -14,7 +14,7 @@
|
||||
"verify:fix": "run-p prettier:fix lint:fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.9.4",
|
||||
"@apollo/client": "^3.9.5",
|
||||
"@mdi/font": "5.9.55",
|
||||
"@nhost/apollo": "workspace:^",
|
||||
"@nhost/nhost-js": "workspace:^",
|
||||
@@ -24,8 +24,8 @@
|
||||
"graphql-tag": "^2.12.6",
|
||||
"roboto-fontface": "^0.10.0",
|
||||
"vite-plugin-vuetify": "^1.0.2",
|
||||
"vue": "^3.4.15",
|
||||
"vue-router": "^4.2.5",
|
||||
"vue": "^3.4.21",
|
||||
"vue-router": "^4.3.0",
|
||||
"vue3-dropzone": "^2.2.1",
|
||||
"vuetify": "3.0.0-beta.10",
|
||||
"webfontloader": "^1.6.28"
|
||||
@@ -37,7 +37,7 @@
|
||||
"@xstate/inspect": "^0.6.5",
|
||||
"sass": "1.32.0",
|
||||
"typescript": "4.9.4",
|
||||
"vite": "^5.0.12",
|
||||
"vite": "^5.1.4",
|
||||
"vue-tsc": "^0.38.9"
|
||||
},
|
||||
"eslintConfig": {
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { useProviderLink } from '@nhost/vue'
|
||||
|
||||
const { github } = useProviderLink({ redirectTo: '/' })
|
||||
const { github } = useProviderLink({ redirectTo: `${window.location.origin}/profile` })
|
||||
</script>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user