Compare commits

..

98 Commits

Author SHA1 Message Date
Hassan Ben Jobrane
55267c680e Merge pull request #2358 from nhost/changeset-release/main
chore: update versions
2023-11-10 16:42:15 +01:00
github-actions[bot]
4d856f557f chore: update versions 2023-11-10 15:22:59 +00:00
Hassan Ben Jobrane
64c579cf8c Merge pull request #2365 from nhost/feat/delete-account
feat: delete account
2023-11-10 16:21:04 +01:00
Hassan Ben Jobrane
eae65c715b fix: disable delete account when user has projects 2023-11-10 15:26:38 +01:00
Hassan Ben Jobrane
9e69f9f235 Merge pull request #2362 from spakanati/feat/export-url-helpers
feat: export urlFromSubdomain helper
2023-11-10 14:30:08 +01:00
Hassan Ben Jobrane
8b127fbb62 chore: add changeset 2023-11-10 14:13:27 +01:00
Hassan Ben Jobrane
86ba2081ec chore: fix docusaurus front matter issue 2023-11-10 14:13:20 +01:00
Hassan Ben Jobrane
7c2c31082a chore: add changeset 2023-11-10 11:50:54 +01:00
Hassan Ben Jobrane
60f705b033 feat: add user account deletion functionality 2023-11-10 11:49:22 +01:00
Sheena Pakanati
ea34635eb2 feat: export urlFromSubdomain helper 2023-11-08 11:30:10 -05:00
Hassan Ben Jobrane
2004687044 Merge pull request #2360 from nhost/fix/examples/react-apollo
fix(react-apollo): update Apple OAuth secrets in nhost.toml
2023-11-07 11:24:51 +01:00
Hassan Ben Jobrane
bd025d43ca fix: update Apple OAuth secrets in nhost.toml 2023-11-07 10:54:16 +01:00
Hassan Ben Jobrane
87a05f7374 Merge pull request #2353 from nhost/feat/react-appollo/signin-with-apple
feat(react-apollo): add SignIn with Apple
2023-11-07 08:53:19 +01:00
Hassan Ben Jobrane
798f147db7 chore: remove console.log statement 2023-11-06 20:13:05 +01:00
Hassan Ben Jobrane
62b7fd2376 chore: update auth version to 0.21.4 2023-11-06 20:11:33 +01:00
David Barroso
1ee021b4a3 chore(docs): remove custom domains from roadmap (#2352) 2023-11-04 12:40:18 +01:00
Hassan Ben Jobrane
6e61dce297 chore: add changeset 2023-11-03 17:28:01 +01:00
Hassan Ben Jobrane
bd744e52dc feat(examples): add SignIn with Apple to the react-apollo example 2023-11-03 17:26:23 +01:00
Nestor Manrique
85723d740b Merge pull request #2343 from nhost/nestor/fix/ingress-tenant-dashboard
fix (observability): ingress tenant dashboard
2023-10-26 21:56:18 +02:00
Hassan Ben Jobrane
36e79e7b32 Merge pull request #2344 from nhost/chore/quickstarts/upgrade-storage
chore: bump quickstarts storage to `0.4.0`
2023-10-26 11:39:58 +01:00
Hassan Ben Jobrane
f61264b319 chore: bump quickstarts storage to 0.4.0 2023-10-26 11:22:41 +01:00
Nestor Manrique
e84d9d2576 Fix legends 2023-10-26 11:28:33 +02:00
Hassan Ben Jobrane
ea69d4f0f1 Merge pull request #2341 from nhost/changeset-release/main
chore: update versions
2023-10-25 14:53:21 +01:00
github-actions[bot]
212d58bee5 chore: update versions 2023-10-25 13:22:09 +00:00
Hassan Ben Jobrane
c3d6b7beec Merge pull request #2333 from nhost/feat/vue-sdk/upload-multiple-files
feat(vue-sdk): add support for uploading multiple files
2023-10-25 14:18:36 +01:00
Hassan Ben Jobrane
5d5d8ef4f3 chore: use @nhost/nhost-js from workspace 2023-10-25 13:31:12 +01:00
Hassan Ben Jobrane
deb61fe97c chore: add @nhost/nhost-js to vue-apollo example 2023-10-25 13:21:36 +01:00
Nestor Manrique
04d36154b0 Merge pull request #2334 from nhost/nestor/feat/add-ingress-dashboard
feat(observability): Add ingress metrics dashboard for tenants
2023-10-25 14:21:01 +02:00
Hassan Ben Jobrane
203cfb10b9 chore: fix JSDoc 2023-10-25 12:43:54 +01:00
Hassan Ben Jobrane
9690f871fa chore: fix JSDoc 2023-10-25 11:44:45 +01:00
Hassan Ben Jobrane
74a6b93971 Merge pull request #2335 from nhost/chore/examples/upgrade-to-node18
chore: update toml files to use node 18
2023-10-25 10:36:37 +01:00
Nestor Manrique
dd4c0d2430 wip 2023-10-25 03:30:52 +02:00
Hassan Ben Jobrane
83f2ca5cde chore: update toml files to use node 18 2023-10-24 16:39:09 +01:00
Hassan Ben Jobrane
0c49e757c8 chore: add changeset 2023-10-24 16:25:07 +01:00
Hassan Ben Jobrane
e90a9d7696 feat: add storage page to vue-apollo example 2023-10-24 16:20:02 +01:00
Hassan Ben Jobrane
00a06466f5 fix: return refs from useFileUpload 2023-10-24 16:20:02 +01:00
Hassan Ben Jobrane
8ca9f76cb2 wip: add support for uploading multiple files 2023-10-24 16:20:02 +01:00
Hassan Ben Jobrane
78113dd62a wip: feat: vue-sdk: introduce new composable to upload multiple files 2023-10-24 16:20:02 +01:00
Nestor Manrique
adb0ee82c6 wip 2023-10-24 14:29:25 +02:00
Nestor Manrique
a41bb6cae6 wip 2023-10-24 14:05:42 +02:00
Hassan Ben Jobrane
1c59c363ee Merge pull request #2328 from nhost/changeset-release/main
chore: update versions
2023-10-24 11:20:22 +01:00
github-actions[bot]
1d99f26fec chore: update versions 2023-10-24 09:59:28 +00:00
Hassan Ben Jobrane
49edb0e627 Merge pull request #2332 from ttiras/patch-1
Update Docs for useChangeEmail.ts Example
2023-10-24 10:56:55 +01:00
Hassan Ben Jobrane
f011e71ae1 chore: fix typo 2023-10-23 19:49:05 +01:00
Hassan Ben Jobrane
00c363f808 chore: add changeset 2023-10-23 18:10:04 +01:00
Hassan Ben Jobrane
0b2f749ae9 fix: docs: vuejs: update changeEmail docs reference 2023-10-23 18:05:24 +01:00
Hassan Ben Jobrane
cf62a1e6e3 Merge pull request #2331 from nhost/fix/custom-domains/reset-domain
fix(dashboard): allow resetting custom domains
2023-10-20 17:58:06 +01:00
Hassan Ben Jobrane
8df84d782f chore: add changeset 2023-10-20 16:01:47 +01:00
Hassan Ben Jobrane
f0deffafe1 fix(dashboard): allow resetting custom domains 2023-10-20 15:59:54 +01:00
Hassan Ben Jobrane
a291da661d Merge pull request #2321 from MainaMary/bug/update-use-change-password-interface
fix: update useChangePassword hook interface
2023-10-20 11:42:41 +01:00
Mary
66c3193bc9 chore: add changeset 2023-10-20 13:03:00 +03:00
Hassan Ben Jobrane
ac7be49cef Merge pull request #2327 from nhost/chore/run/tweaks
chore(dashboard): fixes and tweaks to services form and dialog
2023-10-19 11:53:32 +01:00
Hassan Ben Jobrane
fa79b77093 chore: add changeset 2023-10-19 11:22:18 +01:00
Hassan Ben Jobrane
5823947933 chore: add missing key to service details dialog 2023-10-19 11:21:11 +01:00
Hassan Ben Jobrane
333837fb57 chore: fix update button icon on service form 2023-10-19 11:10:01 +01:00
Hassan Ben Jobrane
7fae68f6cf Merge pull request #2324 from nhost/changeset-release/main
chore: update versions
2023-10-18 17:09:56 +01:00
github-actions[bot]
f2751f4bac chore: update versions 2023-10-18 16:00:42 +00:00
Hassan Ben Jobrane
089acbbe70 Merge pull request #2320 from nhost/feat/custom-domains
feat(dashboard): custom domains
2023-10-18 16:58:03 +01:00
Nuno Pato
6e08a82f49 Merge pull request #2325 from nhost/docs/custom-domains
chore: docs: add custom domains
2023-10-18 15:49:47 +00:00
Hassan Ben Jobrane
6899ef3b39 chore: run pnpm codegen 2023-10-18 16:20:17 +01:00
Hassan Ben Jobrane
cad3686364 fix: tweak text when dark mode is on 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
8f2c002715 fix: fix custom domains page on small screens 2023-10-18 16:18:45 +01:00
Nuno Pato
b70d61198f fix link to docs 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
d29af2ce6f fix: make sure settings container title supports both a ReactNode and a string 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
cdc992b888 chore: update custom domains header message
Co-authored-by: Nuno Pato <nunopato@gmail.com>
2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
205a20de87 fix: use correct database host 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
b092b8fe08 chore: tweak database domain description and docs link 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
2d40cbf624 fix: tweak verification box 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
7b591e8c4c fix dns verification values 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
72b425a5bc fix: remove duplicated nhost.run suffix 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
971ff92ab4 chore: remove comment 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
b7f801874d chore: add changeset 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
ff69f30e47 chore: move docs link to the top section 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
cc1932492d fix: only show services that have ports 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
f45037e79f fix: always show CNAME verification panel 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
48658e2925 feat: added run services port domains 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
b90bb6b924 feat: add database domain form 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
de61f45bd5 fix: auth and hasura domain forms 2023-10-18 16:18:45 +01:00
Hassan Ben Jobrane
fd11e5ca2c feat: add Hasura Domain 2023-10-18 16:18:44 +01:00
Hassan Ben Jobrane
7839c786ef chore: run pnpm codegen 2023-10-18 16:18:44 +01:00
Hassan Ben Jobrane
a2bcd6a4b6 feat: add auth domain form 2023-10-18 16:18:44 +01:00
Hassan Ben Jobrane
2cd5b26e0e chore: run pnpm codegen 2023-10-18 16:18:44 +01:00
Hassan Ben Jobrane
559611af70 feat: add upgrade banner to access custom domains 2023-10-18 16:16:37 +01:00
Hassan Ben Jobrane
ffb45f5a49 Merge pull request #2326 from nhost/feat/database/storage-capacity
feat(dashboard): add database storage capacity setting
2023-10-18 15:53:18 +01:00
Hassan Ben Jobrane
451e80ac12 chore: add a warning message that db storage can't be downgraded 2023-10-18 15:19:18 +01:00
Hassan Ben Jobrane
c9f8e523f2 chore: fix upgrade message text 2023-10-18 13:43:37 +01:00
Hassan Ben Jobrane
331ba03768 chore: add changeset 2023-10-18 13:29:42 +01:00
Hassan Ben Jobrane
611b26bc7d chore: fix mocks 2023-10-18 13:29:33 +01:00
Nuno Pato
a446c3efca use custom-domain.com 2023-10-18 12:24:11 +00:00
Hassan Ben Jobrane
24424ae4dc feat: add postgres storage capacity setting 2023-10-18 13:19:43 +01:00
Hassan Ben Jobrane
2a5b705c26 chore: run pnpm codegen & remove deprecated insertFeedback 2023-10-18 13:18:56 +01:00
Nuno Pato
7f3a32d386 use tabs 2023-10-18 11:51:14 +00:00
Nuno Pato
11fa442aa8 Merge branch 'main' into docs/custom-domains 2023-10-18 11:45:19 +00:00
Nuno Pato
5764f46d99 add changeset 2023-10-18 11:36:26 +00:00
Nuno Pato
78d501801b docs: custom domains 2023-10-18 11:34:38 +00:00
David Barroso
cc8cc8d45d chore(docs): database: added extension http (#2323) 2023-10-18 11:42:50 +02:00
Mary
61fc83996b fix: update useChangePassword hook interface 2023-10-17 13:29:49 +03:00
ttiras
9ddb37e9bb Update useChangeEmail.ts
the wrong example has been modified from;
 
 await changeEmail({
    email: 'new@example.com'
  })

to;

 await changeEmail('new@example.com')
2023-10-14 13:53:07 +03:00
107 changed files with 5870 additions and 824 deletions

View File

@@ -1,5 +1,29 @@
# @nhost/dashboard
## 0.20.28
### Patch Changes
- 7c2c31082: feat: add support for users to delete their account
- @nhost/react-apollo@6.0.1
- @nhost/nextjs@1.13.40
## 0.20.27
### Patch Changes
- fa79b7709: chore(dashboard): tweaks and fixes to the service form and dialog
- 8df84d782: fix(dashboard): allow resetting custom domains
- @nhost/react-apollo@6.0.0
- @nhost/nextjs@1.13.39
## 0.20.26
### Patch Changes
- 331ba0376: feat(dashboard): add postgres storage capacity modifier in the settings
- b7f801874: feat(dashboard): add new settings page for custom domains
## 0.20.25
### Patch Changes

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@@ -0,0 +1,100 @@
import { useDialog } from '@/components/common/DialogProvider';
import { NhostIcon } from '@/components/presentational/NhostIcon';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { ArrowSquareOutIcon } from '@/components/ui/v2/icons/ArrowSquareOutIcon';
import { Link } from '@/components/ui/v2/Link';
import { Text } from '@/components/ui/v2/Text';
import { ChangePlanModal } from '@/features/projects/common/components/ChangePlanModal';
import { useIsCurrentUserOwner } from '@/features/projects/common/hooks/useIsCurrentUserOwner';
import Image from 'next/image';
interface UpgradeToProBannerProps {
title: string;
description: string;
}
export default function UpgradeToProBanner({
title,
description,
}: UpgradeToProBannerProps) {
const { openDialog, openAlertDialog } = useDialog();
const isOwner = useIsCurrentUserOwner();
return (
<Box
sx={{ backgroundColor: 'primary.light' }}
className="flex flex-col p-4 space-y-4 rounded-md lg:flex-row lg:items-center lg:space-y-0"
>
<div className="flex flex-col justify-between space-y-4">
<div className="space-y-2">
<div className="flex flex-col space-y-2 xs:flex-row xs:space-y-0 xs:space-x-2">
<Text>Available with</Text>
<div className="flex flex-row space-x-2">
<NhostIcon />
<Text sx={{ color: 'primary.main' }} className="font-semibold">
Nhost Pro
</Text>
</div>
</div>
<Text variant="h3">{title}</Text>
<Text>{description}</Text>
</div>
<div className="flex flex-col space-y-2 lg:flex-row lg:items-center lg:space-y-0 lg:space-x-2">
<Button
className="rounded-md"
onClick={() => {
if (isOwner) {
openDialog({
component: <ChangePlanModal />,
props: {
PaperProps: { className: 'p-0 max-w-xl w-full' },
},
});
} else {
openAlertDialog({
title: "You can't upgrade this project",
payload: (
<Text variant="subtitle1" component="span">
Ask an owner of this workspace to upgrade the project.
</Text>
),
props: {
secondaryButtonText: 'I understand',
hidePrimaryAction: true,
},
});
}
}}
>
Upgrade to Pro
</Button>
<Link
href="https://nhost.io/pricing"
target="_blank"
rel="noopener noreferrer"
underline="hover"
className="font-medium text-center"
sx={{
color: 'text.secondary',
}}
>
See all features
<ArrowSquareOutIcon className="w-4 h-4 ml-1" />
</Link>
</div>
</div>
<div className="max-w-xs mx-auto">
<Image
src="/illustration-unbox.png"
width={400}
height={260}
objectFit="contain"
/>
</div>
</Box>
);
}

View File

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

View File

@@ -128,7 +128,11 @@ export default function SettingsContainer({
icon}
<div className="grid grid-flow-row gap-1">
<Text className="text-lg font-semibold">{title}</Text>
{typeof title === 'string' ? (
<Text className="text-lg font-semibold">{title}</Text>
) : (
title
)}
{description && <Text color="secondary">{description}</Text>}
</div>

View File

@@ -200,6 +200,14 @@ export default function SettingsSidebar({
>
Secrets
</SettingsNavLink>
<SettingsNavLink
href="/custom-domains"
exact={false}
onClick={handleSelect}
>
Custom Domains
</SettingsNavLink>
</List>
</nav>
</Box>

View File

@@ -0,0 +1,35 @@
import type { ForwardedRef, SVGProps } from 'react';
import { forwardRef } from 'react';
function NhostIcon(
props: SVGProps<SVGSVGElement>,
ref: ForwardedRef<SVGSVGElement>,
) {
return (
<svg
ref={ref}
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-label="Logo of Nhost"
{...props}
>
<g clipPath="url(#clip0_9802_20458)">
<rect width="24" height="24" fill="#0052CD" />
<path
d="M17.4656 7.39804L12.4705 4.51369C12.0223 4.25553 11.466 4.25553 11.0169 4.51369C10.5688 4.77276 10.2906 5.25455 10.2906 5.77179V6.14813L9.96517 5.95996C9.51702 5.70179 8.96069 5.70179 8.51163 5.95996C8.06348 6.21903 7.78531 6.70082 7.78531 7.21896V7.5953L7.45988 7.40713C7.01173 7.14897 6.4554 7.14897 6.00634 7.40713C5.55819 7.66621 5.28003 8.14799 5.28003 8.66614V17.7037C5.28003 17.9637 5.43093 18.2055 5.66546 18.3182C5.89908 18.4318 6.1827 18.4009 6.38632 18.24L8.86342 16.2865L12.6832 18.4918C12.7886 18.5527 12.9068 18.5827 13.025 18.5827C13.1431 18.5827 13.2613 18.5518 13.3668 18.4918C13.5777 18.37 13.7086 18.1437 13.7086 17.9001V12.4613C13.7086 11.5687 13.2286 10.7378 12.4559 10.2915L11.2033 9.56789V5.7727C11.2033 5.57998 11.3069 5.4 11.4742 5.30364C11.6414 5.20728 11.8487 5.20728 12.0159 5.30364L17.0111 8.18708C17.5028 8.4707 17.8083 9.00066 17.8083 9.56789V16.3402C17.8083 16.5329 17.7046 16.7129 17.5374 16.8092L16.2138 17.5737V11.0142C16.2138 10.1215 15.7339 9.29064 14.9612 8.84431L11.8859 7.06897V8.12072L14.5058 9.63334C14.9976 9.91696 15.303 10.446 15.303 11.0142V17.9673C15.303 18.21 15.4339 18.4373 15.6448 18.5591C15.7502 18.62 15.8684 18.65 15.9866 18.65C16.1048 18.65 16.2229 18.6191 16.3284 18.5591L17.9937 17.5974C18.4419 17.3383 18.72 16.8565 18.72 16.3383V9.56608C18.7182 8.67614 18.2382 7.84438 17.4656 7.39804ZM11.9987 11.0805C12.4905 11.3641 12.7959 11.8932 12.7959 12.4613V17.5064L9.63246 15.6802L10.6478 14.8803C10.9996 14.603 11.2014 14.1876 11.2014 13.7394V10.6215L11.9987 11.0805ZM10.2906 10.0942V13.7376C10.2906 13.9049 10.2152 14.0603 10.0842 14.163L6.19088 17.2328V8.66523C6.19088 8.47251 6.29451 8.29253 6.46177 8.19617C6.62903 8.09981 6.83629 8.09981 7.00355 8.19617L7.78531 8.64705V15.1057L8.69616 14.3876V7.21896C8.69616 7.02625 8.79979 6.84626 8.96705 6.7499C9.13431 6.65355 9.34157 6.65355 9.50883 6.7499L10.2906 7.20078V9.04157L9.37975 8.51524V9.56789L10.2906 10.0942Z"
fill="white"
/>
</g>
<defs>
<clipPath id="clip0_9802_20458">
<rect width="24" height="24" rx="4" fill="white" />
</clipPath>
</defs>
</svg>
);
}
export default forwardRef(NhostIcon);

View File

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

View File

@@ -0,0 +1,44 @@
import type { IconProps } from '@/components/ui/v2/icons';
function ArrowsClockwise(props: IconProps) {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
aria-label="Update"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M11.0103 6.23227H14.0103V3.23227"
stroke="currentColor"
strokeWidth="1.5"
strokeLinejoin="round"
/>
<path
d="M4.11084 4.11091C4.62156 3.60019 5.22788 3.19506 5.89517 2.91866C6.56246 2.64226 7.27766 2.5 7.99993 2.5C8.7222 2.5 9.4374 2.64226 10.1047 2.91866C10.772 3.19506 11.3783 3.60019 11.889 4.11091L14.0103 6.23223"
stroke="currentColor"
strokeWidth="1.5"
strokeLinejoin="round"
/>
<path
d="M4.98975 9.76773H1.98975V12.7677"
stroke="currentColor"
strokeWidth="1.5"
strokeLinejoin="round"
/>
<path
d="M11.8892 11.8891C11.3785 12.3998 10.7722 12.8049 10.1049 13.0813C9.43762 13.3577 8.72242 13.5 8.00015 13.5C7.27788 13.5 6.56269 13.3577 5.89539 13.0813C5.2281 12.8049 4.62179 12.3998 4.11107 11.8891L1.98975 9.76776"
stroke="currentColor"
strokeWidth="1.5"
strokeLinejoin="round"
/>
</svg>
);
}
ArrowsClockwise.displayName = 'NhostArrowsClockwise';
export default ArrowsClockwise;

View File

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

View File

@@ -0,0 +1,161 @@
import { useDialog } from '@/components/common/DialogProvider';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { Checkbox } from '@/components/ui/v2/Checkbox';
import { Text } from '@/components/ui/v2/Text';
import { getToastStyleProps } from '@/utils/constants/settings';
import {
useDeleteUserAccountMutation,
useGetAllWorkspacesAndProjectsQuery,
} from '@/utils/__generated__/graphql';
import { type ApolloError } from '@apollo/client';
import { useSignOut, useUserData } from '@nhost/nextjs';
import { useRouter } from 'next/router';
import { useState } from 'react';
import toast from 'react-hot-toast';
import { twMerge } from 'tailwind-merge';
function ConfirmDeleteAccountModal({
close,
onDelete,
}: {
onDelete?: () => Promise<any>;
close: () => void;
}) {
const [remove, setRemove] = useState(false);
const [loadingRemove, setLoadingRemove] = useState(false);
const user = useUserData();
const { data, loading } = useGetAllWorkspacesAndProjectsQuery({
skip: !user,
});
const userHasProjects =
!loading && data?.workspaces.some((workspace) => workspace.projects.length);
const userData = useUserData();
const [deleteUserAccount] = useDeleteUserAccountMutation({
variables: { id: userData?.id },
});
const onClickConfirm = async () => {
setLoadingRemove(true);
await toast.promise(
deleteUserAccount(),
{
loading: 'Deleting your account...',
success: `The account has been deleted successfully.`,
error: (arg: ApolloError) => {
// we need to get the internal error message from the GraphQL error
const { internal } = arg.graphQLErrors[0]?.extensions || {};
const { message } = (internal as Record<string, any>)?.error || {};
// we use the default Apollo error message if we can't find the
// internal error message
return (
message ||
arg.message ||
'An error occurred while deleting your account. Please try again.'
);
},
},
getToastStyleProps(),
);
onDelete?.();
close();
};
return (
<Box className={twMerge('w-full rounded-lg p-6 text-left')}>
<div className="grid grid-flow-row gap-1">
<Text variant="h3" component="h2">
Delete Account?
</Text>
{userHasProjects && (
<Text
variant="subtitle2"
className="font-bold"
sx={{ color: (theme) => `${theme.palette.error.main} !important` }}
>
You still have active projects. Please delete your projects before
proceeding with the account deletion.
</Text>
)}
<Box className="my-4">
<Checkbox
id="accept-1"
label={`I'm sure I want to delete my account`}
className="py-2"
checked={remove}
onChange={(_event, checked) => setRemove(checked)}
aria-label="Confirm Delete Project #1"
/>
</Box>
<div className="grid grid-flow-row gap-2">
<Button
color="error"
onClick={onClickConfirm}
disabled={userHasProjects}
loading={loadingRemove}
>
Delete
</Button>
<Button variant="outlined" color="secondary" onClick={close}>
Cancel
</Button>
</div>
</div>
</Box>
);
}
export default function DeleteAccount() {
const router = useRouter();
const { signOut } = useSignOut();
const { openDialog, closeDialog } = useDialog();
const onDelete = async () => {
await signOut();
await router.push('/signin');
};
const confirmDeleteAccount = async () => {
openDialog({
component: (
<ConfirmDeleteAccountModal close={closeDialog} onDelete={onDelete} />
),
});
};
return (
<SettingsContainer
title="Delete Account"
description="Please proceed with caution as the removal of your Personal Account and its contents from the Nhost platform is irreversible. This action will permanently delete your account and all associated data."
className="px-0"
slotProps={{
submitButton: { className: 'hidden' },
footer: { className: 'hidden' },
}}
>
<Box className="grid grid-flow-row border-t-1">
<Button
color="error"
className="mx-4 mt-4 justify-self-end"
onClick={confirmDeleteAccount}
>
Delete Personal Account
</Button>
</Box>
</SettingsContainer>
);
}

View File

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

View File

@@ -0,0 +1,5 @@
mutation deleteUserAccount($id: uuid!) {
deleteUser(id: $id) {
__typename
}
}

View File

@@ -24,6 +24,13 @@ query GetAuthenticationSettings($appId: uuid!) {
expiresIn
}
}
resources {
networking {
ingresses {
fqdn
}
}
}
user {
email {
allowed

View File

@@ -0,0 +1,161 @@
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Box } from '@/components/ui/v2/Box';
import { Input } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text';
import { UpgradeNotification } from '@/features/projects/common/components/UpgradeNotification';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import {
useGetPostgresSettingsQuery,
useUpdateConfigMutation,
} from '@/generated/graphql';
import { getToastStyleProps } from '@/utils/constants/settings';
import { getServerError } from '@/utils/getServerError';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import * as Yup from 'yup';
const validationSchema = Yup.object({
capacity: Yup.number().required(),
});
export type AuthDomainFormValues = Yup.InferType<typeof validationSchema>;
export default function AuthDomain() {
const { maintenanceActive } = useUI();
const { currentProject } = useCurrentWorkspaceAndProject();
const {
data,
loading,
error,
refetch: refetchPostgresSettings,
} = useGetPostgresSettingsQuery({
variables: { appId: currentProject?.id },
fetchPolicy: 'cache-only',
});
const capacity =
data?.config?.postgres?.resources?.storage?.capacity ??
currentProject.plan.featureMaxDbSize;
const [updateConfig] = useUpdateConfigMutation();
const form = useForm<{ capacity: number }>({
reValidateMode: 'onSubmit',
defaultValues: { capacity },
resolver: yupResolver(validationSchema),
});
const { formState, register, reset } = form;
const isDirty = Object.keys(formState.dirtyFields).length > 0;
useEffect(() => {
if (data && !loading) {
reset({ capacity });
}
}, [loading, data, reset, capacity]);
if (loading) {
return (
<ActivityIndicator
delay={1000}
label="Loading Auth Domain Settings..."
className="justify-center"
/>
);
}
if (error) {
throw error;
}
async function handleSubmit(formValues: AuthDomainFormValues) {
try {
await toast.promise(
updateConfig({
variables: {
appId: currentProject.id,
config: {
postgres: {
resources: {
storage: {
capacity: formValues.capacity,
},
},
},
},
},
}),
{
loading: `Database storage capacity is being updated...`,
success: `Database storage capacity has been updated successfully.`,
error: getServerError(
`An error occurred while trying to update the database storage capacity.`,
),
},
getToastStyleProps(),
);
form.reset(formValues);
await refetchPostgresSettings();
} catch {
// Note: The toast will handle the error.
}
}
return (
<FormProvider {...form}>
<Form onSubmit={handleSubmit}>
<SettingsContainer
title="Storage capacity"
description="Specify the storage capacity for your PostgreSQL database."
slotProps={{
submitButton: {
disabled: !isDirty || maintenanceActive,
loading: formState.isSubmitting,
},
}}
className="flex flex-col"
>
{currentProject.plan.isFree && (
<UpgradeNotification message="Unlock by upgrading your project to the Pro plan." />
)}
<Box className="grid grid-flow-row lg:grid-cols-5">
<Input
{...register('capacity')}
id="capacity"
name="capacity"
type="number"
fullWidth
disabled={currentProject.plan.isFree}
className="lg:col-span-2"
error={Boolean(formState.errors.capacity?.message)}
helperText={formState.errors.capacity?.message}
slotProps={{
inputRoot: {
min: capacity,
},
}}
/>
</Box>
{!currentProject.plan.isFree && (
<Box
className="grid items-center grid-flow-col gap-1 p-3 rounded-lg shadow-sm place-content-between"
sx={{ backgroundColor: 'grey.200' }}
>
<Text>
Please note that once you increase the storage, it cannot be
reduced.
</Text>
</Box>
)}
</SettingsContainer>
</Form>
</FormProvider>
);
}

View File

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

View File

@@ -9,6 +9,11 @@ query GetPostgresSettings($appId: uuid!) {
__typename
postgres {
version
resources {
storage {
capacity
}
}
}
}
}

View File

@@ -18,6 +18,13 @@ query GetHasuraSettings($appId: uuid!) {
events {
httpPoolSize
}
resources {
networking {
ingresses {
fqdn
}
}
}
}
}
}

View File

@@ -0,0 +1,153 @@
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { VerifyDomain } from '@/features/projects/custom-domains/settings/components/VerifyDomain';
import {
useGetAuthenticationSettingsQuery,
useUpdateConfigMutation,
type ConfigIngressUpdateInput,
} from '@/generated/graphql';
import { getToastStyleProps } from '@/utils/constants/settings';
import { getServerError } from '@/utils/getServerError';
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';
const validationSchema = Yup.object({
auth_fqdn: Yup.string(),
});
export type AuthDomainFormValues = Yup.InferType<typeof validationSchema>;
export default function AuthDomain() {
const { maintenanceActive } = useUI();
const [isVerified, setIsVerified] = useState(false);
const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation();
const form = useForm<{ auth_fqdn: string }>({
reValidateMode: 'onSubmit',
defaultValues: { auth_fqdn: null },
resolver: yupResolver(validationSchema),
});
const { data, loading, error } = useGetAuthenticationSettingsQuery({
variables: {
appId: currentProject.id,
},
});
const { networking } = data?.config?.auth?.resources || {};
const initialValue = networking?.ingresses?.[0]?.fqdn?.[0];
useEffect(() => {
if (!loading && data) {
form.reset({ auth_fqdn: initialValue });
}
}, [data, loading, form, initialValue]);
if (loading) {
return (
<ActivityIndicator
delay={1000}
label="Loading Auth Domain Settings..."
className="justify-center"
/>
);
}
if (error) {
throw error;
}
const { formState, register, watch } = form;
const isDirty = Object.keys(formState.dirtyFields).length > 0;
const auth_fqdn = watch('auth_fqdn');
async function handleSubmit(formValues: AuthDomainFormValues) {
const ingresses: ConfigIngressUpdateInput[] =
formValues.auth_fqdn.length > 0 ? [{ fqdn: [formValues.auth_fqdn] }] : [];
const updateConfigPromise = updateConfig({
variables: {
appId: currentProject.id,
config: {
auth: {
resources: {
networking: {
ingresses,
},
},
},
},
},
});
try {
await toast.promise(
updateConfigPromise,
{
loading: `Auth domain is being updated...`,
success: `Auth domain has been updated successfully.`,
error: getServerError(
`An error occurred while trying to update the auth domain.`,
),
},
getToastStyleProps(),
);
form.reset(formValues);
await refetchWorkspaceAndProject();
} catch {
// Note: The toast will handle the error.
}
}
return (
<FormProvider {...form}>
<Form onSubmit={handleSubmit}>
<SettingsContainer
title="Auth Domain"
description="Enter below your custom domain for the authentication service."
slotProps={{
submitButton: {
disabled:
!isDirty || maintenanceActive || (!isVerified && !initialValue),
loading: formState.isSubmitting,
},
}}
className="grid grid-flow-row px-4 gap-y-4 gap-x-4 lg:grid-cols-5"
>
<Input
{...register('auth_fqdn')}
id="auth_fqdn"
name="auth_fqdn"
type="string"
fullWidth
className="col-span-5 lg:col-span-2"
placeholder="auth.mydomain.dev"
error={Boolean(formState.errors.auth_fqdn?.message)}
helperText={formState.errors.auth_fqdn?.message}
slotProps={{ inputRoot: { min: 1, max: 100 } }}
/>
<div className="col-span-5 row-start-2">
<VerifyDomain
recordType="CNAME"
hostname={auth_fqdn}
value={`lb.${currentProject.region.awsName}.${currentProject.region.domain}.`}
onHostNameVerified={() => setIsVerified(true)}
/>
</div>
</SettingsContainer>
</Form>
</FormProvider>
);
}

View File

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

View File

@@ -0,0 +1,61 @@
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { VerifyDomain } from '@/features/projects/custom-domains/settings/components/VerifyDomain';
import { useState } from 'react';
import * as Yup from 'yup';
const validationSchema = Yup.object({
database_fqdn: Yup.string().required(),
});
export type DatabaseDomainFormValues = Yup.InferType<typeof validationSchema>;
export default function DatabaseDomain() {
const { currentProject } = useCurrentWorkspaceAndProject();
const [dbFQDN, setDbFQDN] = useState('');
const postgresHost = generateAppServiceUrl(
currentProject.subdomain,
currentProject.region,
'db',
).replace('https://', '');
return (
<SettingsContainer
title="Database Domain"
description="Enter below your custom domain for the PostgreSQL database to verify. Once verified there is no need to save this value as no configuration on our end is required."
slotProps={{
submitButton: {
hidden: true,
},
footer: {
className: 'hidden',
},
}}
className="grid grid-flow-row px-4 gap-y-4 gap-x-4 lg:grid-cols-5"
>
<Input
id="database_fqdn"
name="database_fqdn"
type="string"
fullWidth
className="col-span-5 lg:col-span-2"
placeholder="db.mydomain.dev"
onChange={(e) => {
setDbFQDN(e.target.value);
}}
slotProps={{ inputRoot: { min: 1, max: 100 } }}
/>
<div className="col-span-5 row-start-2">
<VerifyDomain
recordType="CNAME"
hostname={dbFQDN}
value={`${postgresHost}.`}
/>
</div>
</SettingsContainer>
);
}

View File

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

View File

@@ -0,0 +1,155 @@
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Input } from '@/components/ui/v2/Input';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { VerifyDomain } from '@/features/projects/custom-domains/settings/components/VerifyDomain';
import {
useGetHasuraSettingsQuery,
useUpdateConfigMutation,
type ConfigIngressUpdateInput,
} from '@/generated/graphql';
import { getToastStyleProps } from '@/utils/constants/settings';
import { getServerError } from '@/utils/getServerError';
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';
const validationSchema = Yup.object({
hasura_fqdn: Yup.string(),
});
export type HasuraDomainFormValues = Yup.InferType<typeof validationSchema>;
export default function HasuraDomain() {
const { maintenanceActive } = useUI();
const [isVerified, setIsVerified] = useState(false);
const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation();
const form = useForm<{ hasura_fqdn: string }>({
reValidateMode: 'onSubmit',
defaultValues: { hasura_fqdn: null },
resolver: yupResolver(validationSchema),
});
const { data, loading, error } = useGetHasuraSettingsQuery({
variables: {
appId: currentProject.id,
},
});
const { networking } = data?.config?.hasura?.resources || {};
const initialValue = networking?.ingresses?.[0]?.fqdn?.[0];
useEffect(() => {
if (!loading && data) {
form.reset({ hasura_fqdn: initialValue });
}
}, [data, loading, form, initialValue]);
if (loading) {
return (
<ActivityIndicator
delay={0}
label="Loading Hasura Domain..."
className="justify-center"
/>
);
}
if (error) {
throw error;
}
const { formState, register, watch } = form;
const isDirty = Object.keys(formState.dirtyFields).length > 0;
const hasura_fqdn = watch('hasura_fqdn');
async function handleSubmit(formValues: HasuraDomainFormValues) {
const ingresses: ConfigIngressUpdateInput[] =
formValues.hasura_fqdn.length > 0
? [{ fqdn: [formValues.hasura_fqdn] }]
: [];
const updateConfigPromise = updateConfig({
variables: {
appId: currentProject.id,
config: {
hasura: {
resources: {
networking: {
ingresses,
},
},
},
},
},
});
try {
await toast.promise(
updateConfigPromise,
{
loading: `Hasura domain is being updated...`,
success: `Hasura domain has been updated successfully.`,
error: getServerError(
`An error occurred while trying to update the Hasura domain.`,
),
},
getToastStyleProps(),
);
form.reset(formValues);
await refetchWorkspaceAndProject();
} catch {
// Note: The toast will handle the error.
}
}
return (
<FormProvider {...form}>
<Form onSubmit={handleSubmit}>
<SettingsContainer
title="Hasura Domain"
description="Enter below your custom domain for the Hasura/GraphQL service."
slotProps={{
submitButton: {
disabled:
!isDirty || maintenanceActive || (!isVerified && !initialValue),
loading: formState.isSubmitting,
},
}}
className="grid grid-flow-row px-4 gap-y-4 gap-x-4 lg:grid-cols-5"
>
<Input
{...register('hasura_fqdn')}
id="hasura_fqdn"
name="hasura_fqdn"
type="string"
fullWidth
className="col-span-5 lg:col-span-2"
placeholder="auth.mydomain.dev"
error={Boolean(formState.errors.hasura_fqdn?.message)}
helperText={formState.errors.hasura_fqdn?.message}
slotProps={{ inputRoot: { min: 1, max: 100 } }}
/>
<div className="col-span-5 row-start-2">
<VerifyDomain
recordType="CNAME"
hostname={hasura_fqdn}
value={`lb.${currentProject.region.awsName}.${currentProject.region.domain}.`}
onHostNameVerified={() => setIsVerified(true)}
/>
</div>
</SettingsContainer>
</Form>
</FormProvider>
);
}

View File

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

View File

@@ -0,0 +1,88 @@
import { SettingsContainer } from '@/components/layout/SettingsContainer';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { ArrowSquareOutIcon } from '@/components/ui/v2/icons/ArrowSquareOutIcon';
import { Link } from '@/components/ui/v2/Link';
import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { RunServicePortDomain } from '@/features/projects/custom-domains/settings/components/RunServicePortDomain';
import { useGetRunServicesQuery } from '@/utils/__generated__/graphql';
import { useMemo } from 'react';
export default function RunServiceDomains() {
const { currentProject, currentWorkspace } = useCurrentWorkspaceAndProject();
const {
data,
loading,
// refetch: refetchServices, // TODO refetch after update
} = useGetRunServicesQuery({
variables: {
appID: currentProject.id,
resolve: false,
limit: 1000, // TODO consider pagination
offset: 0,
},
});
const services = useMemo(
() => data?.app?.runServices.map((service) => service) ?? [],
[data],
);
if (loading) {
return (
<ActivityIndicator
delay={1000}
label="Loading Run Services Domains..."
className="justify-center"
/>
);
}
return (
<div className="flex flex-col gap-6">
{services
.filter((service) => service.config?.ports?.length > 0)
.map((service) => (
<SettingsContainer
key={service.id}
title={
<div className="flex flex-row items-center">
<Text className="text-lg font-semibold">
{service.config?.name ?? 'unset'}
</Text>
<Link
href={`/${currentWorkspace.slug}/${currentProject.slug}/services`}
target="_blank"
rel="noopener noreferrer"
underline="hover"
className="font-medium"
>
<ArrowSquareOutIcon className="mb-1 ml-1 h-4 w-4" />
</Link>
</div>
}
description="Enter below your custom domain for the published ports."
docsTitle={service.config?.name ?? 'unset'}
slotProps={{
submitButton: {
hidden: true,
},
footer: {
className: 'hidden',
},
}}
className="grid gap-y-4 gap-x-4 px-4"
>
{service.config?.ports?.map((port) => (
<RunServicePortDomain
key={String(port.port)}
service={service}
port={port.port}
/>
))}
</SettingsContainer>
))}
</div>
);
}

View File

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

View File

@@ -0,0 +1,159 @@
import { useUI } from '@/components/common/UIProvider';
import { Form } from '@/components/form/Form';
import { Button } from '@/components/ui/v2/Button';
import { Input } from '@/components/ui/v2/Input';
import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { VerifyDomain } from '@/features/projects/custom-domains/settings/components/VerifyDomain';
import { useUpdateRunServiceConfigMutation } from '@/generated/graphql';
import { getToastStyleProps } from '@/utils/constants/settings';
import { getServerError } from '@/utils/getServerError';
import { yupResolver } from '@hookform/resolvers/yup';
import { type RunService } from 'pages/[workspaceSlug]/[appSlug]/services';
import { useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import * as Yup from 'yup';
interface RunServicePortProps {
service: RunService;
port: number;
}
const validationSchema = Yup.object({
runServicePortFQDN: Yup.string(),
});
export type RunServicePortFormValues = Yup.InferType<typeof validationSchema>;
export default function RunServicePortDomain({
service,
port,
}: RunServicePortProps) {
const { maintenanceActive } = useUI();
const [loading, setLoading] = useState(false);
const [isVerified, setIsVerified] = useState(false);
const { currentProject } = useCurrentWorkspaceAndProject();
const [updateRunServiceConfig] = useUpdateRunServiceConfigMutation();
const runServicePort = service.config.ports.find((p) => p.port === port);
const initialValue = runServicePort?.ingresses?.[0]?.fqdn?.[0];
const form = useForm<{ runServicePortFQDN: string }>({
reValidateMode: 'onSubmit',
defaultValues: {
runServicePortFQDN: initialValue,
},
resolver: yupResolver(validationSchema),
});
const { formState, register, watch } = form;
const isDirty = Object.keys(formState.dirtyFields).length > 0;
const runServicePortFQDN = watch('runServicePortFQDN');
async function handleSubmit(formValues: RunServicePortFormValues) {
try {
setLoading(true);
await toast.promise(
updateRunServiceConfig({
variables: {
appID: currentProject.id,
serviceID: service.id,
config: {
ports: service?.config?.ports?.map((p) => {
// exclude the `__typename` because the mutation will fail otherwise
const { __typename, ...rest } = p;
if (rest.port === port) {
return {
...rest,
ingresses:
formValues.runServicePortFQDN.length > 0
? [{ fqdn: [formValues.runServicePortFQDN] }]
: [],
};
}
return {
...rest,
// exclude the `__typename` because the mutation will fail otherwise
ingresses: rest.ingresses.map((item) => ({
fqdn: item.fqdn,
})),
};
}),
},
},
}),
{
loading: `Port ${port} is being updated...`,
success: `Port ${port} has been updated successfully.`,
error: getServerError(
`An error occurred while trying to update Port ${port}.`,
),
},
getToastStyleProps(),
);
form.reset(formValues);
// TODO refetch the service config
// await refetchWorkspaceAndProject();
} catch {
// Note: The toast will handle the error.
} finally {
setLoading(false);
}
}
return (
<FormProvider {...form}>
<Form onSubmit={handleSubmit}>
<div className="space-y-2">
<Text className="text-sm font-semibold">{`${runServicePort.type} <--> ${runServicePort.port}`}</Text>
<div className="flex flex-row space-x-4">
<Input
{...register('runServicePortFQDN')}
id="runServicePortFQDN"
name="runServicePortFQDN"
type="string"
fullWidth
className=""
placeholder={`${service.config?.name ?? 'unset'}-${
runServicePort.port
}.mydomain.dev`}
error={Boolean(formState.errors.runServicePortFQDN?.message)}
helperText={formState.errors.runServicePortFQDN?.message}
slotProps={{
inputRoot: { min: 1, max: 100 },
}}
/>
<Button
variant="outlined"
type="submit"
disabled={
loading ||
!isDirty ||
maintenanceActive ||
(!isVerified && !initialValue)
}
>
Save
</Button>
</div>
</div>
<div className="col-span-5 row-start-2 mt-4">
<VerifyDomain
recordType="CNAME"
hostname={runServicePortFQDN}
value={`lb.${currentProject.region.awsName}.${currentProject.region.domain}.`}
onHostNameVerified={() => setIsVerified(true)}
/>
</div>
</Form>
</FormProvider>
);
}

View File

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

View File

@@ -0,0 +1,155 @@
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { IconButton } from '@/components/ui/v2/IconButton';
import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
import { Text } from '@/components/ui/v2/Text';
import { getToastStyleProps } from '@/utils/constants/settings';
import { copy } from '@/utils/copy';
import { useDnsLookupCnameLazyQuery } from '@/utils/__generated__/graphql';
import { ApolloError } from '@apollo/client';
import { useState } from 'react';
import { toast } from 'react-hot-toast';
interface VerifyDomainProps {
recordType: string;
hostname: string;
value: string;
onHostNameVerified?: () => void;
}
export default function VerifyDomain({
recordType,
hostname,
value,
onHostNameVerified,
}: VerifyDomainProps) {
const [verificationFailed, setVerificationFailed] = useState(false);
const [verificationSucceeded, setVerificationSucceeded] = useState(false);
const [loading, setLoading] = useState(false);
const [fireLookupCNAME] = useDnsLookupCnameLazyQuery();
const handleVerifyDomain = async () => {
setLoading(true);
try {
await toast.promise(
fireLookupCNAME({
variables: {
hostname,
},
}).then(({ data: { dnsLookupCNAME } }) => {
if (dnsLookupCNAME !== value) {
throw new Error(`Could not verify ${hostname}`);
}
}),
{
loading: `Verifying ${hostname} ...`,
success: () => {
setVerificationFailed(false);
setVerificationSucceeded(true);
setLoading(false);
onHostNameVerified?.();
return `${hostname} has been verified.`;
},
error: (arg: Error | ApolloError) => {
setVerificationFailed(true);
setVerificationSucceeded(false);
setLoading(false);
if (arg instanceof ApolloError) {
// we need to get the internal error message from the GraphQL error
const { internal } = arg.graphQLErrors[0]?.extensions || {};
const { message } =
(internal as Record<string, any>)?.error || {};
// we use the default Apollo error message if we can't find the
// internal error message
return (
message ||
arg.message ||
`An error occurred while trying to verify ${hostname}. Please try again.`
);
}
return arg.message;
},
},
getToastStyleProps(),
);
} catch (error) {
// Note: The toast will handle the error.
}
};
return (
<Box
sx={[
{ backgroundColor: 'primary.light' },
verificationFailed && {
backgroundColor: 'error.light',
color: 'error.main',
},
verificationSucceeded && {
backgroundColor: 'success.light',
color: 'success.dark',
},
]}
className="flex flex-col p-4 space-y-4 rounded-md"
>
<div className="flex flex-row items-center justify-between">
{!verificationFailed && !verificationSucceeded && (
<Text>
Add the record below in your DNS provider to verify {hostname}
</Text>
)}
{verificationSucceeded && (
<Text>
<span className="font-semibold">{hostname}</span> was verified
successfully. Hit save to apply.
</Text>
)}
{verificationFailed && (
<Text>
An error occurred while trying to verify{' '}
<span className="font-semibold">{hostname}</span>. Make sure you
correctly added the <span className="font-semibold">CNAME</span> and
try again.
</Text>
)}
</div>
<div className="relative flex flex-col text-slate-500">
<div className="flex space-x-2">
<Text>Record type: </Text>
<Text className="font-bold">{recordType}</Text>
</div>
<div className="flex space-x-2">
<Text>Host:</Text>
<Text className="font-bold">{hostname}</Text>
</div>
<div className="flex flex-row space-x-2">
<Text>Value:</Text>
<Text className="font-bold">{value}</Text>
<IconButton
aria-label="Copy Personal Access Token"
variant="borderless"
color="secondary"
onClick={() => copy(value, 'CNAME Value')}
>
<CopyIcon className="w-4 h-4" />
</IconButton>
</div>
<Button
disabled={loading || !hostname}
onClick={handleVerifyDomain}
className="mt-4 sm:absolute sm:bottom-0 sm:right-0 sm:mt-0"
>
Verify
</Button>
</div>
</Box>
);
}

View File

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

View File

@@ -3,6 +3,7 @@ import { Form } from '@/components/form/Form';
import { Alert } from '@/components/ui/v2/Alert';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { ArrowsClockwise } from '@/components/ui/v2/icons/ArrowsClockwise';
import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
@@ -344,7 +345,7 @@ export default function ServiceForm({
<Tooltip title="Name of the service, must be unique per project.">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -384,7 +385,7 @@ export default function ServiceForm({
>
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -415,7 +416,7 @@ export default function ServiceForm({
<Tooltip title="Command to run when to start the service. This is optional as the image may already have a baked-in command.">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
className="w-4 h-4"
color="primary"
/>
</Tooltip>
@@ -459,7 +460,7 @@ export default function ServiceForm({
{createServiceFormError && (
<Alert
severity="error"
className="grid grid-flow-col items-center justify-between px-4 py-3"
className="grid items-center justify-between grid-flow-col px-4 py-3"
>
<span className="text-left">
<strong>Error:</strong> {createServiceFormError.message}
@@ -482,7 +483,7 @@ export default function ServiceForm({
<Button
type="submit"
disabled={isSubmitting}
startIcon={<PlusIcon />}
startIcon={serviceID ? <ArrowsClockwise /> : <PlusIcon />}
>
{serviceID ? 'Update' : 'Create'}
</Button>

View File

@@ -55,7 +55,8 @@ export default function ServiceDetailsDialog({
.filter((port) => port.publish)
.map((port) => (
<InfoCard
title={`${port.type}:${port.port}`}
key={String(port.port)}
title={`${port.type} <--> ${port.port}`}
value={getPortURL(port.port)}
/>
))}

View File

@@ -0,0 +1,3 @@
query dnsLookupCNAME($hostname: String!) {
dnsLookupCNAME(hostname: $hostname)
}

View File

@@ -1,5 +1,12 @@
mutation UpdateConfig($appId: uuid!, $config: ConfigConfigUpdateInput!) {
updateConfig(appID: $appId, config: $config) {
id: __typename
postgres {
resources {
storage {
capacity
}
}
}
}
}

View File

@@ -46,6 +46,7 @@ fragment Project on apps {
name
price
isFree
featureMaxDbSize
}
githubRepository {
fullName

View File

@@ -36,10 +36,12 @@ query getRunServices(
port
type
publish
ingresses {
fqdn
}
}
}
}
runServices_aggregate {
aggregate {
count

View File

@@ -1,5 +0,0 @@
mutation insertFeedbackOne($feedback: feedback_insert_input!) {
insertFeedbackOne(object: $feedback) {
id
}
}

View File

@@ -0,0 +1,71 @@
import { UpgradeToProBanner } from '@/components/common/UpgradeToProBanner';
import { Container } from '@/components/layout/Container';
import { SettingsLayout } from '@/components/layout/SettingsLayout';
import { Box } from '@/components/ui/v2/Box';
import { ArrowSquareOutIcon } from '@/components/ui/v2/icons/ArrowSquareOutIcon';
import { Link } from '@/components/ui/v2/Link';
import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { AuthDomain } from '@/features/projects/custom-domains/settings/components/AuthDomain';
import { DatabaseDomain } from '@/features/projects/custom-domains/settings/components/DatabaseDomain';
import { HasuraDomain } from '@/features/projects/custom-domains/settings/components/HasuraDomain';
import { RunServiceDomains } from '@/features/projects/custom-domains/settings/components/RunServiceDomains';
import { type ReactElement } from 'react';
export default function CustomDomains() {
const { currentProject } = useCurrentWorkspaceAndProject();
if (currentProject.plan.isFree) {
return (
<Container
className="grid grid-flow-row gap-6 bg-transparent"
rootClassName="bg-transparent"
>
<UpgradeToProBanner
title="Upgrade to Nhost Pro to unlock custom domains"
description="In publishing and graphic design, Lorem ipsum is a placeholder text
commonly used to demonstrate the visual form of a document or a
typeface without relying on meaningful content."
/>
</Container>
);
}
return (
<Container
className="grid max-w-5xl grid-flow-row gap-6 bg-transparent"
rootClassName="bg-transparent"
>
<Box className="flex flex-row items-center gap-4 overflow-hidden rounded-lg border-1 p-4">
<div className="flex flex-col space-y-2">
<Text className="text-lg font-semibold">Custom Domains</Text>
<Text color="secondary">
Add a custom domain to Auth, Hasura, PostgreSQL, and your Run
services for only a $10 flat fee 🚀 <br /> Learn more about
<Link
href="https://docs.nhost.io/platform/custom-domains"
target="_blank"
rel="noopener noreferrer"
underline="hover"
className="ml-1 font-medium"
>
Custom Domains
<ArrowSquareOutIcon className="ml-1 h-4 w-4" />
</Link>
</Text>
</div>
</Box>
<AuthDomain />
<HasuraDomain />
<DatabaseDomain />
<RunServiceDomains />
</Container>
);
}
CustomDomains.getLayout = function getLayout(page: ReactElement) {
return <SettingsLayout>{page}</SettingsLayout>;
};

View File

@@ -3,6 +3,7 @@ import { SettingsLayout } from '@/components/layout/SettingsLayout';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { DatabaseConnectionInfo } from '@/features/database/settings/components/DatabaseConnectionInfo';
import { DatabaseServiceVersionSettings } from '@/features/database/settings/components/DatabaseServiceVersionSettings';
import { DatabaseStorageCapacity } from '@/features/database/settings/components/DatabaseStorageCapacity';
import { ResetDatabasePasswordSettings } from '@/features/database/settings/components/ResetDatabasePasswordSettings';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useGetPostgresSettingsQuery } from '@/generated/graphql';
@@ -36,6 +37,7 @@ export default function DatabaseSettingsPage() {
rootClassName="bg-transparent"
>
<DatabaseServiceVersionSettings />
<DatabaseStorageCapacity />
<DatabaseConnectionInfo />
<ResetDatabasePasswordSettings />
</Container>

View File

@@ -1,6 +1,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 { PasswordSettings } from '@/features/account/settings/components/PasswordSettings';
import { PATSettings } from '@/features/account/settings/components/PATSettings';
import type { ReactElement } from 'react';
@@ -18,6 +19,8 @@ export default function AccountSettingsPage() {
<RetryableErrorBoundary>
<PATSettings />
</RetryableErrorBoundary>
<DeleteAccount />
</Container>
);
}

View File

@@ -68,6 +68,7 @@ export const mockApplication: Project = {
name: 'Starter',
isFree: true,
price: 0,
featureMaxDbSize: 1,
},
config: {
observability: {

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,21 @@
# @nhost/docs
## 0.7.1
### Patch Changes
- 1ee021b4a: remove custom domains from roadmap
## 0.7.0
### Minor Changes
- 5764f46d9: Add docs for Custom Domains
### Patch Changes
- cc8cc8d45: database: added extension http
## 0.6.2
### Patch Changes

View File

@@ -157,3 +157,28 @@ DROP EXTENSION pg_stat_statements;
### Resources
* [Documentation](https://www.postgresql.org/docs/14/pgstatstatements.html)
## http
HTTP client for PostgreSQL, retrieve a web page from inside the database.
### Managing
To install the extension you can create a migration with the following contents:
```
SET ROLE postgres;
CREATE EXTENSION http;
```
To uninstall it, you can use the following migration:
```
SET ROLE postgres;
DROP EXTENSION http;
```
### Resources
* [GitHub](https://github.com/pramsey/pgsql-http)

View File

@@ -0,0 +1,73 @@
---
title: 'Custom Domains'
sidebar_position: 10
image: /img/og/platform/metrics.png
---
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
Custom domains empower you to offer a tailored and branded experience for your users. Available only as an add-on for projects on the pro and enterprise plans, custom domains not only enhance your brand's visibility but also provide a more professional appearance compared to using Nhost's default domain.
You can configure Custom Domains for Auth, Hasura, PostgreSQL, and your own Run services using both the Nhost Dashboard or the Config file.
The following examples assume we are configuring custom domains at `*.custom-domain.com`.
<Tabs groupId="package-manager">
<TabItem value="dashboard" label="dashboard" default>
Follow the instructions in the **Custom Domain** section of your project's settings:
1. Add a CNAME record in your DNS provider for each of the services you want a custom domain for, and click "Verify". The verification might take a few seconds to succeed.
2. Once the verification succeeds, click "Save" to update your project.
![exposing a port](/img/custom-domains/custom-domains.png)
</TabItem>
<TabItem value="toml" label="toml">
The first step is to add a CNAME record in your DNS provider for each of the services you want a custom domain for. You can find the instructions in the **dashboard** tab.
For Hasura, Auth, and PostgreSQL, custom domains are defined in the default `./nhost/config.toml` as follows:
```
[hasura]
[hasura.resources.networking]
[[hasura.resources.networking.ingresses]]
fqdn = ['hasura.custom-domain.com']
[auth]
[auth.resources.networking]
[[auth.resources.networking.ingresses]]
fqdn = ['auth.custom-domain.com']
[postgres]
[postgres.resources.networking]
[[postgres.resources.networking.ingresses]]
fqdn = ['postgres.custom-domain.com']
```
For Run services, typically in `nhost-service.toml` specific to the service:
```
name = 'my-service'
[image]
image = 'docker.io/nhost/my-service'
[[ports]]
port = 8080
type= 'http'
publish = true
[[ports.ingresses]]
fqdn = ['my-service.custom-domain.com']
```
</TabItem>
</Tabs>

View File

@@ -43,7 +43,6 @@ Nhost Run works with container images built for the **arm architecture**. Images
Some missing functionality we are currently working on and should be added soon:
1. Custom domains
2. Run services with the CLI alongside your project
3. Ability to connect services to repositories for automated building and deployment (currently this needs to be done via a third party CI, see [Deployment via CI](/run/ci) for more details).
4. Expose TCP/UDP ports
1. Run services with the CLI alongside your project
2. Ability to connect services to repositories for automated building and deployment (currently this needs to be done via a third party CI, see [Deployment via CI](/run/ci) for more details).
3. Expose TCP/UDP ports

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/docs",
"version": "0.6.2",
"version": "0.7.1",
"private": true,
"scripts": {
"docusaurus": "docusaurus",

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.1'

View File

@@ -1,5 +1,12 @@
# @nhost-examples/nextjs-server-components
## 0.1.1
### Patch Changes
- Updated dependencies [8b127fbb6]
- @nhost/nhost-js@2.2.18
## 0.1.0
### Minor Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/nextjs-server-components",
"version": "0.1.0",
"version": "0.1.1",
"private": true,
"scripts": {
"dev": "next dev",

View File

@@ -144,7 +144,7 @@ version = '14.6-20230406-2'
[provider]
[storage]
version = '0.3.5'
version = '0.4.0'
[observability]
[observability.grafana]

View File

@@ -15,7 +15,7 @@
"postinstall": "pnpm add-nhost-js"
},
"devDependencies": {
"@nhost/nhost-js": "2.2.17",
"@nhost/nhost-js": "2.2.18",
"@playwright/test": "^1.31.0",
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/kit": "^1.5.0",

View File

@@ -1,5 +1,13 @@
# @nhost-examples/react-apollo
## 0.1.16
### Patch Changes
- 6e61dce29: feat: add SignIn with Apple
- @nhost/react@2.1.1
- @nhost/react-apollo@6.0.1
## 0.1.15
### Patch Changes

View File

@@ -25,14 +25,14 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.21.2'
version = '0.21.4'
[auth.redirections]
clientUrl = 'https://react-apollo.example.nhost.io/'
allowedUrls = ['https://react-apollo.example.nhost.io/profile']
allowedUrls = ['https://react-apollo.example.nhost.io/profile', 'http://localhost:30000']
[auth.signUp]
enabled = true
@@ -79,7 +79,11 @@ enabled = false
[auth.method.oauth]
[auth.method.oauth.apple]
enabled = false
enabled = true
clientId = '{{ secrets.APPLE_SERVICE_IDENTIFIER }}'
keyId = '{{ secrets.APPLE_KEY_ID }}'
teamId = '{{ secrets.APPLE_TEAM_ID }}'
privateKey = '{{ secrets.APPLE_PRIVATE_KEY }}'
[auth.method.oauth.azuread]
tenant = 'common'

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/react-apollo",
"version": "0.1.15",
"version": "0.1.16",
"private": true,
"dependencies": {
"@apollo/client": "^3.7.14",

View File

@@ -1,12 +1,16 @@
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
dependencies:
'@apollo/client':
specifier: ^3.7.14
version: 3.7.14(graphql@16.6.0)(react-dom@18.2.0)(react@18.2.0)(subscriptions-transport-ws@0.9.19)
'@mantine/core':
specifier: ^4.2.12
version: 4.2.12(@babel/core@7.22.1)(@mantine/hooks@4.2.12)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0)
version: 4.2.12(@babel/core@7.22.1)(@mantine/hooks@4.2.12)(@types/react@18.2.34)(react-dom@18.2.0)(react@18.2.0)
'@mantine/dropzone':
specifier: ^4.2.12
version: 4.2.12(@mantine/core@4.2.12)(@mantine/hooks@4.2.12)(react-dom@18.2.0)(react@18.2.0)
@@ -21,7 +25,7 @@ dependencies:
version: 4.2.12(@mantine/core@4.2.12)(@mantine/hooks@4.2.12)(react-dom@18.2.0)(react@18.2.0)
'@nhost/react':
specifier: '*'
version: 0.2.0(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0)(xstate@4.37.2)
version: 0.2.0(@types/react@18.2.34)(react-dom@18.2.0)(react@18.2.0)(xstate@4.37.2)
'@nhost/react-apollo':
specifier: '*'
version: 1.0.1(@apollo/client@3.7.14)(graphql@16.6.0)(react-dom@18.2.0)(react@18.2.0)
@@ -64,11 +68,11 @@ devDependencies:
specifier: ^6.0.1
version: 6.0.1
'@types/react':
specifier: ^18.2.6
version: 18.2.6
specifier: ^18.2.14
version: 18.2.34
'@types/react-dom':
specifier: ^18.2.4
version: 18.2.4
specifier: ^18.2.6
version: 18.2.14
'@types/totp-generator':
specifier: ^0.0.4
version: 0.0.4
@@ -419,7 +423,7 @@ packages:
resolution: {integrity: sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==}
dev: false
/@emotion/react@11.7.1(@babel/core@7.22.1)(@types/react@18.2.6)(react@18.2.0):
/@emotion/react@11.7.1(@babel/core@7.22.1)(@types/react@18.2.34)(react@18.2.0):
resolution: {integrity: sha512-DV2Xe3yhkF1yT4uAUoJcYL1AmrnO5SVsdfvu+fBuS7IbByDeTVx9+wFmvx9Idzv7/78+9Mgx2Hcmr7Fex3tIyw==}
peerDependencies:
'@babel/core': ^7.0.0
@@ -438,7 +442,7 @@ packages:
'@emotion/sheet': 1.2.2
'@emotion/utils': 1.0.0
'@emotion/weak-memoize': 0.2.5
'@types/react': 18.2.6
'@types/react': 18.2.34
hoist-non-react-statics: 3.3.2
react: 18.2.0
dev: false
@@ -450,7 +454,7 @@ packages:
'@emotion/memoize': 0.7.5
'@emotion/unitless': 0.7.5
'@emotion/utils': 1.0.0
csstype: 3.0.9
csstype: 3.1.2
dev: false
/@emotion/sheet@1.2.2:
@@ -1128,7 +1132,7 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.15
dev: true
/@mantine/core@4.2.12(@babel/core@7.22.1)(@mantine/hooks@4.2.12)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0):
/@mantine/core@4.2.12(@babel/core@7.22.1)(@mantine/hooks@4.2.12)(@types/react@18.2.34)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-PZcVUvcSZiZmLR1moKBJFdFIh6a4C+TE2ao91kzTAlH5Qb8t/V3ONbfPk3swHoYr7OSLJQM8vZ7UD5sFDiq0/g==}
peerDependencies:
'@mantine/hooks': 4.2.12
@@ -1136,13 +1140,13 @@ packages:
react-dom: '>=16.8.0'
dependencies:
'@mantine/hooks': 4.2.12(react@18.2.0)
'@mantine/styles': 4.2.12(@babel/core@7.22.1)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0)
'@mantine/styles': 4.2.12(@babel/core@7.22.1)(@types/react@18.2.34)(react-dom@18.2.0)(react@18.2.0)
'@popperjs/core': 2.11.8
'@radix-ui/react-scroll-area': 0.1.4(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-popper: 2.3.0(@popperjs/core@2.11.8)(react-dom@18.2.0)(react@18.2.0)
react-textarea-autosize: 8.4.1(@types/react@18.2.6)(react@18.2.0)
react-textarea-autosize: 8.4.1(@types/react@18.2.34)(react@18.2.0)
transitivePeerDependencies:
- '@babel/core'
- '@types/react'
@@ -1156,7 +1160,7 @@ packages:
react: '>=16.8.0'
react-dom: '>=16.8.0'
dependencies:
'@mantine/core': 4.2.12(@babel/core@7.22.1)(@mantine/hooks@4.2.12)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0)
'@mantine/core': 4.2.12(@babel/core@7.22.1)(@mantine/hooks@4.2.12)(@types/react@18.2.34)(react-dom@18.2.0)(react@18.2.0)
'@mantine/hooks': 4.2.12(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
@@ -1179,7 +1183,7 @@ packages:
react: '>=16.8.0'
react-dom: '>=16.8.0'
dependencies:
'@mantine/core': 4.2.12(@babel/core@7.22.1)(@mantine/hooks@4.2.12)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0)
'@mantine/core': 4.2.12(@babel/core@7.22.1)(@mantine/hooks@4.2.12)(@types/react@18.2.34)(react-dom@18.2.0)(react@18.2.0)
'@mantine/hooks': 4.2.12(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
@@ -1194,21 +1198,21 @@ packages:
react: '>=16.8.0'
react-dom: '>=16.8.0'
dependencies:
'@mantine/core': 4.2.12(@babel/core@7.22.1)(@mantine/hooks@4.2.12)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0)
'@mantine/core': 4.2.12(@babel/core@7.22.1)(@mantine/hooks@4.2.12)(@types/react@18.2.34)(react-dom@18.2.0)(react@18.2.0)
'@mantine/hooks': 4.2.12(react@18.2.0)
prism-react-renderer: 1.3.5(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@mantine/styles@4.2.12(@babel/core@7.22.1)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0):
/@mantine/styles@4.2.12(@babel/core@7.22.1)(@types/react@18.2.34)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-9q1DzW0UNW/ORMGLHfN2XABOSEm0ZQebhNlLD757R6OQouoLuUf9elUwgGOXSyogMlsAYoy84XbJ3ZbbTm4YCA==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
dependencies:
'@emotion/cache': 11.7.1
'@emotion/react': 11.7.1(@babel/core@7.22.1)(@types/react@18.2.6)(react@18.2.0)
'@emotion/react': 11.7.1(@babel/core@7.22.1)(@types/react@18.2.34)(react@18.2.0)
'@emotion/serialize': 1.0.2
'@emotion/utils': 1.0.0
clsx: 1.2.1
@@ -1256,14 +1260,14 @@ packages:
- utf-8-validate
dev: false
/@nhost/react@0.2.0(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0)(xstate@4.37.2):
/@nhost/react@0.2.0(@types/react@18.2.34)(react-dom@18.2.0)(react@18.2.0)(xstate@4.37.2):
resolution: {integrity: sha512-V8um4+YVN2dNio8u+zKlxHIub7ZU4Cz2D3wf2dJW+fHHgChh5niNw4vQ3L+JldGe4yAXATF94P8VaQav/Ksgqg==}
peerDependencies:
react: ^17.0.0 || ^18.0.0
react-dom: ^17.0.0 || ^18.0.0
dependencies:
'@nhost/client': 0.2.0
'@xstate/react': 2.0.1(@types/react@18.2.6)(react@18.2.0)(xstate@4.37.2)
'@xstate/react': 2.0.1(@types/react@18.2.34)(react@18.2.0)(xstate@4.37.2)
immer: 9.0.21
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
@@ -1501,14 +1505,14 @@ packages:
/@types/prop-types@15.7.5:
resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
/@types/react-dom@18.2.4:
resolution: {integrity: sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==}
/@types/react-dom@18.2.14:
resolution: {integrity: sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ==}
dependencies:
'@types/react': 18.2.6
'@types/react': 18.2.34
dev: true
/@types/react@18.2.6:
resolution: {integrity: sha512-wRZClXn//zxCFW+ye/D2qY65UsYP1Fpex2YXorHc8awoNamkMZSvBxwxdYVInsHOZZd2Ppq8isnSzJL5Mpf8OA==}
/@types/react@18.2.34:
resolution: {integrity: sha512-U6eW/alrRk37FU/MS2RYMjx0Va2JGIVXELTODaTIYgvWGCV4Y4TfTUzG8DdmpDNIT0Xpj/R7GfyHOJJrDttcvg==}
dependencies:
'@types/prop-types': 15.7.5
'@types/scheduler': 0.16.3
@@ -1643,7 +1647,7 @@ packages:
xstate: 4.37.2
dev: true
/@xstate/react@2.0.1(@types/react@18.2.6)(react@18.2.0)(xstate@4.37.2):
/@xstate/react@2.0.1(@types/react@18.2.34)(react@18.2.0)(xstate@4.37.2):
resolution: {integrity: sha512-sT3hxyzNBw+bm7uT3BP+uXzN0MnRqiaj/U9Yl4OYaMAUJXWsRvSA/ipL7EDf0gVLRGrRhJTCsC0cjWaduAAqnw==}
peerDependencies:
'@xstate/fsm': ^1.6.5
@@ -1656,7 +1660,7 @@ packages:
optional: true
dependencies:
react: 18.2.0
use-isomorphic-layout-effect: 1.1.2(@types/react@18.2.6)(react@18.2.0)
use-isomorphic-layout-effect: 1.1.2(@types/react@18.2.34)(react@18.2.0)
use-subscription: 1.8.0(react@18.2.0)
xstate: 4.37.2
transitivePeerDependencies:
@@ -3174,7 +3178,7 @@ packages:
react: 18.2.0
dev: false
/react-textarea-autosize@8.4.1(@types/react@18.2.6)(react@18.2.0):
/react-textarea-autosize@8.4.1(@types/react@18.2.34)(react@18.2.0):
resolution: {integrity: sha512-aD2C+qK6QypknC+lCMzteOdIjoMbNlgSFmJjCV+DrfTPwp59i/it9mMNf2HDzvRjQgKAyBDPyLJhcrzElf2U4Q==}
engines: {node: '>=10'}
peerDependencies:
@@ -3183,7 +3187,7 @@ packages:
'@babel/runtime': 7.22.3
react: 18.2.0
use-composed-ref: 1.3.0(react@18.2.0)
use-latest: 1.2.1(@types/react@18.2.6)(react@18.2.0)
use-latest: 1.2.1(@types/react@18.2.34)(react@18.2.0)
transitivePeerDependencies:
- '@types/react'
dev: false
@@ -3637,7 +3641,7 @@ packages:
react: 18.2.0
dev: false
/use-isomorphic-layout-effect@1.1.2(@types/react@18.2.6)(react@18.2.0):
/use-isomorphic-layout-effect@1.1.2(@types/react@18.2.34)(react@18.2.0):
resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==}
peerDependencies:
'@types/react': '*'
@@ -3646,11 +3650,11 @@ packages:
'@types/react':
optional: true
dependencies:
'@types/react': 18.2.6
'@types/react': 18.2.34
react: 18.2.0
dev: false
/use-latest@1.2.1(@types/react@18.2.6)(react@18.2.0):
/use-latest@1.2.1(@types/react@18.2.34)(react@18.2.0):
resolution: {integrity: sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==}
peerDependencies:
'@types/react': '*'
@@ -3659,9 +3663,9 @@ packages:
'@types/react':
optional: true
dependencies:
'@types/react': 18.2.6
'@types/react': 18.2.34
react: 18.2.0
use-isomorphic-layout-effect: 1.1.2(@types/react@18.2.6)(react@18.2.0)
use-isomorphic-layout-effect: 1.1.2(@types/react@18.2.34)(react@18.2.0)
dev: false
/use-subscription@1.8.0(react@18.2.0):

View File

@@ -1,11 +1,12 @@
import { FaGithub, FaGoogle } from 'react-icons/fa/index.js'
import { FaApple, FaGithub, FaGoogle } from 'react-icons/fa/index.js'
import { useProviderLink } from '@nhost/react'
import AuthLink from './AuthLink'
export default function OauthLinks() {
const { github, google } = useProviderLink({ redirectTo: window.location.origin })
const { github, google, apple } = useProviderLink({ redirectTo: window.location.origin })
return (
<>
<AuthLink leftIcon={<FaGithub />} link={github} color="#333">
@@ -14,6 +15,9 @@ export default function OauthLinks() {
<AuthLink leftIcon={<FaGoogle />} link={google} color="#de5246">
Continue with Google
</AuthLink>
<AuthLink leftIcon={<FaApple />} link={apple} color="#333333">
Sign In With Apple
</AuthLink>
</>
)
}

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -28,7 +28,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -1,5 +1,13 @@
# @nhost-examples/vue-apollo
## 0.0.9
### Patch Changes
- 0c49e757c: feat: add new Storage page to demonstrate how to use the composables `useMultipleFilesUpload` together with `useFileUploadItem`
- Updated dependencies [0c49e757c]
- @nhost/vue@1.14.0
## 0.0.8
### Patch Changes

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -1,7 +1,7 @@
{
"name": "@nhost-examples/vue-apollo",
"private": true,
"version": "0.0.8",
"version": "0.0.9",
"scripts": {
"dev": "vite",
"build": "vite build",
@@ -17,14 +17,16 @@
"@apollo/client": "^3.7.1",
"@mdi/font": "5.9.55",
"@nhost/apollo": "*",
"@nhost/nhost-js": "*",
"@nhost/vue": "*",
"@vue/apollo-composable": "4.0.0-alpha.18",
"graphql": "15.7.2",
"graphql": "^16.8.1",
"graphql-tag": "^2.12.6",
"roboto-fontface": "*",
"vite-plugin-vuetify": "^1.0.1",
"vue": "^3.2.41",
"vue-router": "^4.1.6",
"vue3-dropzone": "^2.1.2",
"vuetify": "3.0.0-beta.10",
"webfontloader": "^1.0.0"
},
@@ -35,7 +37,7 @@
"@xstate/inspect": "^0.6.2",
"sass": "1.32.0",
"typescript": "4.9.4",
"vite": "^4.0.2",
"vite": "^4.5.0",
"vue-tsc": "^0.38.9"
},
"eslintConfig": {

1657
examples/vue-apollo/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,97 @@
<script setup lang="ts">
import { ref, toRaw, unref, type Ref } from 'vue'
import { useDropzone, type FileRejectReason } from 'vue3-dropzone'
const { multiple } = defineProps({
multiple: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['onDrop'])
const files: Ref<File[]> = ref([])
const errors: Ref<FileRejectReason[]> = ref([])
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
multiple
})
function onDrop(acceptFiles: File[], rejectReasons: FileRejectReason[]) {
files.value = acceptFiles
errors.value = rejectReasons
emit('onDrop', toRaw(unref(files)))
}
</script>
<template>
<div>
<div class="dropzone" v-bind="getRootProps()">
<div
class="border"
:class="{
isDragActive
}"
>
<input v-bind="getInputProps()" />
<p v-if="isDragActive">Drop here ...</p>
<p v-else>Drag and drop here, or Click to select</p>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.dropzone,
.files {
width: 100%;
margin: 0 auto;
padding: 10px;
border-radius: 8px;
box-shadow: rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 1px 3px 1px;
font-size: 12px;
line-height: 1.5;
}
.border {
border: 2px dashed #ccc;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
transition: all 0.3s ease;
background: #fff;
&.isDragActive {
border: 2px dashed #ffb300;
background: rgb(255 167 18 / 20%);
}
}
.file-item {
border-radius: 8px;
display: flex;
align-items: center;
justify-content: space-between;
background: rgb(255 167 18 / 20%);
padding: 7px;
padding-left: 15px;
margin-top: 10px;
&:first-child {
margin-top: 0;
}
.delete-file {
background: red;
color: #fff;
padding: 5px 10px;
border-radius: 8px;
cursor: pointer;
}
}
</style>

View File

@@ -0,0 +1,34 @@
<script lang="ts" setup>
import { FileItemRef } from '@nhost/nhost-js'
import { useFileUploadItem } from '@nhost/vue'
const { file } = defineProps<{ file: FileItemRef }>()
const { name, progress } = useFileUploadItem(file)
</script>
<template>
<div class="container">
<span class="file_name">{{ name }}</span>
<v-progress-linear v-model="progress" color="green">
{{ progress }}
</v-progress-linear>
</div>
</template>
<style lang="css" scoped>
.container {
display: flex;
flex-direction: row;
align-items: center;
padding: 0 1rem;
}
.file_name {
margin-right: 1rem;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: 40%;
}
</style>

View File

@@ -3,8 +3,14 @@
<v-list-item title="Home" to="/" value="home" prepend-icon="mdi-home" />
<v-list-item title="Profile" to="/profile" value="profile" prepend-icon="mdi-account" />
<v-list-item title="Apollo" to="/apollo" value="apollo" prepend-icon="mdi-api" />
<v-list-item title="Storage" to="/storage" value="storage" prepend-icon="mdi-server" />
<v-list-item title="About" to="/about" value="about" prepend-icon="mdi-information" />
<v-list-item v-if="authenticated" title="Sign out" prepend-icon="mdi-exit-to-app" @click="signOutHandler" />
<v-list-item
v-if="authenticated"
title="Sign out"
prepend-icon="mdi-exit-to-app"
@click="signOutHandler"
/>
</v-list>
</template>

View File

@@ -0,0 +1,129 @@
<template>
<div className="d-flex align-center flex-column">
<h1>Storage</h1>
<v-card width="400" class="singleFileUpload" tile>
<v-card-title>Upload a single file</v-card-title>
<v-card-text>
<FileDropZone @on-drop="onDropSingleFile" />
<span v-if="isUploading" class="upload_status">Uploading...</span>
<span v-if="isUploaded" class="upload_status upload_success">Upload Succeeded</span>
<span v-if="isError" class="upload_status upload_error">Upload Failed</span>
<div v-if="fileToUpload" class="file_progress">
<span class="file_progress_name">{{ fileToUpload.name }}</span>
<v-progress-linear v-model="progress" color="green">
{{ progress }}
</v-progress-linear>
<button class="clearButton" @click="clearFile">Clear</button>
</div>
</v-card-text>
</v-card>
<v-card width="400" tile class="relative">
<v-card-title>Upload multiple files</v-card-title>
<v-card-text class="footer">
<FileDropZone :multiple="true" @on-drop="onDropMultipleFiles" />
<span v-if="isUploadingAll" class="upload_status">Uploading...</span>
<span v-if="isUploadedAll" class="upload_status upload_success">Upload Succeeded</span>
<span v-if="isErrorAll" class="upload_status upload_error">Upload Failed</span>
</v-card-text>
<div v-for="(file, index) of files" :key="index">
<FileUploadItem :file="file" />
</div>
<div class="relative buttonsContainer">
<v-btn
class="mb-2 text-white w-100"
:disabled="!files.length"
variant="elevated"
color="green"
@click="uploadAll"
>
Upload
</v-btn>
<v-btn class="w-100" @click="clear"> Clear </v-btn>
</div>
</v-card>
</div>
</template>
<script lang="ts" setup>
import { useFileUpload, useMultipleFilesUpload } from '@nhost/vue'
import { ref } from 'vue'
import FileDropZone from '../components/FileDropZone.vue'
import FileUploadItem from '../components/FileUploadItem.vue'
const fileToUpload = ref<File | null>(null)
const { upload, progress, isUploaded, isUploading, isError } = useFileUpload()
const onDropSingleFile = async ([file]: File[]) => {
fileToUpload.value = file
upload({ file })
}
const clearFile = () => {
fileToUpload.value = null
isUploaded.value = false
}
const {
add,
upload: uploadAll,
isUploaded: isUploadedAll,
isUploading: isUploadingAll,
isError: isErrorAll,
files,
clear
} = useMultipleFilesUpload()
const onDropMultipleFiles = (files: File[]) => {
add({ files })
}
</script>
<style lang="css" scoped>
.buttonsContainer {
padding: 1rem;
}
.upload_success {
color: 'green';
}
.upload_error {
color: 'red';
}
.file_progress {
display: flex;
flex-direction: row;
align-items: center;
}
.file_progress_name {
margin-right: 1rem;
}
.upload_status {
display: block;
margin: 1rem 0;
}
.singleFileUpload {
margin-bottom: 1rem;
}
.clearButton {
margin-left: 1rem;
background: red;
color: #fff;
padding: 5px 10px;
border-radius: 8px;
cursor: pointer;
}
.relative {
position: relative;
}
</style>

View File

@@ -13,6 +13,7 @@ import SignUpEmailPasword from './pages/sign-up/EmailPassword.vue'
import SignUpEmailPaswordless from './pages/sign-up/EmailPasswordless.vue'
import SignUp from './pages/sign-up/IndexPage.vue'
import Signout from './pages/SignoutPage.vue'
import StoragePage from './pages/StoragePage.vue'
export const routes: RouteRecordRaw[] = [
{ path: '/', component: Index, meta: { auth: true } },
@@ -50,5 +51,6 @@ export const routes: RouteRecordRaw[] = [
}
]
},
{ path: '/apollo', component: ApolloPage, meta: { auth: true } }
{ path: '/apollo', component: ApolloPage, meta: { auth: true } },
{ path: '/storage', component: StoragePage, meta: { auth: true } }
]

View File

@@ -1,5 +1,12 @@
# @nhost/apollo
## 5.2.22
### Patch Changes
- Updated dependencies [8b127fbb6]
- @nhost/nhost-js@2.2.18
## 5.2.21
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/apollo",
"version": "5.2.21",
"version": "5.2.22",
"description": "Nhost Apollo Client library",
"license": "MIT",
"keywords": [

View File

@@ -1,5 +1,20 @@
# @nhost/react-apollo
## 6.0.1
### Patch Changes
- @nhost/apollo@5.2.22
- @nhost/react@2.1.1
## 6.0.0
### Patch Changes
- Updated dependencies [00c363f80]
- Updated dependencies [66c3193bc]
- @nhost/react@2.1.0
## 5.0.38
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/react-apollo",
"version": "5.0.38",
"version": "6.0.1",
"description": "Nhost React Apollo client",
"license": "MIT",
"keywords": [

View File

@@ -1,5 +1,19 @@
# @nhost/react-urql
## 3.0.1
### Patch Changes
- @nhost/react@2.1.1
## 3.0.0
### Patch Changes
- Updated dependencies [00c363f80]
- Updated dependencies [66c3193bc]
- @nhost/react@2.1.0
## 2.0.33
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/react-urql",
"version": "2.0.33",
"version": "3.0.1",
"description": "Nhost React URQL client",
"license": "MIT",
"keywords": [

View File

@@ -0,0 +1,805 @@
{
"__inputs": [
{
"name": "DS_PROMETHEUS",
"label": "Prometheus",
"description": "",
"type": "datasource",
"pluginId": "prometheus",
"pluginName": "Prometheus"
}
],
"__elements": {},
"__requires": [
{
"type": "grafana",
"id": "grafana",
"name": "Grafana",
"version": "9.2.0"
},
{
"type": "datasource",
"id": "prometheus",
"name": "Prometheus",
"version": "1.0.0"
},
{
"type": "panel",
"id": "timeseries",
"name": "Time series",
"version": ""
}
],
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
},
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": null,
"links": [],
"liveNow": false,
"panels": [
{
"collapsed": false,
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 0
},
"id": 6,
"panels": [],
"title": "General",
"type": "row"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"description": "Number of requests by method/function",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 1
},
"id": 2,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"editorMode": "code",
"expr": "sum by(method, ingress) (increase(nginx_ingress_controller_requests{method=~\"$method\",ingress=~\"$ingress\"}[$__rate_interval]))",
"format": "time_series",
"interval": "2m",
"legendFormat": "{{method}} {{ingress}}",
"range": true,
"refId": "A"
}
],
"title": "Requests",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"description": "Number of requests by status response",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 1
},
"id": 21,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"editorMode": "code",
"expr": "sum by(status) (increase(nginx_ingress_controller_requests{method=~\"$method\",ingress=~\"$ingress\"}[$__rate_interval]))",
"format": "time_series",
"interval": "2m",
"legendFormat": "{{status}}",
"range": true,
"refId": "A"
}
],
"title": "Response Status",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "bytes"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 9
},
"id": 8,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"editorMode": "code",
"expr": "sum by(ingress, method) (increase(nginx_ingress_controller_response_size_sum{ingress=~\"$ingress\",method=~\"$method\"}[$__rate_interval])) / sum by(ingress, method) (increase(nginx_ingress_controller_requests{ingress=~\"$ingress\",method=~\"$method\"}[$__rate_interval]))",
"interval": "2m",
"legendFormat": "{{ method }} - {{ ingress }}",
"range": true,
"refId": "A"
}
],
"title": "Average Response Size",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "continuous-GrYlRd"
},
"custom": {
"align": "auto",
"displayMode": "auto",
"filterable": true,
"inspect": false
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Time"
},
"properties": [
{
"id": "custom.width",
"value": 183
}
]
}
]
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 9
},
"id": 4,
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": [
{
"desc": true,
"displayName": "Value"
}
]
},
"pluginVersion": "9.2.0",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"editorMode": "code",
"exemplar": false,
"expr": "ceil(sum by(ingress, method) (increase(nginx_ingress_controller_requests{ingress=~\"$ingress\",method=~\"$method\"}[$__range])))",
"format": "table",
"instant": true,
"interval": "2m",
"legendFormat": "{{ingress}} {{method}}",
"range": false,
"refId": "A"
}
],
"title": "Total Requests",
"transformations": [],
"type": "table"
},
{
"collapsed": false,
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 17
},
"id": 26,
"panels": [],
"title": "Response Times",
"type": "row"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "s"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 18
},
"id": 11,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"editorMode": "code",
"expr": "sum by(method, ingress) (increase(nginx_ingress_controller_response_duration_seconds_sum{method=~\"$method\", ingress=~\"$ingress\"}[$__rate_interval])) / sum by(method, ingress) (increase(nginx_ingress_controller_response_duration_seconds_count{method=~\"$method\", ingress=~\"$ingress\"}[$__rate_interval]))",
"interval": "2m",
"legendFormat": "{{ ingress }} - {{ method }}",
"range": true,
"refId": "A"
}
],
"title": "Average Response Time",
"type": "timeseries"
},
{
"collapsed": false,
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 26
},
"id": 19,
"panels": [],
"title": "Errors",
"type": "row"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"description": "Number of requests that failed divided by the total number of requests",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "percentunit"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 27
},
"id": 20,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"editorMode": "code",
"expr": "sum by(ingress,method) (increase(nginx_ingress_controller_requests{ingress=~\"$ingress\",method=~\"$method\",status=~\"^[4-5].*\"}[$__rate_interval])) / sum by(ingress, method) (increase(nginx_ingress_controller_requests[$__rate_interval]))",
"format": "time_series",
"interval": "2m",
"legendFormat": "{{method}} {{ ingress }}",
"range": true,
"refId": "A"
}
],
"title": "Error Rate",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "continuous-GrYlRd"
},
"custom": {
"align": "auto",
"displayMode": "auto",
"filterable": true,
"inspect": false
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "Time"
},
"properties": [
{
"id": "custom.width",
"value": 183
}
]
}
]
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 27
},
"id": 10,
"options": {
"footer": {
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"showHeader": true,
"sortBy": [
{
"desc": true,
"displayName": "Value"
}
]
},
"pluginVersion": "9.2.0",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"editorMode": "code",
"exemplar": false,
"expr": "ceil(sum by(ingress, method,status) (increase(nginx_ingress_controller_requests{method=~\"$method\",ingress=~\"$ingress\",status=~\"^[4-5].*\"}[$__range])))",
"format": "table",
"instant": true,
"interval": "2m",
"legendFormat": "{{ingress}} {{method}}",
"range": false,
"refId": "A"
}
],
"title": "Total Errors",
"transformations": [],
"type": "table"
}
],
"schemaVersion": 37,
"style": "dark",
"tags": [],
"templating": {
"list": [
{
"current": {},
"definition": "label_values(nginx_ingress_controller_requests,ingress)",
"hide": 0,
"includeAll": true,
"multi": false,
"name": "ingress",
"options": [],
"query": {
"query": "label_values(nginx_ingress_controller_requests,ingress)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"type": "query"
},
{
"current": {},
"definition": "label_values(nginx_ingress_controller_requests,method)",
"hide": 0,
"includeAll": true,
"multi": false,
"name": "method",
"options": [],
"query": {
"query": "label_values(nginx_ingress_controller_requests,method)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"type": "query"
}
]
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "Ingress Metrics",
"uid": "WOWEHb7Sz",
"version": 16,
"weekStart": ""
}

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.2'

View File

@@ -1,5 +1,19 @@
# @nhost/nextjs
## 1.13.40
### Patch Changes
- @nhost/react@2.1.1
## 1.13.39
### Patch Changes
- Updated dependencies [00c363f80]
- Updated dependencies [66c3193bc]
- @nhost/react@2.1.0
## 1.13.38
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/nextjs",
"version": "1.13.38",
"version": "1.13.40",
"description": "Nhost NextJS library",
"license": "MIT",
"keywords": [

View File

@@ -1,5 +1,11 @@
# @nhost/nhost-js
## 2.2.18
### Patch Changes
- 8b127fbb6: feat: export `urlFromSubdomain` helper
## 2.2.17
### Patch Changes

View File

@@ -25,7 +25,7 @@ httpPoolSize = 100
[functions]
[functions.node]
version = 16
version = 18
[auth]
version = '0.20.1'

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/nhost-js",
"version": "2.2.17",
"version": "2.2.18",
"description": "Nhost JavaScript SDK",
"license": "MIT",
"keywords": [

View File

@@ -1,4 +1,5 @@
export * from '@nhost/hasura-auth-js'
export * from '@nhost/hasura-storage-js'
export * from './clients'
export { urlFromSubdomain } from './utils/helpers'
export * from './utils/types'

View File

@@ -5,9 +5,9 @@ export const LOCALHOST_REGEX =
/^((?<protocol>http[s]?):\/\/)?(?<host>(localhost|local))(:(?<port>(\d+|__\w+__)))?$/
/**
* `backendUrl` should now be used only when self-hosting
* `subdomain` and `region` should be used instead when using the Nhost platform
* `
* \`backendUrl\` should now be used only when self-hosting
* \`subdomain\` and `region` should be used instead when using the Nhost platform
*
* @param backendOrSubdomain
* @param service
* @returns

View File

@@ -1,5 +1,22 @@
# @nhost/react
## 2.1.1
### Patch Changes
- Updated dependencies [8b127fbb6]
- @nhost/nhost-js@2.2.18
## 2.1.0
### Minor Changes
- 66c3193bc: Update useChangePassword hook interface to include ActionLoadingState
### Patch Changes
- 00c363f80: fix(docs): update changeEmail usage reference
## 2.0.32
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/react",
"version": "2.0.32",
"version": "2.1.1",
"description": "Nhost React library",
"license": "MIT",
"keywords": [

View File

@@ -30,9 +30,7 @@ export interface ChangeEmailHookResult extends ChangeEmailState {
* const handleFormSubmit = async (e) => {
* e.preventDefault();
*
* await changeEmail({
* email: 'new@example.com',
* })
* await changeEmail('new@example.com')
* }
* ```
*

View File

@@ -1,5 +1,6 @@
import {
ActionErrorState,
ActionLoadingState,
ActionSuccessState,
ChangePasswordHandlerResult,
changePasswordPromise,
@@ -13,7 +14,7 @@ interface ChangePasswordHandler {
(password: string): Promise<ChangePasswordHandlerResult>
}
export interface ChangePasswordHookResult extends ActionErrorState, ActionSuccessState {
export interface ChangePasswordHookResult extends ActionErrorState, ActionLoadingState, ActionSuccessState {
/** Requests the password change. Returns a promise with the current context */
changePassword: ChangePasswordHandler
}

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