Compare commits
81 Commits
@nhost/vue
...
@nhost/das
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2d590db7e | ||
|
|
3bdbefc015 | ||
|
|
79081b43c2 | ||
|
|
2e2248fd44 | ||
|
|
63358eb80b | ||
|
|
ded674fab6 | ||
|
|
85f2f28902 | ||
|
|
b8e9ad831e | ||
|
|
4e0c5dd1d3 | ||
|
|
b874109c6d | ||
|
|
21b926cc07 | ||
|
|
c35cd47d97 | ||
|
|
8dcd801c7c | ||
|
|
e3199be749 | ||
|
|
284b31e036 | ||
|
|
e7593c7de8 | ||
|
|
e6d862ac1b | ||
|
|
f73672372f | ||
|
|
7f12b98d94 | ||
|
|
d79b66314d | ||
|
|
2a58266592 | ||
|
|
44c2c5467d | ||
|
|
142752cb79 | ||
|
|
b05236a23c | ||
|
|
11a46a0db1 | ||
|
|
cedff501d6 | ||
|
|
7c426dafb2 | ||
|
|
57e7f794f5 | ||
|
|
d4b6cb0acf | ||
|
|
5d0cf8814b | ||
|
|
96cf17bbeb | ||
|
|
ed1a8d458e | ||
|
|
8077495c18 | ||
|
|
b617ec7186 | ||
|
|
bb2da11dd4 | ||
|
|
94fa824e7d | ||
|
|
32d1ee124f | ||
|
|
138bf9eb5a | ||
|
|
d8d9310e0b | ||
|
|
67b2c044b8 | ||
|
|
0b7790ca83 | ||
|
|
55267c680e | ||
|
|
4d856f557f | ||
|
|
64c579cf8c | ||
|
|
eae65c715b | ||
|
|
9e69f9f235 | ||
|
|
8b127fbb62 | ||
|
|
86ba2081ec | ||
|
|
7c2c31082a | ||
|
|
60f705b033 | ||
|
|
ea34635eb2 | ||
|
|
2004687044 | ||
|
|
bd025d43ca | ||
|
|
87a05f7374 | ||
|
|
798f147db7 | ||
|
|
62b7fd2376 | ||
|
|
1ee021b4a3 | ||
|
|
6e61dce297 | ||
|
|
bd744e52dc | ||
|
|
85723d740b | ||
|
|
36e79e7b32 | ||
|
|
f61264b319 | ||
|
|
e84d9d2576 | ||
|
|
ea69d4f0f1 | ||
|
|
212d58bee5 | ||
|
|
c3d6b7beec | ||
|
|
5d5d8ef4f3 | ||
|
|
deb61fe97c | ||
|
|
04d36154b0 | ||
|
|
203cfb10b9 | ||
|
|
9690f871fa | ||
|
|
74a6b93971 | ||
|
|
dd4c0d2430 | ||
|
|
83f2ca5cde | ||
|
|
0c49e757c8 | ||
|
|
e90a9d7696 | ||
|
|
00a06466f5 | ||
|
|
8ca9f76cb2 | ||
|
|
78113dd62a | ||
|
|
adb0ee82c6 | ||
|
|
a41bb6cae6 |
@@ -1,5 +1,20 @@
|
||||
# @nhost/dashboard
|
||||
|
||||
## 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
|
||||
|
||||
- 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/dashboard",
|
||||
"version": "0.20.27",
|
||||
"version": "0.21.0",
|
||||
"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",
|
||||
|
||||
@@ -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;
|
||||
@@ -0,0 +1 @@
|
||||
export { default as TerminalIcon } from './TerminalIcon';
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as DeleteAccount } from './DeleteAccount';
|
||||
@@ -0,0 +1,5 @@
|
||||
mutation deleteUserAccount($id: uuid!) {
|
||||
deleteUser(id: $id) {
|
||||
__typename
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as SQLEditor } from './SQLEditor';
|
||||
@@ -0,0 +1 @@
|
||||
export { default as useRunSQL } from './useRunSQL';
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
40
dashboard/src/utils/__generated__/graphql.ts
generated
40
dashboard/src/utils/__generated__/graphql.ts
generated
@@ -22196,6 +22196,13 @@ export type Workspaces_Updates = {
|
||||
where: Workspaces_Bool_Exp;
|
||||
};
|
||||
|
||||
export type DeleteUserAccountMutationVariables = Exact<{
|
||||
id: Scalars['uuid'];
|
||||
}>;
|
||||
|
||||
|
||||
export type DeleteUserAccountMutation = { __typename?: 'mutation_root', deleteUser?: { __typename: 'users' } | null };
|
||||
|
||||
export type GetPersonalAccessTokensQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
@@ -23190,6 +23197,39 @@ export const GetWorkspaceMembersWorkspaceMemberInviteFragmentDoc = gql`
|
||||
memberType
|
||||
}
|
||||
`;
|
||||
export const DeleteUserAccountDocument = gql`
|
||||
mutation deleteUserAccount($id: uuid!) {
|
||||
deleteUser(id: $id) {
|
||||
__typename
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type DeleteUserAccountMutationFn = Apollo.MutationFunction<DeleteUserAccountMutation, DeleteUserAccountMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useDeleteUserAccountMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useDeleteUserAccountMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useDeleteUserAccountMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [deleteUserAccountMutation, { data, loading, error }] = useDeleteUserAccountMutation({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useDeleteUserAccountMutation(baseOptions?: Apollo.MutationHookOptions<DeleteUserAccountMutation, DeleteUserAccountMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<DeleteUserAccountMutation, DeleteUserAccountMutationVariables>(DeleteUserAccountDocument, options);
|
||||
}
|
||||
export type DeleteUserAccountMutationHookResult = ReturnType<typeof useDeleteUserAccountMutation>;
|
||||
export type DeleteUserAccountMutationResult = Apollo.MutationResult<DeleteUserAccountMutation>;
|
||||
export type DeleteUserAccountMutationOptions = Apollo.BaseMutationOptions<DeleteUserAccountMutation, DeleteUserAccountMutationVariables>;
|
||||
export const GetPersonalAccessTokensDocument = gql`
|
||||
query GetPersonalAccessTokens {
|
||||
personalAccessTokens: authRefreshTokens(
|
||||
|
||||
53
dashboard/src/utils/sql/index.ts
Normal file
53
dashboard/src/utils/sql/index.ts
Normal 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;
|
||||
};
|
||||
@@ -1,5 +1,17 @@
|
||||
# @nhost/docs
|
||||
|
||||
## 0.7.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 138bf9eb5: fix: add instructions for enabling Sign In with LinkedIn using OpenID Connect
|
||||
|
||||
## 0.7.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1ee021b4a: remove custom domains from roadmap
|
||||
|
||||
## 0.7.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/docs",
|
||||
"version": "0.7.0",
|
||||
"version": "0.7.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
|
||||
@@ -25,7 +25,7 @@ httpPoolSize = 100
|
||||
|
||||
[functions]
|
||||
[functions.node]
|
||||
version = 16
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.20.2'
|
||||
|
||||
@@ -25,7 +25,7 @@ httpPoolSize = 100
|
||||
|
||||
[functions]
|
||||
[functions.node]
|
||||
version = 16
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.20.2'
|
||||
|
||||
@@ -25,7 +25,7 @@ httpPoolSize = 100
|
||||
|
||||
[functions]
|
||||
[functions.node]
|
||||
version = 16
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.20.2'
|
||||
|
||||
@@ -25,7 +25,7 @@ httpPoolSize = 100
|
||||
|
||||
[functions]
|
||||
[functions.node]
|
||||
version = 16
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.20.2'
|
||||
|
||||
@@ -25,7 +25,7 @@ httpPoolSize = 100
|
||||
|
||||
[functions]
|
||||
[functions.node]
|
||||
version = 16
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.20.2'
|
||||
|
||||
@@ -25,7 +25,7 @@ httpPoolSize = 100
|
||||
|
||||
[functions]
|
||||
[functions.node]
|
||||
version = 16
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.20.2'
|
||||
|
||||
@@ -25,7 +25,7 @@ httpPoolSize = 100
|
||||
|
||||
[functions]
|
||||
[functions.node]
|
||||
version = 16
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.20.2'
|
||||
|
||||
@@ -25,7 +25,7 @@ httpPoolSize = 100
|
||||
|
||||
[functions]
|
||||
[functions.node]
|
||||
version = 16
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.20.1'
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/nextjs-server-components",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
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'
|
||||
|
||||
@@ -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
|
||||
@@ -10,3 +10,4 @@
|
||||
- "!include public_todos.yaml"
|
||||
- "!include storage_buckets.yaml"
|
||||
- "!include storage_files.yaml"
|
||||
- "!include storage_virus.yaml"
|
||||
|
||||
@@ -28,10 +28,11 @@ httpPoolSize = 100
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.21.3'
|
||||
version = '0.22.1'
|
||||
|
||||
[auth.redirections]
|
||||
clientUrl = 'http://localhost:3000'
|
||||
allowedUrls = ['https://example-nextjs-server-components.nhost.io', 'https://example-sveltekit.nhost.io']
|
||||
|
||||
[auth.signUp]
|
||||
enabled = true
|
||||
@@ -144,7 +145,7 @@ version = '14.6-20230406-2'
|
||||
[provider]
|
||||
|
||||
[storage]
|
||||
version = '0.3.5'
|
||||
version = '0.4.0'
|
||||
|
||||
[observability]
|
||||
[observability.grafana]
|
||||
|
||||
@@ -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",
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
|
||||
8
examples/quickstarts/sveltekit/pnpm-lock.yaml
generated
8
examples/quickstarts/sveltekit/pnpm-lock.yaml
generated
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 }) {
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @nhost-examples/react-apollo
|
||||
|
||||
## 0.1.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 67b2c044b: feat: add sign-in with Linked-In
|
||||
|
||||
## 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
|
||||
|
||||
@@ -25,14 +25,14 @@ httpPoolSize = 100
|
||||
|
||||
[functions]
|
||||
[functions.node]
|
||||
version = 16
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.21.2'
|
||||
version = '0.22.1'
|
||||
|
||||
[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'
|
||||
@@ -108,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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/react-apollo",
|
||||
"version": "0.1.15",
|
||||
"version": "0.1.17",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.7.14",
|
||||
|
||||
70
examples/react-apollo/pnpm-lock.yaml
generated
70
examples/react-apollo/pnpm-lock.yaml
generated
@@ -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):
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { 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 } = useProviderLink({ redirectTo: window.location.origin })
|
||||
const { github, google, apple, linkedin } = useProviderLink({
|
||||
redirectTo: window.location.origin
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<AuthLink leftIcon={<FaGithub />} link={github} color="#333">
|
||||
@@ -14,6 +17,13 @@ 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>
|
||||
|
||||
<AuthLink leftIcon={<FaLinkedin />} link={linkedin} color="#0073B1">
|
||||
Sign In With LinkedIn
|
||||
</AuthLink>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ httpPoolSize = 100
|
||||
|
||||
[functions]
|
||||
[functions.node]
|
||||
version = 16
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.20.2'
|
||||
|
||||
@@ -25,7 +25,7 @@ httpPoolSize = 100
|
||||
|
||||
[functions]
|
||||
[functions.node]
|
||||
version = 16
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.20.2'
|
||||
|
||||
@@ -25,7 +25,7 @@ httpPoolSize = 100
|
||||
|
||||
[functions]
|
||||
[functions.node]
|
||||
version = 16
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.20.2'
|
||||
|
||||
@@ -25,7 +25,7 @@ httpPoolSize = 100
|
||||
|
||||
[functions]
|
||||
[functions.node]
|
||||
version = 16
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.20.2'
|
||||
|
||||
@@ -28,7 +28,7 @@ httpPoolSize = 100
|
||||
|
||||
[functions]
|
||||
[functions.node]
|
||||
version = 16
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.20.2'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -25,7 +25,7 @@ httpPoolSize = 100
|
||||
|
||||
[functions]
|
||||
[functions.node]
|
||||
version = 16
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.20.2'
|
||||
|
||||
@@ -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
1657
examples/vue-apollo/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
97
examples/vue-apollo/src/components/FileDropZone.vue
Normal file
97
examples/vue-apollo/src/components/FileDropZone.vue
Normal 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>
|
||||
34
examples/vue-apollo/src/components/FileUploadItem.vue
Normal file
34
examples/vue-apollo/src/components/FileUploadItem.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
129
examples/vue-apollo/src/pages/StoragePage.vue
Normal file
129
examples/vue-apollo/src/pages/StoragePage.vue
Normal 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>
|
||||
@@ -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 } }
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/apollo",
|
||||
"version": "5.2.21",
|
||||
"version": "5.2.22",
|
||||
"description": "Nhost Apollo Client library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @nhost/react-apollo
|
||||
|
||||
## 6.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/apollo@5.2.22
|
||||
- @nhost/react@2.1.1
|
||||
|
||||
## 6.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/react-apollo",
|
||||
"version": "6.0.0",
|
||||
"version": "6.0.1",
|
||||
"description": "Nhost React Apollo client",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/react-urql
|
||||
|
||||
## 3.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@2.1.1
|
||||
|
||||
## 3.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/react-urql",
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.1",
|
||||
"description": "Nhost React URQL client",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
805
observability/dashboards/ingress_metrics.json
Executable file
805
observability/dashboards/ingress_metrics.json
Executable 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": ""
|
||||
}
|
||||
@@ -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 .",
|
||||
|
||||
@@ -25,7 +25,7 @@ httpPoolSize = 100
|
||||
|
||||
[functions]
|
||||
[functions.node]
|
||||
version = 16
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.20.2'
|
||||
|
||||
@@ -25,7 +25,7 @@ httpPoolSize = 100
|
||||
|
||||
[functions]
|
||||
[functions.node]
|
||||
version = 16
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.20.2'
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/nextjs
|
||||
|
||||
## 1.13.40
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@2.1.1
|
||||
|
||||
## 1.13.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/nextjs",
|
||||
"version": "1.13.39",
|
||||
"version": "1.13.40",
|
||||
"description": "Nhost NextJS library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/nhost-js
|
||||
|
||||
## 2.2.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 8b127fbb6: feat: export `urlFromSubdomain` helper
|
||||
|
||||
## 2.2.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -25,7 +25,7 @@ httpPoolSize = 100
|
||||
|
||||
[functions]
|
||||
[functions.node]
|
||||
version = 16
|
||||
version = 18
|
||||
|
||||
[auth]
|
||||
version = '0.20.1'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/nhost-js",
|
||||
"version": "2.2.17",
|
||||
"version": "2.2.18",
|
||||
"description": "Nhost JavaScript SDK",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @nhost/react
|
||||
|
||||
## 2.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [8b127fbb6]
|
||||
- @nhost/nhost-js@2.2.18
|
||||
|
||||
## 2.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/react",
|
||||
"version": "2.1.0",
|
||||
"version": "2.1.1",
|
||||
"description": "Nhost React library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# @nhost/vue
|
||||
|
||||
## 1.14.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [8b127fbb6]
|
||||
- @nhost/nhost-js@2.2.18
|
||||
|
||||
## 1.14.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 0c49e757c: feat: add new composable `useMultipleFilesUpload`
|
||||
|
||||
## 1.13.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/vue",
|
||||
"version": "1.13.39",
|
||||
"version": "1.14.1",
|
||||
"description": "Nhost Vue library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export * from './client'
|
||||
export * from './useAccessToken'
|
||||
export * from './useAuthInterpreter'
|
||||
export * from './useAuthenticated'
|
||||
export * from './useAuthenticationStatus'
|
||||
export * from './useAuthInterpreter'
|
||||
export * from './useChangeEmail'
|
||||
export * from './useChangePassword'
|
||||
export * from './useConfigMfa'
|
||||
@@ -10,6 +10,7 @@ export * from './useDecodedAccessToken'
|
||||
export * from './useFileUpload'
|
||||
export * from './useHasuraClaim'
|
||||
export * from './useHasuraClaims'
|
||||
export * from './useMultipleFilesUpload'
|
||||
export * from './useNhostClient'
|
||||
export * from './useProviderLink'
|
||||
export * from './useResetPassword'
|
||||
|
||||
@@ -8,10 +8,11 @@ import {
|
||||
uploadFilePromise
|
||||
} from '@nhost/nhost-js'
|
||||
import { useInterpret, useSelector } from '@xstate/vue'
|
||||
import { ToRefs } from 'vue'
|
||||
import { InterpreterFrom } from 'xstate'
|
||||
import { useNhostClient } from './useNhostClient'
|
||||
|
||||
export interface FileUploadComposableResult extends FileUploadState {
|
||||
export interface FileUploadComposableResult extends ToRefs<FileUploadState> {
|
||||
/**
|
||||
* Add the file without uploading it.
|
||||
*/
|
||||
@@ -38,21 +39,41 @@ export type { FileItemRef }
|
||||
/**
|
||||
* Use the composable `useFileUploadItem` to control the file upload of a file in a multiple file upload.
|
||||
*
|
||||
* It has the same signature as `useFileUpload`.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const Item = ({itemRef}) => {
|
||||
* const { name, progress} = useFileUploadItem(itemRef)
|
||||
* return <li>{name} {progress}</li>
|
||||
* }
|
||||
* ```vue
|
||||
* <!-- Parent component or page -->
|
||||
*
|
||||
* const List = () => {
|
||||
* const { list } = useMultipleFilesUpload()
|
||||
* return <ul>
|
||||
* {list.map((itemRef) => <Item key={item.id} itemRef={item} />)}
|
||||
* </ul>
|
||||
* }
|
||||
* <script lang="ts" setup>
|
||||
* const { files } = useMultipleFilesUpload()
|
||||
* <script lang="ts" setup>
|
||||
*
|
||||
* <template>
|
||||
* <div v-for="(file, index) of files" :key="index">
|
||||
* <FileUploadItem :file="file" />
|
||||
* </div>
|
||||
* </template>
|
||||
*
|
||||
*
|
||||
* <!-- FileUploadItem component -->
|
||||
*
|
||||
* <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>
|
||||
* <span>{{ name }}</span>
|
||||
* <v-progress-linear v-model="progress">
|
||||
* {{ progress }}
|
||||
* </v-progress-linear>
|
||||
* </div>
|
||||
* </template>
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
@@ -65,7 +86,7 @@ export const useFileUploadItem = (
|
||||
ref.send({
|
||||
type: 'ADD',
|
||||
file: params.file,
|
||||
bucketId: params.bucketId || bucketId
|
||||
bucketId: params.bucketId || bucketId.value
|
||||
})
|
||||
}
|
||||
|
||||
@@ -88,14 +109,14 @@ export const useFileUploadItem = (
|
||||
ref.send('DESTROY')
|
||||
}
|
||||
|
||||
const isUploading = useSelector(ref, (state) => state.matches('uploading')).value
|
||||
const isUploaded = useSelector(ref, (state) => state.matches('uploaded')).value
|
||||
const isError = useSelector(ref, (state) => state.matches('error')).value
|
||||
const error = useSelector(ref, (state) => state.context.error || null).value
|
||||
const progress = useSelector(ref, (state) => state.context.progress).value
|
||||
const id = useSelector(ref, (state) => state.context.id).value
|
||||
const bucketId = useSelector(ref, (state) => state.context.bucketId).value
|
||||
const name = useSelector(ref, (state) => state.context.file?.name).value
|
||||
const isUploading = useSelector(ref, (state) => state.matches('uploading'))
|
||||
const isUploaded = useSelector(ref, (state) => state.matches('uploaded'))
|
||||
const isError = useSelector(ref, (state) => state.matches('error'))
|
||||
const error = useSelector(ref, (state) => state.context.error || null)
|
||||
const progress = useSelector(ref, (state) => state.context.progress)
|
||||
const id = useSelector(ref, (state) => state.context.id)
|
||||
const bucketId = useSelector(ref, (state) => state.context.bucketId)
|
||||
const name = useSelector(ref, (state) => state.context.file?.name)
|
||||
|
||||
return {
|
||||
add,
|
||||
@@ -117,7 +138,7 @@ export const useFileUploadItem = (
|
||||
* Use the composable `useFileUpload` to upload a file.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* ```ts
|
||||
* const { add,
|
||||
* upload,
|
||||
* cancel,
|
||||
|
||||
117
packages/vue/src/useMultipleFilesUpload.ts
Normal file
117
packages/vue/src/useMultipleFilesUpload.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import {
|
||||
createMultipleFilesUploadMachine,
|
||||
FileItemRef,
|
||||
MultipleFilesHandlerResult,
|
||||
MultipleFilesUploadState,
|
||||
UploadMultipleFilesActionParams,
|
||||
uploadMultipleFilesPromise
|
||||
} from '@nhost/nhost-js'
|
||||
import { useInterpret, useSelector } from '@xstate/vue'
|
||||
import { Ref, ref, ToRefs } from 'vue'
|
||||
import { useNhostClient } from './useNhostClient'
|
||||
|
||||
export interface MultipleFilesUploadComposableResult extends ToRefs<MultipleFilesUploadState> {
|
||||
/**
|
||||
* Add one or multiple files to add to the list of files to upload.
|
||||
*/
|
||||
add: (
|
||||
params: Required<Pick<UploadMultipleFilesActionParams, 'files'>> &
|
||||
UploadMultipleFilesActionParams
|
||||
) => void
|
||||
/**
|
||||
* Upload the files that has been previously added to the list.
|
||||
*/
|
||||
upload: (params?: UploadMultipleFilesActionParams) => Promise<MultipleFilesHandlerResult>
|
||||
/**
|
||||
* Cancel the ongoing upload. The files that have been successfully uploaded will not be deleted from the server.
|
||||
*/
|
||||
cancel: () => void
|
||||
/**
|
||||
* Clear the list of files.
|
||||
*/
|
||||
clear: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the composable `useMultipleFilesUpload` to upload multiple files.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const {
|
||||
* add,
|
||||
* upload
|
||||
* } = useMultipleFilesUpload()
|
||||
*
|
||||
* const addFiles = async (files) => {
|
||||
* add({files})
|
||||
* }
|
||||
*
|
||||
* const handleSubmit = async (e) => {
|
||||
* e.preventDefault()
|
||||
* await upload()
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @docs https://docs.nhost.io/reference/vue/use-multiple-files-upload
|
||||
*/
|
||||
export const useMultipleFilesUpload = (): MultipleFilesUploadComposableResult => {
|
||||
const { nhost } = useNhostClient()
|
||||
const errors: Ref<FileItemRef[]> = ref([])
|
||||
|
||||
const service = useInterpret(createMultipleFilesUploadMachine, {}, (state) => {
|
||||
if (state.event.type === 'UPLOAD_ERROR') {
|
||||
errors.value = state.context.files.filter((ref) => ref.getSnapshot()?.context.error)
|
||||
} else if (
|
||||
(state.matches('uploaded') || state.event.type === 'CLEAR') &&
|
||||
errors.value.length > 0
|
||||
) {
|
||||
errors.value = []
|
||||
}
|
||||
})
|
||||
|
||||
const add = (
|
||||
params: Required<Pick<UploadMultipleFilesActionParams, 'files'>> &
|
||||
UploadMultipleFilesActionParams
|
||||
) => {
|
||||
service.send({ type: 'ADD', ...params })
|
||||
}
|
||||
|
||||
const upload = (params?: UploadMultipleFilesActionParams) =>
|
||||
uploadMultipleFilesPromise(
|
||||
{
|
||||
url: nhost.storage.url,
|
||||
accessToken: nhost.auth.getAccessToken(),
|
||||
adminSecret: nhost.adminSecret,
|
||||
...params
|
||||
},
|
||||
service
|
||||
)
|
||||
|
||||
const cancel = () => {
|
||||
service.send('CANCEL')
|
||||
}
|
||||
|
||||
const clear = () => {
|
||||
service.send('CLEAR')
|
||||
}
|
||||
|
||||
const isUploading = useSelector(service, (state) => state.matches('uploading'))
|
||||
const isUploaded = useSelector(service, (state) => state.matches('uploaded'))
|
||||
const isError = useSelector(service, (state) => state.matches('error'))
|
||||
|
||||
const progress = useSelector(service, (state) => state.context.progress)
|
||||
const files = useSelector(service, (state) => state.context.files)
|
||||
|
||||
return {
|
||||
upload,
|
||||
add,
|
||||
clear,
|
||||
cancel,
|
||||
progress,
|
||||
isUploaded,
|
||||
isUploading,
|
||||
files,
|
||||
isError,
|
||||
errors
|
||||
}
|
||||
}
|
||||
2109
pnpm-lock.yaml
generated
2109
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -5,5 +5,5 @@ packages:
|
||||
- 'docs'
|
||||
- '!**/test/**'
|
||||
- '!out/**'
|
||||
- 'examples/*'
|
||||
- 'examples/**'
|
||||
- '!**/functions'
|
||||
|
||||
Reference in New Issue
Block a user