Compare commits
18 Commits
@nhost/apo
...
@nhost/das
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
551298b568 | ||
|
|
50441a84cb | ||
|
|
c4aa159f1f | ||
|
|
91f0465cbc | ||
|
|
6f61262045 | ||
|
|
257815d519 | ||
|
|
55d8bb5a89 | ||
|
|
18f942f464 | ||
|
|
2a2e54c4d8 | ||
|
|
6a735523b4 | ||
|
|
4d6b7228d9 | ||
|
|
3dcbacf188 | ||
|
|
5c2269ef92 | ||
|
|
52a38feca7 | ||
|
|
f218058c89 | ||
|
|
dda0c67fa4 | ||
|
|
db2f44d7c0 | ||
|
|
9735fa238b |
28
.github/workflows/gen_ai_review.yaml
vendored
Normal file
28
.github/workflows/gen_ai_review.yaml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: "gen: AI review"
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, ready_for_review]
|
||||
issue_comment:
|
||||
jobs:
|
||||
pr_agent_job:
|
||||
if: ${{ github.event.sender.type != 'Bot' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
contents: write
|
||||
name: Run pr agent on every pull request, respond to user comments
|
||||
steps:
|
||||
- name: PR Agent action step
|
||||
id: pragent
|
||||
uses: Codium-ai/pr-agent@v0.24
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
OPENAI_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
config.max_model_tokens: 100000
|
||||
config.model: "anthropic/claude-3-5-sonnet-20240620"
|
||||
config.model_turbo: "anthropic/claude-3-5-sonnet-20240620"
|
||||
ignore.glob: "['pnpm-lock.yaml','**/pnpm-lock.yaml']"
|
||||
@@ -2,5 +2,5 @@
|
||||
// $schema provides code completion hints to IDEs.
|
||||
"$schema": "https://github.com/IBM/audit-ci/raw/main/docs/schema.json",
|
||||
"moderate": true,
|
||||
"allowlist": ["vue-template-compiler", "micromatch"]
|
||||
"allowlist": ["vue-template-compiler", "micromatch", "path-to-regexp"]
|
||||
}
|
||||
|
||||
@@ -24,3 +24,4 @@ NEXT_PUBLIC_ZENDESK_USER_EMAIL=
|
||||
|
||||
CODEGEN_GRAPHQL_URL=https://local.graphql.nhost.run/v1
|
||||
CODEGEN_HASURA_ADMIN_SECRET=nhost-admin-secret
|
||||
NEXT_PUBLIC_TURNSTILE_SITE_KEY=FIXME
|
||||
@@ -1,5 +1,38 @@
|
||||
# @nhost/dashboard
|
||||
|
||||
## 1.30.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 50441a8: feat: add ui for project autoscaler settings and run services autoscaler settings
|
||||
|
||||
## 1.29.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 55d8bb5: feat: integrate turnstile for signup verification
|
||||
- 2a2e54c: fix: update docs url in run services form tooltip
|
||||
- 18f942f: fix: display long error messages in error toast without overflow
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react-apollo@13.0.0
|
||||
- @nhost/nextjs@2.1.22
|
||||
|
||||
## 1.28.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 52a38fe: chore: update dependencies to address security vulnerabilities
|
||||
- Updated dependencies [52a38fe]
|
||||
- @nhost/nextjs@2.1.21
|
||||
|
||||
## 1.28.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 9735fa2: chore: remove broken link
|
||||
|
||||
## 1.28.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -39,22 +39,6 @@ test('should create and delete a run service', async () => {
|
||||
await page.getByPlaceholder(/service name/i).click();
|
||||
await page.getByPlaceholder(/service name/i).fill('test');
|
||||
|
||||
const sliderRail = page.locator(
|
||||
'.space-y-4 > .MuiSlider-root > .MuiSlider-rail',
|
||||
);
|
||||
|
||||
// Get the bounding box of the slider rail to determine where to click
|
||||
const box = await sliderRail.boundingBox();
|
||||
|
||||
if (box) {
|
||||
// Calculate the position to click (start of the rail)
|
||||
const x = box.x + 1; // A little offset to ensure click inside the rail
|
||||
const y = box.y + box.height / 2; // Middle of the rail height-wise
|
||||
|
||||
// Perform the click
|
||||
await page.mouse.click(x, y);
|
||||
}
|
||||
|
||||
await page.getByRole('button', { name: /create/i }).click();
|
||||
|
||||
await expect(
|
||||
|
||||
2
dashboard/next-env.d.ts
vendored
2
dashboard/next-env.d.ts
vendored
@@ -2,4 +2,4 @@
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/dashboard",
|
||||
"version": "1.28.0",
|
||||
"version": "1.30.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
@@ -35,6 +35,7 @@
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@marsidev/react-turnstile": "^1.0.2",
|
||||
"@mui/base": "5.0.0-beta.31",
|
||||
"@mui/material": "^5.15.14",
|
||||
"@mui/system": "^5.15.14",
|
||||
@@ -65,7 +66,7 @@
|
||||
"graphql-ws": "^5.16.0",
|
||||
"just-kebab-case": "^4.2.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"next": "^14.1.4",
|
||||
"next": "^14.2.10",
|
||||
"next-seo": "^6.5.0",
|
||||
"node-pg-format": "^1.3.5",
|
||||
"pluralize": "^8.0.0",
|
||||
@@ -165,7 +166,7 @@
|
||||
"tailwindcss": "^3.4.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig-paths-webpack-plugin": "^4.1.0",
|
||||
"vite": "^5.2.7",
|
||||
"vite": "^5.4.6",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"vitest": "^0.32.4"
|
||||
},
|
||||
|
||||
@@ -112,18 +112,24 @@ export default function ErrorToast({
|
||||
bounce: 0.1,
|
||||
}}
|
||||
>
|
||||
<div className="flex w-full flex-row items-center justify-between space-x-4">
|
||||
<button onClick={close} type="button" aria-label="Close">
|
||||
<div className="flex w-full flex-row items-center justify-between gap-4">
|
||||
<button
|
||||
className="flex-shrink-0"
|
||||
onClick={close}
|
||||
type="button"
|
||||
aria-label="Close"
|
||||
>
|
||||
<XIcon className="h-4 w-4 text-white" />
|
||||
</button>
|
||||
<span>
|
||||
<span className="flex-grow overflow-hidden break-words">
|
||||
{msg ?? 'An unkown error has occured, please try again later!'}
|
||||
</span>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowInfo(!showInfo)}
|
||||
className="flex flex-row items-center justify-center space-x-2 text-white"
|
||||
className="flex flex-shrink-0 flex-row items-center justify-center space-x-2 text-white"
|
||||
aria-label="Show error details"
|
||||
>
|
||||
<span>Info</span>
|
||||
{showInfo ? (
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { IconProps } from '@/components/ui/v2/icons';
|
||||
import { SvgIcon } from '@/components/ui/v2/icons/SvgIcon';
|
||||
import type { ForwardedRef } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
function InfoOutlinedIcon(props: IconProps, ref: ForwardedRef<SVGSVGElement>) {
|
||||
return (
|
||||
<SvgIcon
|
||||
width="24"
|
||||
height="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
aria-label="Info"
|
||||
stroke="currentColor"
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fill="none"
|
||||
d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
d="M12 16V12"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
fill="none"
|
||||
d="M12 8H12.01"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</SvgIcon>
|
||||
);
|
||||
}
|
||||
|
||||
InfoOutlinedIcon.displayName = 'NhostInfoOutlinedIcon';
|
||||
|
||||
export default forwardRef(InfoOutlinedIcon);
|
||||
@@ -0,0 +1 @@
|
||||
export { default as InfoOutlinedIcon } from './InfoOutlinedIcon';
|
||||
@@ -12,7 +12,6 @@ import { Chip } from '@/components/ui/v2/Chip';
|
||||
import { Divider } from '@/components/ui/v2/Divider';
|
||||
import { Dropdown } from '@/components/ui/v2/Dropdown';
|
||||
import { IconButton } from '@/components/ui/v2/IconButton';
|
||||
import { ArrowRightIcon } from '@/components/ui/v2/icons/ArrowRightIcon';
|
||||
import { DotsHorizontalIcon } from '@/components/ui/v2/icons/DotsHorizontalIcon';
|
||||
import { LockIcon } from '@/components/ui/v2/icons/LockIcon';
|
||||
import { PencilIcon } from '@/components/ui/v2/icons/PencilIcon';
|
||||
@@ -20,7 +19,6 @@ 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';
|
||||
import { List } from '@/components/ui/v2/List';
|
||||
import { ListItem } from '@/components/ui/v2/ListItem';
|
||||
import { Option } from '@/components/ui/v2/Option';
|
||||
@@ -312,15 +310,6 @@ function DataBrowserSidebarContent({
|
||||
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 items-center justify-start grid-flow-col gap-1"
|
||||
>
|
||||
Learn More <ArrowRightIcon />
|
||||
</Link>
|
||||
</Box>
|
||||
)}
|
||||
{!isSelectedSchemaLocked && (
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
waitForElementToBeRemoved,
|
||||
within,
|
||||
} from '@/tests/testUtils';
|
||||
@@ -78,7 +79,7 @@ test('should show the sliders if the switch is enabled', async () => {
|
||||
await user.click(screen.getByRole('checkbox'));
|
||||
|
||||
expect(screen.queryByText(/enable this feature/i)).not.toBeInTheDocument();
|
||||
expect(screen.getAllByRole('slider')).toHaveLength(12);
|
||||
expect(screen.getAllByRole('slider')).toHaveLength(9);
|
||||
});
|
||||
|
||||
test('should not show an empty state message if there is data available', async () => {
|
||||
@@ -89,7 +90,7 @@ test('should not show an empty state message if there is data available', async
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(screen.queryByText(/enable this feature/i)).not.toBeInTheDocument();
|
||||
expect(screen.getAllByRole('slider')).toHaveLength(12);
|
||||
expect(screen.getAllByRole('slider')).toHaveLength(9);
|
||||
expect(screen.getByText(/^vcpus:/i)).toHaveTextContent(/vcpus: 8/i);
|
||||
expect(screen.getByText(/^memory:/i)).toHaveTextContent(/memory: 16384 mib/i);
|
||||
});
|
||||
@@ -267,7 +268,7 @@ test('should display a red button when custom resources are disabled', async ()
|
||||
await screen.findByRole('slider', { name: /total available vcpu/i }),
|
||||
).toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByRole('checkbox'));
|
||||
await user.click(screen.getAllByRole('checkbox')[0]);
|
||||
|
||||
expect(screen.getByText(/enable this feature/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/approximate cost:/i)).toHaveTextContent(
|
||||
@@ -297,7 +298,8 @@ test('should hide the pricing information when custom resource allocation is dis
|
||||
await screen.findByRole('slider', { name: /total available vcpu/i }),
|
||||
).toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByRole('checkbox'));
|
||||
await user.click(screen.getAllByRole('checkbox')[0]);
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /save/i }));
|
||||
|
||||
expect(await screen.findByRole('dialog')).toBeInTheDocument();
|
||||
@@ -333,6 +335,8 @@ test('should show a warning message when resources are overallocated', async ()
|
||||
});
|
||||
|
||||
test('should change pricing based on selected replicas', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(<ResourcesForm />);
|
||||
|
||||
expect(
|
||||
@@ -343,23 +347,31 @@ test('should change pricing based on selected replicas', async () => {
|
||||
/approximate cost: \$425\.00\/mo/i,
|
||||
);
|
||||
|
||||
changeSliderValue(
|
||||
screen.getByRole('slider', { name: /hasura graphql replicas/i }),
|
||||
2,
|
||||
const hasuraReplicasInput = screen.getAllByPlaceholderText('Replicas')[0];
|
||||
|
||||
await user.click(hasuraReplicasInput);
|
||||
await user.clear(hasuraReplicasInput);
|
||||
await user.type(hasuraReplicasInput, '2');
|
||||
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 1000);
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText(/approximate cost:/i)).toHaveTextContent(
|
||||
/approximate cost: \$525\.00\/mo/i,
|
||||
),
|
||||
);
|
||||
|
||||
expect(screen.getByText(/approximate cost:/i)).toHaveTextContent(
|
||||
/approximate cost: \$525\.00\/mo/i,
|
||||
);
|
||||
await user.click(hasuraReplicasInput);
|
||||
await user.clear(hasuraReplicasInput);
|
||||
await user.type(hasuraReplicasInput, '1');
|
||||
|
||||
changeSliderValue(
|
||||
screen.getByRole('slider', { name: /hasura graphql replicas/i }),
|
||||
1,
|
||||
);
|
||||
|
||||
expect(screen.getByText(/approximate cost:/i)).toHaveTextContent(
|
||||
/approximate cost: \$425\.00\/mo/i,
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/approximate cost:/i)).toHaveTextContent(
|
||||
/approximate cost: \$425\.00\/mo/i,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('should validate if vCPU and Memory match the 1:2 ratio if more than 1 replica is selected', async () => {
|
||||
@@ -378,10 +390,10 @@ test('should validate if vCPU and Memory match the 1:2 ratio if more than 1 repl
|
||||
20 * RESOURCE_VCPU_MULTIPLIER,
|
||||
);
|
||||
|
||||
changeSliderValue(
|
||||
screen.getByRole('slider', { name: /storage replicas/i }),
|
||||
2,
|
||||
);
|
||||
const storageReplicasInput = screen.getAllByPlaceholderText('Replicas')[2];
|
||||
await user.click(storageReplicasInput);
|
||||
await user.clear(storageReplicasInput);
|
||||
await user.type(storageReplicasInput, '2');
|
||||
|
||||
changeSliderValue(
|
||||
screen.getByRole('slider', { name: /storage vcpu/i }),
|
||||
@@ -402,12 +414,13 @@ test('should validate if vCPU and Memory match the 1:2 ratio if more than 1 repl
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
|
||||
const validationErrorMessage = screen.getByLabelText(
|
||||
/vcpu and memory for this service must match the 1:2 ratio if more than one replica is selected\./i,
|
||||
);
|
||||
|
||||
expect(validationErrorMessage).toBeInTheDocument();
|
||||
expect(validationErrorMessage).toHaveStyle({ color: '#f13154' });
|
||||
await waitFor(() => {
|
||||
const validationErrorMessage = screen.getByText(
|
||||
/vCPU and Memory for this service must follow a 1:2 ratio when more than one replica is selected or when the autoscaler is activated\./i,
|
||||
);
|
||||
expect(validationErrorMessage).toBeInTheDocument();
|
||||
expect(validationErrorMessage).toHaveStyle({ color: '#f13154' });
|
||||
});
|
||||
});
|
||||
|
||||
test('should take replicas into account when confirming the resources', async () => {
|
||||
@@ -436,11 +449,11 @@ test('should take replicas into account when confirming the resources', async ()
|
||||
4 * RESOURCE_MEMORY_MULTIPLIER,
|
||||
);
|
||||
|
||||
// setting up hasura
|
||||
changeSliderValue(
|
||||
screen.getByRole('slider', { name: /hasura graphql replicas/i }),
|
||||
3,
|
||||
);
|
||||
const hasuraReplicasInput = screen.getAllByPlaceholderText('Replicas')[0];
|
||||
await user.click(hasuraReplicasInput);
|
||||
await user.clear(hasuraReplicasInput);
|
||||
await user.type(hasuraReplicasInput, '3');
|
||||
|
||||
changeSliderValue(
|
||||
screen.getByRole('slider', { name: /hasura graphql vcpu/i }),
|
||||
2.5 * RESOURCE_VCPU_MULTIPLIER,
|
||||
@@ -450,8 +463,12 @@ test('should take replicas into account when confirming the resources', async ()
|
||||
5 * RESOURCE_MEMORY_MULTIPLIER,
|
||||
);
|
||||
|
||||
const authReplicasInput = screen.getAllByPlaceholderText('Replicas')[1];
|
||||
// setting up auth
|
||||
changeSliderValue(screen.getByRole('slider', { name: /auth replicas/i }), 2);
|
||||
await user.click(authReplicasInput);
|
||||
await user.clear(authReplicasInput);
|
||||
await user.type(authReplicasInput, '2');
|
||||
|
||||
changeSliderValue(
|
||||
screen.getByRole('slider', { name: /auth vcpu/i }),
|
||||
1.5 * RESOURCE_VCPU_MULTIPLIER,
|
||||
@@ -461,11 +478,12 @@ test('should take replicas into account when confirming the resources', async ()
|
||||
3 * RESOURCE_MEMORY_MULTIPLIER,
|
||||
);
|
||||
|
||||
const storageReplicasInput = screen.getAllByPlaceholderText('Replicas')[2];
|
||||
// setting up storage
|
||||
changeSliderValue(
|
||||
screen.getByRole('slider', { name: /storage replicas/i }),
|
||||
4,
|
||||
);
|
||||
await user.click(storageReplicasInput);
|
||||
await user.clear(storageReplicasInput);
|
||||
await user.type(storageReplicasInput, '4');
|
||||
|
||||
changeSliderValue(
|
||||
screen.getByRole('slider', { name: /storage vcpu/i }),
|
||||
2.5 * RESOURCE_VCPU_MULTIPLIER,
|
||||
|
||||
@@ -37,12 +37,14 @@ function getInitialServiceResources(
|
||||
data: GetResourcesQuery,
|
||||
service: Exclude<keyof GetResourcesQuery['config'], '__typename'>,
|
||||
) {
|
||||
const { compute, replicas } = data?.config?.[service]?.resources || {};
|
||||
const { compute, replicas, autoscaler } =
|
||||
data?.config?.[service]?.resources || {};
|
||||
|
||||
return {
|
||||
replicas,
|
||||
vcpu: compute?.cpu || 0,
|
||||
memory: compute?.memory || 0,
|
||||
autoscale: autoscaler || null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -100,21 +102,29 @@ export default function ResourcesForm() {
|
||||
replicas: initialDatabaseResources.replicas || 1,
|
||||
vcpu: initialDatabaseResources.vcpu || 1000,
|
||||
memory: initialDatabaseResources.memory || 2048,
|
||||
autoscale: !!initialDatabaseResources.autoscale || false,
|
||||
maxReplicas: initialDatabaseResources.autoscale?.maxReplicas || 10,
|
||||
},
|
||||
hasura: {
|
||||
replicas: initialHasuraResources.replicas || 1,
|
||||
vcpu: initialHasuraResources.vcpu || 500,
|
||||
memory: initialHasuraResources.memory || 1536,
|
||||
autoscale: !!initialHasuraResources.autoscale || false,
|
||||
maxReplicas: initialHasuraResources.autoscale?.maxReplicas || 10,
|
||||
},
|
||||
auth: {
|
||||
replicas: initialAuthResources.replicas || 1,
|
||||
vcpu: initialAuthResources.vcpu || 250,
|
||||
memory: initialAuthResources.memory || 256,
|
||||
autoscale: !!initialAuthResources.autoscale || false,
|
||||
maxReplicas: initialAuthResources.autoscale?.maxReplicas || 10,
|
||||
},
|
||||
storage: {
|
||||
replicas: initialStorageResources.replicas || 1,
|
||||
vcpu: initialStorageResources.vcpu || 250,
|
||||
memory: initialStorageResources.memory || 256,
|
||||
autoscale: !!initialStorageResources.autoscale || false,
|
||||
maxReplicas: initialStorageResources.autoscale?.maxReplicas || 10,
|
||||
},
|
||||
},
|
||||
resolver: yupResolver(resourceSettingsValidationSchema),
|
||||
@@ -181,6 +191,11 @@ export default function ResourcesForm() {
|
||||
memory: formValues.database.memory,
|
||||
},
|
||||
replicas: formValues.database.replicas,
|
||||
autoscaler: formValues.database.autoscale
|
||||
? {
|
||||
maxReplicas: formValues.database.maxReplicas,
|
||||
}
|
||||
: null,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
@@ -192,6 +207,11 @@ export default function ResourcesForm() {
|
||||
memory: formValues.hasura.memory,
|
||||
},
|
||||
replicas: formValues.hasura.replicas,
|
||||
autoscaler: formValues.hasura.autoscale
|
||||
? {
|
||||
maxReplicas: formValues.hasura.maxReplicas,
|
||||
}
|
||||
: null,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
@@ -203,6 +223,11 @@ export default function ResourcesForm() {
|
||||
memory: formValues.auth.memory,
|
||||
},
|
||||
replicas: formValues.auth.replicas,
|
||||
autoscaler: formValues.auth.autoscale
|
||||
? {
|
||||
maxReplicas: formValues.auth.maxReplicas,
|
||||
}
|
||||
: null,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
@@ -214,6 +239,11 @@ export default function ResourcesForm() {
|
||||
memory: formValues.storage.memory,
|
||||
},
|
||||
replicas: formValues.storage.replicas,
|
||||
autoscaler: formValues.storage.autoscale
|
||||
? {
|
||||
maxReplicas: formValues.storage.maxReplicas,
|
||||
}
|
||||
: null,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
@@ -253,21 +283,29 @@ export default function ResourcesForm() {
|
||||
totalAvailableMemory: 4096,
|
||||
database: {
|
||||
replicas: 1,
|
||||
maxReplicas: 1,
|
||||
autoscale: false,
|
||||
vcpu: 1000,
|
||||
memory: 2048,
|
||||
},
|
||||
hasura: {
|
||||
replicas: 1,
|
||||
maxReplicas: 1,
|
||||
autoscale: false,
|
||||
vcpu: 500,
|
||||
memory: 1536,
|
||||
},
|
||||
auth: {
|
||||
replicas: 1,
|
||||
maxReplicas: 1,
|
||||
autoscale: false,
|
||||
vcpu: 250,
|
||||
memory: 256,
|
||||
},
|
||||
storage: {
|
||||
replicas: 1,
|
||||
maxReplicas: 1,
|
||||
autoscale: false,
|
||||
vcpu: 250,
|
||||
memory: 256,
|
||||
},
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
import { ControlledSwitch } from '@/components/form/ControlledSwitch';
|
||||
import { Box } from '@/components/ui/v2/Box';
|
||||
import { ExclamationIcon } from '@/components/ui/v2/icons/ExclamationIcon';
|
||||
import { InfoOutlinedIcon } from '@/components/ui/v2/icons/InfoOutlinedIcon';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { Slider } from '@/components/ui/v2/Slider';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { Link } from '@/components/ui/v2/Link';
|
||||
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||
import { prettifyMemory } from '@/features/projects/resources/settings/utils/prettifyMemory';
|
||||
import { prettifyVCPU } from '@/features/projects/resources/settings/utils/prettifyVCPU';
|
||||
import type { ResourceSettingsFormValues } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
||||
import { Alert } from '@/components/ui/v2/Alert';
|
||||
import {
|
||||
MAX_SERVICE_MEMORY,
|
||||
MAX_SERVICE_REPLICAS,
|
||||
MAX_SERVICE_VCPU,
|
||||
MIN_SERVICE_MEMORY,
|
||||
MIN_SERVICE_REPLICAS,
|
||||
MIN_SERVICE_VCPU,
|
||||
} from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
||||
import {
|
||||
RESOURCE_MEMORY_LOCKED_STEP,
|
||||
RESOURCE_MEMORY_STEP,
|
||||
RESOURCE_VCPU_STEP,
|
||||
} from '@/utils/constants/common';
|
||||
import debounce from 'lodash.debounce';
|
||||
import { useFormContext, useWatch } from 'react-hook-form';
|
||||
import { ArrowSquareOutIcon } from '@/components/ui/v2/icons/ArrowSquareOutIcon';
|
||||
|
||||
export interface ServiceResourcesFormFragmentProps {
|
||||
/**
|
||||
@@ -52,10 +58,14 @@ export default function ServiceResourcesFormFragment({
|
||||
setValue,
|
||||
trigger: triggerValidation,
|
||||
formState,
|
||||
register,
|
||||
} = useFormContext<ResourceSettingsFormValues>();
|
||||
const formValues = useWatch<ResourceSettingsFormValues>();
|
||||
const serviceValues = formValues[serviceKey];
|
||||
|
||||
const isRatioLocked = serviceValues.replicas > 1 || serviceValues.autoscale;
|
||||
const resourceMemoryStep = isRatioLocked ? RESOURCE_MEMORY_LOCKED_STEP : RESOURCE_MEMORY_STEP;
|
||||
|
||||
// Total allocated CPU for all resources
|
||||
const totalAllocatedVCPU = Object.keys(formValues)
|
||||
.filter(
|
||||
@@ -83,15 +93,27 @@ export default function ServiceResourcesFormFragment({
|
||||
formValues.totalAvailableMemory - totalAllocatedMemory;
|
||||
const allowedMemory = remainingMemory + serviceValues.memory;
|
||||
|
||||
function handleReplicaChange(value: string) {
|
||||
// Debounce revalidation to prevent excessive re-renders
|
||||
const handleReplicaChange = debounce((value: string) => {
|
||||
const updatedReplicas = parseInt(value, 10);
|
||||
|
||||
if (updatedReplicas < MIN_SERVICE_REPLICAS) {
|
||||
return;
|
||||
}
|
||||
|
||||
setValue(`${serviceKey}.replicas`, updatedReplicas, { shouldDirty: true });
|
||||
triggerValidation(`${serviceKey}.replicas`);
|
||||
triggerValidation(`${serviceKey}.replicas`)
|
||||
triggerValidation(`${serviceKey}.memory`);
|
||||
}, 500);
|
||||
|
||||
const handleMaxReplicasChange = debounce((value: string) => {
|
||||
const updatedMaxReplicas = parseInt(value, 10);
|
||||
|
||||
setValue(`${serviceKey}.maxReplicas`, updatedMaxReplicas, {
|
||||
shouldDirty: true,
|
||||
});
|
||||
triggerValidation(`${serviceKey}.maxReplicas`);
|
||||
triggerValidation(`${serviceKey}.memory`);
|
||||
}, 500);
|
||||
|
||||
const handleSwitchChange = () => {
|
||||
triggerValidation(`${serviceKey}.memory`);
|
||||
}
|
||||
|
||||
function handleVCPUChange(value: string) {
|
||||
@@ -103,9 +125,13 @@ export default function ServiceResourcesFormFragment({
|
||||
|
||||
setValue(`${serviceKey}.vcpu`, updatedVCPU, { shouldDirty: true });
|
||||
|
||||
if (isRatioLocked) {
|
||||
setValue(`${serviceKey}.memory`, updatedVCPU * 2.048, { shouldDirty: true });
|
||||
}
|
||||
|
||||
// trigger validation for "replicas" field
|
||||
if (!disableReplicas) {
|
||||
triggerValidation(`${serviceKey}.replicas`);
|
||||
triggerValidation(`${serviceKey}.memory`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,9 +144,13 @@ export default function ServiceResourcesFormFragment({
|
||||
|
||||
setValue(`${serviceKey}.memory`, updatedMemory, { shouldDirty: true });
|
||||
|
||||
if (isRatioLocked) {
|
||||
setValue(`${serviceKey}.vcpu`, updatedMemory / 2.048, { shouldDirty: true });
|
||||
}
|
||||
|
||||
// trigger validation for "replicas" field
|
||||
if (!disableReplicas) {
|
||||
triggerValidation(`${serviceKey}.replicas`);
|
||||
triggerValidation(`${serviceKey}.memory`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +165,7 @@ export default function ServiceResourcesFormFragment({
|
||||
</Box>
|
||||
|
||||
<Box className="grid grid-flow-row gap-2">
|
||||
<Box className="grid grid-flow-col items-center justify-between gap-2">
|
||||
<Box className="grid items-center justify-between grid-flow-col gap-2">
|
||||
<Text>
|
||||
Allocated vCPUs:{' '}
|
||||
<span className="font-medium">
|
||||
@@ -165,7 +195,7 @@ export default function ServiceResourcesFormFragment({
|
||||
</Box>
|
||||
|
||||
<Box className="grid grid-flow-row gap-2">
|
||||
<Box className="grid grid-flow-col items-center justify-between gap-2">
|
||||
<Box className="grid items-center justify-between grid-flow-col gap-2">
|
||||
<Text>
|
||||
Allocated Memory:{' '}
|
||||
<span className="font-medium">
|
||||
@@ -187,53 +217,112 @@ export default function ServiceResourcesFormFragment({
|
||||
value={serviceValues.memory}
|
||||
onChange={(_event, value) => handleMemoryChange(value.toString())}
|
||||
max={MAX_SERVICE_MEMORY}
|
||||
step={RESOURCE_MEMORY_STEP}
|
||||
step={resourceMemoryStep}
|
||||
allowed={allowedMemory}
|
||||
aria-label={`${title} Memory`}
|
||||
marks
|
||||
/>
|
||||
{formState.errors[serviceKey]?.memory?.message ? (
|
||||
<Alert severity="error">
|
||||
{formState.errors[serviceKey]?.memory?.message}
|
||||
</Alert>
|
||||
) : null}
|
||||
</Box>
|
||||
|
||||
{!disableReplicas && (
|
||||
<Box className="grid grid-flow-row gap-2">
|
||||
<Box className="grid grid-flow-col items-center justify-start gap-2">
|
||||
<Text
|
||||
color={
|
||||
formState.errors?.[serviceKey]?.replicas?.message
|
||||
? 'error'
|
||||
: 'primary'
|
||||
}
|
||||
aria-errormessage={`${serviceKey}-replicas-error-tooltip`}
|
||||
>
|
||||
Replicas:{' '}
|
||||
<span className="font-medium">{serviceValues.replicas}</span>
|
||||
</Text>
|
||||
|
||||
{formState.errors?.[serviceKey]?.replicas?.message ? (
|
||||
<Tooltip
|
||||
title={formState.errors[serviceKey].replicas.message}
|
||||
id={`${serviceKey}-replicas-error-tooltip`}
|
||||
>
|
||||
<ExclamationIcon
|
||||
color="error"
|
||||
className="h-4 w-4"
|
||||
aria-hidden="false"
|
||||
/>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
<Box className="flex flex-col justify-between gap-4 lg:flex-row">
|
||||
<Box className="flex flex-col gap-4 lg:flex-row lg:gap-8">
|
||||
<Box className="flex flex-row items-center gap-2">
|
||||
{formState.errors?.[serviceKey]?.replicas?.message ? (
|
||||
<Tooltip
|
||||
title={formState.errors[serviceKey]?.replicas?.message}
|
||||
id={`${serviceKey}-replicas-error-tooltip`}
|
||||
>
|
||||
<ExclamationIcon
|
||||
color="error"
|
||||
className="w-4 h-4"
|
||||
aria-hidden="false"
|
||||
/>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
<Text className="w-28 lg:w-auto">Replicas</Text>
|
||||
<Input
|
||||
{...register(`${serviceKey}.replicas`)}
|
||||
onChange={(event) => handleReplicaChange(event.target.value)}
|
||||
type="number"
|
||||
id={`${serviceKey}.replicas`}
|
||||
data-testid={`${serviceKey}.replicas`}
|
||||
placeholder="Replicas"
|
||||
className="max-w-28"
|
||||
hideEmptyHelperText
|
||||
error={!!formState.errors?.[serviceKey]?.replicas}
|
||||
fullWidth
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Box>
|
||||
<Box className="flex flex-row items-center gap-2">
|
||||
{formState.errors?.[serviceKey]?.maxReplicas?.message ? (
|
||||
<Tooltip
|
||||
title={formState.errors[serviceKey]?.maxReplicas?.message}
|
||||
id={`${serviceKey}-maxReplicas-error-tooltip`}
|
||||
>
|
||||
<ExclamationIcon
|
||||
color="error"
|
||||
className="w-4 h-4"
|
||||
aria-hidden="false"
|
||||
/>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
<Text className="w-28 text-nowrap lg:w-auto">Max Replicas</Text>
|
||||
<Input
|
||||
{...register(`${serviceKey}.maxReplicas`)}
|
||||
onChange={(event) =>
|
||||
handleMaxReplicasChange(event.target.value)
|
||||
}
|
||||
type="number"
|
||||
id={`${serviceKey}.maxReplicas`}
|
||||
placeholder="10"
|
||||
disabled={!formValues[serviceKey].autoscale}
|
||||
className="max-w-28"
|
||||
hideEmptyHelperText
|
||||
error={!!formState.errors?.[serviceKey]?.maxReplicas}
|
||||
fullWidth
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box className="flex flex-row items-center gap-3">
|
||||
<ControlledSwitch
|
||||
{...register(`${serviceKey}.autoscale`)}
|
||||
onChange={handleSwitchChange}
|
||||
/>
|
||||
<Text>Autoscaler</Text>
|
||||
<Tooltip
|
||||
title={`Enable autoscaler to automatically provision extra ${title} replicas when needed.`}
|
||||
>
|
||||
<InfoOutlinedIcon className="w-4 h-4 text-black" />
|
||||
</Tooltip>
|
||||
</Box>
|
||||
|
||||
<Slider
|
||||
value={serviceValues.replicas}
|
||||
onChange={(_event, value) => handleReplicaChange(value.toString())}
|
||||
min={0}
|
||||
max={MAX_SERVICE_REPLICAS}
|
||||
step={1}
|
||||
aria-label={`${title} Replicas`}
|
||||
marks
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{
|
||||
!disableReplicas && (
|
||||
<Text>
|
||||
Learn more about{' '}
|
||||
<Link
|
||||
href="https://docs.nhost.io/platform/service-replicas"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
underline="hover"
|
||||
className="font-medium"
|
||||
>
|
||||
Service Replicas
|
||||
<ArrowSquareOutIcon className="w-4 h-4 ml-1" />
|
||||
</Link>
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Alert } from '@/components/ui/v2/Alert';
|
||||
import { Box } from '@/components/ui/v2/Box';
|
||||
import { ArrowRightIcon } from '@/components/ui/v2/icons/ArrowRightIcon';
|
||||
import { Slider, sliderClasses } from '@/components/ui/v2/Slider';
|
||||
import { calculateBillableResources } from '@/features/projects/resources/settings/utils/calculateBillableResources';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { useIsPlatform } from '@/features/projects/common/hooks/useIsPlatform';
|
||||
import { useProPlan } from '@/features/projects/common/hooks/useProPlan';
|
||||
@@ -46,6 +47,7 @@ export default function TotalResourcesFormFragment({
|
||||
error: proPlanError,
|
||||
loading: proPlanLoading,
|
||||
} = useProPlan();
|
||||
|
||||
const { setValue } = useFormContext<ResourceSettingsFormValues>();
|
||||
const formValues = useWatch<ResourceSettingsFormValues>();
|
||||
|
||||
@@ -65,9 +67,38 @@ export default function TotalResourcesFormFragment({
|
||||
(formValues.totalAvailableVCPU / RESOURCE_VCPU_MULTIPLIER) *
|
||||
RESOURCE_VCPU_PRICE;
|
||||
|
||||
const updatedPrice = isPlatform
|
||||
? priceForTotalAvailableVCPU + proPlan.price
|
||||
: 0;
|
||||
const billableResources = calculateBillableResources(
|
||||
{
|
||||
replicas: formValues.database?.replicas,
|
||||
vcpu: formValues.database?.vcpu,
|
||||
},
|
||||
{
|
||||
replicas: formValues.hasura?.replicas,
|
||||
vcpu: formValues.hasura?.vcpu,
|
||||
},
|
||||
{
|
||||
replicas: formValues.auth?.replicas,
|
||||
vcpu: formValues.auth?.vcpu,
|
||||
},
|
||||
{
|
||||
replicas: formValues.storage?.replicas,
|
||||
vcpu: formValues.storage?.vcpu,
|
||||
},
|
||||
);
|
||||
|
||||
const computeUpdatedPrice = () => {
|
||||
if (!isPlatform) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (
|
||||
Math.max(
|
||||
priceForTotalAvailableVCPU,
|
||||
(billableResources.vcpu / RESOURCE_VCPU_MULTIPLIER) *
|
||||
RESOURCE_VCPU_PRICE,
|
||||
) + proPlan.price
|
||||
);
|
||||
};
|
||||
|
||||
const { vcpu: allocatedVCPU, memory: allocatedMemory } =
|
||||
getAllocatedResources(formValues);
|
||||
@@ -114,14 +145,14 @@ export default function TotalResourcesFormFragment({
|
||||
Total available compute for your project:
|
||||
</Text>
|
||||
|
||||
{initialPrice !== updatedPrice && (
|
||||
{initialPrice !== computeUpdatedPrice() && (
|
||||
<Text className="flex flex-row items-center justify-end gap-2">
|
||||
<Text component="span" color="secondary">
|
||||
${initialPrice.toFixed(2)}/mo
|
||||
</Text>
|
||||
<ArrowRightIcon />
|
||||
<Text component="span" className="font-medium">
|
||||
${updatedPrice.toFixed(2)}/mo
|
||||
${computeUpdatedPrice().toFixed(2)}/mo
|
||||
</Text>
|
||||
</Text>
|
||||
)}
|
||||
|
||||
@@ -6,6 +6,9 @@ fragment ServiceResources on ConfigConfig {
|
||||
memory
|
||||
}
|
||||
replicas
|
||||
autoscaler {
|
||||
maxReplicas
|
||||
}
|
||||
}
|
||||
}
|
||||
hasura {
|
||||
@@ -15,6 +18,9 @@ fragment ServiceResources on ConfigConfig {
|
||||
memory
|
||||
}
|
||||
replicas
|
||||
autoscaler {
|
||||
maxReplicas
|
||||
}
|
||||
}
|
||||
}
|
||||
postgres {
|
||||
@@ -24,6 +30,9 @@ fragment ServiceResources on ConfigConfig {
|
||||
memory
|
||||
}
|
||||
replicas
|
||||
autoscaler {
|
||||
maxReplicas
|
||||
}
|
||||
}
|
||||
}
|
||||
storage {
|
||||
@@ -33,6 +42,9 @@ fragment ServiceResources on ConfigConfig {
|
||||
memory
|
||||
}
|
||||
replicas
|
||||
autoscaler {
|
||||
maxReplicas
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,23 +81,13 @@ const serviceValidationSchema = Yup.object({
|
||||
.label('Replicas')
|
||||
.required()
|
||||
.min(1)
|
||||
.max(MAX_SERVICE_REPLICAS)
|
||||
.test(
|
||||
'is-matching-ratio',
|
||||
`vCPU and Memory for this service must match the 1:${RESOURCE_VCPU_MEMORY_RATIO} ratio if more than one replica is selected.`,
|
||||
(replicas: number, { parent }) => {
|
||||
if (replicas === 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
parent.memory /
|
||||
RESOURCE_MEMORY_MULTIPLIER /
|
||||
(parent.vcpu / RESOURCE_VCPU_MULTIPLIER) ===
|
||||
RESOURCE_VCPU_MEMORY_RATIO
|
||||
);
|
||||
},
|
||||
),
|
||||
.max(MAX_SERVICE_REPLICAS),
|
||||
maxReplicas: Yup.number()
|
||||
.label('Max Replicas')
|
||||
.required()
|
||||
.min(MIN_SERVICE_REPLICAS)
|
||||
.max(MAX_SERVICE_REPLICAS),
|
||||
autoscale: Yup.boolean().label('Autoscale').required(),
|
||||
vcpu: Yup.number()
|
||||
.label('vCPUs')
|
||||
.required()
|
||||
@@ -106,7 +96,23 @@ const serviceValidationSchema = Yup.object({
|
||||
memory: Yup.number()
|
||||
.required()
|
||||
.min(MIN_SERVICE_MEMORY)
|
||||
.max(MAX_SERVICE_MEMORY),
|
||||
.max(MAX_SERVICE_MEMORY)
|
||||
.test(
|
||||
'is-matching-ratio',
|
||||
`vCPU and Memory for this service must follow a 1:${RESOURCE_VCPU_MEMORY_RATIO} ratio when more than one replica is selected or when the autoscaler is activated.`,
|
||||
(memory: number, { parent }) => {
|
||||
if (parent.replicas === 1 && !parent.autoscale) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
memory /
|
||||
RESOURCE_MEMORY_MULTIPLIER /
|
||||
(parent.vcpu / RESOURCE_VCPU_MULTIPLIER) ===
|
||||
RESOURCE_VCPU_MEMORY_RATIO
|
||||
);
|
||||
},
|
||||
),
|
||||
});
|
||||
|
||||
export const resourceSettingsValidationSchema = Yup.object({
|
||||
|
||||
@@ -78,6 +78,7 @@ export default function ServiceForm({
|
||||
memory: 128,
|
||||
},
|
||||
replicas: 1,
|
||||
autoscaler: null,
|
||||
},
|
||||
reValidateMode: 'onSubmit',
|
||||
resolver: yupResolver(validationSchema),
|
||||
@@ -123,6 +124,11 @@ export default function ServiceForm({
|
||||
capacity: item.capacity,
|
||||
})),
|
||||
replicas: sanitizedValues.replicas,
|
||||
autoscaler: sanitizedValues.autoscaler
|
||||
? {
|
||||
maxReplicas: sanitizedValues.autoscaler?.maxReplicas,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
environment: sanitizedValues.environment.map((item) => ({
|
||||
name: item.name,
|
||||
@@ -316,7 +322,7 @@ export default function ServiceForm({
|
||||
<Tooltip title="Name of the service, must be unique per project.">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="h-4 w-4"
|
||||
className="w-4 h-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -356,7 +362,7 @@ export default function ServiceForm({
|
||||
>
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="h-4 w-4"
|
||||
className="w-4 h-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -387,7 +393,7 @@ export default function ServiceForm({
|
||||
<Tooltip title="Command to run when to start the service. This is optional as the image may already have a baked-in command.">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="h-4 w-4"
|
||||
className="w-4 h-4"
|
||||
color="primary"
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -435,7 +441,7 @@ export default function ServiceForm({
|
||||
{createServiceFormError && (
|
||||
<Alert
|
||||
severity="error"
|
||||
className="grid grid-flow-col items-center justify-between px-4 py-3"
|
||||
className="grid items-center justify-between grid-flow-col px-4 py-3"
|
||||
>
|
||||
<span className="text-left">
|
||||
<strong>Error:</strong> {createServiceFormError.message}
|
||||
|
||||
@@ -25,6 +25,12 @@ export const validationSchema = Yup.object({
|
||||
memory: Yup.number().min(MIN_SERVICES_MEM).max(MAX_SERVICES_MEM).required(),
|
||||
}),
|
||||
replicas: Yup.number().min(0).max(MAX_SERVICE_REPLICAS).required(),
|
||||
autoscaler: Yup.object()
|
||||
.shape({
|
||||
maxReplicas: Yup.number().min(0).max(MAX_SERVICE_REPLICAS),
|
||||
})
|
||||
.nullable()
|
||||
.default(undefined),
|
||||
ports: Yup.array().of(
|
||||
Yup.object().shape({
|
||||
port: Yup.number().required(),
|
||||
|
||||
@@ -70,7 +70,7 @@ export default function ComputeFormSection({
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://docs.nhost.io/run/resources"
|
||||
href="https://docs.nhost.io/guides/run/resources"
|
||||
className="underline"
|
||||
>
|
||||
resources
|
||||
|
||||
@@ -38,7 +38,8 @@ export default function PortsFormSection() {
|
||||
|
||||
const showURL = (index: number) =>
|
||||
formValues.subdomain &&
|
||||
formValues.ports[index]?.type === PortTypes.HTTP &&
|
||||
(formValues.ports[index]?.type === PortTypes.HTTP ||
|
||||
formValues.ports[index]?.type === PortTypes.GRPC) &&
|
||||
formValues.ports[index]?.publish;
|
||||
|
||||
return (
|
||||
@@ -106,7 +107,7 @@ export default function PortsFormSection() {
|
||||
},
|
||||
}}
|
||||
>
|
||||
{['http', 'tcp', 'udp']?.map((portType) => (
|
||||
{['http', 'tcp', 'udp', 'grpc']?.map((portType) => (
|
||||
<Option key={portType} value={portType}>
|
||||
{portType}
|
||||
</Option>
|
||||
|
||||
@@ -2,4 +2,5 @@ export enum PortTypes {
|
||||
HTTP = 'http',
|
||||
TCP = 'tcp',
|
||||
UDP = 'udp',
|
||||
GRPC = 'grpc',
|
||||
}
|
||||
|
||||
@@ -1,16 +1,32 @@
|
||||
import { Box } from '@/components/ui/v2/Box';
|
||||
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
|
||||
import { Slider } from '@/components/ui/v2/Slider';
|
||||
import { InfoOutlinedIcon } from '@/components/ui/v2/icons/InfoOutlinedIcon';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { Switch } from '@/components/ui/v2/Switch';
|
||||
import { Text } from '@/components/ui/v2/Text';
|
||||
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||
import { MAX_SERVICE_REPLICAS } from '@/features/projects/resources/settings/utils/resourceSettingsValidationSchema';
|
||||
import type { ServiceFormValues } from '@/features/services/components/ServiceForm/ServiceFormTypes';
|
||||
import { useState } from 'react';
|
||||
import { useFormContext, useWatch } from 'react-hook-form';
|
||||
|
||||
export default function ReplicasFormSection() {
|
||||
const { setValue } = useFormContext<ServiceFormValues>();
|
||||
const {
|
||||
register,
|
||||
setValue,
|
||||
trigger: triggerValidation,
|
||||
} = useFormContext<ServiceFormValues>();
|
||||
const { replicas, autoscaler } = useWatch<ServiceFormValues>();
|
||||
const [autoscalerEnabled, setAutoscalerEnabled] = useState(!!autoscaler);
|
||||
|
||||
const { replicas } = useWatch<ServiceFormValues>();
|
||||
const toggleAutoscalerEnabled = async (enabled: boolean) => {
|
||||
setAutoscalerEnabled(enabled);
|
||||
|
||||
if (!enabled) {
|
||||
setValue('autoscaler', null);
|
||||
} else {
|
||||
setValue('autoscaler.maxReplicas', 10);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReplicasChange = (value: string) => {
|
||||
const updatedReplicas = parseInt(value, 10);
|
||||
@@ -20,42 +36,85 @@ export default function ReplicasFormSection() {
|
||||
// TODO Trigger revalidate storage
|
||||
};
|
||||
|
||||
const handleMaxReplicasChange = (value: string) => {
|
||||
const updatedReplicas = parseInt(value, 10);
|
||||
|
||||
setValue('autoscaler.maxReplicas', updatedReplicas, { shouldDirty: true });
|
||||
|
||||
triggerValidation('autoscaler.maxReplicas');
|
||||
};
|
||||
|
||||
return (
|
||||
<Box className="space-y-4 rounded border-1 p-4">
|
||||
<Box className="p-4 space-y-4 rounded border-1">
|
||||
<Box className="flex flex-row items-center space-x-2">
|
||||
<Text variant="h4" className="font-semibold">
|
||||
Replicas ({replicas})
|
||||
</Text>
|
||||
<Tooltip
|
||||
title={
|
||||
<span>
|
||||
Number of replicas for the service. Multiple replicas can process
|
||||
requests/work in parallel. You can set replicas to 0 to pause the
|
||||
service. Refer to{' '}
|
||||
<Text className="text-white">
|
||||
Learn more about{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://docs.nhost.io/run/resources"
|
||||
href="https://docs.nhost.io/platform/service-replicas"
|
||||
className="underline"
|
||||
>
|
||||
resources
|
||||
</a>{' '}
|
||||
for more information.
|
||||
</span>
|
||||
Service Replicas
|
||||
</a>
|
||||
</Text>
|
||||
}
|
||||
>
|
||||
<InfoIcon aria-label="Info" className="h-4 w-4" color="primary" />
|
||||
<InfoIcon aria-label="Info" className="w-4 h-4" color="primary" />
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Slider
|
||||
value={replicas}
|
||||
onChange={(_event, value) => handleReplicasChange(value.toString())}
|
||||
min={0}
|
||||
max={MAX_SERVICE_REPLICAS}
|
||||
step={1}
|
||||
aria-label="Replicas"
|
||||
marks
|
||||
/>
|
||||
|
||||
<Box className="flex flex-col justify-between gap-4 lg:flex-row">
|
||||
<Box className="flex flex-col gap-4 lg:flex-row lg:gap-8">
|
||||
<Box className="flex flex-row items-center gap-2">
|
||||
<Text className="w-28 lg:w-auto">Replicas</Text>
|
||||
<Input
|
||||
{...register('replicas')}
|
||||
onChange={(event) => handleReplicasChange(event.target.value)}
|
||||
type="number"
|
||||
id="replicas"
|
||||
placeholder="Replicas"
|
||||
className="max-w-28"
|
||||
hideEmptyHelperText
|
||||
fullWidth
|
||||
onWheel={(e) => (e.target as HTMLInputElement).blur()}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Box>
|
||||
<Box className="flex flex-row items-center gap-2">
|
||||
<Text className="w-28 text-nowrap lg:w-auto">Max Replicas</Text>
|
||||
<Input
|
||||
value={autoscaler?.maxReplicas}
|
||||
onChange={(event) => handleMaxReplicasChange(event.target.value)}
|
||||
type="number"
|
||||
id="maxReplicas"
|
||||
placeholder="10"
|
||||
disabled={!autoscalerEnabled}
|
||||
className="max-w-28"
|
||||
hideEmptyHelperText
|
||||
fullWidth
|
||||
onWheel={(e) => (e.target as HTMLInputElement).blur()}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box className="flex flex-row items-center gap-3">
|
||||
<Switch
|
||||
checked={autoscalerEnabled}
|
||||
onChange={(e) => toggleAutoscalerEnabled(e.target.checked)}
|
||||
className="self-center"
|
||||
/>
|
||||
<Text>Autoscaler</Text>
|
||||
<Tooltip title="Enable autoscaler to automatically provision extra run service replicas when needed.">
|
||||
<InfoOutlinedIcon className="w-4 h-4 text-black" />
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ export default function ServicesList({
|
||||
cpu: 62,
|
||||
memory: 128,
|
||||
},
|
||||
autoscaler: service?.config?.resources?.autoscaler,
|
||||
replicas: service.config?.resources?.replicas,
|
||||
storage: service.config?.resources?.storage,
|
||||
}}
|
||||
@@ -197,4 +198,4 @@ export default function ServicesList({
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,9 @@ query getRunService($id: uuid!, $resolve: Boolean!) {
|
||||
capacity
|
||||
}
|
||||
replicas
|
||||
autoscaler {
|
||||
maxReplicas
|
||||
}
|
||||
}
|
||||
environment {
|
||||
name
|
||||
|
||||
@@ -15,6 +15,9 @@ fragment RunServiceConfig on ConfigRunServiceConfig {
|
||||
capacity
|
||||
}
|
||||
replicas
|
||||
autoscaler {
|
||||
maxReplicas
|
||||
}
|
||||
}
|
||||
environment {
|
||||
name
|
||||
|
||||
@@ -48,7 +48,7 @@ export default function ServicesPage() {
|
||||
openDrawer({
|
||||
title: (
|
||||
<Box className="flex flex-row items-center space-x-2">
|
||||
<CubeIcon className="h-5 w-5" />
|
||||
<CubeIcon className="w-5 h-5" />
|
||||
<Text>Create a new run service</Text>
|
||||
</Box>
|
||||
),
|
||||
@@ -60,6 +60,7 @@ export default function ServicesPage() {
|
||||
cpu: 62,
|
||||
memory: 128,
|
||||
},
|
||||
autoscaler: parsedConfig?.resources?.autoscaler,
|
||||
image: parsedConfig?.image?.image,
|
||||
command: parsedConfig?.command?.join(' '),
|
||||
ports: parsedConfig?.ports.map((item) => ({
|
||||
@@ -104,7 +105,7 @@ export default function ServicesPage() {
|
||||
openDrawer({
|
||||
title: (
|
||||
<Box className="flex flex-row items-center space-x-2">
|
||||
<CubeIcon className="h-5 w-5" />
|
||||
<CubeIcon className="w-5 h-5" />
|
||||
<Text>Create a new service</Text>
|
||||
</Box>
|
||||
),
|
||||
@@ -125,23 +126,23 @@ export default function ServicesPage() {
|
||||
|
||||
if (services.length === 0 && !loading) {
|
||||
return (
|
||||
<Container className="mx-auto max-w-9xl space-y-5 overflow-x-hidden">
|
||||
<Container className="mx-auto space-y-5 overflow-x-hidden max-w-9xl">
|
||||
<div className="flex flex-row place-content-end">
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={openCreateServiceDialog}
|
||||
startIcon={<PlusIcon className="h-4 w-4" />}
|
||||
startIcon={<PlusIcon className="w-4 h-4" />}
|
||||
disabled={!isPlatform}
|
||||
>
|
||||
Add service
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Box className="flex flex-col items-center justify-center space-y-5 rounded-lg border px-48 py-12 shadow-sm">
|
||||
<ServicesIcon className="h-10 w-10" />
|
||||
<Box className="flex flex-col items-center justify-center px-48 py-12 space-y-5 border rounded-lg shadow-sm">
|
||||
<ServicesIcon className="w-10 h-10" />
|
||||
<div className="flex flex-col space-y-1">
|
||||
<Text className="text-center font-medium" variant="h3">
|
||||
<Text className="font-medium text-center" variant="h3">
|
||||
No custom services are available
|
||||
</Text>
|
||||
<Text variant="subtitle1" className="text-center">
|
||||
@@ -149,13 +150,13 @@ export default function ServicesPage() {
|
||||
</Text>
|
||||
</div>
|
||||
{isPlatform ? (
|
||||
<div className="flex flex-row place-content-between rounded-lg ">
|
||||
<div className="flex flex-row rounded-lg place-content-between ">
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className="w-full"
|
||||
onClick={openCreateServiceDialog}
|
||||
startIcon={<PlusIcon className="h-4 w-4" />}
|
||||
startIcon={<PlusIcon className="w-4 h-4" />}
|
||||
>
|
||||
Add service
|
||||
</Button>
|
||||
@@ -168,12 +169,12 @@ export default function ServicesPage() {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<Box className="flex flex-row place-content-end border-b-1 p-4">
|
||||
<Box className="flex flex-row p-4 place-content-end border-b-1">
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={openCreateServiceDialog}
|
||||
startIcon={<PlusIcon className="h-4 w-4" />}
|
||||
startIcon={<PlusIcon className="w-4 h-4" />}
|
||||
disabled={!isPlatform}
|
||||
>
|
||||
Add service
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Text } from '@/components/ui/v2/Text';
|
||||
import { getToastStyleProps } from '@/utils/constants/settings';
|
||||
import { nhost } from '@/utils/nhost';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { Turnstile } from '@marsidev/react-turnstile';
|
||||
import { styled } from '@mui/material';
|
||||
import { useSignUpEmailPassword } from '@nhost/nextjs';
|
||||
import { useRouter } from 'next/router';
|
||||
@@ -39,6 +40,9 @@ export default function SignUpPage() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
// x-cf-turnstile-response
|
||||
const [turnstileResponse, setTurnstileResponse] = useState(null);
|
||||
|
||||
const form = useForm<SignUpFormValues>({
|
||||
reValidateMode: 'onSubmit',
|
||||
defaultValues: {
|
||||
@@ -66,11 +70,27 @@ export default function SignUpPage() {
|
||||
password,
|
||||
displayName,
|
||||
}: SignUpFormValues) {
|
||||
if (!turnstileResponse) {
|
||||
toast.error(
|
||||
'Please complete the signup verification challenge to continue.',
|
||||
getToastStyleProps(),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { needsEmailVerification } = await signUpEmailPassword(
|
||||
email,
|
||||
password,
|
||||
{ displayName },
|
||||
{
|
||||
displayName,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'x-cf-turnstile-response': turnstileResponse,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (needsEmailVerification) {
|
||||
@@ -94,7 +114,7 @@ export default function SignUpPage() {
|
||||
Sign Up
|
||||
</Text>
|
||||
|
||||
<Box className="grid grid-flow-row gap-4 rounded-md border bg-transparent p-6 lg:p-12">
|
||||
<Box className="grid grid-flow-row gap-4 p-6 bg-transparent border rounded-md lg:p-12">
|
||||
<Button
|
||||
variant="borderless"
|
||||
className="!bg-white !text-black hover:ring-2 hover:ring-white hover:ring-opacity-50 disabled:!text-black disabled:!text-opacity-60"
|
||||
@@ -122,7 +142,7 @@ export default function SignUpPage() {
|
||||
|
||||
<div className="relative py-2">
|
||||
<Text
|
||||
className="absolute left-0 right-0 top-1/2 mx-auto w-12 -translate-y-1/2 bg-black px-2 text-center text-sm"
|
||||
className="absolute left-0 right-0 w-12 px-2 mx-auto text-sm text-center -translate-y-1/2 bg-black top-1/2"
|
||||
color="disabled"
|
||||
>
|
||||
OR
|
||||
@@ -172,6 +192,12 @@ export default function SignUpPage() {
|
||||
helperText={formState.errors.password?.message}
|
||||
/>
|
||||
|
||||
<Turnstile
|
||||
siteKey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY}
|
||||
options={{ theme: 'dark', size: 'flexible' }}
|
||||
onSuccess={setTurnstileResponse}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
@@ -188,7 +214,7 @@ export default function SignUpPage() {
|
||||
|
||||
<Divider className="!my-2" />
|
||||
|
||||
<Text color="secondary" className="text-center text-sm">
|
||||
<Text color="secondary" className="text-sm text-center">
|
||||
By signing up, you agree to our{' '}
|
||||
<NavLink
|
||||
href="https://nhost.io/legal/terms-of-service"
|
||||
@@ -212,7 +238,7 @@ export default function SignUpPage() {
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Text color="secondary" className="text-center text-base lg:text-lg">
|
||||
<Text color="secondary" className="text-base text-center lg:text-lg">
|
||||
Already have an account?{' '}
|
||||
<NavLink href="/signin" color="white" className="font-medium">
|
||||
Sign In
|
||||
|
||||
299
dashboard/src/utils/__generated__/graphql.ts
generated
299
dashboard/src/utils/__generated__/graphql.ts
generated
@@ -1412,6 +1412,29 @@ export type ConfigGlobalUpdateInput = {
|
||||
export type ConfigGrafana = {
|
||||
__typename?: 'ConfigGrafana';
|
||||
adminPassword: Scalars['String'];
|
||||
alerting?: Maybe<ConfigGrafanaAlerting>;
|
||||
contacts?: Maybe<ConfigGrafanaContacts>;
|
||||
smtp?: Maybe<ConfigGrafanaSmtp>;
|
||||
};
|
||||
|
||||
export type ConfigGrafanaAlerting = {
|
||||
__typename?: 'ConfigGrafanaAlerting';
|
||||
enabled?: Maybe<Scalars['Boolean']>;
|
||||
};
|
||||
|
||||
export type ConfigGrafanaAlertingComparisonExp = {
|
||||
_and?: InputMaybe<Array<ConfigGrafanaAlertingComparisonExp>>;
|
||||
_not?: InputMaybe<ConfigGrafanaAlertingComparisonExp>;
|
||||
_or?: InputMaybe<Array<ConfigGrafanaAlertingComparisonExp>>;
|
||||
enabled?: InputMaybe<ConfigBooleanComparisonExp>;
|
||||
};
|
||||
|
||||
export type ConfigGrafanaAlertingInsertInput = {
|
||||
enabled?: InputMaybe<Scalars['Boolean']>;
|
||||
};
|
||||
|
||||
export type ConfigGrafanaAlertingUpdateInput = {
|
||||
enabled?: InputMaybe<Scalars['Boolean']>;
|
||||
};
|
||||
|
||||
export type ConfigGrafanaComparisonExp = {
|
||||
@@ -1419,14 +1442,255 @@ export type ConfigGrafanaComparisonExp = {
|
||||
_not?: InputMaybe<ConfigGrafanaComparisonExp>;
|
||||
_or?: InputMaybe<Array<ConfigGrafanaComparisonExp>>;
|
||||
adminPassword?: InputMaybe<ConfigStringComparisonExp>;
|
||||
alerting?: InputMaybe<ConfigGrafanaAlertingComparisonExp>;
|
||||
contacts?: InputMaybe<ConfigGrafanaContactsComparisonExp>;
|
||||
smtp?: InputMaybe<ConfigGrafanaSmtpComparisonExp>;
|
||||
};
|
||||
|
||||
export type ConfigGrafanaContacts = {
|
||||
__typename?: 'ConfigGrafanaContacts';
|
||||
discord?: Maybe<Array<ConfigGrafanacontactsDiscord>>;
|
||||
emails?: Maybe<Array<Scalars['String']>>;
|
||||
pagerduty?: Maybe<Array<ConfigGrafanacontactsPagerduty>>;
|
||||
slack?: Maybe<Array<ConfigGrafanacontactsSlack>>;
|
||||
webhook?: Maybe<Array<ConfigGrafanacontactsWebhook>>;
|
||||
};
|
||||
|
||||
export type ConfigGrafanaContactsComparisonExp = {
|
||||
_and?: InputMaybe<Array<ConfigGrafanaContactsComparisonExp>>;
|
||||
_not?: InputMaybe<ConfigGrafanaContactsComparisonExp>;
|
||||
_or?: InputMaybe<Array<ConfigGrafanaContactsComparisonExp>>;
|
||||
discord?: InputMaybe<ConfigGrafanacontactsDiscordComparisonExp>;
|
||||
emails?: InputMaybe<ConfigStringComparisonExp>;
|
||||
pagerduty?: InputMaybe<ConfigGrafanacontactsPagerdutyComparisonExp>;
|
||||
slack?: InputMaybe<ConfigGrafanacontactsSlackComparisonExp>;
|
||||
webhook?: InputMaybe<ConfigGrafanacontactsWebhookComparisonExp>;
|
||||
};
|
||||
|
||||
export type ConfigGrafanaContactsInsertInput = {
|
||||
discord?: InputMaybe<Array<ConfigGrafanacontactsDiscordInsertInput>>;
|
||||
emails?: InputMaybe<Array<Scalars['String']>>;
|
||||
pagerduty?: InputMaybe<Array<ConfigGrafanacontactsPagerdutyInsertInput>>;
|
||||
slack?: InputMaybe<Array<ConfigGrafanacontactsSlackInsertInput>>;
|
||||
webhook?: InputMaybe<Array<ConfigGrafanacontactsWebhookInsertInput>>;
|
||||
};
|
||||
|
||||
export type ConfigGrafanaContactsUpdateInput = {
|
||||
discord?: InputMaybe<Array<ConfigGrafanacontactsDiscordUpdateInput>>;
|
||||
emails?: InputMaybe<Array<Scalars['String']>>;
|
||||
pagerduty?: InputMaybe<Array<ConfigGrafanacontactsPagerdutyUpdateInput>>;
|
||||
slack?: InputMaybe<Array<ConfigGrafanacontactsSlackUpdateInput>>;
|
||||
webhook?: InputMaybe<Array<ConfigGrafanacontactsWebhookUpdateInput>>;
|
||||
};
|
||||
|
||||
export type ConfigGrafanaInsertInput = {
|
||||
adminPassword: Scalars['String'];
|
||||
alerting?: InputMaybe<ConfigGrafanaAlertingInsertInput>;
|
||||
contacts?: InputMaybe<ConfigGrafanaContactsInsertInput>;
|
||||
smtp?: InputMaybe<ConfigGrafanaSmtpInsertInput>;
|
||||
};
|
||||
|
||||
export type ConfigGrafanaSmtp = {
|
||||
__typename?: 'ConfigGrafanaSmtp';
|
||||
host: Scalars['String'];
|
||||
password: Scalars['String'];
|
||||
port: Scalars['ConfigPort'];
|
||||
sender: Scalars['String'];
|
||||
user: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ConfigGrafanaSmtpComparisonExp = {
|
||||
_and?: InputMaybe<Array<ConfigGrafanaSmtpComparisonExp>>;
|
||||
_not?: InputMaybe<ConfigGrafanaSmtpComparisonExp>;
|
||||
_or?: InputMaybe<Array<ConfigGrafanaSmtpComparisonExp>>;
|
||||
host?: InputMaybe<ConfigStringComparisonExp>;
|
||||
password?: InputMaybe<ConfigStringComparisonExp>;
|
||||
port?: InputMaybe<ConfigPortComparisonExp>;
|
||||
sender?: InputMaybe<ConfigStringComparisonExp>;
|
||||
user?: InputMaybe<ConfigStringComparisonExp>;
|
||||
};
|
||||
|
||||
export type ConfigGrafanaSmtpInsertInput = {
|
||||
host: Scalars['String'];
|
||||
password: Scalars['String'];
|
||||
port: Scalars['ConfigPort'];
|
||||
sender: Scalars['String'];
|
||||
user: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ConfigGrafanaSmtpUpdateInput = {
|
||||
host?: InputMaybe<Scalars['String']>;
|
||||
password?: InputMaybe<Scalars['String']>;
|
||||
port?: InputMaybe<Scalars['ConfigPort']>;
|
||||
sender?: InputMaybe<Scalars['String']>;
|
||||
user?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ConfigGrafanaUpdateInput = {
|
||||
adminPassword?: InputMaybe<Scalars['String']>;
|
||||
alerting?: InputMaybe<ConfigGrafanaAlertingUpdateInput>;
|
||||
contacts?: InputMaybe<ConfigGrafanaContactsUpdateInput>;
|
||||
smtp?: InputMaybe<ConfigGrafanaSmtpUpdateInput>;
|
||||
};
|
||||
|
||||
export type ConfigGrafanacontactsDiscord = {
|
||||
__typename?: 'ConfigGrafanacontactsDiscord';
|
||||
avatarUrl: Scalars['String'];
|
||||
url: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ConfigGrafanacontactsDiscordComparisonExp = {
|
||||
_and?: InputMaybe<Array<ConfigGrafanacontactsDiscordComparisonExp>>;
|
||||
_not?: InputMaybe<ConfigGrafanacontactsDiscordComparisonExp>;
|
||||
_or?: InputMaybe<Array<ConfigGrafanacontactsDiscordComparisonExp>>;
|
||||
avatarUrl?: InputMaybe<ConfigStringComparisonExp>;
|
||||
url?: InputMaybe<ConfigStringComparisonExp>;
|
||||
};
|
||||
|
||||
export type ConfigGrafanacontactsDiscordInsertInput = {
|
||||
avatarUrl: Scalars['String'];
|
||||
url: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ConfigGrafanacontactsDiscordUpdateInput = {
|
||||
avatarUrl?: InputMaybe<Scalars['String']>;
|
||||
url?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ConfigGrafanacontactsPagerduty = {
|
||||
__typename?: 'ConfigGrafanacontactsPagerduty';
|
||||
class: Scalars['String'];
|
||||
component: Scalars['String'];
|
||||
group: Scalars['String'];
|
||||
integrationKey: Scalars['String'];
|
||||
severity: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ConfigGrafanacontactsPagerdutyComparisonExp = {
|
||||
_and?: InputMaybe<Array<ConfigGrafanacontactsPagerdutyComparisonExp>>;
|
||||
_not?: InputMaybe<ConfigGrafanacontactsPagerdutyComparisonExp>;
|
||||
_or?: InputMaybe<Array<ConfigGrafanacontactsPagerdutyComparisonExp>>;
|
||||
class?: InputMaybe<ConfigStringComparisonExp>;
|
||||
component?: InputMaybe<ConfigStringComparisonExp>;
|
||||
group?: InputMaybe<ConfigStringComparisonExp>;
|
||||
integrationKey?: InputMaybe<ConfigStringComparisonExp>;
|
||||
severity?: InputMaybe<ConfigStringComparisonExp>;
|
||||
};
|
||||
|
||||
export type ConfigGrafanacontactsPagerdutyInsertInput = {
|
||||
class: Scalars['String'];
|
||||
component: Scalars['String'];
|
||||
group: Scalars['String'];
|
||||
integrationKey: Scalars['String'];
|
||||
severity: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ConfigGrafanacontactsPagerdutyUpdateInput = {
|
||||
class?: InputMaybe<Scalars['String']>;
|
||||
component?: InputMaybe<Scalars['String']>;
|
||||
group?: InputMaybe<Scalars['String']>;
|
||||
integrationKey?: InputMaybe<Scalars['String']>;
|
||||
severity?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ConfigGrafanacontactsSlack = {
|
||||
__typename?: 'ConfigGrafanacontactsSlack';
|
||||
endpointURL: Scalars['String'];
|
||||
iconEmoji: Scalars['String'];
|
||||
iconURL: Scalars['String'];
|
||||
mentionChannel: Scalars['String'];
|
||||
mentionGroups: Array<Scalars['String']>;
|
||||
mentionUsers: Array<Scalars['String']>;
|
||||
recipient: Scalars['String'];
|
||||
token: Scalars['String'];
|
||||
url: Scalars['String'];
|
||||
username: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ConfigGrafanacontactsSlackComparisonExp = {
|
||||
_and?: InputMaybe<Array<ConfigGrafanacontactsSlackComparisonExp>>;
|
||||
_not?: InputMaybe<ConfigGrafanacontactsSlackComparisonExp>;
|
||||
_or?: InputMaybe<Array<ConfigGrafanacontactsSlackComparisonExp>>;
|
||||
endpointURL?: InputMaybe<ConfigStringComparisonExp>;
|
||||
iconEmoji?: InputMaybe<ConfigStringComparisonExp>;
|
||||
iconURL?: InputMaybe<ConfigStringComparisonExp>;
|
||||
mentionChannel?: InputMaybe<ConfigStringComparisonExp>;
|
||||
mentionGroups?: InputMaybe<ConfigStringComparisonExp>;
|
||||
mentionUsers?: InputMaybe<ConfigStringComparisonExp>;
|
||||
recipient?: InputMaybe<ConfigStringComparisonExp>;
|
||||
token?: InputMaybe<ConfigStringComparisonExp>;
|
||||
url?: InputMaybe<ConfigStringComparisonExp>;
|
||||
username?: InputMaybe<ConfigStringComparisonExp>;
|
||||
};
|
||||
|
||||
export type ConfigGrafanacontactsSlackInsertInput = {
|
||||
endpointURL: Scalars['String'];
|
||||
iconEmoji: Scalars['String'];
|
||||
iconURL: Scalars['String'];
|
||||
mentionChannel: Scalars['String'];
|
||||
mentionGroups: Array<Scalars['String']>;
|
||||
mentionUsers: Array<Scalars['String']>;
|
||||
recipient: Scalars['String'];
|
||||
token: Scalars['String'];
|
||||
url: Scalars['String'];
|
||||
username: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ConfigGrafanacontactsSlackUpdateInput = {
|
||||
endpointURL?: InputMaybe<Scalars['String']>;
|
||||
iconEmoji?: InputMaybe<Scalars['String']>;
|
||||
iconURL?: InputMaybe<Scalars['String']>;
|
||||
mentionChannel?: InputMaybe<Scalars['String']>;
|
||||
mentionGroups?: InputMaybe<Array<Scalars['String']>>;
|
||||
mentionUsers?: InputMaybe<Array<Scalars['String']>>;
|
||||
recipient?: InputMaybe<Scalars['String']>;
|
||||
token?: InputMaybe<Scalars['String']>;
|
||||
url?: InputMaybe<Scalars['String']>;
|
||||
username?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ConfigGrafanacontactsWebhook = {
|
||||
__typename?: 'ConfigGrafanacontactsWebhook';
|
||||
authorizationCredentials: Scalars['String'];
|
||||
authorizationScheme: Scalars['String'];
|
||||
httpMethod: Scalars['String'];
|
||||
maxAlerts: Scalars['Int'];
|
||||
password: Scalars['String'];
|
||||
url: Scalars['String'];
|
||||
username: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ConfigGrafanacontactsWebhookComparisonExp = {
|
||||
_and?: InputMaybe<Array<ConfigGrafanacontactsWebhookComparisonExp>>;
|
||||
_not?: InputMaybe<ConfigGrafanacontactsWebhookComparisonExp>;
|
||||
_or?: InputMaybe<Array<ConfigGrafanacontactsWebhookComparisonExp>>;
|
||||
authorizationCredentials?: InputMaybe<ConfigStringComparisonExp>;
|
||||
authorizationScheme?: InputMaybe<ConfigStringComparisonExp>;
|
||||
httpMethod?: InputMaybe<ConfigStringComparisonExp>;
|
||||
maxAlerts?: InputMaybe<ConfigIntComparisonExp>;
|
||||
password?: InputMaybe<ConfigStringComparisonExp>;
|
||||
url?: InputMaybe<ConfigStringComparisonExp>;
|
||||
username?: InputMaybe<ConfigStringComparisonExp>;
|
||||
};
|
||||
|
||||
export type ConfigGrafanacontactsWebhookInsertInput = {
|
||||
authorizationCredentials: Scalars['String'];
|
||||
authorizationScheme: Scalars['String'];
|
||||
httpMethod: Scalars['String'];
|
||||
maxAlerts: Scalars['Int'];
|
||||
password: Scalars['String'];
|
||||
url: Scalars['String'];
|
||||
username: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ConfigGrafanacontactsWebhookUpdateInput = {
|
||||
authorizationCredentials?: InputMaybe<Scalars['String']>;
|
||||
authorizationScheme?: InputMaybe<Scalars['String']>;
|
||||
httpMethod?: InputMaybe<Scalars['String']>;
|
||||
maxAlerts?: InputMaybe<Scalars['Int']>;
|
||||
password?: InputMaybe<Scalars['String']>;
|
||||
url?: InputMaybe<Scalars['String']>;
|
||||
username?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ConfigGraphql = {
|
||||
@@ -1625,6 +1889,8 @@ export type ConfigHasuraSettings = {
|
||||
enableRemoteSchemaPermissions?: Maybe<Scalars['Boolean']>;
|
||||
/** HASURA_GRAPHQL_ENABLED_APIS */
|
||||
enabledAPIs?: Maybe<Array<Scalars['ConfigHasuraAPIs']>>;
|
||||
/** HASURA_GRAPHQL_INFER_FUNCTION_PERMISSIONS */
|
||||
inferFunctionPermissions?: Maybe<Scalars['Boolean']>;
|
||||
/** HASURA_GRAPHQL_LIVE_QUERIES_MULTIPLEXED_REFETCH_INTERVAL */
|
||||
liveQueriesMultiplexedRefetchInterval?: Maybe<Scalars['ConfigUint32']>;
|
||||
/** HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES */
|
||||
@@ -1641,6 +1907,7 @@ export type ConfigHasuraSettingsComparisonExp = {
|
||||
enableConsole?: InputMaybe<ConfigBooleanComparisonExp>;
|
||||
enableRemoteSchemaPermissions?: InputMaybe<ConfigBooleanComparisonExp>;
|
||||
enabledAPIs?: InputMaybe<ConfigHasuraApIsComparisonExp>;
|
||||
inferFunctionPermissions?: InputMaybe<ConfigBooleanComparisonExp>;
|
||||
liveQueriesMultiplexedRefetchInterval?: InputMaybe<ConfigUint32ComparisonExp>;
|
||||
stringifyNumericTypes?: InputMaybe<ConfigBooleanComparisonExp>;
|
||||
};
|
||||
@@ -1652,6 +1919,7 @@ export type ConfigHasuraSettingsInsertInput = {
|
||||
enableConsole?: InputMaybe<Scalars['Boolean']>;
|
||||
enableRemoteSchemaPermissions?: InputMaybe<Scalars['Boolean']>;
|
||||
enabledAPIs?: InputMaybe<Array<Scalars['ConfigHasuraAPIs']>>;
|
||||
inferFunctionPermissions?: InputMaybe<Scalars['Boolean']>;
|
||||
liveQueriesMultiplexedRefetchInterval?: InputMaybe<Scalars['ConfigUint32']>;
|
||||
stringifyNumericTypes?: InputMaybe<Scalars['Boolean']>;
|
||||
};
|
||||
@@ -1663,6 +1931,7 @@ export type ConfigHasuraSettingsUpdateInput = {
|
||||
enableConsole?: InputMaybe<Scalars['Boolean']>;
|
||||
enableRemoteSchemaPermissions?: InputMaybe<Scalars['Boolean']>;
|
||||
enabledAPIs?: InputMaybe<Array<Scalars['ConfigHasuraAPIs']>>;
|
||||
inferFunctionPermissions?: InputMaybe<Scalars['Boolean']>;
|
||||
liveQueriesMultiplexedRefetchInterval?: InputMaybe<Scalars['ConfigUint32']>;
|
||||
stringifyNumericTypes?: InputMaybe<Scalars['Boolean']>;
|
||||
};
|
||||
@@ -22956,14 +23225,14 @@ export type GetBackupPresignedUrlQueryVariables = Exact<{
|
||||
|
||||
export type GetBackupPresignedUrlQuery = { __typename?: 'query_root', getBackupPresignedUrl: { __typename?: 'BackupPresignedURL', url: string, expiresAt: any } };
|
||||
|
||||
export type ServiceResourcesFragment = { __typename?: 'ConfigConfig', auth?: { __typename?: 'ConfigAuth', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null } | null } | null, hasura: { __typename?: 'ConfigHasura', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null } | null }, postgres?: { __typename?: 'ConfigPostgres', resources?: { __typename?: 'ConfigPostgresResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null } | null } | null, storage?: { __typename?: 'ConfigStorage', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null } | null } | null };
|
||||
export type ServiceResourcesFragment = { __typename?: 'ConfigConfig', auth?: { __typename?: 'ConfigAuth', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null } | null } | null, hasura: { __typename?: 'ConfigHasura', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null } | null }, postgres?: { __typename?: 'ConfigPostgres', resources?: { __typename?: 'ConfigPostgresResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null } | null } | null, storage?: { __typename?: 'ConfigStorage', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null } | null } | null };
|
||||
|
||||
export type GetResourcesQueryVariables = Exact<{
|
||||
appId: Scalars['uuid'];
|
||||
}>;
|
||||
|
||||
|
||||
export type GetResourcesQuery = { __typename?: 'query_root', config?: { __typename?: 'ConfigConfig', auth?: { __typename?: 'ConfigAuth', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null } | null } | null, hasura: { __typename?: 'ConfigHasura', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null } | null }, postgres?: { __typename?: 'ConfigPostgres', resources?: { __typename?: 'ConfigPostgresResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null } | null } | null, storage?: { __typename?: 'ConfigStorage', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null } | null } | null } | null };
|
||||
export type GetResourcesQuery = { __typename?: 'query_root', config?: { __typename?: 'ConfigConfig', auth?: { __typename?: 'ConfigAuth', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null } | null } | null, hasura: { __typename?: 'ConfigHasura', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null } | null }, postgres?: { __typename?: 'ConfigPostgres', resources?: { __typename?: 'ConfigPostgresResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null } | null } | null, storage?: { __typename?: 'ConfigStorage', resources?: { __typename?: 'ConfigResources', replicas?: any | null, compute?: { __typename?: 'ConfigResourcesCompute', cpu: any, memory: any } | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null } | null } | null } | null };
|
||||
|
||||
export type GetServerlessFunctionsSettingsQueryVariables = Exact<{
|
||||
appId: Scalars['uuid'];
|
||||
@@ -23516,9 +23785,9 @@ export type GetRunServiceQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type GetRunServiceQuery = { __typename?: 'query_root', runService?: { __typename?: 'run_service', id: any, subdomain: string, config?: { __typename?: 'ConfigRunServiceConfig', name: any, command?: Array<string> | null, image: { __typename?: 'ConfigRunServiceImage', image: string }, resources: { __typename?: 'ConfigRunServiceResources', replicas: any, compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any }, storage?: Array<{ __typename?: 'ConfigRunServiceResourcesStorage', name: any, path: string, capacity: any }> | null }, environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string }> | null, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null }> | null } | null } | null };
|
||||
export type GetRunServiceQuery = { __typename?: 'query_root', runService?: { __typename?: 'run_service', id: any, subdomain: string, config?: { __typename?: 'ConfigRunServiceConfig', name: any, command?: Array<string> | null, image: { __typename?: 'ConfigRunServiceImage', image: string }, resources: { __typename?: 'ConfigRunServiceResources', replicas: any, compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any }, storage?: Array<{ __typename?: 'ConfigRunServiceResourcesStorage', name: any, path: string, capacity: any }> | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null }, environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string }> | null, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null }> | null } | null } | null };
|
||||
|
||||
export type RunServiceConfigFragment = { __typename?: 'ConfigRunServiceConfig', name: any, command?: Array<string> | null, image: { __typename?: 'ConfigRunServiceImage', image: string }, resources: { __typename?: 'ConfigRunServiceResources', replicas: any, compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any }, storage?: Array<{ __typename?: 'ConfigRunServiceResourcesStorage', name: any, path: string, capacity: any }> | null }, environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string }> | null, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null, rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null }> | null, healthCheck?: { __typename?: 'ConfigHealthCheck', port: any, initialDelaySeconds?: number | null, probePeriodSeconds?: number | null } | null };
|
||||
export type RunServiceConfigFragment = { __typename?: 'ConfigRunServiceConfig', name: any, command?: Array<string> | null, image: { __typename?: 'ConfigRunServiceImage', image: string }, resources: { __typename?: 'ConfigRunServiceResources', replicas: any, compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any }, storage?: Array<{ __typename?: 'ConfigRunServiceResourcesStorage', name: any, path: string, capacity: any }> | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null }, environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string }> | null, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null, rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null }> | null, healthCheck?: { __typename?: 'ConfigHealthCheck', port: any, initialDelaySeconds?: number | null, probePeriodSeconds?: number | null } | null };
|
||||
|
||||
export type GetRunServicesQueryVariables = Exact<{
|
||||
appID: Scalars['uuid'];
|
||||
@@ -23528,7 +23797,7 @@ export type GetRunServicesQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type GetRunServicesQuery = { __typename?: 'query_root', app?: { __typename?: 'apps', runServices: Array<{ __typename?: 'run_service', id: any, createdAt: any, updatedAt: any, subdomain: string, config?: { __typename?: 'ConfigRunServiceConfig', name: any, command?: Array<string> | null, image: { __typename?: 'ConfigRunServiceImage', image: string }, resources: { __typename?: 'ConfigRunServiceResources', replicas: any, compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any }, storage?: Array<{ __typename?: 'ConfigRunServiceResourcesStorage', name: any, path: string, capacity: any }> | null }, environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string }> | null, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null, rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null }> | null, healthCheck?: { __typename?: 'ConfigHealthCheck', port: any, initialDelaySeconds?: number | null, probePeriodSeconds?: number | null } | null } | null }>, runServices_aggregate: { __typename?: 'run_service_aggregate', aggregate?: { __typename?: 'run_service_aggregate_fields', count: number } | null } } | null };
|
||||
export type GetRunServicesQuery = { __typename?: 'query_root', app?: { __typename?: 'apps', runServices: Array<{ __typename?: 'run_service', id: any, createdAt: any, updatedAt: any, subdomain: string, config?: { __typename?: 'ConfigRunServiceConfig', name: any, command?: Array<string> | null, image: { __typename?: 'ConfigRunServiceImage', image: string }, resources: { __typename?: 'ConfigRunServiceResources', replicas: any, compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any }, storage?: Array<{ __typename?: 'ConfigRunServiceResourcesStorage', name: any, path: string, capacity: any }> | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null }, environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string }> | null, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null, rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null }> | null, healthCheck?: { __typename?: 'ConfigHealthCheck', port: any, initialDelaySeconds?: number | null, probePeriodSeconds?: number | null } | null } | null }>, runServices_aggregate: { __typename?: 'run_service_aggregate', aggregate?: { __typename?: 'run_service_aggregate_fields', count: number } | null } } | null };
|
||||
|
||||
export type GetLocalRunServiceConfigsQueryVariables = Exact<{
|
||||
appID: Scalars['uuid'];
|
||||
@@ -23536,7 +23805,7 @@ export type GetLocalRunServiceConfigsQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type GetLocalRunServiceConfigsQuery = { __typename?: 'query_root', runServiceConfigs: Array<{ __typename?: 'ConfigRunServiceConfigWithID', serviceID: any, config: { __typename?: 'ConfigRunServiceConfig', name: any, command?: Array<string> | null, image: { __typename?: 'ConfigRunServiceImage', image: string }, resources: { __typename?: 'ConfigRunServiceResources', replicas: any, compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any }, storage?: Array<{ __typename?: 'ConfigRunServiceResourcesStorage', name: any, path: string, capacity: any }> | null }, environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string }> | null, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null, rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null }> | null, healthCheck?: { __typename?: 'ConfigHealthCheck', port: any, initialDelaySeconds?: number | null, probePeriodSeconds?: number | null } | null } }> };
|
||||
export type GetLocalRunServiceConfigsQuery = { __typename?: 'query_root', runServiceConfigs: Array<{ __typename?: 'ConfigRunServiceConfigWithID', serviceID: any, config: { __typename?: 'ConfigRunServiceConfig', name: any, command?: Array<string> | null, image: { __typename?: 'ConfigRunServiceImage', image: string }, resources: { __typename?: 'ConfigRunServiceResources', replicas: any, compute: { __typename?: 'ConfigComputeResources', cpu: any, memory: any }, storage?: Array<{ __typename?: 'ConfigRunServiceResourcesStorage', name: any, path: string, capacity: any }> | null, autoscaler?: { __typename?: 'ConfigAutoscaler', maxReplicas: any } | null }, environment?: Array<{ __typename?: 'ConfigEnvironmentVariable', name: string, value: string }> | null, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null, rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null }> | null, healthCheck?: { __typename?: 'ConfigHealthCheck', port: any, initialDelaySeconds?: number | null, probePeriodSeconds?: number | null } | null } }> };
|
||||
|
||||
export type RunServiceRateLimitFragment = { __typename?: 'ConfigRunServiceConfig', name: any, ports?: Array<{ __typename?: 'ConfigRunServicePort', port: any, type: string, publish?: boolean | null, rateLimit?: { __typename?: 'ConfigRateLimit', limit: any, interval: string } | null, ingresses?: Array<{ __typename?: 'ConfigIngress', fqdn?: Array<string> | null }> | null }> | null };
|
||||
|
||||
@@ -23691,6 +23960,9 @@ export const ServiceResourcesFragmentDoc = gql`
|
||||
memory
|
||||
}
|
||||
replicas
|
||||
autoscaler {
|
||||
maxReplicas
|
||||
}
|
||||
}
|
||||
}
|
||||
hasura {
|
||||
@@ -23700,6 +23972,9 @@ export const ServiceResourcesFragmentDoc = gql`
|
||||
memory
|
||||
}
|
||||
replicas
|
||||
autoscaler {
|
||||
maxReplicas
|
||||
}
|
||||
}
|
||||
}
|
||||
postgres {
|
||||
@@ -23709,6 +23984,9 @@ export const ServiceResourcesFragmentDoc = gql`
|
||||
memory
|
||||
}
|
||||
replicas
|
||||
autoscaler {
|
||||
maxReplicas
|
||||
}
|
||||
}
|
||||
}
|
||||
storage {
|
||||
@@ -23718,6 +23996,9 @@ export const ServiceResourcesFragmentDoc = gql`
|
||||
memory
|
||||
}
|
||||
replicas
|
||||
autoscaler {
|
||||
maxReplicas
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24005,6 +24286,9 @@ export const RunServiceConfigFragmentDoc = gql`
|
||||
capacity
|
||||
}
|
||||
replicas
|
||||
autoscaler {
|
||||
maxReplicas
|
||||
}
|
||||
}
|
||||
environment {
|
||||
name
|
||||
@@ -27752,6 +28036,9 @@ export const GetRunServiceDocument = gql`
|
||||
capacity
|
||||
}
|
||||
replicas
|
||||
autoscaler {
|
||||
maxReplicas
|
||||
}
|
||||
}
|
||||
environment {
|
||||
name
|
||||
|
||||
@@ -50,6 +50,11 @@ export const RESOURCE_VCPU_STEP = 0.25 * RESOURCE_VCPU_MULTIPLIER;
|
||||
*/
|
||||
export const RESOURCE_MEMORY_STEP = 128;
|
||||
|
||||
/**
|
||||
* Number of steps between GiB of RAM when the ratio is locked.
|
||||
*/
|
||||
export const RESOURCE_MEMORY_LOCKED_STEP = 4 * RESOURCE_MEMORY_STEP;
|
||||
|
||||
/**
|
||||
* Price per vCPU.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
# @nhost/docs
|
||||
|
||||
## 2.18.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- c4aa159: feat: added advanced TLS document
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 91f0465: feat: added turnstile guide
|
||||
|
||||
## 2.17.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 52a38fe: chore: added pg_ivm extension
|
||||
|
||||
## 2.17.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- db2f44d: fix: update rate-limit to reflect reality
|
||||
- dda0c67: chore: udpate metrics documentation with managed configuration
|
||||
|
||||
## 2.17.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
76
docs/guides/auth/bot-protection.mdx
Normal file
76
docs/guides/auth/bot-protection.mdx
Normal file
@@ -0,0 +1,76 @@
|
||||
---
|
||||
title: Bot Protection
|
||||
description: Use turnstile to protect from bots
|
||||
icon: robot
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
To safeguard your Auth API against automated attacks from scripts and bots, you can implement [Cloudflare's Turnstile](https://www.cloudflare.com/en-gb/products/turnstile/). Turnstile offers CAPTCHA-like protection without user friction, as it doesn't require solving puzzles.
|
||||
|
||||

|
||||
|
||||
## Integration Benefits
|
||||
|
||||
1. **Selective Protection**: Auth integrates Turnstile specifically for all signup methods.
|
||||
2. **API Accessibility**: Other API endpoints remain accessible for legitimate programmatic use.
|
||||
3. **Bot Deterrence**: Manual verification during signup discourages unwanted bot activity.
|
||||
|
||||
This approach balances security with usability, ensuring robust protection where it matters most.
|
||||
|
||||
## Guide
|
||||
|
||||
<Steps>
|
||||
<Step title="Create a widget on Cloudflare">
|
||||
Sign up on [Cloudflare](https://dash.cloudflare.com) if you haven't already.
|
||||
|
||||
Go to your account -> Turnstile -> Add Widget. Then:
|
||||
- Set a descriptive name
|
||||
- In the domain, enter your frontend's domain
|
||||
- Set widget mode as "managed"
|
||||
|
||||
Then click on "create" and write down the site key and the secret key.
|
||||
</Step>
|
||||
<Step title="Enable Turnstile integration on auth">
|
||||
Start by adding the following configuration to your Nhost project:
|
||||
<Tabs>
|
||||
<Tab title="Config">
|
||||
```toml
|
||||
[auth.signUp.turnstile]
|
||||
secretKey = 'turnstileSecretKey'
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
Replace `turnstileSecretKey` with the secret key from the first step.
|
||||
</Step>
|
||||
|
||||
<Step title="Integrate turnstile into your sign up form">
|
||||
To integrate turnstile into your sign up form you can refer to [Cloudfare's documentation](https://developers.cloudflare.com/turnstile/tutorials/login-pages). Just keep in mind a few things:
|
||||
|
||||
- You don't need to do any verification of the response, just pass it to the Auth service on the `/signup/...` request in the header `x-cf-turnstile-response`.
|
||||
- The "server side verification" is done by the auth service and will return a forbidden status error if the header is not present or if the check didn't pass.
|
||||
- You will need to use the site key from step 1 to configure turnstile in your form
|
||||
</Step>
|
||||
|
||||
<Step title="Pass turnstile's response to the signup request">
|
||||
To pass the response as a header change your request to include the header. For instance:
|
||||
|
||||
```js
|
||||
await signUpEmailPassword(
|
||||
email,
|
||||
password,
|
||||
{
|
||||
displayName,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'x-cf-turnstile-response': turnstileResponse,
|
||||
},
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
In the following [PR](https://github.com/nhost/nhost/pull/2897/files) you can see the changes that were needed in our very own dashboard to integrate turnstile.
|
||||
</Step>
|
||||
</Steps>
|
||||
@@ -159,6 +159,32 @@ DROP EXTENSION pg_hashids;
|
||||
|
||||
- [GitHub](https://github.com/iCyberon/pg_hashids)
|
||||
|
||||
## pg_ivm
|
||||
|
||||
The pg_ivm module provides Incremental View Maintenance (IVM) feature for PostgreSQL.
|
||||
|
||||
Incremental View Maintenance (IVM) is a way to make materialized views up-to-date in which only incremental changes are computed and applied on views rather than recomputing the contents from scratch as REFRESH MATERIALIZED VIEW does. IVM can update materialized views more efficiently than recomputation when only small parts of the view are changed.
|
||||
|
||||
### Managing
|
||||
|
||||
To install the extension you can create a migration with the following contents:
|
||||
|
||||
```sql SQL
|
||||
SET ROLE postgres;
|
||||
CREATE EXTENSION pg_ivm;
|
||||
```
|
||||
|
||||
To uninstall it, you can use the following migration:
|
||||
|
||||
```sql SQL
|
||||
SET ROLE postgres;
|
||||
DROP EXTENSION pg_ivm;
|
||||
```
|
||||
|
||||
### Resources
|
||||
|
||||
- [GitHub](https://github.com/sraoss/pg_ivm)
|
||||
|
||||
## pg_squeeze
|
||||
|
||||
PostgreSQL extension that removes unused space from a table and optionally sorts tuples according to particular index (as if CLUSTER command was executed concurrently with regular reads / writes). In fact we try to replace pg_repack extension.
|
||||
|
||||
@@ -95,7 +95,7 @@ publish = true
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Info>Currently, only services of type `http` can be exposed to the internet.</Info>
|
||||
<Info>Currently, only services of type `http` and `grpc` can be exposed to the internet.</Info>
|
||||
|
||||
2. Once the service of type `http` is published, you can connect to it using a URL with the following format:
|
||||
|
||||
@@ -105,3 +105,21 @@ publish = true
|
||||
|
||||
`https://zlbmqjfczuwqvsquujno-3000.svc.eu-central-1.nhost.run`
|
||||
|
||||
|
||||
## GRPC
|
||||
|
||||
GRPC services are supported, however, they are only supported via [custom domains](/platform/custom-domains). To expose a GRPC service to the internet you can use the following configuration:
|
||||
|
||||
``` toml
|
||||
...
|
||||
|
||||
[[ports]]
|
||||
type = "grpc"
|
||||
port = 5000
|
||||
publish = false
|
||||
|
||||
[[ports.ingresses]]
|
||||
fqdn = ["grpc.domain.com"]
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
BIN
docs/images/guides/auth/turnstile/turnstile.gif
Normal file
BIN
docs/images/guides/auth/turnstile/turnstile.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 247 KiB |
BIN
docs/images/platform/metrics/alert_rules.png
Normal file
BIN
docs/images/platform/metrics/alert_rules.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 532 KiB |
BIN
docs/images/platform/metrics/contact_points.png
Normal file
BIN
docs/images/platform/metrics/contact_points.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 392 KiB |
BIN
docs/images/platform/metrics/email_notification.png
Normal file
BIN
docs/images/platform/metrics/email_notification.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 255 KiB |
@@ -76,16 +76,13 @@
|
||||
"platform/subdomain",
|
||||
"platform/compute-resources",
|
||||
"platform/service-replicas",
|
||||
{
|
||||
"group": "Monitoring",
|
||||
"icon": "monitor-waveform",
|
||||
"pages": ["platform/metrics"]
|
||||
},
|
||||
"platform/metrics",
|
||||
"platform/environment-variables",
|
||||
"platform/secrets",
|
||||
"platform/deployments",
|
||||
"platform/custom-domains",
|
||||
"platform/rate-limits"
|
||||
"platform/rate-limits",
|
||||
"platform/tls"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -159,6 +156,7 @@
|
||||
"guides/auth/sign-in-phone-number",
|
||||
"guides/auth/sign-in-webauthn",
|
||||
"guides/auth/elevated-permissions",
|
||||
"guides/auth/bot-protection",
|
||||
"guides/auth/email-templates",
|
||||
"guides/auth/custom-jwts"
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/docs",
|
||||
"version": "2.17.0",
|
||||
"version": "2.18.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "mintlify dev"
|
||||
|
||||
@@ -4,6 +4,10 @@ description: 'Grafana Instance configured and tailored to your project'
|
||||
icon: monitor-waveform
|
||||
---
|
||||
|
||||
<Info>
|
||||
This is a Pro/Team/Enterprise feature. This is not available on Starter projects.
|
||||
</Info>
|
||||
|
||||
Insights such as response times, resource usage, and error rates, to help you assess the **performance** and **health** of your services.
|
||||
|
||||
Metrics helps you analyze the performance of your infrastructure, while identifying bottlenecks and optimizing your applications.
|
||||
@@ -23,16 +27,130 @@ Your Grafana instance comes pre-defined with dashboards that cover backend servi
|
||||
|
||||

|
||||
|
||||
### Nhost Dashboard
|
||||
## Accessing Grafana
|
||||
|
||||
You can find the link to Grafana in your project's dashboard, under **Metrics**.
|
||||
You can find the link to Grafana in your project's dashboard, under **Metrics**.
|
||||
|
||||

|
||||
|
||||
## Configuring Grafana
|
||||
|
||||
Grafana comes pre-configured with a datasource with your project's metrics plus a few useful dashboards to observe your projects. In addition, you can enable alerting by configuring one or more contact points and enabling alerts in your configuration file.
|
||||
|
||||
<Info>
|
||||
The configuration below is open source and can be found [here](https://github.com/nhost/nhost/tree/main/observability/grafana). If you want to see improvements, more rules, better dashboards, more options, etc., don't hesitate to contribute them or open an issue.
|
||||
</Info>
|
||||
|
||||
### Configure contact points
|
||||
|
||||
Contact points in Grafana are lists of integrations that send notifications to specific channels or services when alerts are triggered. Supported contact points are:
|
||||
|
||||
- email
|
||||
- pagerduty
|
||||
- discord
|
||||
- slack
|
||||
- webhooks
|
||||
|
||||
To configure them include one ore more sections in your configuration file:
|
||||
|
||||
|
||||
```toml
|
||||
[observability.grafana.contacts]
|
||||
emails = ['engineering@acme.com']
|
||||
|
||||
[[observability.grafana.contacts.pagerduty]]
|
||||
integrationKey = 'integration-key'
|
||||
severity = 'critical'
|
||||
class = 'infra'
|
||||
component = 'backend'
|
||||
group = 'group'
|
||||
|
||||
[[observability.grafana.contacts.discord]]
|
||||
url = 'https://discord.com/api/webhooks/...'
|
||||
avatarUrl = 'https://discord.com/api/avatar/...'
|
||||
|
||||
[[observability.grafana.contacts.slack]]
|
||||
recipient = 'recipient'
|
||||
token = 'token'
|
||||
username = 'username'
|
||||
iconEmoji = 'danger'
|
||||
iconURL = 'https://...'
|
||||
mentionUsers = ['user1', 'user2']
|
||||
mentionGroups = ['group1', 'group2']
|
||||
mentionChannel = 'channel'
|
||||
url = 'https://slack.com/api/webhooks/...'
|
||||
endpointURL = 'https://slack.com/api/endpoint/...'
|
||||
|
||||
[[observability.grafana.contacts.webhook]]
|
||||
url = 'https://webhook.example.com'
|
||||
httpMethod = 'POST'
|
||||
username = 'user'
|
||||
password = 'password'
|
||||
authorizationScheme = 'Bearer'
|
||||
authorizationCredentials = 'token'
|
||||
maxAlerts = 10
|
||||
```
|
||||
|
||||
Once you have added them to your configuration and deployed them you should be able to see them in your grafana dashboard under "Settings" -> "Contact points" -> "Nhost Managed Contacts":
|
||||
|
||||

|
||||
|
||||
If you click on "View" you should be able to see a test button you can use to ensure your contacts are properly configured.
|
||||
|
||||
### SMTP
|
||||
|
||||
If you are planning to send emails as part of your alerting, you need to configure some SMTP settings as well. To do so add to your configuration:
|
||||
|
||||
```toml
|
||||
[observability.grafana.smtp]
|
||||
host = 'localhost'
|
||||
port = 25
|
||||
sender = 'admin@localhost'
|
||||
user = 'smtpUser'
|
||||
password = 'smtpPassword'
|
||||
```
|
||||
|
||||
### Alerting
|
||||
|
||||
To enable alerting simply add to your configuration:
|
||||
|
||||
```toml
|
||||
[observability.grafana.alerting]
|
||||
enabled = true
|
||||
```
|
||||
|
||||
This will enable the following rules, which you can find in your grafana dashboard under "Alert rules":
|
||||
|
||||

|
||||
|
||||
1. **High CPU usage**
|
||||
- Trigger: CPU usage > 75%
|
||||
- Duration: Sustained for 5-10 minutes
|
||||
|
||||
2. **Low disk space**
|
||||
- Trigger: Disk utilization > 75%
|
||||
- Duration: Persistent for 5-10 minutes
|
||||
|
||||
3. **Low free memory**
|
||||
- Trigger: Memory usage > 75%
|
||||
- Duration: Continuous for 5-10 minutes
|
||||
|
||||
4. **Service restarted due to lack of memory**
|
||||
- Trigger: Any service restart due to memory exhaustion
|
||||
- Duration: Immediate upon occurrence
|
||||
|
||||
5. **High request error rate**
|
||||
- Trigger: Request error rate > 25%
|
||||
- Duration: Maintained for 5-10 minutes
|
||||
|
||||
After they have been enabling they will start notifying your contact points when the conditions are met. For instance, here is an email sent due to a high error rate:
|
||||
|
||||

|
||||
|
||||
## Advanced configuration
|
||||
|
||||
In addition, Team and Enterprise projects can perform any changes they want. For instance you can add users, configure an OAuth provider for user authentication, add datasources, you can configure your own alerts, etc.
|
||||
|
||||
## Beta
|
||||
|
||||
Metrics is in beta, its functionality and pricing might change.
|
||||
|
||||
### Limitations
|
||||
|
||||
- Dashboards can be updated or created, but they won't persist after a deployment.
|
||||
|
||||
@@ -45,7 +45,7 @@ Given that not all endpoints are equally sensitive, Auth supports more complex r
|
||||
|
||||
| Endpoints | Key | Limits | Description | Minimum version |
|
||||
| ----------------------|-----|--------|-------------|-----------------|
|
||||
| Any that sends emails<sup>1</sup> | Global | 50 / hour | Not configurable. This limit applies to any project without custom SMTP settings | 0.33.0 |
|
||||
| Any that sends emails<sup>1</sup> | Global | 10 / hour | Not configurable. This limit applies to any project without custom SMTP settings | 0.33.0 |
|
||||
| Any that sends emails<sup>1</sup> | Client IP | 10 / hour | Configurable. This limit applies to any project with custom SMTP settings and is configurable | 0.33.0 |
|
||||
| Any that sends SMS<sup>2</sup> | Client IP | 10 / hour | Configurable. | 0.33.0 |
|
||||
| Any endpoint that an attacker may try to brute-force. This includes sign-in and verify endpoints<sup>3</sup> | Client IP | 10 / 5 minutes | Configurable | 0.33.0 |
|
||||
|
||||
185
docs/platform/tls.mdx
Normal file
185
docs/platform/tls.mdx
Normal file
@@ -0,0 +1,185 @@
|
||||
---
|
||||
title: TLS Configuration
|
||||
description: Advanced TLS settings
|
||||
icon: file-certificate
|
||||
---
|
||||
|
||||
Below you can find some advanced TLS functionality you can enable.
|
||||
|
||||
<Warning>
|
||||
Advanced TLS settings are only available with [custom domains](/platform/custom-domains)
|
||||
</Warning>
|
||||
|
||||
## TLS Client Authentication
|
||||
|
||||
With TLS Client Authentication you can configure our platform to require clients to include a client certificate signed by a CA of your choosing. If the client doesn't provide a certificate or the certificate isn't signed by the correct CA the request will be denied. If the request includes a valid certificate the request will be forwarded to your service and will include information about the TLS configuration.
|
||||
|
||||
To configure TLS client authentication you can use the configuration below:
|
||||
|
||||
```toml
|
||||
[[functions.resources.ingress]]
|
||||
fqdn = ["func.acme.com"]
|
||||
|
||||
[functions.resources.networking.ingresses.tls]
|
||||
clientCA = "{{ secrets.client_ca }}"
|
||||
```
|
||||
|
||||
### Headers
|
||||
|
||||
The following headers will be added to all successful requests:
|
||||
|
||||
- `ssl-client-cert`: The client cetificate that was used
|
||||
- `ssl-client-issuer-dn`: Client certificate's issuer DN
|
||||
- `ssl-client-subject-dn`: Client certificate;s distinguished name
|
||||
- `ssl-client-verify`: Result of the operation. As we only forward requests on success the value should always be `SUCCESS`.
|
||||
|
||||
|
||||
### Guide
|
||||
|
||||
Here is a quick guide on how to enable TLS client authentication for the functions service using self-signed certificates.
|
||||
|
||||
#### Creating the CA
|
||||
|
||||
First we need to create a CA that will be used to sign and validate the client certificates.
|
||||
|
||||
<Steps>
|
||||
<Step title="Generate the CA private key">
|
||||
|
||||
First, we need to create a private key for our Certificate Authority. We'll use a 4096-bit RSA key for strong security.
|
||||
|
||||
```
|
||||
openssl genrsa -aes256 -out ca.key 4096
|
||||
```
|
||||
|
||||
This command will prompt you to enter a passphrase to protect the CA private key. Make sure to choose a strong passphrase and keep it safe.
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Create the CA certificate">
|
||||
Now that we have the private key, let's create a self-signed CA certificate:
|
||||
|
||||
```
|
||||
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt
|
||||
```
|
||||
|
||||
This command will prompt you for various details to include in the certificate. The most important field is the Common Name (CN), which should be a descriptive name for your CA.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
The resulting `ca.key` file will be needed later to sign client certificates while the result `ca.crt` will be needed to validate them.
|
||||
|
||||
|
||||
#### Creating client certificates
|
||||
|
||||
Now we can proceed to create client certificats. You can repeat the steps below to create as many as you need.
|
||||
|
||||
|
||||
<Steps>
|
||||
|
||||
<Step title="Generate a private key for the client">
|
||||
First, we'll create a private key for the client certificate:
|
||||
|
||||
```
|
||||
openssl genrsa -out client.key 2048
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Create a Certificate Signing Request (CSR) for the client">
|
||||
|
||||
Now, we'll create a CSR for the client certificate:
|
||||
|
||||
```
|
||||
openssl req -new -key client.key -out client.csr
|
||||
```
|
||||
|
||||
Fill in the prompted information. The Common Name (CN) should typically be the name of the client or user.
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Create a configuration file for the client certificate">
|
||||
|
||||
Create a file named `client.ext` with the following content:
|
||||
|
||||
```
|
||||
authorityKeyIdentifier=keyid,issuer
|
||||
basicConstraints=CA:FALSE
|
||||
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
|
||||
extendedKeyUsage = clientAuth
|
||||
```
|
||||
|
||||
This configuration specifies that the certificate is for client authentication.
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Generate the client certificate">
|
||||
|
||||
Now, we'll use our CA to sign the client certificate:
|
||||
|
||||
```
|
||||
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 365 -sha256 -extfile client.ext
|
||||
```
|
||||
|
||||
This command will prompt you for the CA private key passphrase you set earlier.
|
||||
</Step>
|
||||
|
||||
The resulting `client.key` and `client.crt` files will be needed by the user to authenticate requests.
|
||||
|
||||
</Steps>
|
||||
|
||||
#### Configure your service
|
||||
|
||||
With everything in place you can configure your service. Imagine we have an already deployed project and we want to enable this for all functions. First we will head to the dashboard -> project -> settings -> secrets and create a new secret named `CLIENT_CA` with the contents of the `ca.crt` file. Afterwards we will deploy the following configuration:
|
||||
|
||||
```toml
|
||||
[[functions.resources.networking.ingresses]]
|
||||
fqdn = [ "functions.acme.com" ]
|
||||
|
||||
[functions.resources.networking.ingresses.tls]
|
||||
clientCA = "{{ secrets.CLIENT_CA }}"
|
||||
```
|
||||
#### Profit
|
||||
|
||||
Our project has a function that echoes back request parameters, including headers. We will use this to inspect the TLS headers added to the request and that you can use for further validation if you need it. First, we can query the function without passing any client certificate:
|
||||
|
||||
```
|
||||
$ curl https://functions.acme.com/v1/echo
|
||||
<html>
|
||||
<head><title>400 No required SSL certificate was sent</title></head>
|
||||
<body>
|
||||
<center><h1>400 Bad Request</h1></center>
|
||||
<center>No required SSL certificate was sent</center>
|
||||
<hr><center>nginx</center>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
As you can see the request was rejected. Now we can add a valid client certificate and the corresponding key to the request:
|
||||
|
||||
|
||||
```
|
||||
$ curl --cert client.crt --key client.key https://functions.acme.com/v1/echo | jq
|
||||
{
|
||||
"headers": {
|
||||
"accept": "*/*",
|
||||
"ssl-client-cert": "-----BEGIN%20CERTIFICATE-----...ommitted for brevity...-----END%20CERTIFICATE-----%0A",
|
||||
"ssl-client-issuer-dn": "OU=IT,O=Acme Org.,L=Stockholm,ST=Stockholm,C=SE",
|
||||
"ssl-client-subject-dn": "emailAddress=jane@acme.org,OU=IT,O=Acme Org.,L=Sausalito,ST=California,C=US",
|
||||
"ssl-client-verify": "SUCCESS",
|
||||
"user-agent": "curl/8.7.1",
|
||||
"x-forwarded-for": "2001:8b1:8ac9:4100:46b1:3412:1342:9319",
|
||||
"x-forwarded-host": "functions.acme.com",
|
||||
"x-forwarded-port": "443",
|
||||
"x-forwarded-proto": "https",
|
||||
"x-forwarded-scheme": "https",
|
||||
"x-real-ip": "2001:8b1:8ac9:4100:46b1:3412:1342:9319",
|
||||
"x-request-id": "8f80c26ee873bfc9db7ce9073eecd17a",
|
||||
"x-scheme": "https",
|
||||
"content-length": 0
|
||||
},
|
||||
"query": {},
|
||||
"node": "v20.17.0",
|
||||
"arch": "arm64"
|
||||
}
|
||||
```
|
||||
|
||||
With a valid certificate you can see the request went through and it includes the `ssl-client-*` headers providing additional information about it.
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost-examples/cli
|
||||
|
||||
## 0.3.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.1.10
|
||||
|
||||
## 0.3.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/cli",
|
||||
"version": "0.3.11",
|
||||
"version": "0.3.12",
|
||||
"main": "src/index.mjs",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @nhost-examples/codegen-react-apollo
|
||||
|
||||
## 0.4.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [55d8bb5]
|
||||
- @nhost/react@3.6.0
|
||||
- @nhost/react-apollo@13.0.0
|
||||
|
||||
## 0.4.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 52a38fe: chore: update dependencies to address security vulnerabilities
|
||||
|
||||
## 0.4.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/codegen-react-apollo",
|
||||
"version": "0.4.11",
|
||||
"version": "0.4.13",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"codegen": "graphql-codegen",
|
||||
@@ -36,6 +36,6 @@
|
||||
"postcss": "^8.4.38",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "^5.2.7"
|
||||
"vite": "^5.4.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# @nhost-examples/codegen-react-query
|
||||
|
||||
## 0.4.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [55d8bb5]
|
||||
- @nhost/react@3.6.0
|
||||
|
||||
## 0.4.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 52a38fe: chore: update dependencies to address security vulnerabilities
|
||||
|
||||
## 0.4.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/codegen-react-query",
|
||||
"version": "0.4.11",
|
||||
"version": "0.4.13",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"codegen": "graphql-codegen",
|
||||
@@ -37,6 +37,6 @@
|
||||
"postcss": "^8.4.38",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "^5.2.7"
|
||||
"vite": "^5.4.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @nhost-examples/react-urql
|
||||
|
||||
## 0.3.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [55d8bb5]
|
||||
- @nhost/react@3.6.0
|
||||
- @nhost/react-urql@10.0.0
|
||||
|
||||
## 0.3.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 52a38fe: chore: update dependencies to address security vulnerabilities
|
||||
|
||||
## 0.3.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/codegen-react-urql",
|
||||
"private": true,
|
||||
"version": "0.3.11",
|
||||
"version": "0.3.13",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
@@ -30,6 +30,6 @@
|
||||
"postcss": "^8.4.38",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "^5.2.7"
|
||||
"vite": "^5.4.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @nhost-examples/multi-tenant-one-to-many
|
||||
|
||||
## 2.2.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.1.10
|
||||
|
||||
## 2.2.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 52a38fe: chore: update dependencies to address security vulnerabilities
|
||||
|
||||
## 2.2.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/multi-tenant-one-to-many",
|
||||
"private": true,
|
||||
"version": "2.2.11",
|
||||
"version": "2.2.13",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {},
|
||||
@@ -10,7 +10,7 @@
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"express": "^4.19.2",
|
||||
"express": "^4.20.0",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# @nhost-examples/nextjs
|
||||
|
||||
## 0.3.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [55d8bb5]
|
||||
- @nhost/react@3.6.0
|
||||
- @nhost/react-apollo@13.0.0
|
||||
- @nhost/nextjs@2.1.22
|
||||
|
||||
## 0.3.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 52a38fe: chore: update dependencies to address security vulnerabilities
|
||||
- Updated dependencies [52a38fe]
|
||||
- @nhost/nextjs@2.1.21
|
||||
|
||||
## 0.3.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
2
examples/nextjs/next-env.d.ts
vendored
2
examples/nextjs/next-env.d.ts
vendored
@@ -2,4 +2,4 @@
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/nextjs",
|
||||
"version": "0.3.11",
|
||||
"version": "0.3.13",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -24,7 +24,7 @@
|
||||
"@nhost/react": "workspace:^",
|
||||
"@nhost/react-apollo": "workspace:^",
|
||||
"graphql": "16.8.1",
|
||||
"next": "^14.1.4",
|
||||
"next": "^14.2.10",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-icons": "^4.12.0"
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost-examples/node-storage
|
||||
|
||||
## 0.2.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.1.10
|
||||
|
||||
## 0.2.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/node-storage",
|
||||
"version": "0.2.11",
|
||||
"version": "0.2.12",
|
||||
"private": true,
|
||||
"description": "This is an example of how to use the Storage with Node.js",
|
||||
"main": "src/index.mjs",
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @nhost-examples/nextjs-server-components
|
||||
|
||||
## 0.4.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.1.10
|
||||
|
||||
## 0.4.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 52a38fe: chore: update dependencies to address security vulnerabilities
|
||||
|
||||
## 0.4.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/nextjs-server-components",
|
||||
"version": "0.4.12",
|
||||
"version": "0.4.14",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -18,7 +18,7 @@
|
||||
"form-data": "^4.0.0",
|
||||
"graphql": "16.8.1",
|
||||
"js-cookie": "^3.0.5",
|
||||
"next": "^14.1.4",
|
||||
"next": "^14.2.10",
|
||||
"postcss": "^8.4.38",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
---
|
||||
|
||||
## 0.4.1
|
||||
### Patch Changes
|
||||
|
||||
- 52a38fe: chore: update dependencies to address security vulnerabilities
|
||||
|
||||
## 0.4.0
|
||||
### Minor Changes
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/sveltekit",
|
||||
"version": "0.4.0",
|
||||
"version": "0.4.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
@@ -29,7 +29,7 @@
|
||||
"svelte-check": "^3.6.8",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript": "^5.4.3",
|
||||
"vite": "^5.2.7",
|
||||
"vite": "^5.4.6",
|
||||
"vitest": "^0.25.8"
|
||||
},
|
||||
"type": "module",
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @nhost-examples/react-apollo
|
||||
|
||||
## 1.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [55d8bb5]
|
||||
- @nhost/react@3.6.0
|
||||
- @nhost/react-apollo@13.0.0
|
||||
|
||||
## 1.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4d6b722: fix: add check for elevated permission before deleting a security key
|
||||
- 3dcbacf: fix: add elevated permission check before adding a security key
|
||||
- 52a38fe: chore: update dependencies to address security vulnerabilities
|
||||
|
||||
## 1.0.0
|
||||
|
||||
### Major Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/react-apollo",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.2",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -32,8 +32,8 @@
|
||||
"next-themes": "^0.3.0",
|
||||
"prism-react-renderer": "^2.3.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-code-block": "^1.0.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-hook-form": "^7.52.2",
|
||||
"react-router-dom": "^6.22.3",
|
||||
@@ -62,6 +62,6 @@
|
||||
"totp-generator": "^0.0.13",
|
||||
"typescript": "^5.5.3",
|
||||
"typescript-eslint": "^8.0.0",
|
||||
"vite": "^5.4.0"
|
||||
"vite": "^5.4.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,12 @@ import { Form, FormControl, FormField, FormItem, FormMessage } from '@/component
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { ApolloError, gql, useMutation } from '@apollo/client'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useAddSecurityKey, useUserId } from '@nhost/react'
|
||||
import {
|
||||
useAddSecurityKey,
|
||||
useElevateSecurityKeyEmail,
|
||||
useUserEmail,
|
||||
useUserId
|
||||
} from '@nhost/react'
|
||||
import { useAuthQuery } from '@nhost/react-apollo'
|
||||
import { Fingerprint, Info, Plus, Trash } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
@@ -29,9 +34,11 @@ const addSecurityKeySchema = z.object({
|
||||
|
||||
export default function SecurityKeys() {
|
||||
const userId = useUserId()
|
||||
const [showAddSecurityKeyDialog, setShowAddSecurityDialog] = useState(false)
|
||||
const email = useUserEmail()
|
||||
const { add } = useAddSecurityKey()
|
||||
const [keys, setKeys] = useState<SecurityKey[]>([])
|
||||
const { elevated, elevateEmailSecurityKey } = useElevateSecurityKeyEmail()
|
||||
const [showAddSecurityKeyDialog, setShowAddSecurityDialog] = useState(false)
|
||||
|
||||
const { refetch: refetchSecurityKeys } = useAuthQuery<SecurityKeysQuery>(
|
||||
gql`
|
||||
@@ -52,30 +59,6 @@ export default function SecurityKeys() {
|
||||
}
|
||||
)
|
||||
|
||||
const form = useForm<z.infer<typeof addSecurityKeySchema>>({
|
||||
resolver: zodResolver(addSecurityKeySchema),
|
||||
defaultValues: {
|
||||
nickname: ''
|
||||
}
|
||||
})
|
||||
|
||||
const onSubmit = async (values: z.infer<typeof addSecurityKeySchema>) => {
|
||||
const { nickname } = values
|
||||
const { key, isError, error } = await add(nickname)
|
||||
|
||||
if (isError) {
|
||||
toast.error(error?.message)
|
||||
} else {
|
||||
if (key) {
|
||||
setKeys((previousKeys) => [...previousKeys, key])
|
||||
setShowAddSecurityDialog(false)
|
||||
}
|
||||
|
||||
form.reset()
|
||||
await refetchSecurityKeys()
|
||||
}
|
||||
}
|
||||
|
||||
const [removeKey] = useMutation<{
|
||||
deleteAuthUserSecurityKey?: {
|
||||
id: string
|
||||
@@ -97,7 +80,58 @@ export default function SecurityKeys() {
|
||||
}
|
||||
)
|
||||
|
||||
const form = useForm<z.infer<typeof addSecurityKeySchema>>({
|
||||
resolver: zodResolver(addSecurityKeySchema),
|
||||
defaultValues: {
|
||||
nickname: ''
|
||||
}
|
||||
})
|
||||
|
||||
const elevatePermission = async () => {
|
||||
if (!elevated && keys.length > 0) {
|
||||
try {
|
||||
const { elevated } = await elevateEmailSecurityKey(email as string)
|
||||
|
||||
if (!elevated) {
|
||||
throw new Error('Permissions were not elevated')
|
||||
}
|
||||
return true
|
||||
} catch {
|
||||
toast.error('Could not elevate permissions')
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true // Return true if already elevated or no keys
|
||||
}
|
||||
|
||||
const onSubmit = async (values: z.infer<typeof addSecurityKeySchema>) => {
|
||||
const { nickname } = values
|
||||
|
||||
const permissionGranted = await elevatePermission()
|
||||
|
||||
if (!permissionGranted) {
|
||||
return
|
||||
}
|
||||
|
||||
const { key, isError, error } = await add(nickname)
|
||||
|
||||
if (isError) {
|
||||
toast.error(error?.message)
|
||||
} else if (key) {
|
||||
setKeys((previousKeys) => [...previousKeys, key])
|
||||
setShowAddSecurityDialog(false)
|
||||
form.reset()
|
||||
await refetchSecurityKeys()
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteSecurityKey = async (id: string) => {
|
||||
const permissionGranted = await elevatePermission()
|
||||
|
||||
if (!permissionGranted) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await removeKey({ variables: { id } })
|
||||
await refetchSecurityKeys()
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# @nhost-examples/react-gqty
|
||||
|
||||
## 1.2.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [55d8bb5]
|
||||
- @nhost/react@3.6.0
|
||||
|
||||
## 1.2.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 52a38fe: chore: update dependencies to address security vulnerabilities
|
||||
|
||||
## 1.2.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/react-gqty",
|
||||
"private": true,
|
||||
"version": "1.2.11",
|
||||
"version": "1.2.13",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -27,6 +27,6 @@
|
||||
"postcss": "^8.4.38",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"typescript": "^4.9.5",
|
||||
"vite": "^5.2.7"
|
||||
"vite": "^5.4.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @nhost-examples/react-native
|
||||
|
||||
## 0.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [55d8bb5]
|
||||
- @nhost/react@3.6.0
|
||||
- @nhost/react-apollo@13.0.0
|
||||
|
||||
## 0.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/react-native",
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.6",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"android": "react-native run-android",
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @nhost-examples/vue-apollo
|
||||
|
||||
## 0.6.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.1.10
|
||||
- @nhost/apollo@7.1.7
|
||||
- @nhost/vue@2.6.7
|
||||
|
||||
## 0.6.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 52a38fe: chore: update dependencies to address security vulnerabilities
|
||||
|
||||
## 0.6.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/vue-apollo",
|
||||
"private": true,
|
||||
"version": "0.6.11",
|
||||
"version": "0.6.13",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
@@ -37,7 +37,7 @@
|
||||
"@xstate/inspect": "^0.6.5",
|
||||
"sass": "1.32.0",
|
||||
"typescript": "4.9.4",
|
||||
"vite": "^5.2.7",
|
||||
"vite": "^5.4.6",
|
||||
"vue-tsc": "^0.38.9"
|
||||
},
|
||||
"eslintConfig": {
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# @nhost-examples/vue-quickstart
|
||||
|
||||
## 0.2.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/apollo@7.1.7
|
||||
- @nhost/vue@2.6.7
|
||||
|
||||
## 0.2.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 52a38fe: chore: update dependencies to address security vulnerabilities
|
||||
|
||||
## 0.2.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/vue-quickstart",
|
||||
"version": "0.2.11",
|
||||
"version": "0.2.13",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
@@ -35,7 +35,7 @@
|
||||
"unocss": "^0.33.5",
|
||||
"unplugin-auto-import": "^0.17.5",
|
||||
"unplugin-vue-components": "^0.26.0",
|
||||
"vite": "^5.2.7",
|
||||
"vite": "^5.4.6",
|
||||
"vite-plugin-pages": "^0.28.0",
|
||||
"vue-tsc": "^0.38.9"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/apollo
|
||||
|
||||
## 7.1.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.1.10
|
||||
|
||||
## 7.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/apollo",
|
||||
"version": "7.1.6",
|
||||
"version": "7.1.7",
|
||||
"description": "Nhost Apollo Client library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @nhost/react-apollo
|
||||
|
||||
## 13.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [55d8bb5]
|
||||
- @nhost/react@3.6.0
|
||||
- @nhost/apollo@7.1.7
|
||||
|
||||
## 12.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/react-apollo",
|
||||
"version": "12.0.6",
|
||||
"version": "13.0.0",
|
||||
"description": "Nhost React Apollo client",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @nhost/react-urql
|
||||
|
||||
## 10.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [55d8bb5]
|
||||
- @nhost/react@3.6.0
|
||||
|
||||
## 9.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/react-urql",
|
||||
"version": "9.0.6",
|
||||
"version": "10.0.0",
|
||||
"description": "Nhost React URQL client",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
58
observability/grafana/contact_points.yaml
Normal file
58
observability/grafana/contact_points.yaml
Normal file
@@ -0,0 +1,58 @@
|
||||
apiVersion: 1
|
||||
contactPoints:
|
||||
- orgId: 1
|
||||
name: Nhost Managed Contacts
|
||||
receivers:
|
||||
{{ if .Contacts.Emails }}
|
||||
- uid: 1
|
||||
type: email
|
||||
settings:
|
||||
addresses: {{ join .Contacts.Emails "," }}
|
||||
singleEmail: false
|
||||
sendReminder: true
|
||||
{{ end }}
|
||||
{{- range $i, $c := .Contacts.Pagerduty }}
|
||||
- uid: {{ add 100 $i }}
|
||||
type: pagerduty
|
||||
settings:
|
||||
integrationKey: {{ $c.IntegrationKey }}
|
||||
severity: {{ $c.Severity }}
|
||||
class: {{ $c.Class }}
|
||||
component: {{ $c.Component }}
|
||||
group: {{ $c.Group }}
|
||||
{{- end }}
|
||||
{{- range $i, $c := .Contacts.Discord }}
|
||||
- uid: {{ add 200 $i }}
|
||||
type: discord
|
||||
settings:
|
||||
url: {{ $c.URL }}
|
||||
avatar_url: {{ $c.AvatarURL }}
|
||||
use_discord_username: true
|
||||
{{- end }}
|
||||
{{- range $i, $c := .Contacts.Slack }}
|
||||
- uid: {{ add 300 $i }}
|
||||
type: slack
|
||||
settings:
|
||||
recipient: {{ $c.Recipient }}
|
||||
token: {{ $c.Token }}
|
||||
username: {{ $c.Username }}
|
||||
icon_emoji: {{ $c.IconEmoji }}
|
||||
icon_url: {{ $c.IconURL }}
|
||||
mentionUsers: {{ join $c.MentionUsers "," }}
|
||||
mentionGroups: {{ join $c.MentionGroups "," }}
|
||||
mentionChannel: {{ $c.MentionChannel }}
|
||||
url: {{ $c.URL }}
|
||||
endpointUrl: {{ $c.EndpointURL }}
|
||||
{{- end }}
|
||||
{{- range $i, $c := .Contacts.Webhook }}
|
||||
- uid: {{ add 400 $i }}
|
||||
type: webhook
|
||||
settings:
|
||||
url: {{ $c.URL }}
|
||||
httpMethod: {{ $c.HTTPMethod }}
|
||||
username: {{ $c.Username }}
|
||||
password: {{ $c.Password }}
|
||||
authorization_scheme: {{ $c.AuthorizationScheme }}
|
||||
authorization_credentials: {{ $c.AuthorizationCredentials }}
|
||||
maxAlerts: '{{ $c.MaxAlerts }}'
|
||||
{{- end }}
|
||||
@@ -44,7 +44,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -91,7 +91,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
@@ -108,7 +108,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -155,7 +155,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
@@ -172,7 +172,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -219,7 +219,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
@@ -249,7 +249,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "Number of invocations by method/function",
|
||||
"fieldConfig": {
|
||||
@@ -327,13 +327,13 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(method, route) (increase(functions_requests_total{method=~\"$method\",route=~\"$route\"}[$__rate_interval]))",
|
||||
"format": "time_series",
|
||||
"interval": "2m",
|
||||
"legendFormat": "{{method}} {{route}}",
|
||||
"legendFormat": "{{ print "{{ method }} - {{ route }}" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -344,7 +344,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "Number of invocations by status response",
|
||||
"fieldConfig": {
|
||||
@@ -422,13 +422,13 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(status) (increase(functions_requests_total{method=~\"$method\",route=~\"$route\"}[$__rate_interval]))",
|
||||
"format": "time_series",
|
||||
"interval": "2m",
|
||||
"legendFormat": "{{status}}",
|
||||
"legendFormat": "{{ print "{{status}}" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -439,7 +439,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
@@ -518,12 +518,12 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(method, route) (increase(functions_bytes_sent{method=~\"$method\", route=~\"$route\"}[$__rate_interval])) / sum by(method, route) (increase(functions_requests_total{method=~\"$method\", route=~\"$route\"}[$__rate_interval]))",
|
||||
"interval": "2m",
|
||||
"legendFormat": "{{ method }} - {{ route }}",
|
||||
"legendFormat": "{{ print "{{ method }} - {{ route }}" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -534,7 +534,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -601,7 +601,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
@@ -609,7 +609,7 @@
|
||||
"format": "table",
|
||||
"instant": true,
|
||||
"interval": "2m",
|
||||
"legendFormat": "{{method}} {{route}}",
|
||||
"legendFormat": "{{ print "{{ method }} - {{ route }}" }}",
|
||||
"range": false,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -634,7 +634,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "Time the slowest response took",
|
||||
"fieldConfig": {
|
||||
@@ -712,12 +712,12 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "histogram_quantile(1, rate(functions_duration_seconds_bucket[$__rate_interval]))",
|
||||
"interval": "2m",
|
||||
"legendFormat": "{{ method }} - {{ route }}",
|
||||
"legendFormat": "{{ print "{{ method }} - {{ route }}" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -728,7 +728,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "The 95th percentile of response times refers to the value below which 95% of response times fall. In other words, it is the point at which only 5% of response times are higher",
|
||||
"fieldConfig": {
|
||||
@@ -806,12 +806,12 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "histogram_quantile(0.95, rate(functions_duration_seconds_bucket[$__rate_interval]))",
|
||||
"interval": "2m",
|
||||
"legendFormat": "{{ method }} - {{ route }}",
|
||||
"legendFormat": "{{ print "{{ method }} - {{ route }}" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -822,7 +822,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "The 75th percentile of response times refers to the value below which 75% of response times fall. In other words, it is the point at which 25% of response times are higher",
|
||||
"fieldConfig": {
|
||||
@@ -900,12 +900,12 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "histogram_quantile(0.75, rate(functions_duration_seconds_bucket[$__rate_interval]))",
|
||||
"interval": "2m",
|
||||
"legendFormat": "{{ method }} - {{ route }}",
|
||||
"legendFormat": "{{ print "{{ method }} - {{ route }}" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -916,7 +916,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
@@ -994,12 +994,12 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "builder",
|
||||
"expr": "sum by(method, route) (increase(functions_duration_seconds_sum{method=~\"$method\", route=~\"$route\"}[$__rate_interval])) / sum by(method, route) (increase(functions_duration_seconds_count{method=~\"$method\", route=~\"$route\"}[$__rate_interval]))",
|
||||
"interval": "2m",
|
||||
"legendFormat": "{{ method }} - {{ route }}",
|
||||
"legendFormat": "{{ print "{{ method }} - {{ route }}" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -1023,7 +1023,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "Number of invocations that failed divided by the total number of invocations",
|
||||
"fieldConfig": {
|
||||
@@ -1101,13 +1101,13 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(method, route) (increase(functions_requests_total{method=~\"$method\",route=~\"$route\",status=~\"^[4-5].*\"}[$__rate_interval])) / sum by(method, route) (increase(functions_requests_total[$__rate_interval]))",
|
||||
"format": "time_series",
|
||||
"interval": "2m",
|
||||
"legendFormat": "{{method}} {{ route }}",
|
||||
"legendFormat": "{{ print "{{ method }} - {{ route }}" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -1118,7 +1118,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -1184,7 +1184,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
@@ -1192,7 +1192,7 @@
|
||||
"format": "table",
|
||||
"instant": true,
|
||||
"interval": "2m",
|
||||
"legendFormat": "{{method}} {{route}}",
|
||||
"legendFormat": "{{ print "{{ method }} - {{ route }}" }}",
|
||||
"range": false,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -1205,7 +1205,9 @@
|
||||
"refresh": false,
|
||||
"schemaVersion": 37,
|
||||
"style": "dark",
|
||||
"tags": [],
|
||||
"tags": [
|
||||
"nhost"
|
||||
],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
@@ -1217,7 +1219,7 @@
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"definition": "label_values(functions_requests_total, method)",
|
||||
"hide": 0,
|
||||
@@ -1244,7 +1246,7 @@
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"definition": "label_values(functions_requests_total, route)",
|
||||
"hide": 0,
|
||||
@@ -31,7 +31,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "",
|
||||
"gridPos": {
|
||||
@@ -67,7 +67,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -144,12 +144,12 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(container,pod) (irate(container_cpu_usage_seconds_total{container=~\"hasura|hasura-graphi\"}[$__rate_interval])) * 1000",
|
||||
"interval": "2m",
|
||||
"legendFormat": "{{pod}}::{{container}}",
|
||||
"legendFormat": "{{ print "{{pod}}::{{container}}" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -160,7 +160,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -238,12 +238,12 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(container,pod) (container_memory_usage_bytes{container=~\"hasura|hasura-graphi\"})",
|
||||
"interval": "2m",
|
||||
"legendFormat": "{{pod}}::{{container}}",
|
||||
"legendFormat": "{{ print "{{pod}}::{{container}}" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -265,7 +265,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -342,7 +342,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(rate(graphql_requests_total{service=\"hasura-service\"}[$__rate_interval]))",
|
||||
@@ -354,13 +354,13 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "rate(graphql_requests_total{service=\"hasura-service\"}[$__rate_interval])",
|
||||
"hide": false,
|
||||
"interval": "2m",
|
||||
"legendFormat": "{{operation}}::{{name}}::{{field}}",
|
||||
"legendFormat": "{{ print "{{operation}}::{{name}}::{{field}}" }}",
|
||||
"range": true,
|
||||
"refId": "B"
|
||||
}
|
||||
@@ -371,7 +371,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -448,7 +448,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "graphql_websocket_connections_started_total{service=\"hasura-service\"} - graphql_websocket_connections_completed_total{service=\"hasura-service\"}",
|
||||
@@ -460,7 +460,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "rate(graphql_websocket_connections_started_total{service=\"hasura-service\"}[$__rate_interval])",
|
||||
@@ -473,7 +473,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "rate(graphql_websocket_connections_completed_total{service=\"hasura-service\"}[$__rate_interval])",
|
||||
@@ -489,7 +489,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -567,12 +567,12 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "histogram_quantile(0.95, sum(rate(graphql_requests_duration_miliseconds_bucket{service=\"hasura-service\"}[$__rate_interval])) by (le,operation,name,field))",
|
||||
"interval": "2m",
|
||||
"legendFormat": "{{operation}}::{{ name }}::{{field}}",
|
||||
"legendFormat": "{{ print "{{operation}}::{{ name }}::{{field}}" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -583,7 +583,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -660,12 +660,12 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "rate(graphql_requests_total{service=\"hasura-service\", result=\"failure\"}[$__rate_interval])",
|
||||
"interval": "2m",
|
||||
"legendFormat": "{{operation}}::{{ name }}::{{field}}",
|
||||
"legendFormat": "{{ print "{{operation}}::{{ name }}::{{field}}" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -676,7 +676,9 @@
|
||||
],
|
||||
"schemaVersion": 37,
|
||||
"style": "dark",
|
||||
"tags": [],
|
||||
"tags": [
|
||||
"nhost"
|
||||
],
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
@@ -1,35 +1,4 @@
|
||||
{
|
||||
"__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": [
|
||||
{
|
||||
@@ -75,7 +44,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "Number of requests by method/function",
|
||||
"fieldConfig": {
|
||||
@@ -153,13 +122,13 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"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}}",
|
||||
"legendFormat": "{{ print "{{method}} {{ingress}}" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -170,7 +139,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "Number of requests by status response",
|
||||
"fieldConfig": {
|
||||
@@ -248,13 +217,13 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(status) (increase(nginx_ingress_controller_requests{method=~\"$method\",ingress=~\"$ingress\"}[$__rate_interval]))",
|
||||
"format": "time_series",
|
||||
"interval": "2m",
|
||||
"legendFormat": "{{status}}",
|
||||
"legendFormat": "{{ print "{{status}}" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -265,7 +234,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
@@ -344,12 +313,12 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"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 }}",
|
||||
"legendFormat": "{{ print "{{method}} {{ingress}}" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -360,7 +329,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -427,7 +396,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
@@ -435,7 +404,7 @@
|
||||
"format": "table",
|
||||
"instant": true,
|
||||
"interval": "2m",
|
||||
"legendFormat": "{{ingress}} {{method}}",
|
||||
"legendFormat": "{{ print "{{method}} {{ingress}}" }}",
|
||||
"range": false,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -460,7 +429,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
@@ -539,12 +508,12 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"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 }}",
|
||||
"legendFormat": "{{ print "{{method}} {{ingress}}" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -568,7 +537,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "Number of requests that failed divided by the total number of requests",
|
||||
"fieldConfig": {
|
||||
@@ -647,13 +616,13 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"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 }}",
|
||||
"legendFormat": "{{ print "{{method}} {{ingress}}" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -664,7 +633,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -731,7 +700,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "prometheus"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
@@ -739,7 +708,7 @@
|
||||
"format": "table",
|
||||
"instant": true,
|
||||
"interval": "2m",
|
||||
"legendFormat": "{{ingress}} {{method}}",
|
||||
"legendFormat": "{{ print "{{method}} {{ingress}}" }}",
|
||||
"range": false,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -751,7 +720,9 @@
|
||||
],
|
||||
"schemaVersion": 37,
|
||||
"style": "dark",
|
||||
"tags": [],
|
||||
"tags": [
|
||||
"nhost"
|
||||
],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
@@ -802,4 +773,4 @@
|
||||
"uid": "WOWEHb7Sz",
|
||||
"version": 16,
|
||||
"weekStart": ""
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,4 @@
|
||||
{
|
||||
"__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": "text",
|
||||
"name": "Text",
|
||||
"version": ""
|
||||
},
|
||||
{
|
||||
"type": "panel",
|
||||
"id": "timeseries",
|
||||
"name": "Time series",
|
||||
"version": ""
|
||||
}
|
||||
],
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
@@ -62,7 +25,6 @@
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"links": [],
|
||||
"liveNow": false,
|
||||
"panels": [
|
||||
@@ -82,7 +44,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 11,
|
||||
@@ -100,13 +62,13 @@
|
||||
"content": "This dashboard shows the overall resources used by every service in your project.\n\nMetrics service is currently in **beta** so things may change.\n\nKeep in mind that while you might be change settings, edit the dashboard or even create new ones these changes are not persisted. If you want to have different settings or even your own dashboards, please, contact us as we are looking for use cases to build the feature.\n\nDocumentation about our platform:\n\n- [Compute Resources](https://docs.nhost.io/platform/compute)\n- [Service Replicas](https://docs.nhost.io/platform/service-replicas)",
|
||||
"mode": "markdown"
|
||||
},
|
||||
"pluginVersion": "9.2.0",
|
||||
"pluginVersion": "11.2.0",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 11,
|
||||
@@ -124,7 +86,7 @@
|
||||
"content": "#### Pods\n\nEach service is comprised by at least one \"pod\" and, in the case of [replicas](https://docs.nhost.io/platform/service-replicas), you should see as many pods as replicas configured. Each pod is identified by the sevice name + some unique identifier, for instance, `\nhasura-auth-7995bfd767-mvthp`.\n\nPods can come and go for various reasons:\n\n1. When there is a configuration change. When this happens a new pod is created witht he new configuration. After the new pod is ready the old one is decommissioned. This means changes in configuration are hitless. The exception is postgres, when postgres configuration changes we need to bring it down cleanly before we start a new one so there is a short donwtime while this occurrs (1-2 min).\n2. When the process crashes due to an unexpected error. In this case the platform should detect the event and create a new pod immediately.\n3. When the process exceeds its allotted memory the pod is terminated and a new one is created.\n\n#### Throttling\n\nAs pro projects have shared CPUs services can throttle when they attempt to use more resources than they have available. Throttling metrics are hard to grasp but it is important to understand they can have a big impact on response times. Thottling happens in internvals of time (100ms) so it is important to look at both the throttling time and throttling % metrics. If the % is low throttling time might not be very impactful but the higher the percentage gets the higher the impact it can have.\n\nTo avoid throttling consider using [dedicated compute resources](https://docs.nhost.io/platform/compute#dedicated-compute)",
|
||||
"mode": "markdown"
|
||||
},
|
||||
"pluginVersion": "9.2.0",
|
||||
"pluginVersion": "11.2.0",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
@@ -143,7 +105,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "CPU utilization is calculated by calculating the average usage between two datapoints. At maximum granularity this is a 1 minute average, but when selecting longer periods of time granularity can decrease.\n\nGiven that the graph shows average usage it might be difficult to detect very sudden spikes.",
|
||||
"fieldConfig": {
|
||||
@@ -152,11 +114,13 @@
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"barWidthFactor": 0.6,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
@@ -165,6 +129,7 @@
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
@@ -222,7 +187,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(pod) (irate(container_cpu_usage_seconds_total{container!~\"POD|\"}[$__rate_interval])) * 1000",
|
||||
@@ -238,7 +203,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -246,11 +211,13 @@
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"barWidthFactor": 0.6,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
@@ -259,6 +226,7 @@
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
@@ -316,7 +284,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by (pod) (container_memory_usage_bytes{container!~\"POD|\"})",
|
||||
@@ -331,7 +299,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "When a service lacks resources the process is throttled until resources are available again.\n\nThis graph shows for how long pods are being throttled. This can incur in added latency to requests.",
|
||||
"fieldConfig": {
|
||||
@@ -340,11 +308,13 @@
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"barWidthFactor": 0.6,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
@@ -353,6 +323,7 @@
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
@@ -411,7 +382,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(pod) (rate(container_cpu_cfs_throttled_seconds_total{container!~\"POD|\"}[$__rate_interval]))",
|
||||
@@ -428,7 +399,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "When a service lacks resources the process is throttled until resources are available again.\n\nThis graph shows how often the process is being throttled. As throttling happens in intervals of 100ms here you can see how many of those intervals required throttling.",
|
||||
"fieldConfig": {
|
||||
@@ -437,11 +408,13 @@
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"barWidthFactor": 0.6,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
@@ -450,6 +423,7 @@
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
@@ -508,7 +482,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(pod) (rate(container_cpu_cfs_throttled_periods_total{container!~\"POD|\"}[$__rate_interval]))/sum by(pod) (rate(container_cpu_cfs_periods_total{container!~\"POD|\"}[$__rate_interval]))",
|
||||
@@ -525,7 +499,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -567,8 +541,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -603,7 +576,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(pod) (rate(container_network_transmit_bytes_total[$__rate_interval]))",
|
||||
@@ -615,7 +588,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "- sum by(pod) (rate(container_network_receive_bytes_total[$__rate_interval]))",
|
||||
@@ -632,7 +605,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "This graph shows when a service was restarted. There are two main reasons why a service may be restarted:\n\n- OOMKilled - This means the service tried to use more memory than it has available and had to be restarted. For more information on resources you can check the [documentation](https://docs.nhost.io/platform/compute).\n- Error - This can show for mainly two reasons; when new configuration needs to be applied the service is terminated and due to limitations this shows as \"Error\" but it is, in fact, part of normal operations. This can also show if your service is misconfigured and/or can't start correctly for some reason. If this error doesn't show constantly it is safe to ignore this error.",
|
||||
"fieldConfig": {
|
||||
@@ -676,8 +649,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -712,13 +684,13 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(container, reason) (increase(pod_terminated_total[$__rate_interval]))",
|
||||
"hide": false,
|
||||
"interval": "2m",
|
||||
"legendFormat": "{{container}}-{{reason}}",
|
||||
"legendFormat": "{{ print "{{container}}-{{reason}}" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
@@ -729,7 +701,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -771,8 +743,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -807,7 +778,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(ingress) (irate(nginx_ingress_controller_response_size_sum[$__rate_interval]))",
|
||||
@@ -819,7 +790,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum(irate(fastly_prom_exporter_bytes_sent[$__rate_interval]))",
|
||||
@@ -836,7 +807,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -878,8 +849,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -913,7 +883,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "builder",
|
||||
"expr": "sum by(ingress) (irate(nginx_ingress_controller_requests[$__interval]))",
|
||||
@@ -925,7 +895,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "builder",
|
||||
"expr": "sum(irate(fastly_prom_exporter_requests_total[$__interval]))",
|
||||
@@ -955,7 +925,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "CPU utilization is calculated by calculating the average usage between two datapoints. At maximum granularity this is a 1 minute average, but when selecting longer periods of time granularity can decrease.\n\nGiven that the graph shows average usage it might be difficult to detect very sudden spikes.\n\nThe allotted line indicates how many CPU cycles are dedicated for the service. As free and pro projects have only shared CPU this line should show only a symbolic number. For projects with [dedicated compute resources](https://docs.nhost.io/platform/compute) this line should match the amount of resources configured.",
|
||||
"fieldConfig": {
|
||||
@@ -998,8 +968,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1033,24 +1002,24 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(pod) (irate(container_cpu_usage_seconds_total{container=\"postgres\"}[$__rate_interval])) * 1000",
|
||||
"interval": "2m",
|
||||
"legendFormat": "{{pod}}-used",
|
||||
"legendFormat": "{{ print "{{pod}}-used" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(pod) (container_spec_cpu_shares{container=\"postgres\"}) / 1.024",
|
||||
"hide": false,
|
||||
"legendFormat": "{{pod}}-allotted",
|
||||
"legendFormat": "{{ print "{{pod}}-alloted" }}",
|
||||
"range": true,
|
||||
"refId": "B"
|
||||
}
|
||||
@@ -1061,7 +1030,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "This graph shows memory utilization for the service. The allotted line shows what's the amount of memory a service is allowed to consume. As resources are shared there is the possibility that the actual memory available is slightly lower. If a service exceeds the amount of memory it can use, it is restarted automatically.",
|
||||
"fieldConfig": {
|
||||
@@ -1104,8 +1073,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1140,24 +1108,24 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(pod) (container_memory_usage_bytes{container=\"postgres\"})",
|
||||
"hide": false,
|
||||
"legendFormat": "{{pod}}-used",
|
||||
"legendFormat": "{{ print "{{pod}}-used" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(pod) (container_spec_memory_limit_bytes{container=\"postgres\"})",
|
||||
"hide": false,
|
||||
"legendFormat": "{{pod}}-allotted",
|
||||
"legendFormat": "{{ print "{{pod}}-alloted" }}",
|
||||
"range": true,
|
||||
"refId": "B"
|
||||
}
|
||||
@@ -1168,7 +1136,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "This shows the amount of data utilized by the postgres volume. This number may differ from the database size reported by postgres depending on the features configured. For instance, when archiving is enabled postgres needs to write on disk a lot of supporting files which might lead to big increase in disk usage.\n\nWhen postgres runs out of disk space it fails to start so it is important to ensure you don't fill the volume. If you need to increase your disk capacity don't hesitate to let us know.",
|
||||
"fieldConfig": {
|
||||
@@ -1211,8 +1179,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1247,7 +1214,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "kubelet_volume_stats_used_bytes{persistentvolumeclaim=\"postgres-pv-claim\"}",
|
||||
@@ -1258,7 +1225,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "kubelet_volume_stats_capacity_bytes{persistentvolumeclaim=\"postgres-pv-claim\"}",
|
||||
@@ -1274,7 +1241,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
@@ -1316,8 +1283,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1352,7 +1318,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(pod) (rate(container_fs_reads_bytes_total{container=\"postgres\"}[$__rate_interval]))",
|
||||
@@ -1364,7 +1330,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(pod) (rate(container_fs_writes_bytes_total{container=\"postgres\"}[$__rate_interval]))",
|
||||
@@ -1394,7 +1360,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "CPU utilization is calculated by calculating the average usage between two datapoints. At maximum granularity this is a 1 minute average, but when selecting longer periods of time granularity can decrease.\n\nGiven that the graph shows average usage it might be difficult to detect very sudden spikes.\n\nThe allotted line indicates how many CPU cycles are dedicated for the service. As free and pro projects have only shared CPU this line should show only a symbolic number. For projects with [dedicated compute resources](https://docs.nhost.io/platform/compute) this line should match the amount of resources configured.",
|
||||
"fieldConfig": {
|
||||
@@ -1437,8 +1403,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1472,24 +1437,24 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(pod) (irate(container_cpu_usage_seconds_total{container=\"hasura\"}[$__rate_interval])) * 1000",
|
||||
"interval": "2m",
|
||||
"legendFormat": "{{pod}}-used",
|
||||
"legendFormat": "{{ print "{{pod}}-used" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(pod) (container_spec_cpu_shares{container=\"hasura\"}) / 1.024",
|
||||
"hide": false,
|
||||
"legendFormat": "{{pod}}-allotted",
|
||||
"legendFormat": "{{ print "{{pod}}-alloted" }}",
|
||||
"range": true,
|
||||
"refId": "B"
|
||||
}
|
||||
@@ -1500,7 +1465,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "This graph shows memory utilization for the service. The allotted line shows what's the amount of memory a service is allowed to consume. As resources are shared there is the possibility that the actual memory available is slightly lower. If a service exceeds the amount of memory it can use, it is restarted automatically.",
|
||||
"fieldConfig": {
|
||||
@@ -1543,8 +1508,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1579,24 +1543,24 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(pod) (container_memory_usage_bytes{container=\"hasura\"})",
|
||||
"hide": false,
|
||||
"legendFormat": "{{pod}}-used",
|
||||
"legendFormat": "{{ print "{{pod}}-used" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(pod) (container_spec_memory_limit_bytes{container=\"hasura\"})",
|
||||
"hide": false,
|
||||
"legendFormat": "{{pod}}-allotted",
|
||||
"legendFormat": "{{ print "{{pod}}-alloted" }}",
|
||||
"range": true,
|
||||
"refId": "B"
|
||||
}
|
||||
@@ -1620,7 +1584,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "CPU utilization is calculated by calculating the average usage between two datapoints. At maximum granularity this is a 1 minute average, but when selecting longer periods of time granularity can decrease.\n\nGiven that the graph shows average usage it might be difficult to detect very sudden spikes.\n\nThe allotted line indicates how many CPU cycles are dedicated for the service. As free and pro projects have only shared CPU this line should show only a symbolic number. For projects with [dedicated compute resources](https://docs.nhost.io/platform/compute) this line should match the amount of resources configured.",
|
||||
"fieldConfig": {
|
||||
@@ -1663,8 +1627,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1698,24 +1661,24 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(pod) (irate(container_cpu_usage_seconds_total{container=\"hasura-auth\"}[$__rate_interval])) * 1000",
|
||||
"interval": "2m",
|
||||
"legendFormat": "{{pod}}-used",
|
||||
"legendFormat": "{{ print "{{pod}}-used" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(pod) (container_spec_cpu_shares{container=\"hasura-auth\"}) / 1.024",
|
||||
"hide": false,
|
||||
"legendFormat": "{{pod}}-allotted",
|
||||
"legendFormat": "{{ print "{{pod}}-alloted" }}",
|
||||
"range": true,
|
||||
"refId": "B"
|
||||
}
|
||||
@@ -1726,7 +1689,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "This graph shows memory utilization for the service. The allotted line shows what's the amount of memory a service is allowed to consume. As resources are shared there is the possibility that the actual memory available is slightly lower. If a service exceeds the amount of memory it can use, it is restarted automatically.",
|
||||
"fieldConfig": {
|
||||
@@ -1769,8 +1732,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1805,24 +1767,24 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(pod) (container_memory_usage_bytes{container=\"hasura-auth\"})",
|
||||
"hide": false,
|
||||
"legendFormat": "{{pod}}-used",
|
||||
"legendFormat": "{{ print "{{pod}}-used" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(pod) (container_spec_memory_limit_bytes{container=\"hasura-auth\"})",
|
||||
"hide": false,
|
||||
"legendFormat": "{{pod}}-allotted",
|
||||
"legendFormat": "{{ print "{{pod}}-alloted" }}",
|
||||
"range": true,
|
||||
"refId": "B"
|
||||
}
|
||||
@@ -1846,7 +1808,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "CPU utilization is calculated by calculating the average usage between two datapoints. At maximum granularity this is a 1 minute average, but when selecting longer periods of time granularity can decrease.\n\nGiven that the graph shows average usage it might be difficult to detect very sudden spikes.\n\nThe allotted line indicates how many CPU cycles are dedicated for the service. As free and pro projects have only shared CPU this line should show only a symbolic number. For projects with [dedicated compute resources](https://docs.nhost.io/platform/compute) this line should match the amount of resources configured.",
|
||||
"fieldConfig": {
|
||||
@@ -1889,8 +1851,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -1924,24 +1885,24 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(pod) (irate(container_cpu_usage_seconds_total{container=\"hasura-storage\"}[$__rate_interval])) * 1000",
|
||||
"interval": "2m",
|
||||
"legendFormat": "{{pod}}-used",
|
||||
"legendFormat": "{{ print "{{pod}}-used" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(pod) (container_spec_cpu_shares{container=\"hasura-storage\"}) / 1.024",
|
||||
"hide": false,
|
||||
"legendFormat": "{{pod}}-allotted",
|
||||
"legendFormat": "{{ print "{{pod}}-alloted" }}",
|
||||
"range": true,
|
||||
"refId": "B"
|
||||
}
|
||||
@@ -1952,7 +1913,7 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"description": "This graph shows memory utilization for the service. The allotted line shows what's the amount of memory a service is allowed to consume. As resources are shared there is the possibility that the actual memory available is slightly lower. If a service exceeds the amount of memory it can use, it is restarted automatically.",
|
||||
"fieldConfig": {
|
||||
@@ -1995,8 +1956,7 @@
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
@@ -2031,24 +1991,24 @@
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(pod) (container_memory_usage_bytes{container=\"hasura-storage\"})",
|
||||
"hide": false,
|
||||
"legendFormat": "{{pod}}-used",
|
||||
"legendFormat": "{{ print "{{pod}}-used" }}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
"uid": "nhost"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by(pod) (container_spec_memory_limit_bytes{container=\"hasura-storage\"})",
|
||||
"hide": false,
|
||||
"legendFormat": "{{pod}}-allotted",
|
||||
"legendFormat": "{{ print "{{pod}}-alloted" }}",
|
||||
"range": true,
|
||||
"refId": "B"
|
||||
}
|
||||
@@ -2057,31 +2017,12 @@
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 37,
|
||||
"style": "dark",
|
||||
"schemaVersion": 39,
|
||||
"tags": [
|
||||
"nhost"
|
||||
],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"current": {
|
||||
"selected": false,
|
||||
"text": "Prometheus",
|
||||
"value": "Prometheus"
|
||||
},
|
||||
"hide": 2,
|
||||
"includeAll": false,
|
||||
"multi": false,
|
||||
"name": "DS_PROMETHEUS",
|
||||
"options": [],
|
||||
"query": "prometheus",
|
||||
"refresh": 1,
|
||||
"regex": "Prometheus",
|
||||
"skipUrlSync": false,
|
||||
"type": "datasource"
|
||||
}
|
||||
]
|
||||
"list": []
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
10
observability/grafana/dashboards_providers.yaml
Normal file
10
observability/grafana/dashboards_providers.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
apiVersion: 1
|
||||
providers:
|
||||
- disableDeletion: false
|
||||
editable: false
|
||||
folder: "Nhost - {{ .Subdomain }} ({{ .ProjectName }})"
|
||||
name: default
|
||||
options:
|
||||
path: /var/lib/grafana/dashboards/default
|
||||
orgId: 1
|
||||
type: file
|
||||
17
observability/grafana/datasources.yaml.tmpl
Normal file
17
observability/grafana/datasources.yaml.tmpl
Normal file
@@ -0,0 +1,17 @@
|
||||
apiVersion: 1
|
||||
datasources:
|
||||
- access: proxy
|
||||
isDefault: true
|
||||
name: Nhost
|
||||
type: prometheus
|
||||
url: http://amp-signer.nhost-services:8080
|
||||
uid: nhost
|
||||
jsonData:
|
||||
customQueryParameters: app_id=${APP_ID}
|
||||
httpHeaderName1: 'Authorization'
|
||||
manageAlerts: false
|
||||
cacheLevel: 'High'
|
||||
disableRecordingRules: true
|
||||
timeInterval: '60s'
|
||||
secureJsonData:
|
||||
httpHeaderValue1: 'Bearer ${TOKEN}'
|
||||
23
observability/grafana/grafana.ini
Normal file
23
observability/grafana/grafana.ini
Normal file
@@ -0,0 +1,23 @@
|
||||
[analytics]
|
||||
check_for_updates = false
|
||||
[grafana_net]
|
||||
url = https://grafana.net
|
||||
[log]
|
||||
mode = console
|
||||
[paths]
|
||||
data = /var/lib/grafana/
|
||||
logs = /var/log/grafana
|
||||
plugins = /var/lib/grafana/plugins
|
||||
provisioning = /var/lib/grafana/provisioning
|
||||
[server]
|
||||
domain = ''
|
||||
root_url = '{{ .RootURL }}'
|
||||
|
||||
{{ if .SMTP }}
|
||||
[smtp]
|
||||
enabled=true
|
||||
host={{ .SMTP.Host }}:{{ .SMTP.Port }}
|
||||
user={{ .SMTP.User }}
|
||||
password={{ .SMTP.Password }}
|
||||
from_address={{ .SMTP.Sender }}
|
||||
{{ end }}
|
||||
7
observability/grafana/notification_policies.yaml
Normal file
7
observability/grafana/notification_policies.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
apiVersion: 1
|
||||
policies:
|
||||
- orgId: 1
|
||||
receiver: Nhost Managed Contacts
|
||||
group_by:
|
||||
- grafana_folder
|
||||
- alertname
|
||||
369
observability/grafana/rules_nhost.yaml
Normal file
369
observability/grafana/rules_nhost.yaml
Normal file
@@ -0,0 +1,369 @@
|
||||
apiVersion: 1
|
||||
groups:
|
||||
- orgId: 1
|
||||
name: core
|
||||
folder: "Nhost - {{ .Subdomain }} ({{ .ProjectName }})"
|
||||
interval: 5m
|
||||
rules:
|
||||
- uid: nhosthighcpuusage
|
||||
title: High CPU usage
|
||||
condition: B
|
||||
data:
|
||||
- refId: A
|
||||
relativeTimeRange:
|
||||
from: 600
|
||||
to: 0
|
||||
datasourceUid: nhost
|
||||
model:
|
||||
editorMode: code
|
||||
expr: sum by(pod) (irate(container_cpu_usage_seconds_total{container!~"grafana|POD|"}[$__rate_interval])) / (sum by(pod) (container_spec_cpu_quota{container!~"grafana|POD|"}) / sum by(pod) (container_spec_cpu_period{container!~"POD|"})) * 100
|
||||
instant: true
|
||||
intervalMs: 1000
|
||||
legendFormat: __auto
|
||||
maxDataPoints: 43200
|
||||
range: false
|
||||
refId: A
|
||||
- refId: B
|
||||
relativeTimeRange:
|
||||
from: 600
|
||||
to: 0
|
||||
datasourceUid: __expr__
|
||||
model:
|
||||
conditions:
|
||||
- evaluator:
|
||||
params:
|
||||
- 75
|
||||
type: gt
|
||||
operator:
|
||||
type: and
|
||||
query:
|
||||
params:
|
||||
- C
|
||||
reducer:
|
||||
params: []
|
||||
type: last
|
||||
type: query
|
||||
datasource:
|
||||
type: __expr__
|
||||
uid: __expr__
|
||||
expression: A
|
||||
intervalMs: 1000
|
||||
maxDataPoints: 43200
|
||||
refId: B
|
||||
type: threshold
|
||||
noDataState: NoData
|
||||
execErrState: Error
|
||||
for: 5m
|
||||
annotations:
|
||||
runbook_url: https://docs.nhost.io/platform/compute-resources
|
||||
Project Subdomain: {{ .Subdomain }}
|
||||
Project Name: {{ .ProjectName }}
|
||||
description: |
|
||||
High CPU usage can be caused by a number of factors, including but not limited to:
|
||||
- High traffic
|
||||
- Inefficient code/queries
|
||||
- Inadequate resources
|
||||
|
||||
To resolve this issue, consider the following:
|
||||
- Optimize your code/queries
|
||||
- Increase the number of replicas
|
||||
- Increase the CPU resources allocated to your service
|
||||
|
||||
High CPU usage can lead to service instability, increased latency and downtime.
|
||||
|
||||
For more information, see the [Nhost documentation](https://docs.nhost.io/platform/compute-resources)
|
||||
summary: |
|
||||
The service replica {{ print "{{ index $labels \"pod\" }}" }} is experiencing, or has experienced, high CPU usage. Current usage is at {{ print "{{ index $values \"A\" }}" }}%.
|
||||
labels: {}
|
||||
isPaused: false
|
||||
|
||||
- uid: nhostlowdiskspace
|
||||
title: Low disk space
|
||||
condition: B
|
||||
data:
|
||||
- refId: A
|
||||
relativeTimeRange:
|
||||
from: 600
|
||||
to: 0
|
||||
datasourceUid: nhost
|
||||
model:
|
||||
editorMode: code
|
||||
expr: sum by(persistentvolumeclaim) (kubelet_volume_stats_used_bytes) / sum by(persistentvolumeclaim) (kubelet_volume_stats_capacity_bytes) * 100
|
||||
instant: true
|
||||
intervalMs: 1000
|
||||
legendFormat: __auto
|
||||
maxDataPoints: 43200
|
||||
range: false
|
||||
refId: A
|
||||
- refId: B
|
||||
relativeTimeRange:
|
||||
from: 600
|
||||
to: 0
|
||||
datasourceUid: __expr__
|
||||
model:
|
||||
conditions:
|
||||
- evaluator:
|
||||
params:
|
||||
- 75
|
||||
type: gt
|
||||
operator:
|
||||
type: and
|
||||
query:
|
||||
params:
|
||||
- C
|
||||
reducer:
|
||||
params: []
|
||||
type: last
|
||||
type: query
|
||||
datasource:
|
||||
type: __expr__
|
||||
uid: __expr__
|
||||
expression: A
|
||||
intervalMs: 1000
|
||||
maxDataPoints: 43200
|
||||
refId: B
|
||||
type: threshold
|
||||
noDataState: NoData
|
||||
execErrState: Error
|
||||
for: 5m
|
||||
annotations:
|
||||
runbook_url: https://docs.nhost.io/guides/database/configuring-postgres
|
||||
Subdomain: {{ .Subdomain }}
|
||||
Project Name: {{ .ProjectName }}
|
||||
description: |
|
||||
An increase in disk space usage can be caused by a number of factors, including but not limited to:
|
||||
- Large amounts of data
|
||||
- Changing in WAL settings
|
||||
|
||||
To resolve this issue, consider the following:
|
||||
- If you recently changed your WAL settings, consider reverting to the previous settings
|
||||
- Optimize your database tables
|
||||
- Remove data that is no longer needed
|
||||
- Increase the disk space allocated to your database
|
||||
|
||||
Running out of disk space can lead to service downtime and potential data loss.
|
||||
|
||||
For more information, see the [Nhost documentation](https://docs.nhost.io/guides/database/configuring-postgres)
|
||||
summary: |
|
||||
The persistent volume claim {{ print "{{ index $labels \"persistentvolumeclaim\" }}" }} current usage is at {{ print "{{ index $values \"A\" }}" }}%.
|
||||
labels: {}
|
||||
isPaused: false
|
||||
|
||||
- uid: nhostlowmemory
|
||||
title: Low free memory
|
||||
condition: B
|
||||
data:
|
||||
- refId: A
|
||||
relativeTimeRange:
|
||||
from: 600
|
||||
to: 0
|
||||
datasourceUid: nhost
|
||||
model:
|
||||
editorMode: code
|
||||
expr: sum by(pod) (container_memory_usage_bytes{container!~"grafana|"}) / sum by(pod) (container_spec_memory_limit_bytes{container!~"grafana|"}) * 100
|
||||
instant: true
|
||||
intervalMs: 1000
|
||||
legendFormat: __auto
|
||||
maxDataPoints: 43200
|
||||
range: false
|
||||
refId: A
|
||||
- refId: B
|
||||
relativeTimeRange:
|
||||
from: 600
|
||||
to: 0
|
||||
datasourceUid: __expr__
|
||||
model:
|
||||
conditions:
|
||||
- evaluator:
|
||||
params:
|
||||
- 75
|
||||
type: gt
|
||||
operator:
|
||||
type: and
|
||||
query:
|
||||
params:
|
||||
- C
|
||||
reducer:
|
||||
params: []
|
||||
type: last
|
||||
type: query
|
||||
datasource:
|
||||
type: __expr__
|
||||
uid: __expr__
|
||||
expression: A
|
||||
intervalMs: 1000
|
||||
maxDataPoints: 43200
|
||||
refId: B
|
||||
type: threshold
|
||||
noDataState: NoData
|
||||
execErrState: Error
|
||||
for: 5m
|
||||
annotations:
|
||||
runbook_url: https://docs.nhost.io/platform/compute-resources
|
||||
Subdomain: {{ .Subdomain }}
|
||||
Project Name: {{ .ProjectName }}
|
||||
description: |
|
||||
Low memory can be caused by a number of factors, including but not limited to:
|
||||
- High traffic
|
||||
- Inefficient code/queries
|
||||
- Inadequate resources
|
||||
|
||||
To resolve this issue, consider the following:
|
||||
- Optimize your code/queries
|
||||
- Increase the memory resources allocated to your service
|
||||
|
||||
Running out of memory can lead to service instability, increased latency and downtime.
|
||||
|
||||
For more information, see the [Nhost documentation](https://docs.nhost.io/platform/compute-resources)
|
||||
|
||||
summary: |
|
||||
The service replica {{ print "{{ index $labels \"pod\" }}" }} is experiencing, or has experienced, low memory. Current usage is at {{ print "{{ index $values \"A\" }}" }}%.
|
||||
labels: {}
|
||||
isPaused: false
|
||||
|
||||
- uid: nhostoom
|
||||
title: Service restarted due to lack of memory
|
||||
condition: B
|
||||
data:
|
||||
- refId: A
|
||||
relativeTimeRange:
|
||||
from: 600
|
||||
to: 0
|
||||
datasourceUid: nhost
|
||||
model:
|
||||
editorMode: code
|
||||
expr: sum by(pod) (increase(pod_terminated_total{reason="OOMKilled", pod!="grafana"}[$__rate_interval]))
|
||||
instant: true
|
||||
intervalMs: 1000
|
||||
legendFormat: __auto
|
||||
maxDataPoints: 43200
|
||||
range: false
|
||||
refId: A
|
||||
- refId: B
|
||||
relativeTimeRange:
|
||||
from: 600
|
||||
to: 0
|
||||
datasourceUid: __expr__
|
||||
model:
|
||||
conditions:
|
||||
- evaluator:
|
||||
params:
|
||||
- 0
|
||||
type: gt
|
||||
operator:
|
||||
type: and
|
||||
query:
|
||||
params:
|
||||
- C
|
||||
reducer:
|
||||
params: []
|
||||
type: last
|
||||
type: query
|
||||
datasource:
|
||||
type: __expr__
|
||||
uid: __expr__
|
||||
expression: A
|
||||
intervalMs: 1000
|
||||
maxDataPoints: 43200
|
||||
refId: B
|
||||
type: threshold
|
||||
noDataState: OK
|
||||
execErrState: Error
|
||||
for: 0s
|
||||
annotations:
|
||||
summary: |
|
||||
The service replica {{ print "{{ index $labels \"pod\" }}" }} has been restarted due to lack of memory.
|
||||
description: |
|
||||
When a service runs out of memory and is unable to allocate more, it is terminated by the
|
||||
OOM Killer. This is primarily caused by trying to allocate more memory than is permitted,
|
||||
which in turn can be caused by:
|
||||
|
||||
- High traffic
|
||||
- Inefficient code/queries
|
||||
- Inadequate resources
|
||||
|
||||
To resolve this issue, consider the following:
|
||||
- Optimize your code/queries
|
||||
- Increase the memory resources allocated to your service
|
||||
|
||||
This can lead to service instability, increased latency and downtime.
|
||||
|
||||
|
||||
For more information, see the [Nhost documentation](https://docs.nhost.io/platform/compute-resources)
|
||||
runbook_url: https://docs.nhost.io/platform/compute-resources
|
||||
Subdomain: {{ .Subdomain }}
|
||||
Project Name: {{ .ProjectName }}
|
||||
labels: {}
|
||||
isPaused: false
|
||||
|
||||
- uid: nhosthigherrorrate
|
||||
title: High request error rate
|
||||
condition: B
|
||||
data:
|
||||
- refId: A
|
||||
relativeTimeRange:
|
||||
from: 600
|
||||
to: 0
|
||||
datasourceUid: nhost
|
||||
model:
|
||||
editorMode: code
|
||||
expr: sum by(ingress,method) (increase(nginx_ingress_controller_requests{ingress!="grafana",status=~"^[4-5].*"}[$__rate_interval])) / sum by(ingress, method) (increase(nginx_ingress_controller_requests[$__rate_interval])) * 100
|
||||
instant: true
|
||||
intervalMs: 1000
|
||||
legendFormat: __auto
|
||||
maxDataPoints: 43200
|
||||
range: false
|
||||
refId: A
|
||||
- refId: B
|
||||
relativeTimeRange:
|
||||
from: 600
|
||||
to: 0
|
||||
datasourceUid: __expr__
|
||||
model:
|
||||
conditions:
|
||||
- evaluator:
|
||||
params:
|
||||
- 25
|
||||
type: gt
|
||||
operator:
|
||||
type: and
|
||||
query:
|
||||
params:
|
||||
- C
|
||||
reducer:
|
||||
params: []
|
||||
type: last
|
||||
type: query
|
||||
datasource:
|
||||
type: __expr__
|
||||
uid: __expr__
|
||||
expression: A
|
||||
intervalMs: 1000
|
||||
maxDataPoints: 43200
|
||||
refId: B
|
||||
type: threshold
|
||||
noDataState: OK
|
||||
execErrState: Error
|
||||
for: 5m
|
||||
annotations:
|
||||
Subdomain: {{ .Subdomain }}
|
||||
Project Name: {{ .ProjectName }}
|
||||
summary: |
|
||||
The service {{ print "{{ index $labels \"ingress\" }}" }} is experiencing, or has experienced, a high error rate. Current error rate is at {{ print "{{ index $values \"A\" }}" }}%.
|
||||
description: |
|
||||
A high error rate can be caused by a number of factors, including but not limited to:
|
||||
- High traffic
|
||||
- Inefficient code/queries
|
||||
- Inadequate resources
|
||||
- Network issues
|
||||
- Code errors
|
||||
- Permission issues
|
||||
|
||||
To resolve this issue, consider the following:
|
||||
- Observe the service logs for more information
|
||||
|
||||
A high error rate means there is something fundamentally wrong with the service or your application. It can lead to service instability, increased latency and downtime.
|
||||
|
||||
For more information, see the [Nhost documentation](https://docs.nhost.io/platform/compute-resources)
|
||||
labels: {}
|
||||
isPaused: false
|
||||
11
observability/grafana/setup_config.sh
Normal file
11
observability/grafana/setup_config.sh
Normal file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env sh
|
||||
set -euf
|
||||
|
||||
mkdir -p /var/lib/grafana/provisioning/datasources
|
||||
|
||||
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
|
||||
APP_ID=$(sed "s/nhost-//g" /var/run/secrets/kubernetes.io/serviceaccount/namespace)
|
||||
|
||||
sed "s/\${TOKEN}/$TOKEN/g; s/\${APP_ID}/$APP_ID/g" \
|
||||
< /datasources.yaml.tmpl \
|
||||
> /var/lib/grafana/provisioning/datasources/datasources.yaml
|
||||
@@ -83,7 +83,7 @@
|
||||
"turbo": "1.11.3",
|
||||
"typedoc": "^0.22.18",
|
||||
"typescript": "4.9.5",
|
||||
"vite": "^5.2.13",
|
||||
"vite": "^5.4.6",
|
||||
"vite-plugin-dts": "^3.9.1",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"vitest": "^0.32.4"
|
||||
@@ -154,7 +154,10 @@
|
||||
"nth-check": "^2.0.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"@graphiql/react": "^0.22.3"
|
||||
"@graphiql/react": "^0.22.3",
|
||||
"send": "^0.19.0",
|
||||
"dset": "^3.1.4",
|
||||
"rollup": "^4.22.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/hasura-auth-js
|
||||
|
||||
## 2.6.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 55d8bb5: feat: support custom headers in sign-up and deanonymize requests
|
||||
|
||||
## 2.5.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/hasura-auth-js",
|
||||
"version": "2.5.6",
|
||||
"version": "2.6.0",
|
||||
"description": "Hasura-auth client",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type {
|
||||
NhostSession,
|
||||
PasswordlessOptions,
|
||||
RequestOptions,
|
||||
SignUpOptions,
|
||||
SignUpSecurityKeyOptions
|
||||
} from '../../types'
|
||||
@@ -23,7 +24,13 @@ export type AuthEvents =
|
||||
options?: PasswordlessOptions
|
||||
}
|
||||
| { type: 'PASSWORDLESS_SMS_OTP'; phoneNumber?: string; otp?: string }
|
||||
| { type: 'SIGNUP_EMAIL_PASSWORD'; email?: string; password?: string; options?: SignUpOptions }
|
||||
| {
|
||||
type: 'SIGNUP_EMAIL_PASSWORD'
|
||||
email?: string
|
||||
password?: string
|
||||
options?: SignUpOptions
|
||||
requestOptions?: RequestOptions
|
||||
}
|
||||
| { type: 'SIGNUP_SECURITY_KEY'; email?: string; options?: SignUpSecurityKeyOptions }
|
||||
| { type: 'SIGNOUT'; all?: boolean }
|
||||
| { type: 'SIGNIN_MFA_TOTP'; ticket?: string; otp?: string }
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
PasswordlessSmsOtpResponse,
|
||||
PasswordlessSmsResponse,
|
||||
RefreshSessionResponse,
|
||||
RequestOptions,
|
||||
SignInAnonymousResponse,
|
||||
SignInMfaTotpResponse,
|
||||
SignInPATResponse,
|
||||
@@ -92,9 +93,10 @@ export const createAuthMachine = ({
|
||||
const postRequest = async <T = any, D = any>(
|
||||
url: string,
|
||||
data?: D,
|
||||
token?: string | null
|
||||
token?: string | null,
|
||||
headers?: Record<string, string>
|
||||
): Promise<T> => {
|
||||
const result = await postFetch<T>(`${backendUrl}${url}`, data, token)
|
||||
const result = await postFetch<T>(`${backendUrl}${url}`, data, token, headers)
|
||||
|
||||
return result.data
|
||||
}
|
||||
@@ -910,13 +912,14 @@ export const createAuthMachine = ({
|
||||
|
||||
return signOutResponse
|
||||
},
|
||||
signUpEmailPassword: async (context, { email, password, options }) => {
|
||||
signUpEmailPassword: async (context, { email, password, options, requestOptions }) => {
|
||||
if (!isValidEmail(email)) {
|
||||
return Promise.reject<SignUpResponse>({ error: INVALID_EMAIL_ERROR })
|
||||
}
|
||||
if (!isValidPassword(password)) {
|
||||
return Promise.reject<SignUpResponse>({ error: INVALID_PASSWORD_ERROR })
|
||||
}
|
||||
|
||||
if (context.user?.isAnonymous) {
|
||||
return postRequest<SignUpResponse>(
|
||||
'/user/deanonymize',
|
||||
@@ -926,14 +929,20 @@ export const createAuthMachine = ({
|
||||
password,
|
||||
options: rewriteRedirectTo(clientUrl, options)
|
||||
},
|
||||
context.accessToken.value
|
||||
context.accessToken.value,
|
||||
requestOptions?.headers
|
||||
)
|
||||
} else {
|
||||
return postRequest<SignUpResponse>('/signup/email-password', {
|
||||
email,
|
||||
password,
|
||||
options: rewriteRedirectTo(clientUrl, options)
|
||||
})
|
||||
return postRequest<SignUpResponse>(
|
||||
'/signup/email-password',
|
||||
{
|
||||
email,
|
||||
password,
|
||||
options: rewriteRedirectTo(clientUrl, options)
|
||||
},
|
||||
null,
|
||||
requestOptions?.headers
|
||||
)
|
||||
}
|
||||
},
|
||||
signUpSecurityKey: async (_, { email, options }) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { USER_ALREADY_SIGNED_IN } from '../errors'
|
||||
import { AuthInterpreter } from '../machines'
|
||||
import { SignUpOptions } from '../types'
|
||||
import { RequestOptions, SignUpOptions } from '../types'
|
||||
|
||||
import {
|
||||
AuthActionLoadingState,
|
||||
@@ -20,13 +20,15 @@ export const signUpEmailPasswordPromise = (
|
||||
interpreter: AuthInterpreter,
|
||||
email: string,
|
||||
password: string,
|
||||
options?: SignUpOptions
|
||||
options?: SignUpOptions,
|
||||
requestOptions?: RequestOptions
|
||||
): Promise<SignUpEmailPasswordHandlerResult> =>
|
||||
new Promise<SignUpEmailPasswordHandlerResult>((resolve) => {
|
||||
const { changed, context } = interpreter.send('SIGNUP_EMAIL_PASSWORD', {
|
||||
email,
|
||||
password,
|
||||
options
|
||||
options,
|
||||
requestOptions
|
||||
})
|
||||
if (!changed) {
|
||||
return resolve({
|
||||
|
||||
@@ -57,3 +57,8 @@ export interface WorkOsOptions extends CommonProviderOptions {
|
||||
provider?: string
|
||||
}
|
||||
export interface ProviderOptions extends CommonProviderOptions, WorkOsOptions {}
|
||||
|
||||
export interface RequestOptions {
|
||||
// optional extra headers to be sent with request (ex: x-cf-turnstile-response)
|
||||
headers?: Record<string, string>
|
||||
}
|
||||
|
||||
@@ -17,7 +17,11 @@ if (typeof EdgeRuntime !== 'string') {
|
||||
const fetchWrapper = async <T>(
|
||||
url: string,
|
||||
method: 'GET' | 'POST',
|
||||
{ token, body }: { token?: string | null; body?: any } = {}
|
||||
{
|
||||
token,
|
||||
body,
|
||||
extraHeaders
|
||||
}: { token?: string | null; body?: any; extraHeaders?: HeadersInit } = {}
|
||||
): Promise<FetcResponse<T>> => {
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -26,9 +30,12 @@ const fetchWrapper = async <T>(
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`
|
||||
}
|
||||
|
||||
const mergedHeaders = { ...headers, ...extraHeaders }
|
||||
|
||||
const options: RequestInit = {
|
||||
method,
|
||||
headers
|
||||
headers: mergedHeaders
|
||||
}
|
||||
if (body) {
|
||||
options.body = JSON.stringify(body)
|
||||
@@ -59,8 +66,9 @@ const fetchWrapper = async <T>(
|
||||
export const postFetch = async <T>(
|
||||
url: string,
|
||||
body: any,
|
||||
token?: string | null
|
||||
): Promise<FetcResponse<T>> => fetchWrapper<T>(url, 'POST', { token, body })
|
||||
token?: string | null,
|
||||
extraHeaders?: HeadersInit
|
||||
): Promise<FetcResponse<T>> => fetchWrapper<T>(url, 'POST', { token, body, extraHeaders })
|
||||
|
||||
export const getFetch = <T>(url: string, token?: string | null): Promise<FetcResponse<T>> =>
|
||||
fetchWrapper<T>(url, 'GET', { token })
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# @nhost/nextjs
|
||||
|
||||
## 2.1.22
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [55d8bb5]
|
||||
- @nhost/react@3.6.0
|
||||
|
||||
## 2.1.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 52a38fe: chore: update dependencies to address security vulnerabilities
|
||||
|
||||
## 2.1.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user