Compare commits

..

91 Commits

Author SHA1 Message Date
Hassan Ben Jobrane
90e8843314 Merge pull request #2421 from nhost/changeset-release/main
chore: update versions
2023-12-22 11:51:01 +01:00
github-actions[bot]
aa5b360932 chore: update versions 2023-12-22 10:30:28 +00:00
Hassan Ben Jobrane
daa4b8b2ad Merge pull request #2400 from nhost/changeset-release/main
chore: update versions
2023-12-22 11:28:17 +01:00
Seth Deegan
a1c5c97a59 chore (examples/docker-compose): update README.md to explain why hasura-console is needed (#2395) 2023-12-11 20:14:59 +01:00
Alex Nguyen
b338793d6d Update hasura-auth-client.ts (#2408) 2023-12-11 13:44:00 +01:00
Hassan Ben Jobrane
b1fb4b2400 chore: run pnpm install 2023-12-07 19:49:14 +01:00
github-actions[bot]
f75e023672 chore: update versions 2023-12-05 15:18:53 +00:00
Hassan Ben Jobrane
8e78c1ff00 Merge pull request #2406 from nhost/fix/ci/revert
chore(ci): revert ci changes to use `pull_request`
2023-12-05 16:16:39 +01:00
Hassan Ben Jobrane
9cbb0b2986 chore(ci): revert ci changes to use pull_request 2023-12-05 14:09:53 +01:00
Hassan Ben Jobrane
363a3b92e5 Merge pull request #2405 from nhost/fix/ci/checkout-ref
fix(ci): add ref to all checkout steps
2023-12-05 12:58:47 +01:00
Hassan Ben Jobrane
6a078fc972 fix(ci): add ref to all checkout steps 2023-12-05 12:51:47 +01:00
Hassan Ben Jobrane
1091e9674a Merge pull request #2404 from nhost/fix/ci-checkout-step
chore(ci): add ref to checkout step
2023-12-05 12:26:57 +01:00
Hassan Ben Jobrane
9738108d58 chore(ci): add ref to checkout step 2023-12-05 12:13:53 +01:00
Hassan Ben Jobrane
65951e1d1d Merge pull request #2403 from nhost/ci_target
chore(ci): change to pull_request_target to run workflows "locally"
2023-12-05 11:55:30 +01:00
David Barroso
b4af994a58 chore(ci): change pull_request to pull_request_target to run workflows locally 2023-12-05 11:39:58 +01:00
Hassan Ben Jobrane
c6347e10bc Merge pull request #2402 from nhost/fix/ci/pin-install-nhost-dep
fix(ci): pin `@nhost/nhost-js` dep version in sveltekit quickstart
2023-12-04 17:30:10 +01:00
Hassan Ben Jobrane
278a641bc1 fix(ci): pin @nhost/nhost-js dep version in sveltekit quickstart 2023-12-04 16:18:02 +01:00
Hassan Ben Jobrane
3320ddd8c8 Merge pull request #2393 from nhost/chore/sdk/remove-backendUrl
chore: remove support for using `backendUrl`
2023-12-04 15:05:52 +01:00
Hassan Ben Jobrane
bc9eff6e41 chore: update the changeset to reflect a major version increment 2023-12-04 14:38:56 +01:00
Hassan Ben Jobrane
258c608882 Revert "chore: hardcode staging auth URL for testing"
This reverts commit d8c0bb5ea4e073a7131df3726728845b2bc5e1a1.
2023-12-04 14:38:56 +01:00
Hassan Ben Jobrane
ae84f269d4 chore: hardcode staging auth URL for testing 2023-12-04 14:38:56 +01:00
Hassan Ben Jobrane
0327250b19 Revert "chore: test different subdomain"
This reverts commit 9dfd9399a0a0b1ec931e02304dbe62183b2cb500.
2023-12-04 14:38:56 +01:00
Hassan Ben Jobrane
7f56eabd24 chore: test different subdomain 2023-12-04 14:38:56 +01:00
Hassan Ben Jobrane
be110df83a fix: refactor urlFromSubdomain and fix unit tests 2023-12-04 14:38:56 +01:00
Hassan Ben Jobrane
361e648daf chore: add changeset 2023-12-04 14:38:56 +01:00
Hassan Ben Jobrane
8a72e20e3d chore: refactor generateAppServiceUrl function and remove unused code 2023-12-04 14:38:56 +01:00
Hassan Ben Jobrane
125ec390ca chore: add storage service URL to Nhost client
configuration
2023-12-04 14:38:56 +01:00
Hassan Ben Jobrane
7cc788a373 refactor: remove backendUrl from Nhost client initialization 2023-12-04 14:38:56 +01:00
David Barroso
2a04bc9e5d chore(docs): added functions to custom domains documentation (#2399) 2023-12-04 11:11:08 +01:00
Hassan Ben Jobrane
f7c2148ace Merge pull request #2392 from nhost/feat/dashboard/functions-custom-domains
feat(dashboard): add serverless functions custom domain settings
2023-11-30 14:43:18 +01:00
Hassan Ben Jobrane
78d35eed09 feat(dashboard): add serverless functions custom domain settings 2023-11-30 12:11:22 +01:00
Hassan Ben Jobrane
c5ff53c622 Merge pull request #2389 from nhost/changeset-release/main
chore: update versions
2023-11-29 12:51:40 +01:00
github-actions[bot]
d21714d169 chore: update versions 2023-11-29 10:58:39 +00:00
Hassan Ben Jobrane
0d16ad41b8 Merge pull request #2384 from nhost/fix/quickstarts-auth
fix: update auth version and webauthn origins
2023-11-29 11:56:40 +01:00
Hassan Ben Jobrane
82c328eeda Merge pull request #2388 from nhost/fix/dashboard/secrets
fix: make sure secrets are not resolved
2023-11-29 11:26:23 +01:00
Hassan Ben Jobrane
d991cd8c7e chore: run pnpm install 2023-11-29 11:25:09 +01:00
Hassan Ben Jobrane
e469628ebe chore: add changeset 2023-11-29 11:22:07 +01:00
Hassan Ben Jobrane
856bc0a4bb chore: use workspace nhost-js 2023-11-28 17:36:45 +01:00
Hassan Ben Jobrane
9b1fb1ce28 chore: update dependencies in package.json and fix
NHOST_SESSION_KEY constant
2023-11-28 17:34:19 +01:00
Hassan Ben Jobrane
a4d16f1835 fix: update NHOST_SESSION_KEY value for webauthn 2023-11-28 15:43:27 +01:00
Hassan Ben Jobrane
3db8644075 chore: update pnpm-workspace.yaml 2023-11-28 15:03:02 +01:00
Hassan Ben Jobrane
7f667f6acb chore: update auth version to 0.24.0 2023-11-28 11:48:32 +01:00
Hassan Ben Jobrane
685dc6c1e4 chore: update auth version and webauthn id & origins 2023-11-28 11:46:53 +01:00
Hassan Ben Jobrane
6f7f2b0a65 chore: update changeset 2023-11-27 14:49:32 +01:00
Hassan Ben Jobrane
6d0167b33f fix: update config resolve to true in project.gql 2023-11-27 14:41:48 +01:00
David Barroso
3ffb60f0ae fix(docs): typo in Run deploy example script (#2345) 2023-11-27 13:49:32 +01:00
Hassan Ben Jobrane
97ced73a3c chore: add changeset 2023-11-27 13:17:24 +01:00
Hassan Ben Jobrane
39c86cea25 fix: make sure secrets are not resolved 2023-11-27 13:15:52 +01:00
Hassan Ben Jobrane
d2d590db7e Merge pull request #2369 from nhost/changeset-release/main
chore: update versions
2023-11-24 16:37:42 +01:00
github-actions[bot]
3bdbefc015 chore: update versions 2023-11-24 13:05:27 +00:00
Hassan Ben Jobrane
79081b43c2 Merge pull request #2376 from nhost/feat/database/sql-editor
feat(dashboard): add sql editor
2023-11-24 14:03:15 +01:00
Hassan Ben Jobrane
a4b541f100 fix(quickstarts): update webauthn origins 2023-11-24 10:45:03 +01:00
Hassan Ben Jobrane
4523020c33 fix: update auth version and webauthn origins 2023-11-24 10:31:41 +01:00
Hassan Ben Jobrane
2e2248fd44 chore: add changeset 2023-11-24 10:07:21 +01:00
Hassan Ben Jobrane
63358eb80b chore: add comments 2023-11-24 09:59:55 +01:00
Hassan Ben Jobrane
ded674fab6 fix: add min height to codemirror 2023-11-24 09:55:16 +01:00
Hassan Ben Jobrane
85f2f28902 refactor(dashboard): move run-sql logic to a custom hook 2023-11-24 09:55:16 +01:00
Hassan Ben Jobrane
b8e9ad831e refactor(dashboard): add proper error handling 2023-11-24 09:55:16 +01:00
Hassan Ben Jobrane
4e0c5dd1d3 refactor(dashboard): improve SQL parsing in SQLEditor 2023-11-24 09:55:16 +01:00
Hassan Ben Jobrane
b874109c6d fix: rely on error returned from api call to update metadata 2023-11-24 09:55:16 +01:00
Hassan Ben Jobrane
21b926cc07 feat(dashboard): add create migration option to the sql editor 2023-11-24 09:55:16 +01:00
Hassan Ben Jobrane
c35cd47d97 feat: implement track tables in the sql editor 2023-11-24 09:55:16 +01:00
Hassan Ben Jobrane
8dcd801c7c feat(dashboard): add support for resizing the sql query results container 2023-11-24 09:55:16 +01:00
Hassan Ben Jobrane
e3199be749 feat(dashboard): add sql editor tab and basic func 2023-11-24 09:55:16 +01:00
Hassan Ben Jobrane
284b31e036 Merge pull request #2383 from nhost/chore/dashboard/update-node
chore: update node to v18
2023-11-24 09:18:42 +01:00
Hassan Ben Jobrane
e7593c7de8 chore: update node to v18 in Dockerfile 2023-11-23 16:15:07 +01:00
Hassan Ben Jobrane
e6d862ac1b Merge pull request #2342 from nhost/fix/quickstarts-workspace-deps
fix: ensure `pnpm clean` and `pnpm install` work correctly for the quickstarts
2023-11-15 21:30:21 +01:00
Hassan Ben Jobrane
f73672372f chore: update baseURL in playwright.config.js 2023-11-15 21:08:03 +01:00
Hassan Ben Jobrane
7f12b98d94 chore: fix linter issue 2023-11-15 21:01:07 +01:00
Hassan Ben Jobrane
d79b66314d chore: fix linter issues 2023-11-15 20:49:11 +01:00
Hassan Ben Jobrane
2a58266592 chore: add allowedUrls to auth.redirections and set redirect option for Google sign-in 2023-11-15 20:24:53 +01:00
Hassan Ben Jobrane
44c2c5467d fix: replace @apollo/client with graphql-tag 2023-11-15 20:21:54 +01:00
Hassan Ben Jobrane
142752cb79 Revert "fix: update Apollo client import"
This reverts commit 11a46a0db1.
2023-11-15 20:13:48 +01:00
Hassan Ben Jobrane
b05236a23c chore: run pnpm install 2023-11-15 19:53:11 +01:00
Hassan Ben Jobrane
11a46a0db1 fix: update Apollo client import 2023-11-15 19:53:11 +01:00
Hassan Ben Jobrane
cedff501d6 chore: update auth version to 0.22.1 2023-11-15 19:53:11 +01:00
Hassan Ben Jobrane
7c426dafb2 fix: rectify clean scripts 2023-11-15 19:53:11 +01:00
Hassan Ben Jobrane
57e7f794f5 fix: make sure pnpm clean and pnpm install work correctly for the quickstarts 2023-11-15 19:53:11 +01:00
Hassan Ben Jobrane
d4b6cb0acf Merge pull request #2370 from nhost/chore/quickstarts/update-metadata
chore(quickstarts): add virus table metadata
2023-11-15 19:50:17 +01:00
Nuno Pato
5d0cf8814b Merge pull request #2372 from nhost/chore/dashboard-update-storage-capacity-alert
Chore/dashboard update storage capacity alert
2023-11-13 16:30:17 -01:00
Nuno Pato
96cf17bbeb Apply suggestions from code review
fix typos

Co-authored-by: Hassan Ben Jobrane <hsanbenjobrane@gmail.com>
2023-11-13 16:26:16 -01:00
Nuno Pato
ed1a8d458e add changeset 2023-11-13 16:12:20 -01:00
Nuno Pato
8077495c18 Change dashboard alert for volume capacity 2023-11-13 16:09:33 -01:00
Hassan Ben Jobrane
b617ec7186 chore(quickstarts): add virus table metadata 2023-11-13 17:02:43 +01:00
Hassan Ben Jobrane
bb2da11dd4 Merge pull request #2367 from nhost/fix/docs/signin-linkedin-guide
fix(docs): add instructions for enabling Sign In with LinkedIn using OpenID Connect
2023-11-13 16:18:03 +01:00
Hassan Ben Jobrane
94fa824e7d Merge pull request #2366 from nhost/feat/react-apollo/sign-in-with-linked-in
feat(examples): add sign-in with Linked to react-apollo
2023-11-13 15:56:30 +01:00
Hassan Ben Jobrane
32d1ee124f chore(react-apollo): update auth version to 0.22.1 2023-11-13 15:29:28 +01:00
Hassan Ben Jobrane
138bf9eb5a chore: add changeset 2023-11-11 20:26:50 +01:00
Hassan Ben Jobrane
d8d9310e0b fix: add instructions for enabling Sign In with LinkedIn using OpenID Connect 2023-11-11 20:26:05 +01:00
Hassan Ben Jobrane
67b2c044b8 chore: add changeset 2023-11-11 16:14:33 +01:00
Hassan Ben Jobrane
0b7790ca83 feat(examples): add sign-in with Linked to react-apollo 2023-11-11 16:11:56 +01:00
106 changed files with 3014 additions and 594 deletions

View File

@@ -1,5 +1,37 @@
# @nhost/dashboard
## 1.0.1
### Patch Changes
- @nhost/react-apollo@7.0.1
- @nhost/nextjs@2.0.1
## 1.0.0
### Major Changes
- bc9eff6e4: chore: remove support for using backendUrl when instantiating the Nhost client
### Patch Changes
- Updated dependencies [bc9eff6e4]
- @nhost/nextjs@2.0.0
- @nhost/react-apollo@7.0.0
## 0.21.1
### Patch Changes
- 97ced73a3: fix(dashboard): prevent dashboard from resolving secrets
## 0.21.0
### Minor Changes
- ed1a8d458: Update alert message on increasing PostgreSQL's volume capacity
- 2e2248fd4: feat(dashboard): add SQL editor
## 0.20.28
### Patch Changes

View File

@@ -1,4 +1,4 @@
FROM node:16-alpine AS pruner
FROM node:18-alpine AS pruner
RUN apk add --no-cache libc6-compat
RUN apk update
WORKDIR /app
@@ -7,7 +7,7 @@ RUN yarn global add turbo@1.10.11
COPY . .
RUN turbo prune --scope="@nhost/dashboard" --docker
FROM node:16-alpine AS builder
FROM node:18-alpine AS builder
ARG TURBO_TOKEN
ARG TURBO_TEAM
@@ -40,7 +40,7 @@ COPY turbo.json turbo.json
COPY config/ config/
RUN pnpm build:dashboard
FROM node:16-alpine AS runner
FROM node:18-alpine AS runner
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/dashboard",
"version": "0.20.28",
"version": "1.0.1",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
@@ -19,7 +19,7 @@
},
"dependencies": {
"@apollo/client": "^3.7.10",
"@codemirror/language": "^6.3.0",
"@codemirror/lang-sql": "^6.5.4",
"@emotion/cache": "^11.10.5",
"@emotion/react": "^11.10.5",
"@emotion/server": "^11.4.0",
@@ -44,6 +44,8 @@
"@tanstack/react-query": "^4.16.1",
"@tanstack/react-table": "^8.5.30",
"@tanstack/react-virtual": "^3.0.0-beta.23",
"@uiw/codemirror-theme-github": "^4.21.20",
"@uiw/react-codemirror": "^4.21.20",
"analytics-node": "^6.2.0",
"bcryptjs": "^2.4.3",
"clsx": "^1.2.1",
@@ -70,6 +72,7 @@
"react-is": "18.2.0",
"react-loading-skeleton": "^2.2.0",
"react-merge-refs": "^1.1.0",
"react-resizable-layout": "^0.7.2",
"react-syntax-highlighter": "^15.4.5",
"react-table": "^7.8.0",
"sharp": "^0.32.0",

View File

@@ -0,0 +1,26 @@
import type { IconProps } from '@/components/ui/v2/icons';
import { SvgIcon } from '@/components/ui/v2/icons/SvgIcon';
function TerminalIcon(props: IconProps) {
return (
<SvgIcon
width="16"
height="16"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
aria-label="Trash"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M3.49851 3.43968L2.93795 2.94141L1.94141 4.06252L2.50196 4.56079L6.37134 8.00024L2.50196 11.4397L1.94141 11.938L2.93795 13.0591L3.49851 12.5608L7.99851 8.56079C8.15863 8.41847 8.25024 8.21446 8.25024 8.00024C8.25024 7.78601 8.15863 7.582 7.99851 7.43968L3.49851 3.43968ZM7.99987 11.2502H7.24987V12.7502H7.99987H13.9999H14.7499V11.2502H13.9999H7.99987Z"
fill="currentColor"
/>
</SvgIcon>
);
}
TerminalIcon.displayName = 'NhostTerminalIcon';
export default TerminalIcon;

View File

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

View File

@@ -1,5 +1,5 @@
query GetAuthenticationSettings($appId: uuid!) {
config(appID: $appId, resolve: true) {
config(appID: $appId, resolve: false) {
id: __typename
__typename
auth {

View File

@@ -17,6 +17,7 @@ import { DotsHorizontalIcon } from '@/components/ui/v2/icons/DotsHorizontalIcon'
import { LockIcon } from '@/components/ui/v2/icons/LockIcon';
import { PencilIcon } from '@/components/ui/v2/icons/PencilIcon';
import { PlusIcon } from '@/components/ui/v2/icons/PlusIcon';
import { TerminalIcon } from '@/components/ui/v2/icons/TerminalIcon';
import { TrashIcon } from '@/components/ui/v2/icons/TrashIcon';
import { UsersIcon } from '@/components/ui/v2/icons/UsersIcon';
import { Link } from '@/components/ui/v2/Link';
@@ -86,7 +87,9 @@ function DataBrowserSidebarContent({
const isGitHubConnected = !!currentProject?.githubRepository;
const router = useRouter();
const {
asPath,
query: { workspaceSlug, appSlug, dataSourceSlug, schemaSlug, tableSlug },
} = router;
@@ -108,6 +111,8 @@ function DataBrowserSidebarContent({
*/
const [sidebarMenuTable, setSidebarMenuTable] = useState<string>();
const sqlEditorHref = `/${workspaceSlug}/${appSlug}/database/browser/default/editor`;
useEffect(() => {
if (selectedSchema) {
return;
@@ -258,194 +263,135 @@ function DataBrowserSidebarContent({
}
return (
<div className="grid gap-1">
{schemas && schemas.length > 0 && (
<Select
renderValue={(option) => (
<span className="grid grid-flow-col items-center gap-1">
{option?.label}
</span>
)}
slotProps={{
listbox: { className: 'max-w-[220px] min-w-[initial] w-full' },
popper: { className: 'max-w-[220px] min-w-[initial] w-full' },
}}
value={selectedSchema}
onChange={(_event, value) => setSelectedSchema(value as string)}
>
{schemas.map((schema) => (
<Option
className="grid grid-flow-col items-center gap-1"
value={schema.schema_name}
key={schema.schema_name}
>
<Text className="text-sm">
<Text component="span" color="disabled">
schema.
</Text>
<Text component="span" className="font-medium">
{schema.schema_name}
</Text>
</Text>
{(isSchemaLocked(schema.schema_name) || isGitHubConnected) && (
<LockIcon
className="h-3 w-3"
sx={{ color: 'text.secondary' }}
/>
)}
</Option>
))}
</Select>
)}
{isGitHubConnected && (
<Box
className="mt-1.5 grid grid-flow-row justify-items-start gap-2 rounded-md p-2"
sx={{ backgroundColor: 'grey.200' }}
>
<Text>
Your project is connected to GitHub. Please use the CLI to make
schema changes.
</Text>
<Link
href="https://docs.nhost.io/platform/github-integration"
target="_blank"
rel="noopener noreferrer"
underline="hover"
className="grid grid-flow-col items-center justify-start gap-1"
<Box className="flex h-full flex-col justify-between">
<Box className="flex flex-col px-2">
{schemas && schemas.length > 0 && (
<Select
renderValue={(option) => (
<span className="grid grid-flow-col items-center gap-1">
{option?.label}
</span>
)}
slotProps={{
listbox: { className: 'max-w-[220px] min-w-[initial] w-full' },
popper: { className: 'max-w-[220px] min-w-[initial] w-full' },
}}
value={selectedSchema}
onChange={(_event, value) => setSelectedSchema(value as string)}
>
Learn More <ArrowRightIcon />
</Link>
</Box>
)}
{!isSelectedSchemaLocked && (
<Button
variant="borderless"
endIcon={<PlusIcon />}
className="mt-1 w-full justify-between px-2"
onClick={() => {
openDrawer({
title: 'Create a New Table',
component: (
<CreateTableForm onSubmit={refetch} schema={selectedSchema} />
),
});
onSidebarItemClick();
}}
disabled={isGitHubConnected}
>
New Table
</Button>
)}
{schemas && schemas.length > 0 && tablesInSelectedSchema.length === 0 && (
<Text className="py-1.5 px-2 text-xs" color="disabled">
No tables found.
</Text>
)}
<nav aria-label="Database navigation">
{tablesInSelectedSchema.length > 0 && (
<List className="grid gap-1 pb-6">
{tablesInSelectedSchema.map((table) => {
const tablePath = `${table.table_schema}.${table.table_name}`;
const isSelected = `${schemaSlug}.${tableSlug}` === tablePath;
const isSidebarMenuOpen = sidebarMenuTable === tablePath;
return (
<ListItem.Root
className="group"
key={tablePath}
secondaryAction={
<Dropdown.Root
id="table-management-menu"
onOpen={() => setSidebarMenuTable(tablePath)}
onClose={() => setSidebarMenuTable(undefined)}
>
<Dropdown.Trigger
asChild
hideChevron
disabled={tablePath === removableTable}
{schemas.map((schema) => (
<Option
className="grid grid-flow-col items-center gap-1"
value={schema.schema_name}
key={schema.schema_name}
>
<Text className="text-sm">
<Text component="span" color="disabled">
schema.
</Text>
<Text component="span" className="font-medium">
{schema.schema_name}
</Text>
</Text>
{(isSchemaLocked(schema.schema_name) || isGitHubConnected) && (
<LockIcon
className="h-3 w-3"
sx={{ color: 'text.secondary' }}
/>
)}
</Option>
))}
</Select>
)}
{isGitHubConnected && (
<Box
className="mt-1.5 grid grid-flow-row justify-items-start gap-2 rounded-md p-2"
sx={{ backgroundColor: 'grey.200' }}
>
<Text>
Your project is connected to GitHub. Please use the CLI to make
schema changes.
</Text>
<Link
href="https://docs.nhost.io/platform/github-integration"
target="_blank"
rel="noopener noreferrer"
underline="hover"
className="grid grid-flow-col items-center justify-start gap-1"
>
Learn More <ArrowRightIcon />
</Link>
</Box>
)}
{!isSelectedSchemaLocked && (
<Button
variant="borderless"
endIcon={<PlusIcon />}
className="mt-1 w-full justify-between px-2"
onClick={() => {
openDrawer({
title: 'Create a New Table',
component: (
<CreateTableForm onSubmit={refetch} schema={selectedSchema} />
),
});
onSidebarItemClick();
}}
disabled={isGitHubConnected}
>
New Table
</Button>
)}
{schemas && schemas.length > 0 && tablesInSelectedSchema.length === 0 && (
<Text className="py-1.5 px-2 text-xs" color="disabled">
No tables found.
</Text>
)}
<nav aria-label="Database navigation">
{tablesInSelectedSchema.length > 0 && (
<List className="grid gap-1 pb-6">
{tablesInSelectedSchema.map((table) => {
const tablePath = `${table.table_schema}.${table.table_name}`;
const isSelected = `${schemaSlug}.${tableSlug}` === tablePath;
const isSidebarMenuOpen = sidebarMenuTable === tablePath;
return (
<ListItem.Root
className="group"
key={tablePath}
secondaryAction={
<Dropdown.Root
id="table-management-menu"
onOpen={() => setSidebarMenuTable(tablePath)}
onClose={() => setSidebarMenuTable(undefined)}
>
<IconButton
variant="borderless"
color={isSelected ? 'primary' : 'secondary'}
className={twMerge(
!isSelected &&
'opacity-0 group-focus-within:opacity-100 group-hover:opacity-100 group-active:opacity-100',
)}
<Dropdown.Trigger
asChild
hideChevron
disabled={tablePath === removableTable}
>
<DotsHorizontalIcon />
</IconButton>
</Dropdown.Trigger>
<Dropdown.Content menu PaperProps={{ className: 'w-52' }}>
{isGitHubConnected ? (
<Dropdown.Item
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
onClick={() =>
handleEditPermissionClick(
table.table_schema,
table.table_name,
true,
)
}
<IconButton
variant="borderless"
color={isSelected ? 'primary' : 'secondary'}
className={twMerge(
!isSelected &&
'opacity-0 group-focus-within:opacity-100 group-hover:opacity-100 group-active:opacity-100',
)}
>
<UsersIcon
className="h-4 w-4"
sx={{ color: 'text.secondary' }}
/>
<span>View Permissions</span>
</Dropdown.Item>
) : (
[
!isSelectedSchemaLocked && (
<Dropdown.Item
key="edit-table"
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
onClick={() =>
openDrawer({
title: 'Edit Table',
component: (
<EditTableForm
onSubmit={async () => {
await queryClient.refetchQueries([
`${dataSourceSlug}.${table.table_schema}.${table.table_name}`,
]);
await refetch();
}}
schema={table.table_schema}
table={table}
/>
),
})
}
>
<PencilIcon
className="h-4 w-4"
sx={{ color: 'text.secondary' }}
/>
<span>Edit Table</span>
</Dropdown.Item>
),
!isSelectedSchemaLocked && (
<Divider
key="edit-table-separator"
component="li"
/>
),
<DotsHorizontalIcon />
</IconButton>
</Dropdown.Trigger>
<Dropdown.Content
menu
PaperProps={{ className: 'w-52' }}
>
{isGitHubConnected ? (
<Dropdown.Item
key="edit-permissions"
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
onClick={() =>
handleEditPermissionClick(
table.table_schema,
table.table_name,
true,
)
}
>
@@ -453,68 +399,135 @@ function DataBrowserSidebarContent({
className="h-4 w-4"
sx={{ color: 'text.secondary' }}
/>
<span>Edit Permissions</span>
</Dropdown.Item>,
!isSelectedSchemaLocked && (
<Divider
key="edit-permissions-separator"
component="li"
/>
),
!isSelectedSchemaLocked && (
<span>View Permissions</span>
</Dropdown.Item>
) : (
[
!isSelectedSchemaLocked && (
<Dropdown.Item
key="edit-table"
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
onClick={() =>
openDrawer({
title: 'Edit Table',
component: (
<EditTableForm
onSubmit={async () => {
await queryClient.refetchQueries([
`${dataSourceSlug}.${table.table_schema}.${table.table_name}`,
]);
await refetch();
}}
schema={table.table_schema}
table={table}
/>
),
})
}
>
<PencilIcon
className="h-4 w-4"
sx={{ color: 'text.secondary' }}
/>
<span>Edit Table</span>
</Dropdown.Item>
),
!isSelectedSchemaLocked && (
<Divider
key="edit-table-separator"
component="li"
/>
),
<Dropdown.Item
key="delete-table"
key="edit-permissions"
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
sx={{ color: 'error.main' }}
onClick={() =>
handleDeleteTableClick(
handleEditPermissionClick(
table.table_schema,
table.table_name,
)
}
>
<TrashIcon
<UsersIcon
className="h-4 w-4"
sx={{ color: 'error.main' }}
sx={{ color: 'text.secondary' }}
/>
<span>Delete Table</span>
</Dropdown.Item>
),
]
)}
</Dropdown.Content>
</Dropdown.Root>
}
>
<ListItem.Button
dense
selected={isSelected}
disabled={tablePath === removableTable}
className="group-focus-within:pr-9 group-hover:pr-9 group-active:pr-9"
sx={{
paddingRight:
(isSelected || isSidebarMenuOpen) &&
'2.25rem !important',
}}
component={NavLink}
href={`/${workspaceSlug}/${appSlug}/database/browser/default/${table.table_schema}/${table.table_name}`}
onClick={() => {
if (onSidebarItemClick) {
onSidebarItemClick(`default.${tablePath}`);
}
}}
<span>Edit Permissions</span>
</Dropdown.Item>,
!isSelectedSchemaLocked && (
<Divider
key="edit-permissions-separator"
component="li"
/>
),
!isSelectedSchemaLocked && (
<Dropdown.Item
key="delete-table"
className="grid grid-flow-col items-center gap-2 p-2 text-sm+ font-medium"
sx={{ color: 'error.main' }}
onClick={() =>
handleDeleteTableClick(
table.table_schema,
table.table_name,
)
}
>
<TrashIcon
className="h-4 w-4"
sx={{ color: 'error.main' }}
/>
<span>Delete Table</span>
</Dropdown.Item>
),
]
)}
</Dropdown.Content>
</Dropdown.Root>
}
>
<ListItem.Text>{table.table_name}</ListItem.Text>
</ListItem.Button>
</ListItem.Root>
);
})}
</List>
)}
</nav>
</div>
<ListItem.Button
dense
selected={isSelected}
disabled={tablePath === removableTable}
className="group-focus-within:pr-9 group-hover:pr-9 group-active:pr-9"
sx={{
paddingRight:
(isSelected || isSidebarMenuOpen) &&
'2.25rem !important',
}}
component={NavLink}
href={`/${workspaceSlug}/${appSlug}/database/browser/default/${table.table_schema}/${table.table_name}`}
onClick={() => {
if (onSidebarItemClick) {
onSidebarItemClick(`default.${tablePath}`);
}
}}
>
<ListItem.Text>{table.table_name}</ListItem.Text>
</ListItem.Button>
</ListItem.Root>
);
})}
</List>
)}
</nav>
</Box>
<Box className="border-t">
<ListItem.Button
dense
selected={asPath === sqlEditorHref}
className="flex border group-focus-within:pr-9 group-hover:pr-9 group-active:pr-9"
component={NavLink}
href={sqlEditorHref}
>
<div className="flex w-full flex-row items-center justify-center space-x-4">
<TerminalIcon />
<span className="flex">SQL Editor</span>
</div>
</ListItem.Button>
</Box>
</Box>
);
}
@@ -580,7 +593,7 @@ export default function DataBrowserSidebar({
<Box
component="aside"
className={twMerge(
'absolute top-0 z-[35] h-full w-full overflow-auto border-r-1 px-2 pt-2 pb-17 motion-safe:transition-transform sm:relative sm:z-0 sm:h-full sm:py-2.5 sm:transition-none',
'absolute top-0 z-[35] h-full w-full overflow-auto border-r-1 pt-2 pb-17 motion-safe:transition-transform sm:relative sm:z-0 sm:h-full sm:pt-2.5 sm:pb-0 sm:transition-none',
expanded ? 'translate-x-0' : '-translate-x-full sm:translate-x-0',
className,
)}

View File

@@ -0,0 +1,263 @@
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Alert } from '@/components/ui/v2/Alert';
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
import { PlayIcon } from '@/components/ui/v2/icons/PlayIcon';
import { Input } from '@/components/ui/v2/Input';
import { Switch } from '@/components/ui/v2/Switch';
import { Table } from '@/components/ui/v2/Table';
import { TableBody } from '@/components/ui/v2/TableBody';
import { TableCell } from '@/components/ui/v2/TableCell';
import { TableHead } from '@/components/ui/v2/TableHead';
import { TableRow } from '@/components/ui/v2/TableRow';
import { Text } from '@/components/ui/v2/Text';
import { Tooltip } from '@/components/ui/v2/Tooltip';
import { useRunSQL } from '@/features/database/dataGrid/hooks/useRunSQL';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import { PostgreSQL, sql } from '@codemirror/lang-sql';
import { useTheme } from '@mui/material';
import { githubDark, githubLight } from '@uiw/codemirror-theme-github';
import CodeMirror from '@uiw/react-codemirror';
import { useCallback, useState } from 'react';
import { useResizable } from 'react-resizable-layout';
export default function SQLEditor() {
const theme = useTheme();
const isPlatform = useIsPlatform();
const [sqlCode, setSQLCode] = useState('');
const [track, setTrack] = useState(false);
const [cascade, setCascade] = useState(false);
const [readOnly, setReadOnly] = useState(false);
const [isMigration, setIsMigration] = useState(false);
const [migrationName, setMigrationName] = useState('');
const onChange = useCallback((value: string) => setSQLCode(value), []);
const { runSQL, loading, errorMessage, commandOk, rows, columns } = useRunSQL(
sqlCode,
track,
cascade,
readOnly,
isMigration,
migrationName,
);
const { position, separatorProps } = useResizable({
axis: 'y',
initial: 400,
min: 50,
reverse: true,
});
return (
<Box className="flex flex-1 flex-col justify-center overflow-hidden">
<Box className="flex flex-col space-y-2 border-b p-4">
<Text className="font-semibold">Raw SQL</Text>
<Box className="flex flex-col justify-between space-y-2 lg:flex-row lg:space-y-0 lg:space-x-4">
<Box className="flex w-full flex-col space-y-2 lg:flex-row lg:space-x-4 lg:space-y-0 xl:h-10">
<Box className="flex items-center space-x-2">
<Switch
label={
<Text variant="subtitle1" component="span">
Track this
</Text>
}
checked={track}
onChange={(event) => setTrack(event.currentTarget.checked)}
/>
<Tooltip title="If you are creating tables, views or functions, checking this will also expose them over the GraphQL API as top level fields. Functions only intended to be used as computed fields should not be tracked.">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
color="primary"
/>
</Tooltip>
</Box>
<Box className="flex items-center space-x-2">
<Switch
label={
<Text variant="subtitle1" component="span">
Cascade metadata
</Text>
}
checked={cascade}
onChange={(e) => setCascade(e.target.checked)}
/>
<Tooltip title="Cascade actions on all dependent metadata references, like relationships and permissions">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
color="primary"
/>
</Tooltip>
</Box>
<Box className="flex items-center space-x-2">
<Switch
label={
<Text variant="subtitle1" component="span">
Read only
</Text>
}
checked={readOnly}
onChange={(e) => setReadOnly(e.target.checked)}
/>
<Tooltip title="When set to true, the request will be run in READ ONLY transaction access mode which means only select queries will be successful. This flag ensures that the GraphQL schema is not modified and is hence highly performant.">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
color="primary"
/>
</Tooltip>
</Box>
{!isPlatform && (
<Box className="flex flex-col space-x-0 space-y-2 xl:flex-row xl:space-x-4 xl:space-y-0">
<Box className="flex items-center space-x-2">
<Switch
label={
<Text variant="subtitle1" component="span">
This is a migration
</Text>
}
checked={isMigration}
onChange={(e) => setIsMigration(e.target.checked)}
/>
<Tooltip title="Create a migration file with the SQL statement">
<InfoIcon
aria-label="Info"
className="h-4 w-4"
color="primary"
/>
</Tooltip>
</Box>
{isMigration && (
<Input
name="isMigration"
id="isMigration"
placeholder="migration_name"
className="h-auto w-auto max-w-md"
fullWidth
hideEmptyHelperText
onChange={(e) => setMigrationName(e.target.value)}
/>
)}
</Box>
)}
</Box>
<Button
disabled={loading || !sqlCode.trim()}
variant="contained"
className="self-start"
startIcon={<PlayIcon />}
onClick={runSQL}
>
Run
</Button>
</Box>
</Box>
<CodeMirror
value={sqlCode}
height="100%"
className="min-h-[100px] flex-1 overflow-y-auto"
theme={theme.palette.mode === 'light' ? githubLight : githubDark}
extensions={[sql({ dialect: PostgreSQL })]}
onChange={onChange}
/>
<Box
className="h-2 border-t hover:cursor-row-resize"
sx={{
background: theme.palette.background.default,
}}
{...separatorProps}
/>
<Box
className="flex items-start overflow-auto p-4"
style={{ height: position }}
>
{loading && (
<ActivityIndicator
className="mx-auto self-center"
circularProgressProps={{
className: 'w-5 h-5',
}}
/>
)}
{errorMessage && (
<Alert
severity="error"
className="mx-auto grid grid-flow-row place-content-center gap-2 self-center"
>
<code>{errorMessage}</code>
</Alert>
)}
{!loading && !errorMessage && commandOk && (
<Alert
severity="success"
className="mx-auto grid grid-flow-row place-content-center gap-2 self-center"
>
<code>Success, no rows returned</code>
</Alert>
)}
{!loading && !errorMessage && (
<Table
style={{
tableLayout: 'auto',
}}
className="w-auto"
>
<TableHead
sx={{
background: theme.palette.background.default,
}}
>
<TableRow>
{columns.map((header) => (
<TableCell
key={header}
scope="col"
className="whitespace-nowrap border px-6 py-3 font-bold"
>
{header}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{rows.map((row, rowIndex) => (
<TableRow
// eslint-disable-next-line react/no-array-index-key
key={String(rowIndex)}
// className="px-6 py-4 border whitespace-nowrap"
>
{row.map((value, valueIndex) => (
<TableCell
// eslint-disable-next-line react/no-array-index-key
key={`${value}-${valueIndex}`}
className="whitespace-nowrap border px-6 py-4"
>
{value}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
)}
</Box>
</Box>
);
}

View File

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

View File

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

View File

@@ -0,0 +1,283 @@
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { generateAppServiceUrl } from '@/features/projects/common/utils/generateAppServiceUrl';
import { getToastStyleProps } from '@/utils/constants/settings';
import { getHasuraAdminSecret } from '@/utils/env';
import { parseIdentifiersFromSQL } from '@/utils/sql';
import toast from 'react-hot-toast';
import { useState } from 'react';
export default function useRunSQL(
sqlCode: string,
track: boolean,
cascade: boolean,
readOnly: boolean,
isMigration: boolean,
migrationName: string,
) {
const { currentProject } = useCurrentWorkspaceAndProject();
const [loading, setLoading] = useState(false);
const [commandOk, setCommandOk] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
const [columns, setColumns] = useState<string[]>([]);
const [rows, setRows] = useState<string[][]>([[]]);
const appUrl = generateAppServiceUrl(
currentProject?.subdomain,
currentProject?.region,
'hasura',
);
const adminSecret =
process.env.NEXT_PUBLIC_ENV === 'dev'
? getHasuraAdminSecret()
: currentProject?.config?.hasura.adminSecret;
const toastStyle = getToastStyleProps();
const createMigration = async (
inputSQL: string,
migration: string,
isCascade: boolean,
) => {
try {
const migrationApiResponse = await fetch(`${appUrl}/apis/migrate`, {
method: 'POST',
headers: { 'x-hasura-admin-secret': adminSecret },
body: JSON.stringify({
name: migration,
datasource: 'default',
up: [
{
type: 'run_sql',
args: {
source: 'default',
sql: inputSQL,
cascade: isCascade,
read_only: false,
},
},
],
down: [
{
type: 'run_sql',
args: {
source: 'default',
sql: '-- Could not auto-generate a down migration.',
cascade: isCascade,
read_only: false,
},
},
],
}),
});
if (!migrationApiResponse.ok) {
throw new Error('Migration API call failed');
}
return {
error: null,
};
} catch (createMigrationError) {
toast.error('An error happened when calling the migration API', {
style: toastStyle.style,
...toastStyle.error,
});
return {
error: createMigrationError,
};
}
};
const sendSQLToHasura = async (
inputSQL: string,
isCascade: boolean,
isReadOnly: boolean,
) => {
try {
if (!inputSQL) {
return {
result_type: 'error',
columns: [],
rows: [],
queryApiError: 'No SQL provided',
};
}
const response = await fetch(`${appUrl}/v2/query`, {
method: 'POST',
headers: { 'x-hasura-admin-secret': adminSecret },
body: JSON.stringify({
type: 'run_sql',
args: {
source: 'default',
sql: inputSQL,
cascade: isCascade,
read_only: isReadOnly,
},
}),
});
if (!response.ok) {
const errorResponse = await response.json();
const queryApiError =
errorResponse?.internal?.error?.message || 'Unknown error';
return {
result_type: 'error',
columns: [],
rows: [],
error: queryApiError,
};
}
const responseBody = await response.json();
if (responseBody?.result_type === 'TuplesOk') {
return {
result_type: 'TuplesOk',
columns: responseBody.result[0],
rows: responseBody.result.slice(1),
error: '',
};
}
if (responseBody?.result_type === 'CommandOk') {
return {
result_type: 'CommandOk',
columns: [],
rows: [],
error: '',
};
}
// If the result_type is neither TuplesOk nor CommandOk
return {
result_type: 'error',
columns: [],
rows: [],
error: 'Unknown response type',
};
} catch (error) {
return {
result_type: 'error',
columns: [],
rows: [],
error: error.message || 'Unknown error',
};
}
};
const updateMetadata = async (inputSQL: string) => {
const entities = parseIdentifiersFromSQL(inputSQL);
const tablesOrViewEntities = entities.filter(
(entity) => entity.type !== 'function',
);
const functionEntities = entities.filter(
(entity) => entity.type === 'function',
);
const trackTablesOrViews = tablesOrViewEntities.map(({ name, schema }) => ({
type: 'pg_track_table',
args: {
source: 'default',
table: {
name,
schema,
},
},
}));
const trackFunctions = functionEntities.map(({ name, schema }) => ({
type: 'pg_track_function',
args: {
source: 'default',
function: {
name,
schema,
configuration: {},
},
},
}));
const metaDataPayload = {
source: 'default',
type: 'bulk',
args: [...trackTablesOrViews, ...trackFunctions],
};
try {
if (entities.length > 0) {
const metadataApiResponse = await fetch(`${appUrl}/v1/metadata`, {
method: 'POST',
headers: { 'x-hasura-admin-secret': adminSecret },
body: JSON.stringify(metaDataPayload),
});
if (!metadataApiResponse.ok) {
throw new Error('Metadata API call failed');
}
}
} catch (error) {
toast.error('An error happened when calling the metadata API', {
style: toastStyle.style,
...toastStyle.error,
});
}
};
const runSQL = async () => {
setLoading(true);
setCommandOk(false);
setErrorMessage('');
if (isMigration) {
const { error: createMigrationError } = await createMigration(
sqlCode,
migrationName,
cascade,
);
setCommandOk(!createMigrationError);
if (createMigrationError) {
setErrorMessage('An unknown error occurred');
}
// if running the migration fails then we don't update the metadata
if (track && !createMigrationError) {
await updateMetadata(sqlCode);
}
} else {
const {
result_type,
error: $error,
columns: $columns,
rows: $rows,
} = await sendSQLToHasura(sqlCode, cascade, readOnly);
setCommandOk(result_type === 'CommandOk');
setColumns($columns);
setRows($rows);
setErrorMessage($error);
// if running the sql fails then we don't update the metadata
if (track && !$error) {
await updateMetadata(sqlCode);
}
}
setLoading(false);
};
return {
runSQL,
loading,
errorMessage,
commandOk,
rows,
columns,
};
}

View File

@@ -2,9 +2,9 @@ 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 { Alert } from '@/components/ui/v2/Alert';
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 {
@@ -144,15 +144,11 @@ export default function AuthDomain() {
/>
</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>
<Alert severity="info" className="col-span-6 text-left">
Note that volumes can only be increased (not decreased). Also, due
to an AWS limitation, the same volume can only be increased once
every 6 hours.
</Alert>
)}
</SettingsContainer>
</Form>

View File

@@ -4,7 +4,7 @@ query GetPostgresSettings($appId: uuid!) {
database
}
}
config(appID: $appId, resolve: true) {
config(appID: $appId, resolve: false) {
id: __typename
__typename
postgres {

View File

@@ -1,5 +1,5 @@
query GetHasuraSettings($appId: uuid!) {
config(appID: $appId, resolve: true) {
config(appID: $appId, resolve: false) {
id: __typename
__typename
hasura {

View File

@@ -1,7 +1,6 @@
import type { ProjectFragment } from '@/utils/__generated__/graphql';
import { test, vi } from 'vitest';
import generateAppServiceUrl, {
defaultLocalBackendSlugs,
defaultRemoteBackendSlugs,
} from './generateAppServiceUrl';
@@ -138,7 +137,7 @@ test('should be able to override the default remote backend slugs', () => {
process.env.NEXT_PUBLIC_ENV = 'production';
expect(
generateAppServiceUrl('test', region, 'hasura', defaultLocalBackendSlugs, {
generateAppServiceUrl('test', region, 'hasura', {
...defaultRemoteBackendSlugs,
hasura: '/lorem-ipsum',
}),
@@ -187,24 +186,3 @@ test('should construct service URLs based on environment variables', () => {
'https://localdev4.nhost.run/v1/functions',
);
});
test('should generate a basic subdomain with a custom port if provided', () => {
process.env.NEXT_PUBLIC_NHOST_BACKEND_URL = `http://localhost:1338`;
process.env.NEXT_PUBLIC_NHOST_PLATFORM = 'true';
expect(generateAppServiceUrl('test', region, 'auth')).toBe(
`http://localhost:1338/v1/auth`,
);
expect(generateAppServiceUrl('test', region, 'storage')).toBe(
`http://localhost:1338/v1/files`,
);
expect(generateAppServiceUrl('test', region, 'graphql')).toBe(
`http://localhost:1338/v1/graphql`,
);
expect(generateAppServiceUrl('test', region, 'functions')).toBe(
`http://localhost:1338/v1/functions`,
);
});

View File

@@ -62,7 +62,6 @@ export default function generateAppServiceUrl(
subdomain: string,
region: ProjectFragment['region'],
service: NhostService,
localBackendSlugs = defaultLocalBackendSlugs,
remoteBackendSlugs = defaultRemoteBackendSlugs,
) {
const IS_PLATFORM = isPlatform();
@@ -87,12 +86,6 @@ export default function generateAppServiceUrl(
return serviceUrls[service];
}
// This is only used when running the dashboard locally against its own
// backend.
if (process.env.NEXT_PUBLIC_ENV === 'dev') {
return `${process.env.NEXT_PUBLIC_NHOST_BACKEND_URL}${localBackendSlugs[service]}`;
}
const constructedDomain = [
subdomain,
service,

View File

@@ -0,0 +1,157 @@
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 {
useGetServerlessFunctionsSettingsQuery,
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({
functions_fqdn: Yup.string(),
});
export type ServerlessFunctionsDomainFormValues = Yup.InferType<
typeof validationSchema
>;
export default function ServerlessFunctionsDomain() {
const { maintenanceActive } = useUI();
const [isVerified, setIsVerified] = useState(false);
const { currentProject, refetch: refetchWorkspaceAndProject } =
useCurrentWorkspaceAndProject();
const [updateConfig] = useUpdateConfigMutation();
const form = useForm<{ functions_fqdn: string }>({
reValidateMode: 'onSubmit',
defaultValues: { functions_fqdn: null },
resolver: yupResolver(validationSchema),
});
const { data, loading, error } = useGetServerlessFunctionsSettingsQuery({
variables: {
appId: currentProject.id,
},
});
const { networking } = data?.config?.functions?.resources || {};
const initialValue = networking?.ingresses?.[0]?.fqdn?.[0];
useEffect(() => {
if (!loading && data) {
form.reset({ functions_fqdn: initialValue });
}
}, [data, loading, form, initialValue]);
if (loading) {
return (
<ActivityIndicator
delay={1000}
label="Loading Serverless Functions Domain Settings..."
className="justify-center"
/>
);
}
if (error) {
throw error;
}
const { formState, register, watch } = form;
const isDirty = Object.keys(formState.dirtyFields).length > 0;
const functions_fqdn = watch('functions_fqdn');
async function handleSubmit(formValues: ServerlessFunctionsDomainFormValues) {
const ingresses: ConfigIngressUpdateInput[] =
formValues.functions_fqdn.length > 0
? [{ fqdn: [formValues.functions_fqdn] }]
: [];
const updateConfigPromise = updateConfig({
variables: {
appId: currentProject.id,
config: {
functions: {
resources: {
networking: {
ingresses,
},
},
},
},
},
});
try {
await toast.promise(
updateConfigPromise,
{
loading: `Serverless Functions domain is being updated...`,
success: `Serverless Functions domain has been updated successfully.`,
error: getServerError(
`An error occurred while trying to update the Serverless Functions domain.`,
),
},
getToastStyleProps(),
);
form.reset(formValues);
await refetchWorkspaceAndProject();
} catch {
// Note: The toast will handle the error.
}
}
return (
<FormProvider {...form}>
<Form onSubmit={handleSubmit}>
<SettingsContainer
title="Serverless Functions Domain"
description="Enter below your custom domain for Serverless Functions."
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('functions_fqdn')}
id="functions_fqdn"
name="functions_fqdn"
type="string"
fullWidth
className="col-span-5 lg:col-span-2"
placeholder="functions.mydomain.dev"
error={Boolean(formState.errors.functions_fqdn?.message)}
helperText={formState.errors.functions_fqdn?.message}
slotProps={{ inputRoot: { min: 1, max: 100 } }}
/>
<div className="col-span-5 row-start-2">
<VerifyDomain
recordType="CNAME"
hostname={functions_fqdn}
value={`lb.${currentProject.region.awsName}.${currentProject.region.domain}.`}
onHostNameVerified={() => setIsVerified(true)}
/>
</div>
</SettingsContainer>
</Form>
</FormProvider>
);
}

View File

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

View File

@@ -16,7 +16,6 @@ import { useAppClient } from '@/features/projects/common/hooks/useAppClient';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
defaultLocalBackendSlugs,
defaultRemoteBackendSlugs,
generateAppServiceUrl,
} from '@/features/projects/common/utils/generateAppServiceUrl';
@@ -110,7 +109,6 @@ export default function SystemEnvironmentVariableSettings() {
currentProject?.subdomain,
currentProject?.region,
'hasura',
defaultLocalBackendSlugs,
{ ...defaultRemoteBackendSlugs, hasura: '/console' },
),
},

View File

@@ -38,7 +38,7 @@ fragment ServiceResources on ConfigConfig {
}
query GetResources($appId: uuid!) {
config(appID: $appId, resolve: true) {
config(appID: $appId, resolve: false) {
...ServiceResources
}
}

View File

@@ -0,0 +1,13 @@
query GetServerlessFunctionsSettings($appId: uuid!) {
config(appID: $appId, resolve: false) {
functions {
resources {
networking {
ingresses {
fqdn
}
}
}
}
}
}

View File

@@ -1,5 +1,5 @@
query GetStorageSettings($appId: uuid!) {
config(appID: $appId, resolve: true) {
config(appID: $appId, resolve: false) {
id: __typename
__typename
storage {

View File

@@ -1,5 +1,5 @@
query getProjectLocales($appId: uuid!) {
config(appID: $appId, resolve: true) {
config(appID: $appId, resolve: false) {
auth {
user {
locale {

View File

@@ -18,7 +18,7 @@ fragment JWTSecret on ConfigJWTSecret {
}
query GetEnvironmentVariables($appId: uuid!) {
config(appID: $appId, resolve: true) {
config(appID: $appId, resolve: false) {
id: __typename
__typename
global {

View File

@@ -5,7 +5,7 @@ fragment PermissionVariable on ConfigAuthsessionaccessTokenCustomClaims {
}
query GetRolesPermissions($appId: uuid!) {
config(appID: $appId, resolve: true) {
config(appID: $appId, resolve: false) {
id: __typename
__typename
auth {

View File

@@ -1,5 +1,5 @@
query GetSignInMethods($appId: uuid!) {
config(appID: $appId, resolve: true) {
config(appID: $appId, resolve: false) {
id: __typename
__typename
provider {

View File

@@ -1,5 +1,5 @@
query GetSmtpSettings($appId: uuid!) {
config(appID: $appId, resolve: true) {
config(appID: $appId, resolve: false) {
id: __typename
__typename
provider {

View File

@@ -0,0 +1,27 @@
import { LoadingScreen } from '@/components/presentational/LoadingScreen';
import { RetryableErrorBoundary } from '@/components/presentational/RetryableErrorBoundary';
// import { useTablePath } from '@/features/database/common/hooks/useTablePath';
import { DataBrowserLayout } from '@/features/database/dataGrid/components/DataBrowserLayout';
import { SQLEditor } from '@/features/database/dataGrid/components/SQLEditor';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import type { ReactElement } from 'react';
export default function Editor() {
const isPlatform = useIsPlatform();
const { currentProject } = useCurrentWorkspaceAndProject();
if (isPlatform && !currentProject?.config?.hasura.adminSecret) {
return <LoadingScreen />;
}
return (
<RetryableErrorBoundary>
<SQLEditor />
</RetryableErrorBoundary>
);
}
Editor.getLayout = function getLayout(page: ReactElement) {
return <DataBrowserLayout>{page}</DataBrowserLayout>;
};

View File

@@ -11,7 +11,6 @@ import { Text } from '@/components/ui/v2/Text';
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
import {
defaultLocalBackendSlugs,
defaultRemoteBackendSlugs,
generateAppServiceUrl,
} from '@/features/projects/common/utils/generateAppServiceUrl';
@@ -39,7 +38,6 @@ export default function HasuraPage() {
currentProject?.subdomain,
currentProject?.region,
'hasura',
defaultLocalBackendSlugs,
{ ...defaultRemoteBackendSlugs, hasura: '/console' },
);

View File

@@ -10,6 +10,7 @@ import { AuthDomain } from '@/features/projects/custom-domains/settings/componen
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 { ServerlessFunctionsDomain } from '@/features/projects/custom-domains/settings/components/ServerlessFunctionsDomain';
import { type ReactElement } from 'react';
export default function CustomDomains() {
@@ -36,12 +37,12 @@ export default function CustomDomains() {
className="grid max-w-5xl grid-flow-row gap-6 bg-transparent"
rootClassName="bg-transparent"
>
<Box className="flex flex-row items-center gap-4 overflow-hidden rounded-lg border-1 p-4">
<Box className="flex flex-row items-center gap-4 p-4 overflow-hidden rounded-lg border-1">
<div className="flex flex-col space-y-2">
<Text className="text-lg font-semibold">Custom Domains</Text>
<Text color="secondary">
Add a custom domain to Auth, Hasura, PostgreSQL, and your Run
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"
@@ -51,7 +52,7 @@ export default function CustomDomains() {
className="ml-1 font-medium"
>
Custom Domains
<ArrowSquareOutIcon className="ml-1 h-4 w-4" />
<ArrowSquareOutIcon className="w-4 h-4 ml-1" />
</Link>
</Text>
</div>
@@ -61,6 +62,7 @@ export default function CustomDomains() {
<HasuraDomain />
<DatabaseDomain />
<ServerlessFunctionsDomain />
<RunServiceDomains />
</Container>
);

View File

@@ -528,6 +528,7 @@ export type ConfigAuthMethodWebauthnInsertInput = {
export type ConfigAuthMethodWebauthnRelyingParty = {
__typename?: 'ConfigAuthMethodWebauthnRelyingParty';
id?: Maybe<Scalars['String']>;
name?: Maybe<Scalars['String']>;
origins?: Maybe<Array<Scalars['ConfigUrl']>>;
};
@@ -536,16 +537,19 @@ export type ConfigAuthMethodWebauthnRelyingPartyComparisonExp = {
_and?: InputMaybe<Array<ConfigAuthMethodWebauthnRelyingPartyComparisonExp>>;
_not?: InputMaybe<ConfigAuthMethodWebauthnRelyingPartyComparisonExp>;
_or?: InputMaybe<Array<ConfigAuthMethodWebauthnRelyingPartyComparisonExp>>;
id?: InputMaybe<ConfigStringComparisonExp>;
name?: InputMaybe<ConfigStringComparisonExp>;
origins?: InputMaybe<ConfigUrlComparisonExp>;
};
export type ConfigAuthMethodWebauthnRelyingPartyInsertInput = {
id?: InputMaybe<Scalars['String']>;
name?: InputMaybe<Scalars['String']>;
origins?: InputMaybe<Array<Scalars['ConfigUrl']>>;
};
export type ConfigAuthMethodWebauthnRelyingPartyUpdateInput = {
id?: InputMaybe<Scalars['String']>;
name?: InputMaybe<Scalars['String']>;
origins?: InputMaybe<Array<Scalars['ConfigUrl']>>;
};
@@ -655,7 +659,9 @@ export type ConfigAuthSessionUpdateInput = {
export type ConfigAuthSignUp = {
__typename?: 'ConfigAuthSignUp';
/** Inverse of AUTH_DISABLE_NEW_USERS */
/** AUTH_DISABLE_NEW_USERS */
disableNewUsers?: Maybe<Scalars['Boolean']>;
/** Inverse of AUTH_DISABLE_SIGNUP */
enabled?: Maybe<Scalars['Boolean']>;
};
@@ -663,14 +669,17 @@ export type ConfigAuthSignUpComparisonExp = {
_and?: InputMaybe<Array<ConfigAuthSignUpComparisonExp>>;
_not?: InputMaybe<ConfigAuthSignUpComparisonExp>;
_or?: InputMaybe<Array<ConfigAuthSignUpComparisonExp>>;
disableNewUsers?: InputMaybe<ConfigBooleanComparisonExp>;
enabled?: InputMaybe<ConfigBooleanComparisonExp>;
};
export type ConfigAuthSignUpInsertInput = {
disableNewUsers?: InputMaybe<Scalars['Boolean']>;
enabled?: InputMaybe<Scalars['Boolean']>;
};
export type ConfigAuthSignUpUpdateInput = {
disableNewUsers?: InputMaybe<Scalars['Boolean']>;
enabled?: InputMaybe<Scalars['Boolean']>;
};
@@ -1042,6 +1051,7 @@ export type ConfigFloatComparisonExp = {
export type ConfigFunctions = {
__typename?: 'ConfigFunctions';
node?: Maybe<ConfigFunctionsNode>;
resources?: Maybe<ConfigFunctionsResources>;
};
export type ConfigFunctionsComparisonExp = {
@@ -1049,10 +1059,12 @@ export type ConfigFunctionsComparisonExp = {
_not?: InputMaybe<ConfigFunctionsComparisonExp>;
_or?: InputMaybe<Array<ConfigFunctionsComparisonExp>>;
node?: InputMaybe<ConfigFunctionsNodeComparisonExp>;
resources?: InputMaybe<ConfigFunctionsResourcesComparisonExp>;
};
export type ConfigFunctionsInsertInput = {
node?: InputMaybe<ConfigFunctionsNodeInsertInput>;
resources?: InputMaybe<ConfigFunctionsResourcesInsertInput>;
};
export type ConfigFunctionsNode = {
@@ -1075,8 +1087,29 @@ export type ConfigFunctionsNodeUpdateInput = {
version?: InputMaybe<Scalars['Int']>;
};
export type ConfigFunctionsResources = {
__typename?: 'ConfigFunctionsResources';
networking?: Maybe<ConfigNetworking>;
};
export type ConfigFunctionsResourcesComparisonExp = {
_and?: InputMaybe<Array<ConfigFunctionsResourcesComparisonExp>>;
_not?: InputMaybe<ConfigFunctionsResourcesComparisonExp>;
_or?: InputMaybe<Array<ConfigFunctionsResourcesComparisonExp>>;
networking?: InputMaybe<ConfigNetworkingComparisonExp>;
};
export type ConfigFunctionsResourcesInsertInput = {
networking?: InputMaybe<ConfigNetworkingInsertInput>;
};
export type ConfigFunctionsResourcesUpdateInput = {
networking?: InputMaybe<ConfigNetworkingUpdateInput>;
};
export type ConfigFunctionsUpdateInput = {
node?: InputMaybe<ConfigFunctionsNodeUpdateInput>;
resources?: InputMaybe<ConfigFunctionsResourcesUpdateInput>;
};
/** Global configuration that applies to all services */
@@ -12087,6 +12120,7 @@ export type Mutation_RootBackupApplicationDatabaseArgs = {
export type Mutation_RootBillingFinishSubscriptionArgs = {
appID: Scalars['uuid'];
appName: Scalars['String'];
planID: Scalars['uuid'];
subdomain: Scalars['String'];
subscriptionID: Scalars['String'];
};
@@ -22271,6 +22305,13 @@ export type GetResourcesQueryVariables = Exact<{
export type GetResourcesQuery = { __typename?: 'query_root', config?: { __typename?: 'ConfigConfig', auth?: { __typename?: 'ConfigAuth', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null } | null } | null, hasura: { __typename?: 'ConfigHasura', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null } | null }, postgres?: { __typename?: 'ConfigPostgres', resources?: { __typename?: 'ConfigPostgresResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null } | null } | null, storage?: { __typename?: 'ConfigStorage', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null } | null } | null } | null };
export type GetServerlessFunctionsSettingsQueryVariables = Exact<{
appId: Scalars['uuid'];
}>;
export type GetServerlessFunctionsSettingsQuery = { __typename?: 'query_root', config?: { __typename?: 'ConfigConfig', functions?: { __typename?: 'ConfigFunctions', resources?: { __typename?: 'ConfigFunctionsResources', networking?: { __typename?: 'ConfigNetworking', ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null } | null } | null } | null } | null };
export type GetStorageSettingsQueryVariables = Exact<{
appId: Scalars['uuid'];
}>;
@@ -23309,7 +23350,7 @@ export type DeletePersonalAccessTokenMutationResult = Apollo.MutationResult<Dele
export type DeletePersonalAccessTokenMutationOptions = Apollo.BaseMutationOptions<DeletePersonalAccessTokenMutation, DeletePersonalAccessTokenMutationVariables>;
export const GetAuthenticationSettingsDocument = gql`
query GetAuthenticationSettings($appId: uuid!) {
config(appID: $appId, resolve: true) {
config(appID: $appId, resolve: false) {
id: __typename
__typename
auth {
@@ -23403,7 +23444,7 @@ export const GetPostgresSettingsDocument = gql`
database
}
}
config(appID: $appId, resolve: true) {
config(appID: $appId, resolve: false) {
id: __typename
__typename
postgres {
@@ -23482,7 +23523,7 @@ export type ResetDatabasePasswordMutationResult = Apollo.MutationResult<ResetDat
export type ResetDatabasePasswordMutationOptions = Apollo.BaseMutationOptions<ResetDatabasePasswordMutation, ResetDatabasePasswordMutationVariables>;
export const GetHasuraSettingsDocument = gql`
query GetHasuraSettings($appId: uuid!) {
config(appID: $appId, resolve: true) {
config(appID: $appId, resolve: false) {
id: __typename
__typename
hasura {
@@ -23630,7 +23671,7 @@ export function refetchGetBackupPresignedUrlQuery(variables: GetBackupPresignedU
}
export const GetResourcesDocument = gql`
query GetResources($appId: uuid!) {
config(appID: $appId, resolve: true) {
config(appID: $appId, resolve: false) {
...ServiceResources
}
}
@@ -23666,9 +23707,55 @@ export type GetResourcesQueryResult = Apollo.QueryResult<GetResourcesQuery, GetR
export function refetchGetResourcesQuery(variables: GetResourcesQueryVariables) {
return { query: GetResourcesDocument, variables: variables }
}
export const GetServerlessFunctionsSettingsDocument = gql`
query GetServerlessFunctionsSettings($appId: uuid!) {
config(appID: $appId, resolve: false) {
functions {
resources {
networking {
ingresses {
fqdn
}
}
}
}
}
}
`;
/**
* __useGetServerlessFunctionsSettingsQuery__
*
* To run a query within a React component, call `useGetServerlessFunctionsSettingsQuery` and pass it any options that fit your needs.
* When your component renders, `useGetServerlessFunctionsSettingsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetServerlessFunctionsSettingsQuery({
* variables: {
* appId: // value for 'appId'
* },
* });
*/
export function useGetServerlessFunctionsSettingsQuery(baseOptions: Apollo.QueryHookOptions<GetServerlessFunctionsSettingsQuery, GetServerlessFunctionsSettingsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GetServerlessFunctionsSettingsQuery, GetServerlessFunctionsSettingsQueryVariables>(GetServerlessFunctionsSettingsDocument, options);
}
export function useGetServerlessFunctionsSettingsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetServerlessFunctionsSettingsQuery, GetServerlessFunctionsSettingsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetServerlessFunctionsSettingsQuery, GetServerlessFunctionsSettingsQueryVariables>(GetServerlessFunctionsSettingsDocument, options);
}
export type GetServerlessFunctionsSettingsQueryHookResult = ReturnType<typeof useGetServerlessFunctionsSettingsQuery>;
export type GetServerlessFunctionsSettingsLazyQueryHookResult = ReturnType<typeof useGetServerlessFunctionsSettingsLazyQuery>;
export type GetServerlessFunctionsSettingsQueryResult = Apollo.QueryResult<GetServerlessFunctionsSettingsQuery, GetServerlessFunctionsSettingsQueryVariables>;
export function refetchGetServerlessFunctionsSettingsQuery(variables: GetServerlessFunctionsSettingsQueryVariables) {
return { query: GetServerlessFunctionsSettingsDocument, variables: variables }
}
export const GetStorageSettingsDocument = gql`
query GetStorageSettings($appId: uuid!) {
config(appID: $appId, resolve: true) {
config(appID: $appId, resolve: false) {
id: __typename
__typename
storage {
@@ -23914,7 +24001,7 @@ export function refetchGetApplicationStateQuery(variables: GetApplicationStateQu
}
export const GetProjectLocalesDocument = gql`
query getProjectLocales($appId: uuid!) {
config(appID: $appId, resolve: true) {
config(appID: $appId, resolve: false) {
auth {
user {
locale {
@@ -24260,7 +24347,7 @@ export function refetchDnsLookupCnameQuery(variables: DnsLookupCnameQueryVariabl
}
export const GetEnvironmentVariablesDocument = gql`
query GetEnvironmentVariables($appId: uuid!) {
config(appID: $appId, resolve: true) {
config(appID: $appId, resolve: false) {
id: __typename
__typename
global {
@@ -24312,7 +24399,7 @@ export function refetchGetEnvironmentVariablesQuery(variables: GetEnvironmentVar
}
export const GetRolesPermissionsDocument = gql`
query GetRolesPermissions($appId: uuid!) {
config(appID: $appId, resolve: true) {
config(appID: $appId, resolve: false) {
id: __typename
__typename
auth {
@@ -24506,7 +24593,7 @@ export type UpdateSecretMutationResult = Apollo.MutationResult<UpdateSecretMutat
export type UpdateSecretMutationOptions = Apollo.BaseMutationOptions<UpdateSecretMutation, UpdateSecretMutationVariables>;
export const GetSignInMethodsDocument = gql`
query GetSignInMethods($appId: uuid!) {
config(appID: $appId, resolve: true) {
config(appID: $appId, resolve: false) {
id: __typename
__typename
provider {
@@ -24670,7 +24757,7 @@ export function refetchGetSignInMethodsQuery(variables: GetSignInMethodsQueryVar
}
export const GetSmtpSettingsDocument = gql`
query GetSmtpSettings($appId: uuid!) {
config(appID: $appId, resolve: true) {
config(appID: $appId, resolve: false) {
id: __typename
__typename
provider {

View File

@@ -5,16 +5,6 @@ export function isPlatform() {
return process.env.NEXT_PUBLIC_NHOST_PLATFORM === 'true';
}
/**
* Backend URL for the locally running instance. This is only used when running
* the Nhost Dashboard locally.
*/
export function getLocalBackendUrl() {
return `http://localhost:${
process.env.NEXT_PUBLIC_NHOST_LOCAL_SERVICES_PORT || '1337'
}`;
}
/**
* Admin secret for Hasura.
*/

View File

@@ -9,16 +9,11 @@ import { NhostClient } from '@nhost/nextjs';
// eslint-disable-next-line no-nested-ternary
const nhost = isPlatform()
? new NhostClient({ backendUrl: process.env.NEXT_PUBLIC_NHOST_BACKEND_URL })
: getAuthServiceUrl() &&
getGraphqlServiceUrl() &&
getStorageServiceUrl() &&
getFunctionsServiceUrl()
? new NhostClient({
authUrl: getAuthServiceUrl(),
graphqlUrl: getGraphqlServiceUrl(),
storageUrl: getStorageServiceUrl(),
functionsUrl: getFunctionsServiceUrl(),
storageUrl: getStorageServiceUrl(),
})
: new NhostClient({ subdomain: 'local' });

View File

@@ -0,0 +1,53 @@
// The parsing was inspired by code from the hasura/graphql-engine repo
export interface ParsedSQLEntity {
type: string;
name: string;
schema: string;
}
const sanitizeValue = (value: string) => {
let val = value;
if (!/^".*"$/.test(value)) {
val = value?.toLowerCase() ?? '';
}
return val.replace(/['"]+/g, '');
};
const stripComments = (sql: string) => {
const regExp = /(--[^\r\n]*)|(\/\*[\w\W]*?(?=\*\/)\*\/)/; // eslint-disable-line
const comments = sql.match(new RegExp(regExp, 'gmi'));
if (!comments?.length) {
return sql;
}
return comments.reduce(
(acc: string, comment: string) => acc.replace(comment, ''),
sql,
);
};
export const parseIdentifiersFromSQL = (sql: string): ParsedSQLEntity[] => {
const objects: ParsedSQLEntity[] = [];
const sanitizedSql = stripComments(sql);
const regExp =
/create\s*(?:|or\s*replace)\s*(?<type>view|table|function)\s*(?:\s*if*\s*not\s*exists\s*)?((?<schema>\"?\w+\"?)\.(?<nameWithSchema>\"?\w+\"?)|(?<name>\"?\w+\"?))\s*(?<partition>partition\s*of)?/gim; // eslint-disable-line
Array.from(sanitizedSql.matchAll(regExp)).forEach((result) => {
const { type, schema, name, nameWithSchema } = result.groups ?? {};
if (type && (name || nameWithSchema)) {
objects.push({
type: type.toLowerCase(),
schema: sanitizeValue(schema || 'public'),
name: sanitizeValue(name || nameWithSchema),
});
}
});
return objects;
};

View File

@@ -1,5 +1,23 @@
# @nhost/docs
## 0.7.4
### Patch Changes
- 2a04bc9e5: added functions to custom domains documentation
## 0.7.3
### Patch Changes
- 3ffb60f0a: fixed typo in Run deploy example script
## 0.7.2
### Patch Changes
- 138bf9eb5: fix: add instructions for enabling Sign In with LinkedIn using OpenID Connect
## 0.7.1
### Patch Changes

View File

@@ -36,6 +36,14 @@ Follow this guide to sign in users with LinkedIn.
- Copy and paste the **OAuth Callback URL** from Nhost.
- Click **Update**.
## Enable Sign In with LinkedIn using OpenID Connect
- Click on **Products** in the top menu.
- Scroll down and look for **Sign In with LinkedIn using OpenID Connect**.
- Click **Request Access**.
- Click the checkbox **I have read and agree to these terms**.
- Click **Request Access**.
## Configure Nhost
- Copy and paste the **Client ID** and **Client Secret** from LinkedIn to your Nhost OAuth settings for LinkedIn.

View File

@@ -29,26 +29,20 @@ Follow the instructions in the **Custom Domain** section of your project's setti
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:
For Hasura, Auth, Functions, 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']
[[functions.resources.networking.ingresses]]
fqdn = ['functions.custom-domain.com']
```
For Run services, typically in `nhost-service.toml` specific to the service:

View File

@@ -36,7 +36,7 @@ docker buildx build \
.
nhost run config-deploy \
--config $CONFIGURATION \
--config $CONFIGURATION_FILE \
--service-id $SERVICE_ID
```

View File

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

View File

@@ -1,5 +1,11 @@
# @nhost-examples/docker-compose
## 0.0.6
### Patch Changes
- a1c5c97a5: Clarify instructions for running the Nhost dashboard with Docker Compose
## 0.0.5
### Patch Changes

View File

@@ -25,10 +25,12 @@ The following endpoints are now exposed:
## Running the Nhost dashboard locally
In order to use the Nhost dashboard, you need to run the [Hasura console locally from the Hasura CLI](https://hasura.io/docs/latest/hasura-cli/commands/hasura_console/):
In order for you to be able to make edits to the database from the Nhost dashboard, you need to run the [Hasura console locally from the Hasura CLI](https://hasura.io/docs/latest/hasura-cli/commands/hasura_console/):
```sh
hasura console
```
The Nhost Dashboard also requires the Hasura admin secret to `nhost-admin-secret`. This will change in the future. If you can't wait, don't hesitate to contribute.
The Nhost Dashboard [uses](https://github.com/nhost/nhost/discussions/2398) the [Hasura migrations API](https://hasura.io/docs/latest/hasura-cli/commands/hasura_console/#options) in order to make edits to the database. It runs over port 9693 and is only accessible through running the Hasura console from the CLI. Because the Docker compose still only uses the graphql-engine Hasura Docker image and does not include the CLI image, that is why you need to run it locally. See https://github.com/nhost/nhost/issues/1220. Users are welcome to contibute a Docker compose that includes the CLI image to resolve this.
The Nhost Dashboard also requires the Hasura admin secret to `nhost-admin-secret` specified in the `.env` file.

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/docker-compose",
"version": "0.0.5",
"version": "0.0.6",
"private": true,
"scripts": {
"e2e": "vitest run"

View File

@@ -1,5 +1,16 @@
# @nhost-examples/multi-tenant-one-to-many
## 2.0.0
### Major Changes
- bc9eff6e4: chore: remove support for using backendUrl when instantiating the Nhost client
### Patch Changes
- Updated dependencies [bc9eff6e4]
- @nhost/nhost-js@3.0.0
## 1.0.4
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@nhost-examples/multi-tenant-one-to-many",
"private": true,
"version": "1.0.4",
"version": "2.0.0",
"description": "",
"main": "index.js",
"scripts": {},

View File

@@ -1,7 +1,7 @@
import { NhostClient } from "@nhost/nhost-js";
import { NhostClient } from '@nhost/nhost-js'
const nhost = new NhostClient({
backendUrl: "http://localhost:1337",
});
subdomain: 'local'
})
export { nhost };
export { nhost }

View File

@@ -1,5 +1,5 @@
module.exports = {
extends: ['../../config/.eslintrc.js', 'plugin:@next/next/recommended'],
extends: ['../../../config/.eslintrc.js', 'plugin:@next/next/recommended'],
rules: {
'react/react-in-jsx-scope': 'off'
}

View File

@@ -1,5 +1,24 @@
# @nhost-examples/nextjs-server-components
## 0.1.4
### Patch Changes
- @nhost/nhost-js@3.0.1
## 0.1.3
### Patch Changes
- Updated dependencies [bc9eff6e4]
- @nhost/nhost-js@3.0.0
## 0.1.2
### Patch Changes
- e469628eb: fix: resolve issue with WebAuthn authentication
## 0.1.1
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost-examples/nextjs-server-components",
"version": "0.1.1",
"version": "0.1.4",
"private": true,
"scripts": {
"dev": "next dev",
@@ -16,6 +16,7 @@
"eslint": "8.48.0",
"eslint-config-next": "13.4.19",
"form-data": "^4.0.0",
"graphql": "16.7.1",
"js-cookie": "^3.0.5",
"next": "13.4.19",
"postcss": "8.4.29",

View File

@@ -32,7 +32,7 @@ export default function SignInWithSecurityKey() {
}
if (session) {
Cookies.set(NHOST_SESSION_KEY, btoa(JSON.stringify(session)), { sameSite: 'strict' })
Cookies.set(NHOST_SESSION_KEY, btoa(JSON.stringify(session)), { path: '/' })
router.push('/protected/todos')
}
}

View File

@@ -37,7 +37,7 @@ export default function SignUpWebAuthn() {
})
if (session) {
Cookies.set(NHOST_SESSION_KEY, btoa(JSON.stringify(session)), { sameSite: 'strict' })
Cookies.set(NHOST_SESSION_KEY, btoa(JSON.stringify(session)), { path: '/' })
router.push('/protected/todos')
}
}

View File

@@ -5,7 +5,7 @@ import { getNhost } from '@utils/nhost'
import Head from 'next/head'
import Link from 'next/link'
const PAT = async ({
const PATs = async ({
params
}: {
params: {
@@ -100,4 +100,4 @@ const PAT = async ({
)
}
export default withAuthAsync(PAT)
export default withAuthAsync(PATs)

View File

@@ -54,6 +54,7 @@ const TodoItem = ({ todo }: { todo: Todo }) => {
<Link
className="w-6 h-6"
target="_blank"
passHref
href={nhost.storage.getPublicUrl({ fileId: todo.attachment.id })}
>
<svg

View File

@@ -1,4 +1,6 @@
import { manageAuthSession } from '@utils/nhost'
// eslint-disable-next-line @next/next/no-server-import-in-page
import { NextRequest, NextResponse } from 'next/server'
export async function middleware(request: NextRequest) {

View File

@@ -1,10 +1,12 @@
import { AuthErrorPayload, NhostClient, NhostSession } from '@nhost/nhost-js'
import { cookies } from 'next/headers'
// eslint-disable-next-line @next/next/no-server-import-in-page
import { NextRequest, NextResponse } from 'next/server'
import { type StateFrom } from 'xstate/lib/types'
import { waitFor } from 'xstate/lib/waitFor'
export const NHOST_SESSION_KEY = 'nhost-session'
export const NHOST_SESSION_KEY = 'nhostSession'
export const getNhost = async (request?: NextRequest) => {
const $cookies = request?.cookies || cookies()

View File

@@ -0,0 +1,42 @@
table:
name: virus
schema: storage
configuration:
column_config:
created_at:
custom_name: createdAt
file_id:
custom_name: fileId
filename:
custom_name: filename
id:
custom_name: id
updated_at:
custom_name: updatedAt
user_session:
custom_name: userSession
virus:
custom_name: virus
custom_column_names:
created_at: createdAt
file_id: fileId
filename: filename
id: id
updated_at: updatedAt
user_session: userSession
virus: virus
custom_name: virus
custom_root_fields:
delete: deleteViruses
delete_by_pk: deleteVirus
insert: insertViruses
insert_one: insertVirus
select: viruses
select_aggregate: virusesAggregate
select_by_pk: virus
update: updateViruses
update_by_pk: updateVirus
object_relationships:
- name: file
using:
foreign_key_constraint_on: file_id

View File

@@ -10,3 +10,4 @@
- "!include public_todos.yaml"
- "!include storage_buckets.yaml"
- "!include storage_files.yaml"
- "!include storage_virus.yaml"

View File

@@ -28,10 +28,11 @@ httpPoolSize = 100
version = 18
[auth]
version = '0.21.3'
version = '0.24.0'
[auth.redirections]
clientUrl = 'http://localhost:3000'
allowedUrls = ['https://example-nextjs-server-components.nhost.io', 'https://example-sveltekit.nhost.io']
[auth.signUp]
enabled = true
@@ -129,8 +130,9 @@ enabled = false
enabled = true
[auth.method.webauthn.relyingParty]
name = 'nextjs-server-components'
origins = ['http://localhost:3000']
name = 'quickstarts'
id = 'examples.nhost.io'
origins = ['https://sveltekit.examples.nhost.io', 'https://nextjs-server-components.examples.nhost.io']
[auth.method.webauthn.attestation]
timeout = 60000

View File

@@ -0,0 +1,15 @@
[
{
"op": "remove",
"path": "/auth/method/webauthn/relyingParty/id"
},
{
"value": "http://localhost:3000",
"op": "replace",
"path": "/auth/method/webauthn/relyingParty/origins/0"
},
{
"op": "remove",
"path": "/auth/method/webauthn/relyingParty/origins/1"
}
]

View File

@@ -9,7 +9,7 @@
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
"install-browsers": "pnpm dlx playwright@1.31.0 install --with-deps",
"add-nhost-js": "pnpm add @nhost/nhost-js --ignore-workspace",
"add-nhost-js": "pnpm add @nhost/nhost-js@2.2.18 --ignore-workspace",
"test": "pnpm install-browsers && pnpm add-nhost-js && pnpm dlx playwright@1.31.0 test",
"lint": "eslint .",
"postinstall": "pnpm add-nhost-js"
@@ -36,7 +36,6 @@
},
"type": "module",
"dependencies": {
"@apollo/client": "^3.8.1",
"graphql": "^16.7.1",
"graphql-tag": "^2.12.6",
"js-cookie": "^3.0.5",

View File

@@ -25,7 +25,7 @@ export default defineConfig({
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://localhost:5173',
baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry'
@@ -51,7 +51,7 @@ export default defineConfig({
webServer: {
command: 'pnpm dev',
url: 'http://localhost:5173',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI
}
})

View File

@@ -11,8 +11,8 @@ dependencies:
devDependencies:
'@nhost/nhost-js':
specifier: 2.2.17
version: 2.2.17(graphql@16.8.0)
specifier: 2.2.18
version: 2.2.18(graphql@16.8.0)
packages:
@@ -58,8 +58,8 @@ packages:
- encoding
dev: true
/@nhost/nhost-js@2.2.17(graphql@16.8.0):
resolution: {integrity: sha512-6KRzhqmx7JcOmbp91/YZaBavGKdyGdx7kDrzRLoP1RYYOAIMpdMhHzeIju9LQfujY/8nFARRq97vFpPSbpnhSg==}
/@nhost/nhost-js@2.2.18(graphql@16.8.0):
resolution: {integrity: sha512-aHn6p75fuG7SEUyB/yfX5TXtVTqwCT88zdN9Mmgo/8hnFOGV1XM7B4fxuGpNQCz18tG6kjM24tWx8EGXAEZ1sw==}
peerDependencies:
graphql: ^14.0.0 || ^15.0.0 || ^16.0.0
dependencies:

View File

@@ -3,10 +3,14 @@ import { redirect } from '@sveltejs/kit'
/** @type {import('./$types').Actions} */
export const actions = {
default: async ({ cookies }) => {
default: async ({ request, cookies }) => {
const nhost = await getNhost(cookies)
const { providerUrl } = await nhost.auth.signIn({ provider: 'google' })
const { providerUrl } = await nhost.auth.signIn({
provider: 'google',
options: {
redirectTo: new URL(request.url).origin
}
})
if (providerUrl) {
throw redirect(307, providerUrl)

View File

@@ -1,5 +1,5 @@
import { getNhost } from '$lib/nhost'
import { gql } from '@apollo/client'
import gql from 'graphql-tag'
/** @type {import('./$types').PageServerLoad} */
export const load = async ({ url, cookies }) => {

View File

@@ -1,6 +1,6 @@
import { getNhost } from '$lib/nhost.js'
import { gql } from '@apollo/client'
import { json, redirect } from '@sveltejs/kit'
import gql from 'graphql-tag'
export const DELETE = async ({ cookies, params }) => {
const nhost = await getNhost(cookies)

View File

@@ -1,5 +1,5 @@
import { getNhost } from '$lib/nhost'
import { gql } from '@apollo/client'
import gql from 'graphql-tag'
/** @type {import('./$types').PageServerLoad} */
export const load = async ({ url, cookies }) => {

View File

@@ -1,6 +1,6 @@
import { getNhost } from '$lib/nhost'
import { gql } from '@apollo/client'
import { redirect } from '@sveltejs/kit'
import gql from 'graphql-tag'
/** @type {import('./$types').Actions} */
export const actions = {

View File

@@ -1,6 +1,6 @@
import { getNhost } from '$lib/nhost'
import { gql } from '@apollo/client'
import { redirect } from '@sveltejs/kit'
import gql from 'graphql-tag'
/** @type {import('./$types').Actions} */
export const actions = {

View File

@@ -1,6 +1,6 @@
import { getNhost } from '$lib/nhost'
import { gql } from '@apollo/client'
import { json } from '@sveltejs/kit'
import gql from 'graphql-tag'
/** @type {import('./$types').RequestHandler} */
export async function POST({ request, cookies }) {

View File

@@ -1,5 +1,11 @@
# @nhost-examples/react-apollo
## 0.1.17
### Patch Changes
- 67b2c044b: feat: add sign-in with Linked-In
## 0.1.16
### Patch Changes

View File

@@ -28,7 +28,7 @@ httpPoolSize = 100
version = 18
[auth]
version = '0.21.4'
version = '0.22.1'
[auth.redirections]
clientUrl = 'https://react-apollo.example.nhost.io/'
@@ -112,7 +112,9 @@ clientId = '{{ secrets.GOOGLE_CLIENT_ID }}'
clientSecret = '{{ secrets.GOOGLE_CLIENT_SECRET }}'
[auth.method.oauth.linkedin]
enabled = false
enabled = true
clientId='{{ secrets.LINKEDIN_CLIENT_ID }}'
clientSecret='{{ secrets.LINKEDIN_CLIENT_SECRET }}'
[auth.method.oauth.spotify]
enabled = false

View File

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

View File

@@ -1,11 +1,13 @@
import { FaApple, FaGithub, FaGoogle } from 'react-icons/fa/index.js'
import { FaApple, FaGithub, FaGoogle, FaLinkedin } from 'react-icons/fa/index.js'
import { useProviderLink } from '@nhost/react'
import AuthLink from './AuthLink'
export default function OauthLinks() {
const { github, google, apple } = useProviderLink({ redirectTo: window.location.origin })
const { github, google, apple, linkedin } = useProviderLink({
redirectTo: window.location.origin
})
return (
<>
@@ -18,6 +20,10 @@ export default function OauthLinks() {
<AuthLink leftIcon={<FaApple />} link={apple} color="#333333">
Sign In With Apple
</AuthLink>
<AuthLink leftIcon={<FaLinkedin />} link={linkedin} color="#0073B1">
Sign In With LinkedIn
</AuthLink>
</>
)
}

View File

@@ -1,5 +1,16 @@
# @nhost-examples/react-gqty
## 1.0.0
### Major Changes
- bc9eff6e4: chore: remove support for using backendUrl when instantiating the Nhost client
### Patch Changes
- Updated dependencies [bc9eff6e4]
- @nhost/react@3.0.0
## 0.0.9
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@nhost-examples/react-gqty",
"private": true,
"version": "0.0.9",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,7 +1,8 @@
import { NhostClient } from '@nhost/react'
const nhost = new NhostClient({
backendUrl: import.meta.env.VITE_NHOST_URL || 'http://localhost:1337'
subdomain: import.meta.env.VITE_NHOST_SUBDOMAIN || 'local',
region: import.meta.env.VITE_NHOST_REGION
})
export { nhost }

View File

@@ -1,5 +1,18 @@
# @nhost/apollo
## 6.0.1
### Patch Changes
- @nhost/nhost-js@3.0.1
## 6.0.0
### Patch Changes
- Updated dependencies [bc9eff6e4]
- @nhost/nhost-js@3.0.0
## 5.2.22
### Patch Changes

View File

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

View File

@@ -1,5 +1,20 @@
# @nhost/react-apollo
## 7.0.1
### Patch Changes
- @nhost/apollo@6.0.1
- @nhost/react@3.0.1
## 7.0.0
### Patch Changes
- Updated dependencies [bc9eff6e4]
- @nhost/react@3.0.0
- @nhost/apollo@6.0.0
## 6.0.1
### Patch Changes

View File

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

View File

@@ -1,5 +1,18 @@
# @nhost/react-urql
## 4.0.1
### Patch Changes
- @nhost/react@3.0.1
## 4.0.0
### Patch Changes
- Updated dependencies [bc9eff6e4]
- @nhost/react@3.0.0
## 3.0.1
### Patch Changes

View File

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

View File

@@ -21,8 +21,8 @@
"build:docs": "turbo run build --filter=@nhost/docs",
"build:all": "turbo run build --include-dependencies",
"dev": "turbo run dev --filter=!@nhost/dashboard --filter=!@nhost/docs --filter=!@nhost-examples/* --filter=!@nhost/docgen --no-deps --include-dependencies",
"clean:all": "pnpm clean && rm -rf ./{{packages,examples}/*,docs,dashboard}/{.nhost,node_modules} node_modules",
"clean": "rm -rf ./{{packages,examples}/*,docs,dashboard}/{dist,umd,.next,.turbo,coverage}",
"clean:all": "pnpm clean && rm -rf ./{{packages,examples/**}/*,docs,dashboard}/{.nhost,node_modules} node_modules",
"clean": "rm -rf ./{{packages,examples/**}/*,docs,dashboard}/{dist,umd,.next,.turbo,coverage}",
"ci:version": "changeset version && pnpm install --frozen-lockfile false",
"coverage": "pnpm run test -- --coverage",
"prettier": "prettier --check .",

View File

@@ -1,5 +1,11 @@
# @nhost/hasura-auth-js
## 2.1.10
### Patch Changes
- b338793d6: Fix typo
## 2.1.9
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/hasura-auth-js",
"version": "2.1.9",
"version": "2.1.10",
"description": "Hasura-auth client",
"license": "MIT",
"keywords": [

View File

@@ -633,7 +633,7 @@ export class HasuraAuthClient {
* @example
* ```ts
* // if `x-hasura-company-id` exists as a custom claim
* const companyId = nhost.auth.getHsauraClaim('company-id')
* const companyId = nhost.auth.getHasuraClaim('company-id')
* ```
*
* @param name Name of the variable. You don't have to specify `x-hasura-`.

View File

@@ -1,5 +1,22 @@
# @nhost/nextjs
## 2.0.1
### Patch Changes
- @nhost/react@3.0.1
## 2.0.0
### Major Changes
- bc9eff6e4: chore: remove support for using backendUrl when instantiating the Nhost client
### Patch Changes
- Updated dependencies [bc9eff6e4]
- @nhost/react@3.0.0
## 1.13.40
### Patch Changes

View File

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

View File

@@ -1,9 +1,9 @@
import {
AuthMachine,
NHOST_REFRESH_TOKEN_KEY,
NhostClient,
NhostReactClientConstructorParams,
NhostSession,
NHOST_REFRESH_TOKEN_KEY,
VanillaNhostClient
} from '@nhost/react'
import Cookies from 'js-cookie'
@@ -29,27 +29,11 @@ export type CreateServerSideClientParams = Partial<
* @returns instance of `NhostClient` that is ready to use on the server side (signed in or signed out)
*/
export const createServerSideClient = async (
params: string | CreateServerSideClientParams,
params: CreateServerSideClientParams,
context: GetServerSidePropsContext
): Promise<NhostClient> => {
let clientParams: NhostReactClientConstructorParams
if (typeof params === 'string') {
console.warn(
'Deprecation Notice: Backend URL is no longer supported. Please use subdomain + region or individual service URLs.'
)
clientParams = {
backendUrl: params
}
} else {
clientParams = {
...params
}
}
const nhost = new VanillaNhostClient({
...clientParams,
...params,
clientStorageType: 'custom',
clientStorage: {
getItem: (key) => {

View File

@@ -41,12 +41,13 @@ import { createServerSideClient, CreateServerSideClientParams } from './create-s
* }
* ```
*
* @param backendUrl - URL of your Nhost application
* @param subdomain - URL of your Nhost application
* @param region - Region of your Nhost application
* @param context - Next.js context
* @returns Nhost session
*/
export const getNhostSession = async (
params: string | CreateServerSideClientParams,
params: CreateServerSideClientParams,
context: GetServerSidePropsContext
): Promise<NhostSession | null> => {
const nhost = await createServerSideClient(params, context)

View File

@@ -1,5 +1,18 @@
# @nhost/nhost-js
## 3.0.1
### Patch Changes
- Updated dependencies [b338793d6]
- @nhost/hasura-auth-js@2.1.10
## 3.0.0
### Major Changes
- bc9eff6e4: chore: remove support for using backendUrl when instantiating the Nhost client
## 2.2.18
### Patch Changes

View File

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

View File

@@ -7,10 +7,7 @@ import { NhostClientConstructorParams } from '../utils/types'
* Creates a client for Auth from either a subdomain or a URL
*/
export function createAuthClient(params: NhostClientConstructorParams) {
const authUrl =
'subdomain' in params || 'backendUrl' in params
? urlFromSubdomain(params, 'auth')
: params.authUrl
const authUrl = 'subdomain' in params ? urlFromSubdomain(params, 'auth') : params.authUrl
if (!authUrl) {
throw new Error('Please provide `subdomain` or `authUrl`.')

View File

@@ -11,7 +11,7 @@ import {
*/
export function createFunctionsClient(params: NhostClientConstructorParams) {
const functionsUrl =
'subdomain' in params || 'backendUrl' in params
'subdomain' in params
? urlFromSubdomain(params, 'functions')
: params.functionsUrl

View File

@@ -6,10 +6,7 @@ import { NhostClientConstructorParams } from '../utils/types'
* Creates a client for GraphQL from either a subdomain or a URL
*/
export function createGraphqlClient(params: NhostClientConstructorParams) {
const graphqlUrl =
'subdomain' in params || 'backendUrl' in params
? urlFromSubdomain(params, 'graphql')
: params.graphqlUrl
const graphqlUrl = 'subdomain' in params ? urlFromSubdomain(params, 'graphql') : params.graphqlUrl
if (!graphqlUrl) {
throw new Error('Please provide `subdomain` or `graphqlUrl`.')

View File

@@ -7,10 +7,7 @@ import { NhostClientConstructorParams } from '../utils/types'
* Creates a client for Storage from either a subdomain or a URL
*/
export function createStorageClient(params: NhostClientConstructorParams) {
const storageUrl =
'subdomain' in params || 'backendUrl' in params
? urlFromSubdomain(params, 'storage')
: params.storageUrl
const storageUrl = 'subdomain' in params ? urlFromSubdomain(params, 'storage') : params.storageUrl
if (!storageUrl) {
throw new Error('Please provide `subdomain` or `storageUrl`.')

View File

@@ -5,25 +5,20 @@ 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
* \`subdomain\` and `region` should be used when running the Nhost platform
*
* @param backendOrSubdomain
* @param subdomainAndRegion
* @param service
* @returns
*/
export function urlFromSubdomain(
backendOrSubdomain: Pick<NhostClientConstructorParams, 'region' | 'subdomain' | 'backendUrl'>,
subdomainAndRegion: Pick<NhostClientConstructorParams, 'region' | 'subdomain'>,
service: string
): string {
const { backendUrl, subdomain, region } = backendOrSubdomain
if (backendUrl) {
return `${backendUrl}/v1/${service}`
}
const { subdomain, region } = subdomainAndRegion
if (!subdomain) {
throw new Error('Either `backendUrl` or `subdomain` must be set.')
throw new Error('A `subdomain` must be set.')
}
// check if subdomain is [http[s]://]localhost[:port] or [http[s]://]local[:port]

View File

@@ -32,14 +32,6 @@ export interface ActionSuccessState {
isSuccess: boolean
}
export type BackendUrl = {
/**
* Nhost backend URL
* Will be deprecated in a future release. Please look at 'subdomain' and 'region' instead.
*/
backendUrl: string
}
export type Subdomain = {
/**
* Project subdomain (e.g. `ieingiwnginwnfnegqwvdqwdwq`)
@@ -66,11 +58,8 @@ export type ServiceUrls = {
functionsUrl?: string
}
export type BackendOrSubdomain = BackendUrl | Subdomain
export interface NhostClientConstructorParams
extends Partial<BackendUrl>,
Partial<Subdomain>,
extends Partial<Subdomain>,
Partial<ServiceUrls>,
Omit<NhostAuthConstructorParams, 'url'> {
/**

View File

@@ -2,20 +2,6 @@ import { describe, expect, it } from 'vitest'
import { buildUrl, LOCALHOST_REGEX, urlFromSubdomain } from '../src/utils/helpers'
describe('urlFromParams', () => {
describe('when using backendUrl', () => {
it('should return the full url with the path "/v1/auth" concatenated', async () => {
const url = urlFromSubdomain({ backendUrl: 'http://localhost' }, 'auth')
expect(url).toBe('http://localhost/v1/auth')
})
it('should return the full url with the path "/v1/storage" concatenated', async () => {
const url = urlFromSubdomain({ backendUrl: 'http://localhost:1337' }, 'storage')
expect(url).toBe('http://localhost:1337/v1/storage')
})
})
describe('using subdomain', () => {
describe('other than "localhost" and a region', () => {
it('should return the full authentication url', async () => {
@@ -140,7 +126,9 @@ describe('buildUrl', () => {
})
it('should handle missing parameters', () => {
// @ts-ignore
expect(() => buildUrl()).toThrow()
// @ts-ignore
expect(() => buildUrl('https://example.com')).toThrow()
})
})

View File

@@ -1,5 +1,22 @@
# @nhost/react
## 3.0.1
### Patch Changes
- @nhost/nhost-js@3.0.1
## 3.0.0
### Major Changes
- bc9eff6e4: chore: remove support for using backendUrl when instantiating the Nhost client
### Patch Changes
- Updated dependencies [bc9eff6e4]
- @nhost/nhost-js@3.0.0
## 2.1.1
### Patch Changes

View File

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

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