Compare commits

..

18 Commits

Author SHA1 Message Date
Szilárd Dóró
d4a0aad2dd Merge pull request #1913 from nhost/changeset-release/main
chore: update versions
2023-05-10 14:24:09 +02:00
github-actions[bot]
1030813279 chore: update versions 2023-05-10 11:50:46 +00:00
Nuno Pato
917a14aa40 Merge pull request #1912 from nhost/docs/add-metrics
Add section on Metrics to the documentation
2023-05-10 11:49:27 +00:00
Szilárd Dóró
6381d1b095 Merge pull request #1893 from nhost/feat/metrics-page 2023-05-10 13:43:34 +02:00
Nuno Pato
8dbdc0bf50 asd 2023-05-10 11:32:34 +00:00
Nuno Pato
8c072a4c6e asd 2023-05-10 10:10:38 +00:00
Nuno Pato
fe341519f7 Add section on Metrics to the documentation 2023-05-10 10:09:12 +00:00
Szilárd Dóró
757c888656 Merge pull request #1910 from nhost/changeset-release/main
chore: update versions
2023-05-09 11:40:16 +02:00
github-actions[bot]
7c13eb5f9b chore: update versions 2023-05-09 09:17:43 +00:00
Szilárd Dóró
a84608e086 Merge pull request #1907 from nhost/fix/upgrade
fix(dashboard): unpause after upgrading a paused project to pro
2023-05-09 11:13:44 +02:00
Szilárd Dóró
e43c079b9c feat: poll project state after unpausing with upgrade 2023-05-09 10:50:34 +02:00
Szilárd Dóró
3f396a9ebb chore: add changesets 2023-05-08 19:28:42 +02:00
Szilárd Dóró
6ed605beb8 fix: update desiredState on plan change 2023-05-08 17:58:06 +02:00
Szilárd Dóró
edd223d29c fix: don't go to 404 page unnecessarily 2023-05-08 17:47:04 +02:00
Szilárd Dóró
b17e8d6f3c fix: don't break e2e tests 2023-05-03 10:00:29 +02:00
Szilárd Dóró
12e2855f01 chore: update description and prevent free access
- bump `jsdom` to v22
- increase test timeout
2023-05-03 09:57:11 +02:00
Szilárd Dóró
c1080d9e63 fix: don't break the UI 2023-05-03 09:38:48 +02:00
Szilárd Dóró
e4972b8307 feat: add Grafana page 2023-05-03 09:35:06 +02:00
25 changed files with 484 additions and 161 deletions

View File

@@ -1,5 +1,19 @@
# @nhost/dashboard
## 0.16.5
### Patch Changes
- 12e2855f: chore(deps): bump `jsdom` to v22
- e4972b83: feat(metrics): add Grafana page
## 0.16.4
### Patch Changes
- 3f396a9e: fix(projects): unpause after upgrading a paused project to pro
- 3f396a9e: fix(projects): don't redirect to 404 page after project creation
## 0.16.3
### Patch Changes

View File

@@ -30,7 +30,7 @@ test('should show a sidebar with menu items', async () => {
const navLocator = page.getByRole('navigation', { name: /main navigation/i });
await expect(navLocator).toBeVisible();
await expect(navLocator.getByRole('list').getByRole('listitem')).toHaveCount(
10,
11,
);
await expect(
navLocator.getByRole('link', { name: /overview/i }),
@@ -53,6 +53,9 @@ test('should show a sidebar with menu items', async () => {
navLocator.getByRole('link', { name: /backups/i }),
).toBeVisible();
await expect(navLocator.getByRole('link', { name: /logs/i })).toBeVisible();
await expect(
navLocator.getByRole('link', { name: /metrics/i }),
).toBeVisible();
await expect(
navLocator.getByRole('link', { name: /settings/i }),
).toBeVisible();

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/dashboard",
"version": "0.16.3",
"version": "0.16.5",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
@@ -129,7 +129,7 @@
"eslint-plugin-jsx-a11y": "^6.6.1",
"eslint-plugin-react": "^7.31.11",
"eslint-plugin-react-hooks": "^4.6.0",
"jsdom": "^21.0.0",
"jsdom": "^22.0.0",
"lint-staged": ">=13",
"msw": "^1.0.1",
"msw-storybook-addon": "^1.6.3",

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -33,7 +33,8 @@ export default function ApplicationPaused() {
} = useCurrentWorkspaceAndProject();
const user = useUserData();
const isOwner = currentWorkspace.workspaceMembers.some(
({ id, type }) => id === user?.id && type === 'owner',
({ type, user: workspaceUser }) =>
workspaceUser.id === user?.id && type === 'owner',
);
const [showDeletingModal, setShowDeletingModal] = useState(false);
const [unpauseApplication, { loading: changingApplicationStateLoading }] =
@@ -120,20 +121,22 @@ export default function ApplicationPaused() {
</Box>
<Box className="grid grid-flow-row gap-2">
<Button
className="mx-auto w-full max-w-[280px]"
onClick={() => {
openDialog({
component: <ChangePlanModal />,
props: {
PaperProps: { className: 'p-0' },
maxWidth: 'lg',
},
});
}}
>
Upgrade to Pro
</Button>
{isOwner && (
<Button
className="mx-auto w-full max-w-[280px]"
onClick={() => {
openDialog({
component: <ChangePlanModal />,
props: {
PaperProps: { className: 'p-0' },
maxWidth: 'lg',
},
});
}}
>
Upgrade to Pro
</Button>
)}
<div className="grid grid-flow-row gap-2">
<Button

View File

@@ -6,7 +6,9 @@ import {
useGetPaymentMethodsQuery,
useUpdateApplicationMutation,
} from '@/generated/graphql';
import useApplicationState from '@/hooks/useApplicationState';
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
import { ApplicationStatus } from '@/types/application';
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import Box from '@/ui/v2/Box';
import Button from '@/ui/v2/Button';
@@ -15,11 +17,11 @@ import { BaseDialog } from '@/ui/v2/Dialog';
import Link from '@/ui/v2/Link';
import Text from '@/ui/v2/Text';
import { planDescriptions } from '@/utils/planDescriptions';
import getServerError from '@/utils/settings/getServerError/getServerError';
import getServerError from '@/utils/settings/getServerError';
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
import Image from 'next/image';
import { useRouter } from 'next/router';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { toast } from 'react-hot-toast';
function Plan({ planName, price, setPlan, planId, selectedPlanId }: any) {
@@ -53,7 +55,7 @@ function Plan({ planName, price, setPlan, planId, selectedPlanId }: any) {
</div>
<Text variant="h3" component="p">
$ {price}/mo
${price}/mo
</Text>
</button>
);
@@ -62,12 +64,14 @@ function Plan({ planName, price, setPlan, planId, selectedPlanId }: any) {
export function ChangePlanModalWithData({ app, plans, close }: any) {
const [selectedPlanId, setSelectedPlanId] = useState('');
const { closeAlertDialog } = useDialog();
const [pollingCurrentProject, setPollingCurrentProject] = useState(false);
const {
currentWorkspace,
currentProject,
refetch: refetchWorkspaceAndProject,
} = useCurrentWorkspaceAndProject();
const { state } = useApplicationState();
const { data } = useGetPaymentMethodsQuery({
variables: {
@@ -82,6 +86,29 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
const currentPlan = plans.find((plan) => plan.id === app.plan.id);
const selectedPlan = plans.find((plan) => plan.id === selectedPlanId);
useEffect(() => {
if (!pollingCurrentProject || state === ApplicationStatus.Paused) {
return;
}
close?.();
closeAlertDialog();
setShowPaymentModal(false);
setPollingCurrentProject(false);
}, [state, pollingCurrentProject, close, closeAlertDialog]);
useEffect(() => {
if (!pollingCurrentProject) {
return () => {};
}
const interval = setInterval(() => {
refetchWorkspaceAndProject();
}, 1000);
return () => clearInterval(interval);
}, [pollingCurrentProject, refetchWorkspaceAndProject, currentProject]);
const [updateApp] = useUpdateApplicationMutation({
refetchQueries: [
refetchGetApplicationPlanQuery({
@@ -99,6 +126,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
appId: app.id,
app: {
planId: selectedPlan.id,
desiredState: 5,
},
},
}),
@@ -112,11 +140,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
getToastStyleProps(),
);
await refetchWorkspaceAndProject();
close?.();
closeAlertDialog();
setShowPaymentModal(false);
setPollingCurrentProject(true);
} catch (error) {
// Note: Error is handled by the toast.
}
@@ -134,12 +158,49 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
}
await handleUpdateAppPlan();
setShowPaymentModal(false);
close?.();
closeAlertDialog();
};
if (pollingCurrentProject) {
return (
<Box className="mx-auto w-full max-w-xl rounded-lg p-6 text-left">
<div className="flex flex-col">
<div className="mx-auto">
<Image
src="/assets/upgrade.svg"
alt="Nhost Logo"
width={72}
height={72}
/>
</div>
<Text variant="h3" component="h2" className="mt-2 text-center">
Successfully upgraded to {currentPlan.name}
</Text>
<ActivityIndicator
label="We are unpausing your project. This may take some time..."
className="mx-auto mt-2"
/>
<Button
variant="outlined"
color="secondary"
className="mx-auto mt-4 w-full max-w-sm"
onClick={() => {
if (close) {
close();
}
closeAlertDialog();
}}
>
Cancel
</Button>
</div>
</Box>
);
}
if (app.plan.id !== plans.find((plan) => plan.isFree)?.id) {
return (
<Box className="mx-auto w-full max-w-xl rounded-lg p-6 text-left">
@@ -215,7 +276,7 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
You&apos;re currently on the <strong>{app.plan.name}</strong> plan.
</Text>
<div className="mt-5">
<div className="mt-2">
{plans
.filter((plan) => plan.id !== app.plan.id)
.map((plan) => (
@@ -233,8 +294,12 @@ export function ChangePlanModalWithData({ app, plans, close }: any) {
))}
</div>
<div className="mt-6 grid grid-flow-row gap-2">
<Button onClick={handleChangePlanClick} disabled={!selectedPlan}>
<div className="mt-2 grid grid-flow-row gap-2">
<Button
onClick={handleChangePlanClick}
disabled={!selectedPlan}
loading={pollingCurrentProject}
>
Upgrade
</Button>

View File

@@ -56,7 +56,9 @@ export default function ResetDatabasePasswordSettings() {
const handleGenerateRandomPassword = () => {
const newRandomDatabasePassword = generateRandomDatabasePassword();
triggerToast('New random database password generated.');
setValue('databasePassword', newRandomDatabasePassword);
setValue('databasePassword', newRandomDatabasePassword, {
shouldDirty: true,
});
};
const handleChangeDatabasePassword = async (

View File

@@ -0,0 +1,26 @@
import type { IconProps } from '@/ui/v2/icons';
import SvgIcon from '@/ui/v2/icons/SvgIcon';
function GaugeIcon(props: IconProps) {
return (
<SvgIcon
width="16"
height="16"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
aria-label="A gauge"
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10.757 3.295A7.25 7.25 0 0 0 8 2.75h-.026c-3.286.011-6.032 2.231-6.92 5.227a.753.753 0 0 0-.081.293 7.359 7.359 0 0 0-.223 1.8v1.43A1.25 1.25 0 0 0 2 12.75h12a1.25 1.25 0 0 0 1.25-1.25V10a7.25 7.25 0 0 0-.246-1.872l-.001-.004V8.12a7.248 7.248 0 0 0-4.246-4.825Zm-2.77 7.955h5.763V10c0-.252-.017-.503-.05-.751l-1.16.31a.75.75 0 1 1-.387-1.448l1.16-.31-.003-.006a5.751 5.751 0 0 0-4.56-3.496V5.5a.75.75 0 0 1-1.5 0V4.3c-2.053.271-3.764 1.645-4.545 3.505l1.142.306A.75.75 0 1 1 3.46 9.56L2.307 9.25a5.895 5.895 0 0 0-.057.82v1.179h3.845l4.05-5.277a.75.75 0 0 1 1.19.913L7.985 11.25Z"
fill="currentColor"
/>
</SvgIcon>
);
}
GaugeIcon.displayName = 'NhostGaugeIcon';
export default GaugeIcon;

View File

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

View File

@@ -10,6 +10,11 @@ fragment Project on apps {
nhostBaseFolder
providersUpdated
config(resolve: true) {
observability {
grafana {
adminPassword
}
}
hasura {
adminSecret
}

View File

@@ -4,6 +4,7 @@ import CloudIcon from '@/ui/v2/icons/CloudIcon';
import CogIcon from '@/ui/v2/icons/CogIcon';
import DatabaseIcon from '@/ui/v2/icons/DatabaseIcon';
import FileTextIcon from '@/ui/v2/icons/FileTextIcon';
import { GaugeIcon } from '@/ui/v2/icons/GaugeIcon';
import GraphQLIcon from '@/ui/v2/icons/GraphQLIcon';
import HasuraIcon from '@/ui/v2/icons/HasuraIcon';
import HomeIcon from '@/ui/v2/icons/HomeIcon';
@@ -77,6 +78,13 @@ export default function useProjectRoutes() {
icon: <FileTextIcon />,
disabled: !isPlatform,
},
{
relativePath: '/metrics',
exact: false,
label: 'Metrics',
icon: <GaugeIcon />,
disabled: !isPlatform,
},
{
relativeMainPath: '/settings',
relativePath: '/settings/general',
@@ -124,7 +132,6 @@ export default function useProjectRoutes() {
label: 'Storage',
icon: <StorageIcon />,
},
...nhostRoutes,
];

View File

@@ -6,52 +6,36 @@ import { useEffect } from 'react';
* Redirects to 404 page if either currentWorkspace/currentProject resolves to undefined.
*/
export default function useNotFoundRedirect() {
const { currentProject, currentWorkspace, loading } =
useCurrentWorkspaceAndProject();
const router = useRouter();
const {
query: { workspaceSlug, appSlug, updating },
isReady,
} = router;
const notIn404Already = router.pathname !== '/404';
const noResolvedWorkspace =
isReady && !loading && workspaceSlug && currentWorkspace === undefined;
const noResolvedApplication =
isReady &&
!loading &&
workspaceSlug &&
appSlug &&
currentProject === undefined;
const inSettingsDatabasePage = router.pathname.includes('/settings/database');
const { currentProject, currentWorkspace, loading } =
useCurrentWorkspaceAndProject();
useEffect(() => {
// This code is checking if the URL has a query of the form `?updating=true`
// If it does (`updating` is true) this useEffect will immediately exit without executing
// any further statements (e.g. the page will show a loader until `updating` is false).
// This is to prevent the user from being redirected to the 404 page while we are updating
// either the workspace slug or application slug.
if (updating) {
if (
updating ||
!isReady ||
loading ||
router.pathname === '/404' ||
(workspaceSlug && currentWorkspace && appSlug && currentProject) ||
(workspaceSlug && currentWorkspace)
) {
return;
}
if (noResolvedWorkspace && notIn404Already) {
router.replace('/404');
}
if (noResolvedApplication && notIn404Already) {
router.replace('/404');
}
router.replace('/404');
}, [
isReady,
updating,
currentProject,
currentWorkspace,
noResolvedApplication,
noResolvedWorkspace,
notIn404Already,
isReady,
loading,
appSlug,
router,
inSettingsDatabasePage,
updating,
workspaceSlug,
]);
}

View File

@@ -0,0 +1,130 @@
import { UnlockFeatureByUpgrading } from '@/components/applications/UnlockFeatureByUpgrading';
import Container from '@/components/layout/Container';
import ProjectLayout from '@/components/layout/ProjectLayout';
import { useCurrentWorkspaceAndProject } from '@/hooks/v2/useCurrentWorkspaceAndProject';
import ActivityIndicator from '@/ui/v2/ActivityIndicator';
import Box from '@/ui/v2/Box';
import Button from '@/ui/v2/Button/Button';
import Divider from '@/ui/v2/Divider';
import IconButton from '@/ui/v2/IconButton';
import Text from '@/ui/v2/Text';
import ArrowSquareOutIcon from '@/ui/v2/icons/ArrowSquareOutIcon';
import CopyIcon from '@/ui/v2/icons/CopyIcon';
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
import { copy } from '@/utils/copy';
import Image from 'next/image';
import type { ReactElement } from 'react';
export default function MetricsPage() {
const { currentProject, loading } = useCurrentWorkspaceAndProject();
const adminPassword =
currentProject?.config?.observability?.grafana?.adminPassword;
if (loading) {
return (
<Container>
<ActivityIndicator label="Loading project..." delay={1000} />
</Container>
);
}
if (currentProject.plan.isFree) {
return (
<Container>
<UnlockFeatureByUpgrading message="Unlock metrics by upgrading your project to the Pro plan." />
</Container>
);
}
return (
<Container>
<div className="mx-auto w-full max-w-md px-6 py-4 text-left">
<div className="grid grid-flow-row gap-1">
<div className="mx-auto">
<Image
src="/assets/grafana.svg"
width={72}
height={72}
alt="Grafana"
/>
</div>
<Text variant="h3" component="h1" className="text-center">
Open Grafana
</Text>
<Text className="text-center">
Grafana is the observability dashboard for your project. Here you
will be able to see various metrics about its usage and performance.
Copy the admin password to your clipboard and enter it in the next
screen.
</Text>
<Box className="mt-6 grid grid-flow-row gap-0 border-y-1">
<div className="grid w-full grid-cols-1 place-content-between items-center py-2 sm:grid-cols-3">
<Text className="col-span-1 text-center font-medium sm:justify-start sm:text-left">
Username
</Text>
<div className="col-span-1 grid grid-flow-col items-center justify-center gap-2 sm:col-span-2 sm:justify-end">
<Text color="secondary" className="text-sm">
admin
</Text>
</div>
</div>
<Divider />
<div className="grid w-full grid-cols-1 place-content-between items-center py-2 sm:grid-cols-3">
<Text className="col-span-1 text-center font-medium sm:justify-start sm:text-left">
Password
</Text>
<div className="col-span-1 grid grid-flow-col items-center justify-center gap-2 sm:col-span-2 sm:justify-end">
<Text className="font-medium" variant="subtitle2">
{adminPassword
? Array(adminPassword.length).fill('•').join('')
: 'N/A'}
</Text>
{adminPassword && (
<IconButton
onClick={() => copy(adminPassword, 'Grafana password')}
variant="borderless"
color="secondary"
className="min-w-0 p-1"
aria-label="Copy password"
>
<CopyIcon className="h-4 w-4" />
</IconButton>
)}
</div>
</div>
</Box>
<div className="mt-6 grid grid-flow-row gap-2">
<Button
href={generateAppServiceUrl(
currentProject.subdomain,
currentProject.region.awsName,
'grafana',
)}
// Both `target` and `rel` are available when `href` is set. This is
// a limitation of MUI.
// @ts-ignore
target="_blank"
rel="noreferrer noopener"
endIcon={<ArrowSquareOutIcon className="h-4 w-4" />}
>
Open Grafana
</Button>
</div>
</div>
</div>
</Container>
);
}
MetricsPage.getLayout = function getLayout(page: ReactElement) {
return <ProjectLayout>{page}</ProjectLayout>;
};

View File

@@ -13091,6 +13091,7 @@ export type Query_Root = {
/** fetch aggregated fields from the table: "cli_tokens" */
cliTokensAggregate: CliTokens_Aggregate;
config?: Maybe<ConfigConfig>;
configRawJSON: Scalars['String'];
configs: Array<ConfigAppConfig>;
/** fetch data from the table: "continents" */
continents: Array<Continents>;
@@ -13614,6 +13615,12 @@ export type Query_RootConfigArgs = {
};
export type Query_RootConfigRawJsonArgs = {
appID: Scalars['uuid'];
resolve: Scalars['Boolean'];
};
export type Query_RootConfigsArgs = {
resolve: Scalars['Boolean'];
where?: InputMaybe<ConfigConfigComparisonExp>;
@@ -17775,7 +17782,7 @@ export type DeleteApplicationMutation = { __typename?: 'mutation_root', deleteAp
export type GetAllWorkspacesAndProjectsQueryVariables = Exact<{ [key: string]: never; }>;
export type GetAllWorkspacesAndProjectsQuery = { __typename?: 'query_root', workspaces: Array<{ __typename?: 'workspaces', id: any, name: string, slug: string, creatorUserId?: any | null, workspaceMembers: Array<{ __typename?: 'workspaceMembers', id: any, type: string, user: { __typename?: 'users', id: any, email?: any | null, displayName: string } }>, projects: Array<{ __typename?: 'apps', id: any, slug: string, name: string, repositoryProductionBranch: string, subdomain: string, isProvisioned: boolean, createdAt: any, desiredState: number, nhostBaseFolder: string, providersUpdated?: boolean | null, config?: { __typename?: 'ConfigConfig', hasura: { __typename?: 'ConfigHasura', adminSecret: string } } | null, featureFlags: Array<{ __typename?: 'featureFlags', description: string, id: any, name: string, value: string }>, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }>, region: { __typename?: 'regions', id: any, countryCode: string, awsName: string, city: string }, plan: { __typename?: 'plans', id: any, name: string, price: number, isFree: boolean }, githubRepository?: { __typename?: 'githubRepositories', fullName: string } | null, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null }> }> };
export type GetAllWorkspacesAndProjectsQuery = { __typename?: 'query_root', workspaces: Array<{ __typename?: 'workspaces', id: any, name: string, slug: string, creatorUserId?: any | null, workspaceMembers: Array<{ __typename?: 'workspaceMembers', id: any, type: string, user: { __typename?: 'users', id: any, email?: any | null, displayName: string } }>, projects: Array<{ __typename?: 'apps', id: any, slug: string, name: string, repositoryProductionBranch: string, subdomain: string, isProvisioned: boolean, createdAt: any, desiredState: number, nhostBaseFolder: string, providersUpdated?: boolean | null, config?: { __typename?: 'ConfigConfig', observability?: { __typename?: 'ConfigObservability', grafana?: { __typename?: 'ConfigGrafana', adminPassword: string } | null } | null, hasura: { __typename?: 'ConfigHasura', adminSecret: string } } | null, featureFlags: Array<{ __typename?: 'featureFlags', description: string, id: any, name: string, value: string }>, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }>, region: { __typename?: 'regions', id: any, countryCode: string, awsName: string, city: string }, plan: { __typename?: 'plans', id: any, name: string, price: number, isFree: boolean }, githubRepository?: { __typename?: 'githubRepositories', fullName: string } | null, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null }> }> };
export type GetAppPlanAndGlobalPlansAppFragment = { __typename?: 'apps', id: any, subdomain: string, workspace: { __typename?: 'workspaces', id: any, paymentMethods: Array<{ __typename?: 'paymentMethods', id: any }> }, plan: { __typename?: 'plans', id: any, name: string } };
@@ -17832,7 +17839,7 @@ export type GetWorkspaceAndProjectQueryVariables = Exact<{
}>;
export type GetWorkspaceAndProjectQuery = { __typename?: 'query_root', workspaces: Array<{ __typename?: 'workspaces', id: any, name: string, slug: string, creatorUserId?: any | null, workspaceMembers: Array<{ __typename?: 'workspaceMembers', id: any, type: string, user: { __typename?: 'users', id: any, email?: any | null, displayName: string } }>, projects: Array<{ __typename?: 'apps', id: any, slug: string, name: string, repositoryProductionBranch: string, subdomain: string, isProvisioned: boolean, createdAt: any, desiredState: number, nhostBaseFolder: string, providersUpdated?: boolean | null, config?: { __typename?: 'ConfigConfig', hasura: { __typename?: 'ConfigHasura', adminSecret: string } } | null, featureFlags: Array<{ __typename?: 'featureFlags', description: string, id: any, name: string, value: string }>, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }>, region: { __typename?: 'regions', id: any, countryCode: string, awsName: string, city: string }, plan: { __typename?: 'plans', id: any, name: string, price: number, isFree: boolean }, githubRepository?: { __typename?: 'githubRepositories', fullName: string } | null, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null }> }> };
export type GetWorkspaceAndProjectQuery = { __typename?: 'query_root', workspaces: Array<{ __typename?: 'workspaces', id: any, name: string, slug: string, creatorUserId?: any | null, workspaceMembers: Array<{ __typename?: 'workspaceMembers', id: any, type: string, user: { __typename?: 'users', id: any, email?: any | null, displayName: string } }>, projects: Array<{ __typename?: 'apps', id: any, slug: string, name: string, repositoryProductionBranch: string, subdomain: string, isProvisioned: boolean, createdAt: any, desiredState: number, nhostBaseFolder: string, providersUpdated?: boolean | null, config?: { __typename?: 'ConfigConfig', observability?: { __typename?: 'ConfigObservability', grafana?: { __typename?: 'ConfigGrafana', adminPassword: string } | null } | null, hasura: { __typename?: 'ConfigHasura', adminSecret: string } } | null, featureFlags: Array<{ __typename?: 'featureFlags', description: string, id: any, name: string, value: string }>, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }>, region: { __typename?: 'regions', id: any, countryCode: string, awsName: string, city: string }, plan: { __typename?: 'plans', id: any, name: string, price: number, isFree: boolean }, githubRepository?: { __typename?: 'githubRepositories', fullName: string } | null, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null }> }> };
export type InsertApplicationMutationVariables = Exact<{
app: Apps_Insert_Input;
@@ -18039,9 +18046,9 @@ export type GetFilesAggregateQueryVariables = Exact<{
export type GetFilesAggregateQuery = { __typename?: 'query_root', filesAggregate: { __typename?: 'files_aggregate', aggregate?: { __typename?: 'files_aggregate_fields', count: number } | null } };
export type ProjectFragment = { __typename?: 'apps', id: any, slug: string, name: string, repositoryProductionBranch: string, subdomain: string, isProvisioned: boolean, createdAt: any, desiredState: number, nhostBaseFolder: string, providersUpdated?: boolean | null, config?: { __typename?: 'ConfigConfig', hasura: { __typename?: 'ConfigHasura', adminSecret: string } } | null, featureFlags: Array<{ __typename?: 'featureFlags', description: string, id: any, name: string, value: string }>, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }>, region: { __typename?: 'regions', id: any, countryCode: string, awsName: string, city: string }, plan: { __typename?: 'plans', id: any, name: string, price: number, isFree: boolean }, githubRepository?: { __typename?: 'githubRepositories', fullName: string } | null, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null };
export type ProjectFragment = { __typename?: 'apps', id: any, slug: string, name: string, repositoryProductionBranch: string, subdomain: string, isProvisioned: boolean, createdAt: any, desiredState: number, nhostBaseFolder: string, providersUpdated?: boolean | null, config?: { __typename?: 'ConfigConfig', observability?: { __typename?: 'ConfigObservability', grafana?: { __typename?: 'ConfigGrafana', adminPassword: string } | null } | null, hasura: { __typename?: 'ConfigHasura', adminSecret: string } } | null, featureFlags: Array<{ __typename?: 'featureFlags', description: string, id: any, name: string, value: string }>, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }>, region: { __typename?: 'regions', id: any, countryCode: string, awsName: string, city: string }, plan: { __typename?: 'plans', id: any, name: string, price: number, isFree: boolean }, githubRepository?: { __typename?: 'githubRepositories', fullName: string } | null, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null };
export type WorkspaceFragment = { __typename?: 'workspaces', id: any, name: string, slug: string, creatorUserId?: any | null, workspaceMembers: Array<{ __typename?: 'workspaceMembers', id: any, type: string, user: { __typename?: 'users', id: any, email?: any | null, displayName: string } }>, projects: Array<{ __typename?: 'apps', id: any, slug: string, name: string, repositoryProductionBranch: string, subdomain: string, isProvisioned: boolean, createdAt: any, desiredState: number, nhostBaseFolder: string, providersUpdated?: boolean | null, config?: { __typename?: 'ConfigConfig', hasura: { __typename?: 'ConfigHasura', adminSecret: string } } | null, featureFlags: Array<{ __typename?: 'featureFlags', description: string, id: any, name: string, value: string }>, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }>, region: { __typename?: 'regions', id: any, countryCode: string, awsName: string, city: string }, plan: { __typename?: 'plans', id: any, name: string, price: number, isFree: boolean }, githubRepository?: { __typename?: 'githubRepositories', fullName: string } | null, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null }> };
export type WorkspaceFragment = { __typename?: 'workspaces', id: any, name: string, slug: string, creatorUserId?: any | null, workspaceMembers: Array<{ __typename?: 'workspaceMembers', id: any, type: string, user: { __typename?: 'users', id: any, email?: any | null, displayName: string } }>, projects: Array<{ __typename?: 'apps', id: any, slug: string, name: string, repositoryProductionBranch: string, subdomain: string, isProvisioned: boolean, createdAt: any, desiredState: number, nhostBaseFolder: string, providersUpdated?: boolean | null, config?: { __typename?: 'ConfigConfig', observability?: { __typename?: 'ConfigObservability', grafana?: { __typename?: 'ConfigGrafana', adminPassword: string } | null } | null, hasura: { __typename?: 'ConfigHasura', adminSecret: string } } | null, featureFlags: Array<{ __typename?: 'featureFlags', description: string, id: any, name: string, value: string }>, appStates: Array<{ __typename?: 'appStateHistory', id: any, appId: any, message?: string | null, stateId: number, createdAt: any }>, region: { __typename?: 'regions', id: any, countryCode: string, awsName: string, city: string }, plan: { __typename?: 'plans', id: any, name: string, price: number, isFree: boolean }, githubRepository?: { __typename?: 'githubRepositories', fullName: string } | null, deployments: Array<{ __typename?: 'deployments', id: any, commitSHA: string, commitMessage?: string | null, commitUserName?: string | null, deploymentStartedAt?: any | null, deploymentEndedAt?: any | null, commitUserAvatarUrl?: string | null, deploymentStatus?: string | null }>, creator?: { __typename?: 'users', id: any, email?: any | null, displayName: string } | null }> };
export type GithubRepositoryFragment = { __typename?: 'githubRepositories', id: any, name: string, fullName: string, private: boolean, githubAppInstallation: { __typename?: 'githubAppInstallations', id: any, accountLogin?: string | null, accountType?: string | null, accountAvatarUrl?: string | null } };
@@ -18452,6 +18459,11 @@ export const ProjectFragmentDoc = gql`
nhostBaseFolder
providersUpdated
config(resolve: true) {
observability {
grafana {
adminPassword
}
}
hasura {
adminSecret
}

View File

@@ -52,6 +52,10 @@ test('should generate a per service subdomain in remote mode', () => {
expect(generateAppServiceUrl('test', 'eu-west-1', 'hasura')).toBe(
'https://test.hasura.eu-west-1.nhost.run',
);
expect(generateAppServiceUrl('test', 'eu-west-1', 'grafana')).toBe(
'https://test.grafana.eu-west-1.nhost.run',
);
});
test('should generate staging subdomains in staging environment', () => {
@@ -77,9 +81,13 @@ test('should generate staging subdomains in staging environment', () => {
expect(generateAppServiceUrl('test', 'eu-west-1', 'hasura')).toBe(
'https://test.hasura.eu-west-1.staging.nhost.run',
);
expect(generateAppServiceUrl('test', 'eu-west-1', 'grafana')).toBe(
'https://test.grafana.eu-west-1.staging.nhost.run',
);
});
test('should generate no slug for Hasura neither in local mode nor in remote mode', () => {
test('should generate no slug for Hasura and Grafana neither in local mode nor in remote mode', () => {
process.env.NEXT_PUBLIC_NHOST_HASURA_API_URL = 'http://localhost:8082';
expect(generateAppServiceUrl('test', 'eu-west-1', 'hasura')).toBe(
@@ -92,12 +100,18 @@ test('should generate no slug for Hasura neither in local mode nor in remote mod
expect(generateAppServiceUrl('test', 'eu-west-1', 'hasura')).toBe(
'https://test.hasura.eu-west-1.staging.nhost.run',
);
expect(generateAppServiceUrl('test', 'eu-west-1', 'grafana')).toBe(
'https://test.grafana.eu-west-1.staging.nhost.run',
);
process.env.NEXT_PUBLIC_ENV = 'production';
expect(generateAppServiceUrl('test', 'eu-west-1', 'hasura')).toBe(
'https://test.hasura.eu-west-1.nhost.run',
);
expect(generateAppServiceUrl('test', 'eu-west-1', 'grafana')).toBe(
'https://test.grafana.eu-west-1.nhost.run',
);
});
test('should be able to override the default remote backend slugs', () => {

View File

@@ -12,7 +12,8 @@ export type NhostService =
| 'graphql'
| 'functions'
| 'storage'
| 'hasura';
| 'hasura'
| 'grafana';
/**
* The default slugs that are used when running the dashboard locally. These
@@ -25,6 +26,7 @@ export const defaultLocalBackendSlugs: Record<NhostService, string> = {
functions: '/v1/functions',
storage: '/v1/files',
hasura: '',
grafana: '',
};
/**
@@ -37,6 +39,7 @@ export const defaultRemoteBackendSlugs: Record<NhostService, string> = {
functions: '/v1',
storage: '/v1',
hasura: '',
grafana: '',
};
/**
@@ -53,7 +56,7 @@ export const defaultRemoteBackendSlugs: Record<NhostService, string> = {
export default function generateAppServiceUrl(
subdomain: string,
region: string,
service: 'auth' | 'graphql' | 'functions' | 'storage' | 'hasura',
service: 'auth' | 'graphql' | 'functions' | 'storage' | 'hasura' | 'grafana',
localBackendSlugs = defaultLocalBackendSlugs,
remoteBackendSlugs = defaultRemoteBackendSlugs,
) {
@@ -66,6 +69,7 @@ export default function generateAppServiceUrl(
storage: getStorageServiceUrl(),
functions: getFunctionsServiceUrl(),
hasura: getHasuraApiUrl(),
grafana: '',
};
if (!serviceUrls[service]) {

View File

@@ -6,7 +6,7 @@ export default defineConfig({
// @ts-ignore
plugins: [tsconfigPaths({ projects: ['./tsconfig.test.json'] }), react()],
test: {
testTimeout: 20000,
testTimeout: 30000,
environment: 'jsdom',
globals: true,
setupFiles: 'src/setupTests.ts',

View File

@@ -1,5 +1,11 @@
# @nhost/docs
## 0.2.0
### Minor Changes
- fe341519: Add section on Metrics to the docs
## 0.1.1
### Patch Changes

View File

@@ -7,7 +7,7 @@ image: /img/og/platform/compute-resources.png
Compute resources are the fundamental units that represent the processing power and memory available to your Nhost projects. The primary compute resources are vCPU and RAM. This documentation outlines the key aspects of compute resources in the context of the Nhost Cloud Platform.
## Shared vs Dedicated Compute
### Shared vs Dedicated Compute
Free Projects are given a total of 2 shared vCPUs and 1 GiB of RAM:

View File

@@ -0,0 +1,34 @@
---
title: 'Metrics (beta)'
sidebar_position: 1
image: /img/og/platform/metrics.png
---
Metrics provide insights into your Nhost projects by integrating a managed Grafana instance tailored to them. This feature is available on the Pro plan and allows you to analyze your project's performance, identify potential bottlenecks, and optimize your application.
### Available Dashboards
Nhost Metrics includes pre-configured dashboards with the following metrics:
- vCPU/memory usage by Service replica for all services
- Throttling time / percentage
- Postgres volume usage
- Networking
![Metrics](/img/platform/metrics/grafana.png)
### Accessing Metrics
Metrics can be found in your project's dashboard.
![Metrics](/img/platform/metrics/nhost-dashboard-metrics.png)
### Limitations (Beta)
Please note that while Metrics is in beta, its functionality and pricing might change.
- Users cannot save or use custom dashboards in the current beta version.
- Additional categories of metrics like application and database metrics will be added in future updates.
- Future updates will centralize logs using Loki and integrate alerts directly into the Grafana dashboard.
Using Nhost Metrics allows you to identify bottlenecks in your applications, which you can then fix by leveraging [Compute Resources](https://docs.nhost.io/platform/compute) and [Service Replicas](https://docs.nhost.io/platform/service-replicas) to address these performance issues.

View File

@@ -10,7 +10,7 @@ Service Replicas is a feature that allows you to create multiple replicas of you
To read the announcement, check out our [blog post](https://nhost.io/blog/service-replicas).
## Supported Services
### Supported Services
Replicas can be configured for the following services:
@@ -20,7 +20,7 @@ Replicas can be configured for the following services:
Currently, we don't support replicas for Postgres.
## Configuring Service Replicas
### Configuring Service Replicas
To configure Service Replicas for your project, follow these steps:
@@ -32,13 +32,13 @@ To configure Service Replicas for your project, follow these steps:
Please note that when setting multiple replicas for a service, the 1:2 ratio between CPU and RAM for that service has to be respected. With only one replica, this ratio does not need to be respected at the service level.
## Benefits
### Benefits
- Improved fault tolerance: Multiple replicas ensure that if one instance crashes duo to an unexpected issue, the other replicas can continue to serve user requests.
- Improved availability: Distributing user requests among multiple replicas allows your apps to handle more traffic and maintain a high level of performance.
- Load balancing: Distributing workloads evenly among replicas to prevent bottlenecks and ensure smooth performance during peak times.
## Pricing
### Pricing
Pricing is based on the size of each replica and the total number of replicas you have configured for each service:
@@ -47,7 +47,7 @@ Pricing is based on the size of each replica and the total number of replicas yo
Please note that Service Replicas is available only for projects on the Pro plan or higher.
## Caveats
### Caveats
- Postgres replication: As mentioned earlier, we do not have support for multiple replicas of Postgres. This feature may be added in the future.
- Resource ratio: When configuring multiple replicas for a service, you must adhere to the 1:2 ratio between CPU and RAM for that service.

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

170
pnpm-lock.yaml generated
View File

@@ -160,7 +160,7 @@ importers:
graphql-request: ^6.0.0
graphql-tag: ^2.12.6
graphql-ws: ^5.11.2
jsdom: ^21.0.0
jsdom: ^22.0.0
just-kebab-case: ^4.1.1
lint-staged: '>=13'
lodash.debounce: ^4.0.8
@@ -315,7 +315,7 @@ importers:
eslint-plugin-jsx-a11y: 6.6.1_eslint@8.28.0
eslint-plugin-react: 7.31.11_eslint@8.28.0
eslint-plugin-react-hooks: 4.6.0_eslint@8.28.0
jsdom: 21.0.0
jsdom: 22.0.0
lint-staged: 13.0.3
msw: 1.0.1_encoding@0.1.13
msw-storybook-addon: 1.6.3_wfed36xf2cj7dcilohayxz5mxm
@@ -333,7 +333,7 @@ importers:
tsconfig-paths-webpack-plugin: 4.0.0
vite: 4.0.4_@types+node@16.18.11
vite-tsconfig-paths: 4.0.3_vite@4.0.4
vitest: 0.30.1_jsdom@21.0.0
vitest: 0.30.1_jsdom@22.0.0
webpack: 5.75.0
docs:
@@ -5619,7 +5619,6 @@ packages:
engines: {node: '>=12'}
dependencies:
'@jridgewell/trace-mapping': 0.3.9
dev: true
/@date-io/core/2.16.0:
resolution: {integrity: sha512-DYmSzkr+jToahwWrsiRA2/pzMEtz9Bq1euJwoOuYwuwIYXnZFtHajY2E6a1VNVDc9jP8YUXK1BvnZH9mmT19Zg==}
@@ -8940,11 +8939,11 @@ packages:
extract-files: 11.0.0
graphql: 16.6.0
graphql-ws: 5.11.3_graphql@16.6.0
isomorphic-ws: 5.0.0_ws@8.12.1
isomorphic-ws: 5.0.0_ws@8.13.0
meros: 1.2.1_@types+node@16.18.11
tslib: 2.5.0
value-or-promise: 1.0.12
ws: 8.12.1
ws: 8.13.0
transitivePeerDependencies:
- '@types/node'
- bufferutil
@@ -9051,10 +9050,10 @@ packages:
'@types/ws': 8.5.3
'@whatwg-node/fetch': 0.8.1_@types+node@16.18.11
graphql: 16.6.0
isomorphic-ws: 5.0.0_ws@8.12.1
isomorphic-ws: 5.0.0_ws@8.13.0
tslib: 2.5.0
value-or-promise: 1.0.12
ws: 8.12.1
ws: 8.13.0
transitivePeerDependencies:
- '@types/node'
- bufferutil
@@ -9544,7 +9543,6 @@ packages:
dependencies:
'@jridgewell/resolve-uri': 3.1.0
'@jridgewell/sourcemap-codec': 1.4.14
dev: true
/@leichtgewicht/ip-codec/2.0.3:
resolution: {integrity: sha512-nkalE/f1RvRGChwBnEIoBfSEYOXnCRdleKuv6+lePbMDrMZXeDQnqak5XDOeBgrPPyPfAdcCu/B5z+v3VhplGg==}
@@ -12049,7 +12047,7 @@ packages:
util-deprecate: 1.0.2
watchpack: 2.4.0
webpack: 4.46.0
ws: 8.12.1
ws: 8.13.0
x-default-browser: 0.4.0
transitivePeerDependencies:
- '@storybook/mdx2-csf'
@@ -13045,7 +13043,7 @@ packages:
tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1'
dependencies:
mini-svg-data-uri: 1.4.4
tailwindcss: 3.2.1_postcss@8.4.20
tailwindcss: 3.2.1_v776zzvn44o7tpgzieipaairwm
/@tailwindcss/forms/0.5.3_tailwindcss@3.2.4:
resolution: {integrity: sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==}
@@ -13254,19 +13252,15 @@ packages:
/@tsconfig/node10/1.0.8:
resolution: {integrity: sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==}
dev: true
/@tsconfig/node12/1.0.9:
resolution: {integrity: sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==}
dev: true
/@tsconfig/node14/1.0.1:
resolution: {integrity: sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==}
dev: true
/@tsconfig/node16/1.0.2:
resolution: {integrity: sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==}
dev: true
/@types/argparse/1.0.38:
resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==}
@@ -14560,7 +14554,7 @@ packages:
c8: 7.13.0
picocolors: 1.0.0
std-env: 3.3.2
vitest: 0.30.1_jsdom@21.0.0
vitest: 0.30.1_jsdom@22.0.0
dev: true
/@vitest/expect/0.30.0:
@@ -15503,13 +15497,6 @@ packages:
acorn-walk: 7.2.0
dev: true
/acorn-globals/7.0.1:
resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==}
dependencies:
acorn: 8.8.2
acorn-walk: 8.2.0
dev: true
/acorn-import-assertions/1.8.0_acorn@8.8.1:
resolution: {integrity: sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==}
peerDependencies:
@@ -15562,7 +15549,6 @@ packages:
resolution: {integrity: sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==}
engines: {node: '>=0.4.0'}
hasBin: true
dev: true
/acorn/8.8.1:
resolution: {integrity: sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==}
@@ -15816,7 +15802,6 @@ packages:
/arg/4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
dev: true
/arg/5.0.2:
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
@@ -17962,7 +17947,6 @@ packages:
/create-require/1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
dev: true
/cross-fetch/3.1.5:
resolution: {integrity: sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==}
@@ -18297,6 +18281,13 @@ packages:
cssom: 0.3.8
dev: true
/cssstyle/3.0.0:
resolution: {integrity: sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==}
engines: {node: '>=14'}
dependencies:
rrweb-cssom: 0.6.0
dev: true
/csstype/2.6.21:
resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==}
@@ -18667,13 +18658,13 @@ packages:
whatwg-url: 10.0.0
dev: true
/data-urls/3.0.2:
resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==}
engines: {node: '>=12'}
/data-urls/4.0.0:
resolution: {integrity: sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g==}
engines: {node: '>=14'}
dependencies:
abab: 2.0.6
whatwg-mimetype: 3.0.0
whatwg-url: 11.0.0
whatwg-url: 12.0.1
dev: true
/dataloader/2.1.0:
@@ -18748,8 +18739,8 @@ packages:
resolution: {integrity: sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==}
dev: true
/decimal.js/10.4.2:
resolution: {integrity: sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA==}
/decimal.js/10.4.3:
resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
dev: true
/decode-uri-component/0.2.0:
@@ -19011,7 +19002,6 @@ packages:
/diff/4.0.2:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'}
dev: true
/diffie-hellman/5.0.3:
resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==}
@@ -23630,6 +23620,14 @@ packages:
ws: 8.12.1
dev: true
/isomorphic-ws/5.0.0_ws@8.13.0:
resolution: {integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==}
peerDependencies:
ws: '*'
dependencies:
ws: 8.13.0
dev: true
/istanbul-lib-coverage/3.2.0:
resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==}
engines: {node: '>=8'}
@@ -23941,9 +23939,9 @@ packages:
- utf-8-validate
dev: true
/jsdom/21.0.0:
resolution: {integrity: sha512-AIw+3ZakSUtDYvhwPwWHiZsUi3zHugpMEKlNPaurviseYoBqo0zBd3zqoUi3LPCNtPFlEP8FiW9MqCZdjb2IYA==}
engines: {node: '>=14'}
/jsdom/22.0.0:
resolution: {integrity: sha512-p5ZTEb5h+O+iU02t0GfEjAnkdYPrQSkfuTSMkMYyIoMvUNEHsbG0bHHbfXIcfTqD2UfvjQX7mmgiFsyRwGscVw==}
engines: {node: '>=16'}
peerDependencies:
canvas: ^2.5.0
peerDependenciesMeta:
@@ -23951,21 +23949,18 @@ packages:
optional: true
dependencies:
abab: 2.0.6
acorn: 8.8.1
acorn-globals: 7.0.1
cssom: 0.5.0
cssstyle: 2.3.0
data-urls: 3.0.2
decimal.js: 10.4.2
cssstyle: 3.0.0
data-urls: 4.0.0
decimal.js: 10.4.3
domexception: 4.0.0
escodegen: 2.0.0
form-data: 4.0.0
html-encoding-sniffer: 3.0.0
http-proxy-agent: 5.0.0
https-proxy-agent: 5.0.1
is-potential-custom-element-name: 1.0.1
nwsapi: 2.2.2
parse5: 7.1.1
nwsapi: 2.2.4
parse5: 7.1.2
rrweb-cssom: 0.6.0
saxes: 6.0.0
symbol-tree: 3.2.4
tough-cookie: 4.1.2
@@ -23973,8 +23968,8 @@ packages:
webidl-conversions: 7.0.0
whatwg-encoding: 2.0.0
whatwg-mimetype: 3.0.0
whatwg-url: 11.0.0
ws: 8.11.0
whatwg-url: 12.0.1
ws: 8.13.0
xml-name-validator: 4.0.0
transitivePeerDependencies:
- bufferutil
@@ -24678,7 +24673,6 @@ packages:
/make-error/1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
dev: true
/makeerror/1.0.12:
resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==}
@@ -25830,8 +25824,8 @@ packages:
resolution: {integrity: sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==}
dev: true
/nwsapi/2.2.2:
resolution: {integrity: sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==}
/nwsapi/2.2.4:
resolution: {integrity: sha512-NHj4rzRo0tQdijE9ZqAx6kYDcoRwYwSYzCA8MY3JzfxlrvEU0jhnhJT9BhqhJs7I/dKcrDm6TyulaRqZPIhN5g==}
dev: true
/object-assign/4.1.1:
@@ -26329,6 +26323,12 @@ packages:
dependencies:
entities: 4.4.0
/parse5/7.1.2:
resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
dependencies:
entities: 4.4.0
dev: true
/parseurl/1.3.3:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'}
@@ -26747,7 +26747,6 @@ packages:
postcss-value-parser: 4.2.0
read-cache: 1.0.0
resolve: 1.22.1
dev: true
/postcss-import/14.1.0_postcss@8.4.20:
resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==}
@@ -26759,6 +26758,7 @@ packages:
postcss-value-parser: 4.2.0
read-cache: 1.0.0
resolve: 1.22.1
dev: true
/postcss-js/4.0.0_postcss@8.4.18:
resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==}
@@ -26778,7 +26778,6 @@ packages:
dependencies:
camelcase-css: 2.0.1
postcss: 8.4.19
dev: true
/postcss-js/4.0.0_postcss@8.4.20:
resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==}
@@ -26788,6 +26787,7 @@ packages:
dependencies:
camelcase-css: 2.0.1
postcss: 8.4.20
dev: true
/postcss-load-config/3.1.4_postcss@8.4.18:
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
@@ -26821,6 +26821,7 @@ packages:
lilconfig: 2.0.6
postcss: 8.4.20
yaml: 1.10.2
dev: true
/postcss-load-config/3.1.4_v776zzvn44o7tpgzieipaairwm:
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
@@ -26838,7 +26839,6 @@ packages:
postcss: 8.4.19
ts-node: 10.9.1_@types+node@16.18.11
yaml: 1.10.2
dev: true
/postcss-loader/4.3.0_gzaxsinx64nntyd3vmdqwl7coe:
resolution: {integrity: sha512-M/dSoIiNDOo8Rk0mUqoj4kpGq91gcxCfb9PoyZVdZ76/AuhxylHDYZblNE8o+EQ9AMSASeMFEKxZf5aU6wlx1Q==}
@@ -27052,7 +27052,6 @@ packages:
dependencies:
postcss: 8.4.19
postcss-selector-parser: 6.0.10
dev: true
/postcss-nested/6.0.0_postcss@8.4.20:
resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==}
@@ -27062,6 +27061,7 @@ packages:
dependencies:
postcss: 8.4.20
postcss-selector-parser: 6.0.10
dev: true
/postcss-normalize-charset/5.1.0_postcss@8.4.21:
resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==}
@@ -27278,7 +27278,6 @@ packages:
nanoid: 3.3.4
picocolors: 1.0.0
source-map-js: 1.0.2
dev: true
/postcss/8.4.20:
resolution: {integrity: sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==}
@@ -27638,6 +27637,11 @@ packages:
/punycode/2.1.1:
resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==}
engines: {node: '>=6'}
dev: true
/punycode/2.3.0:
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
engines: {node: '>=6'}
/pupa/2.1.1:
resolution: {integrity: sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==}
@@ -28820,6 +28824,10 @@ packages:
optionalDependencies:
fsevents: 2.3.2
/rrweb-cssom/0.6.0:
resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==}
dev: true
/rsvp/4.8.5:
resolution: {integrity: sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==}
engines: {node: 6.* || >= 7.*}
@@ -30202,6 +30210,7 @@ packages:
resolve: 1.22.1
transitivePeerDependencies:
- ts-node
dev: true
/tailwindcss/3.2.1_v776zzvn44o7tpgzieipaairwm:
resolution: {integrity: sha512-Uw+GVSxp5CM48krnjHObqoOwlCt5Qo6nw1jlCRwfGy68dSYb/LwS9ZFidYGRiM+w6rMawkZiu1mEMAsHYAfoLg==}
@@ -30235,7 +30244,6 @@ packages:
resolve: 1.22.1
transitivePeerDependencies:
- ts-node
dev: true
/tailwindcss/3.2.4_postcss@8.4.20:
resolution: {integrity: sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==}
@@ -30625,6 +30633,13 @@ packages:
punycode: 2.1.1
dev: true
/tr46/4.1.1:
resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==}
engines: {node: '>=14'}
dependencies:
punycode: 2.3.0
dev: true
/tree-kill/1.2.2:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
hasBin: true
@@ -30776,7 +30791,6 @@ packages:
make-error: 1.3.6
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
dev: true
/ts-node/10.9.1_moeqx3xmzxqxagf2sz6mqkbb7m:
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
@@ -31607,7 +31621,7 @@ packages:
/uri-js/4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
dependencies:
punycode: 2.1.1
punycode: 2.3.0
/urix/0.1.0:
resolution: {integrity: sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==}
@@ -31871,7 +31885,6 @@ packages:
/v8-compile-cache-lib/3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
dev: true
/v8-to-istanbul/9.0.1:
resolution: {integrity: sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==}
@@ -32388,7 +32401,7 @@ packages:
- terser
dev: true
/vitest/0.30.1_jsdom@21.0.0:
/vitest/0.30.1_jsdom@22.0.0:
resolution: {integrity: sha512-y35WTrSTlTxfMLttgQk4rHcaDkbHQwDP++SNwPb+7H8yb13Q3cu2EixrtHzF27iZ8v0XCciSsLg00RkPAzB/aA==}
engines: {node: '>=v14.18.0'}
hasBin: true
@@ -32433,7 +32446,7 @@ packages:
chai: 4.3.7
concordance: 5.0.4
debug: 4.3.4
jsdom: 21.0.0
jsdom: 22.0.0
local-pkg: 0.4.3
magic-string: 0.30.0
pathe: 1.1.0
@@ -33111,11 +33124,11 @@ packages:
webidl-conversions: 7.0.0
dev: true
/whatwg-url/11.0.0:
resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==}
engines: {node: '>=12'}
/whatwg-url/12.0.1:
resolution: {integrity: sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ==}
engines: {node: '>=14'}
dependencies:
tr46: 3.0.0
tr46: 4.1.1
webidl-conversions: 7.0.0
dev: true
@@ -33301,19 +33314,6 @@ packages:
optional: true
dev: true
/ws/8.11.0:
resolution: {integrity: sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ^5.0.2
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
dev: true
/ws/8.12.1:
resolution: {integrity: sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==}
engines: {node: '>=10.0.0'}
@@ -33326,6 +33326,19 @@ packages:
utf-8-validate:
optional: true
/ws/8.13.0:
resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
dev: true
/ws/8.9.0:
resolution: {integrity: sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==}
engines: {node: '>=10.0.0'}
@@ -33504,7 +33517,6 @@ packages:
/yn/3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'}
dev: true
/yocto-queue/0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}