Compare commits
10 Commits
@nhost/rea
...
@nhost/das
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68b3d23cd9 | ||
|
|
d86e5c9c16 | ||
|
|
b2cc1411d7 | ||
|
|
407feeac37 | ||
|
|
7b25c37c26 | ||
|
|
6df4f02e95 | ||
|
|
aaae98f019 | ||
|
|
dc23dc0f49 | ||
|
|
82728da100 | ||
|
|
2d68fee54c |
@@ -1,5 +1,31 @@
|
||||
# @nhost/dashboard
|
||||
|
||||
## 1.9.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- d86e5c9: feat: add support for filtering the logs using a RegExp
|
||||
|
||||
## 1.8.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react-apollo@9.0.3
|
||||
- @nhost/nextjs@2.1.5
|
||||
|
||||
## 1.8.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 6df4f02: fix: use custom error toast and show correct message when sending an invite
|
||||
|
||||
## 1.8.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react-apollo@9.0.2
|
||||
- @nhost/nextjs@2.1.4
|
||||
|
||||
## 1.8.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -16,8 +16,6 @@ const cspHeader = `
|
||||
form-action 'self';
|
||||
frame-ancestors 'none';
|
||||
frame-src 'self' js.stripe.com;
|
||||
block-all-mixed-content;
|
||||
upgrade-insecure-requests;
|
||||
`;
|
||||
|
||||
module.exports = withBundleAnalyzer({
|
||||
@@ -42,10 +40,6 @@ module.exports = withBundleAnalyzer({
|
||||
key: 'X-Frame-Options',
|
||||
value: 'SAMEORIGIN',
|
||||
},
|
||||
{
|
||||
key: 'Content-Security-Policy',
|
||||
value: cspHeader.replace(/\n/g, ''),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/dashboard",
|
||||
"version": "1.8.0",
|
||||
"version": "1.9.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
@@ -44,7 +44,7 @@
|
||||
"@tailwindcss/forms": "^0.5.7",
|
||||
"@tanstack/react-query": "^4.36.1",
|
||||
"@tanstack/react-table": "^8.11.7",
|
||||
"@tanstack/react-virtual": "^3.0.2",
|
||||
"@tanstack/react-virtual": "^3.1.2",
|
||||
"@uiw/codemirror-theme-github": "^4.21.21",
|
||||
"@uiw/react-codemirror": "^4.21.21",
|
||||
"analytics-node": "^6.2.0",
|
||||
|
||||
@@ -5,7 +5,7 @@ import { XIcon } from '@/components/ui/v2/icons/XIcon';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { getToastBackgroundColor } from '@/utils/constants/settings';
|
||||
import { copy } from '@/utils/copy';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import type { ApolloError } from '@apollo/client';
|
||||
import { useUserData } from '@nhost/nextjs';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useRouter } from 'next/router';
|
||||
@@ -27,10 +27,11 @@ const getInternalErrorMessage = (
|
||||
return null;
|
||||
}
|
||||
|
||||
if (error instanceof ApolloError) {
|
||||
const internalError = error.graphQLErrors?.[0]?.extensions?.internal as
|
||||
| { error: { message: string } }
|
||||
| undefined;
|
||||
if (error.name === 'ApolloError') {
|
||||
// @ts-ignore
|
||||
const internalError = error.graphQLErrors?.[0]?.extensions?.internal as {
|
||||
error: { message: string };
|
||||
};
|
||||
return internalError?.error?.message || null;
|
||||
}
|
||||
|
||||
@@ -42,7 +43,7 @@ const getInternalErrorMessage = (
|
||||
};
|
||||
|
||||
const errorToObject = (error: ApolloError | Error) => {
|
||||
if (error instanceof ApolloError) {
|
||||
if (error.name === 'ApolloError') {
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ export default function LogsBody({ logsData, loading, error }: LogsBodyProps) {
|
||||
count: rows.length,
|
||||
getScrollElement: () => tableRef.current,
|
||||
estimateSize: () => 63,
|
||||
overscan: 5,
|
||||
overscan: 50,
|
||||
});
|
||||
|
||||
if (loading && !error) {
|
||||
@@ -214,7 +214,7 @@ export default function LogsBody({ logsData, loading, error }: LogsBodyProps) {
|
||||
<TableCell
|
||||
key={cell.id}
|
||||
component="td"
|
||||
className="break-words py-2.5 px-2 align-top text-xs- font-normal tracking-tight"
|
||||
className="break-words px-2 py-2.5 align-top text-xs- font-normal tracking-tight"
|
||||
style={{
|
||||
width: cell.column.getSize() || 'auto',
|
||||
minWidth: !cell.column.getSize() ? 300 : 'initial',
|
||||
|
||||
@@ -52,7 +52,7 @@ function LogsDatePicker({
|
||||
<Text
|
||||
htmlFor={label}
|
||||
component="label"
|
||||
className="self-center text-sm+ font-normal"
|
||||
className="min-w-14 self-center text-sm+ font-normal"
|
||||
color="secondary"
|
||||
>
|
||||
{label}
|
||||
|
||||
@@ -1,246 +1,240 @@
|
||||
import { ControlledSelect } from '@/components/form/ControlledSelect';
|
||||
import { Form } from '@/components/form/Form';
|
||||
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
|
||||
import type { BoxProps } from '@/components/ui/v2/Box';
|
||||
import { Box } from '@/components/ui/v2/Box';
|
||||
import { Button } from '@/components/ui/v2/Button';
|
||||
import { ClockIcon } from '@/components/ui/v2/icons/ClockIcon';
|
||||
import { InfoIcon } from '@/components/ui/v2/icons/InfoIcon';
|
||||
import { SearchIcon } from '@/components/ui/v2/icons/SearchIcon';
|
||||
import { Input } from '@/components/ui/v2/Input';
|
||||
import { Link } from '@/components/ui/v2/Link';
|
||||
import { Option } from '@/components/ui/v2/Option';
|
||||
import { Select } from '@/components/ui/v2/Select';
|
||||
import { Tooltip } from '@/components/ui/v2/Tooltip';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { LogsDatePicker } from '@/features/projects/logs/components/LogsDatePicker';
|
||||
import type { LogsCustomInterval } from '@/features/projects/logs/utils/constants/intervals';
|
||||
import { LOGS_AVAILABLE_INTERVALS } from '@/features/projects/logs/utils/constants/intervals';
|
||||
import type { AvailableLogsService } from '@/features/projects/logs/utils/constants/services';
|
||||
import { LOGS_AVAILABLE_SERVICES } from '@/features/projects/logs/utils/constants/services';
|
||||
import { useGetRunServicesQuery } from '@/utils/__generated__/graphql';
|
||||
import { LogsRangeSelector } from '@/features/projects/logs/components/LogsRangeSelector';
|
||||
import { AvailableLogsService } from '@/features/projects/logs/utils/constants/services';
|
||||
import { MINUTES_TO_DECREASE_FROM_CURRENT_DATE } from '@/utils/constants/common';
|
||||
import { useGetServiceLabelValuesQuery } from '@/utils/__generated__/graphql';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { subMinutes } from 'date-fns';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
export interface LogsHeaderProps extends Omit<BoxProps, 'children'> {
|
||||
export const validationSchema = Yup.object({
|
||||
from: Yup.date(),
|
||||
to: Yup.date().nullable(),
|
||||
service: Yup.string().oneOf(Object.values(AvailableLogsService)),
|
||||
regexFilter: Yup.string(),
|
||||
});
|
||||
|
||||
export type LogsFilterFormValues = Yup.InferType<typeof validationSchema>;
|
||||
|
||||
interface LogsHeaderProps extends Omit<BoxProps, 'children'> {
|
||||
/**
|
||||
* The date to be displayed in the date picker for the from date.
|
||||
* This is used to indicate that a query is currently inflight
|
||||
*/
|
||||
fromDate: Date;
|
||||
loading: boolean;
|
||||
/**
|
||||
* The date to be displayed in the date picker for the to date.
|
||||
*
|
||||
* Function to be called when the user submits the filters form
|
||||
*/
|
||||
toDate: Date | null;
|
||||
/**
|
||||
* Service to where to fetch logs from.
|
||||
*/
|
||||
service: AvailableLogsService;
|
||||
/**
|
||||
* Function to be called when the user changes the from date.
|
||||
*/
|
||||
onFromDateChange: (value: Date) => void;
|
||||
/**
|
||||
* Function to be called when the user changes the `to` date.
|
||||
*/
|
||||
onToDateChange: (value: Date) => void;
|
||||
/**
|
||||
* Function to be called when the user changes service to which to query logs from.
|
||||
*/
|
||||
onServiceChange: (value: AvailableLogsService) => void;
|
||||
}
|
||||
|
||||
type LogsToDatePickerLiveButtonProps = Pick<
|
||||
LogsHeaderProps,
|
||||
'fromDate' | 'toDate' | 'onToDateChange'
|
||||
>;
|
||||
|
||||
function LogsToDatePickerLiveButton({
|
||||
fromDate,
|
||||
toDate,
|
||||
onToDateChange,
|
||||
}: LogsToDatePickerLiveButtonProps) {
|
||||
const [currentTime, setCurrentTime] = useState(new Date());
|
||||
const isLive = !toDate;
|
||||
|
||||
function handleLiveButtonClick() {
|
||||
if (isLive) {
|
||||
return;
|
||||
}
|
||||
|
||||
onToDateChange(null);
|
||||
setCurrentTime(new Date());
|
||||
}
|
||||
|
||||
// if isLive is true, we want to update the current time every second
|
||||
// and set the toDate to the current time.
|
||||
useEffect(() => {
|
||||
let interval = null;
|
||||
|
||||
if (!interval && isLive) {
|
||||
interval = setInterval(() => {
|
||||
setCurrentTime(new Date());
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [isLive, onToDateChange]);
|
||||
|
||||
return (
|
||||
<div className="text-greyscaleMedium grid grid-flow-col">
|
||||
<LogsDatePicker
|
||||
label="To"
|
||||
value={!isLive ? toDate : currentTime}
|
||||
disabled={isLive}
|
||||
onChange={onToDateChange}
|
||||
minDate={fromDate}
|
||||
maxDate={toDate || new Date()}
|
||||
componentsProps={{
|
||||
button: {
|
||||
className: twMerge(
|
||||
'rounded-r-none pr-3',
|
||||
isLive ? 'border-r-0 hover:border-r-0 z-0' : 'z-10',
|
||||
),
|
||||
color: toDate ? 'inherit' : 'secondary',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
color={isLive ? 'primary' : 'secondary'}
|
||||
sx={{
|
||||
backgroundColor: (theme) =>
|
||||
!isLive ? `${theme.palette.grey[200]} !important` : 'transparent',
|
||||
color: !isLive ? 'text.secondary' : undefined,
|
||||
}}
|
||||
className={twMerge(
|
||||
'min-w-[77px] rounded-l-none',
|
||||
!isLive ? 'z-0 border-l-0 hover:border-l-0' : 'z-10',
|
||||
)}
|
||||
startIcon={<ClockIcon className="h-4 w-4 self-center align-middle" />}
|
||||
onClick={handleLiveButtonClick}
|
||||
>
|
||||
Live
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
onSubmitFilterValues: (value: LogsFilterFormValues) => void;
|
||||
}
|
||||
|
||||
export default function LogsHeader({
|
||||
fromDate,
|
||||
toDate,
|
||||
service,
|
||||
onFromDateChange,
|
||||
onToDateChange,
|
||||
onServiceChange,
|
||||
loading,
|
||||
onSubmitFilterValues,
|
||||
...props
|
||||
}: LogsHeaderProps) {
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const applicationCreationDate = new Date(currentProject.createdAt);
|
||||
|
||||
const [runServices, setRunServices] = useState<
|
||||
{
|
||||
label: string;
|
||||
value: string;
|
||||
}[]
|
||||
const [serviceLabels, setServiceLabels] = useState<
|
||||
{ label: string; value: string }[]
|
||||
>([]);
|
||||
|
||||
const { data, loading } = useGetRunServicesQuery({
|
||||
variables: {
|
||||
appID: currentProject.id,
|
||||
resolve: false,
|
||||
limit: 1000,
|
||||
offset: 0,
|
||||
},
|
||||
});
|
||||
const { data, loading: loadingServiceLabelValues } =
|
||||
useGetServiceLabelValuesQuery({
|
||||
variables: { appID: currentProject.id },
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
const services = data.app?.runServices ?? [];
|
||||
if (!loadingServiceLabelValues) {
|
||||
const labels = data.getServiceLabelValues ?? [];
|
||||
setServiceLabels(labels.map((l) => ({ label: l, value: l })));
|
||||
}
|
||||
}, [loadingServiceLabelValues, data]);
|
||||
|
||||
setRunServices(
|
||||
services
|
||||
.filter((s) => !!s.config?.name)
|
||||
.map((s) => ({
|
||||
label: s.config.name,
|
||||
value: `run-${s.config.name}`,
|
||||
})),
|
||||
useEffect(() => {
|
||||
if (!loadingServiceLabelValues) {
|
||||
const labels = data.getServiceLabelValues ?? [];
|
||||
|
||||
const labelMappings = {
|
||||
'hasura-auth': 'Auth',
|
||||
'hasura-storage': 'Storage',
|
||||
postgres: 'Postgres',
|
||||
functions: 'Functions',
|
||||
hasura: 'Hasura',
|
||||
grafana: 'Grafana',
|
||||
'job-backup': 'Backup Jobs',
|
||||
ai: 'AI',
|
||||
};
|
||||
|
||||
setServiceLabels(
|
||||
labels.map((l) => ({ label: labelMappings[l] ?? l, value: l })),
|
||||
);
|
||||
}
|
||||
}, [loading, data]);
|
||||
}, [loadingServiceLabelValues, data]);
|
||||
|
||||
/**
|
||||
* Will subtract the `customInterval` time in minutes from the current date.
|
||||
*/
|
||||
function handleIntervalChange({
|
||||
minutesToDecreaseFromCurrentDate,
|
||||
}: LogsCustomInterval) {
|
||||
onFromDateChange(subMinutes(new Date(), minutesToDecreaseFromCurrentDate));
|
||||
onToDateChange(new Date());
|
||||
}
|
||||
const form = useForm<LogsFilterFormValues>({
|
||||
defaultValues: {
|
||||
from: subMinutes(new Date(), MINUTES_TO_DECREASE_FROM_CURRENT_DATE),
|
||||
to: new Date(),
|
||||
regexFilter: '',
|
||||
service: AvailableLogsService.ALL,
|
||||
},
|
||||
reValidateMode: 'onSubmit',
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
const { register, watch, getValues } = form;
|
||||
|
||||
const service = watch('service');
|
||||
|
||||
useEffect(() => {
|
||||
onSubmitFilterValues(getValues());
|
||||
}, [service, getValues, onSubmitFilterValues]);
|
||||
|
||||
const handleSubmit = (values: LogsFilterFormValues) =>
|
||||
onSubmitFilterValues(values);
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="sticky top-0 z-10 grid w-full grid-flow-row gap-x-6 gap-y-2 border-b py-2.5 px-4 lg:grid-flow-col lg:justify-between"
|
||||
className="sticky top-0 z-10 grid w-full grid-flow-row gap-x-6 gap-y-2 border-b px-4 py-2.5 lg:grid-flow-col"
|
||||
{...props}
|
||||
>
|
||||
<Box className="grid w-full grid-flow-row items-center justify-center gap-2 md:w-[initial] md:grid-flow-col md:gap-3 lg:justify-start">
|
||||
<div className="grid grid-flow-col items-center gap-3 md:justify-start">
|
||||
<LogsDatePicker
|
||||
label="From"
|
||||
value={fromDate}
|
||||
onChange={onFromDateChange}
|
||||
minDate={applicationCreationDate}
|
||||
maxDate={toDate || new Date()}
|
||||
/>
|
||||
<FormProvider {...form}>
|
||||
<Form
|
||||
onSubmit={handleSubmit}
|
||||
className="grid w-full grid-flow-row items-center gap-2 md:w-[initial] md:grid-flow-col md:gap-3 lg:justify-end"
|
||||
>
|
||||
<Box className="flex flex-row space-x-2">
|
||||
<ControlledSelect
|
||||
{...register('service')}
|
||||
className="w-full text-sm font-normal min-w-fit"
|
||||
placeholder="All Services"
|
||||
aria-label="Select service"
|
||||
hideEmptyHelperText
|
||||
slotProps={{
|
||||
root: {
|
||||
className: 'min-h-[initial] h-10 leading-[initial]',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{[{ label: 'All services', value: '' }, ...serviceLabels].map(
|
||||
({ value, label }) => (
|
||||
<Option
|
||||
key={value}
|
||||
value={value}
|
||||
className="text-sm+ font-medium"
|
||||
>
|
||||
{label}
|
||||
</Option>
|
||||
),
|
||||
)}
|
||||
</ControlledSelect>
|
||||
<div className="w-full min-w-fit">
|
||||
<LogsRangeSelector onSubmitFilterValues={onSubmitFilterValues} />
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<LogsToDatePickerLiveButton
|
||||
fromDate={fromDate}
|
||||
toDate={toDate}
|
||||
onToDateChange={onToDateChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Box className="-my-2.5 px-0 py-2.5 lg:border-l lg:px-3">
|
||||
<Select
|
||||
className="w-full text-sm font-normal"
|
||||
placeholder="All Services"
|
||||
onChange={(_e, value) => {
|
||||
if (typeof value !== 'string') {
|
||||
return;
|
||||
}
|
||||
onServiceChange(value as AvailableLogsService);
|
||||
}}
|
||||
value={service}
|
||||
aria-label="Select service"
|
||||
<Input
|
||||
{...register('regexFilter')}
|
||||
placeholder="Filter logs with a regular expression"
|
||||
hideEmptyHelperText
|
||||
slotProps={{
|
||||
root: { className: 'min-h-[initial] h-9 leading-[initial]' },
|
||||
}}
|
||||
>
|
||||
{[...LOGS_AVAILABLE_SERVICES, ...runServices].map(
|
||||
({ value, label }) => (
|
||||
<Option
|
||||
key={value}
|
||||
value={value}
|
||||
className="text-sm+ font-medium"
|
||||
>
|
||||
{label}
|
||||
</Option>
|
||||
),
|
||||
)}
|
||||
</Select>
|
||||
</Box>
|
||||
</Box>
|
||||
autoComplete="off"
|
||||
fullWidth
|
||||
className="min-w-80"
|
||||
startAdornment={
|
||||
<Tooltip
|
||||
componentsProps={{
|
||||
tooltip: {
|
||||
sx: {
|
||||
maxWidth: '30rem',
|
||||
},
|
||||
},
|
||||
}}
|
||||
title={
|
||||
<div className="p-2 space-y-4">
|
||||
<h2>Here are some useful regular expressions:</h2>
|
||||
<ul className="pl-3 space-y-2 list-disc">
|
||||
<li>
|
||||
use
|
||||
<code className="px-1 py-px mx-1 rounded-md bg-slate-500 text-slate-100">
|
||||
(?i)error
|
||||
</code>
|
||||
to search for lines with the word <b>error</b> (case
|
||||
insenstive)
|
||||
</li>
|
||||
<li>
|
||||
use
|
||||
<code className="px-1 py-px mx-1 rounded-md bg-slate-500 text-slate-100">
|
||||
error
|
||||
</code>
|
||||
to search for lines with the word <b>error</b> (case
|
||||
sensitive)
|
||||
</li>
|
||||
<li>
|
||||
use
|
||||
<code className="px-1 py-px mx-1 rounded-md bg-slate-500 text-slate-100">
|
||||
/metadata.*error
|
||||
</code>
|
||||
to search for errors in hasura's metadata endpoint
|
||||
</li>
|
||||
<li>
|
||||
See
|
||||
<Link
|
||||
href="https://github.com/google/re2/wiki/Syntax"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
underline="hover"
|
||||
className="mx-1"
|
||||
>
|
||||
here
|
||||
</Link>
|
||||
for more patterns
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Box className="ml-2 rounded-full cursor-pointer">
|
||||
<InfoIcon
|
||||
aria-label="Info"
|
||||
className="w-5 h-5"
|
||||
color="info"
|
||||
/>
|
||||
</Box>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
|
||||
<Box className="hidden grid-flow-col items-center justify-center gap-3 md:grid lg:justify-end">
|
||||
{LOGS_AVAILABLE_INTERVALS.map((logInterval) => (
|
||||
<Button
|
||||
key={logInterval.label}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
className="self-center"
|
||||
onClick={() => handleIntervalChange(logInterval)}
|
||||
type="submit"
|
||||
className="h-10"
|
||||
startIcon={
|
||||
loading ? (
|
||||
<ActivityIndicator className="w-4 h-4" />
|
||||
) : (
|
||||
<SearchIcon />
|
||||
)
|
||||
}
|
||||
disabled={loading}
|
||||
>
|
||||
{logInterval.label}
|
||||
Search
|
||||
</Button>
|
||||
))}
|
||||
</Box>
|
||||
</Form>
|
||||
</FormProvider>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export * from './LogsHeader';
|
||||
export { default as LogsHeader } from './LogsHeader';
|
||||
export { default as LogsHeader, type LogsFilterFormValues } from './LogsHeader';
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
import { Box } from '@/components/ui/v2/Box';
|
||||
import { Button } from '@/components/ui/v2/Button';
|
||||
import { Dropdown, useDropdown } from '@/components/ui/v2/Dropdown';
|
||||
import { ClockIcon } from '@/components/ui/v2/icons/ClockIcon';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { LogsDatePicker } from '@/features/projects/logs/components/LogsDatePicker';
|
||||
import type { LogsFilterFormValues } from '@/features/projects/logs/components/LogsHeader';
|
||||
import {
|
||||
LOGS_AVAILABLE_INTERVALS,
|
||||
type LogsCustomInterval,
|
||||
} from '@/features/projects/logs/utils/constants/intervals';
|
||||
import { useInterval } from '@/hooks/useInterval';
|
||||
import { ChevronDownIcon } from '@graphiql/react';
|
||||
import { formatDistance, subMinutes } from 'date-fns';
|
||||
import { useState } from 'react';
|
||||
import { useFormContext, useWatch } from 'react-hook-form';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
function LogsToDatePickerLiveButton() {
|
||||
const [currentTime, setCurrentTime] = useState(new Date());
|
||||
|
||||
const { setValue } = useFormContext<LogsFilterFormValues>();
|
||||
const { from, to } = useWatch<LogsFilterFormValues>();
|
||||
const isLive = !to;
|
||||
|
||||
function handleLiveButtonClick() {
|
||||
if (isLive) {
|
||||
setValue('from', subMinutes(new Date(), 20));
|
||||
setValue('to', new Date());
|
||||
return;
|
||||
}
|
||||
|
||||
setValue('to', null);
|
||||
setCurrentTime(new Date());
|
||||
}
|
||||
|
||||
useInterval(() => setCurrentTime(new Date()), isLive ? 1000 : 0);
|
||||
|
||||
return (
|
||||
<div className="text-greyscaleMedium flex flex-col">
|
||||
{!isLive && (
|
||||
<LogsDatePicker
|
||||
label="To"
|
||||
value={!isLive ? to : currentTime}
|
||||
disabled={isLive}
|
||||
onChange={(date: Date) => setValue('to', date)}
|
||||
minDate={from}
|
||||
maxDate={new Date()}
|
||||
componentsProps={{
|
||||
button: {
|
||||
className: twMerge('rounded-r-none', isLive ? 'z-0' : 'z-10'),
|
||||
color: to ? 'inherit' : 'secondary',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
color={isLive ? 'primary' : 'secondary'}
|
||||
sx={{
|
||||
backgroundColor: (theme) =>
|
||||
!isLive ? `${theme.palette.grey[200]} !important` : 'transparent',
|
||||
color: !isLive ? 'text.secondary' : undefined,
|
||||
}}
|
||||
className={twMerge(!isLive ? 'z-0 mt-4' : 'z-10')}
|
||||
startIcon={<ClockIcon className="h-4 w-4 self-center align-middle" />}
|
||||
onClick={handleLiveButtonClick}
|
||||
>
|
||||
Live
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface LogsRangeSelectorProps {
|
||||
onSubmitFilterValues: (value: LogsFilterFormValues) => void;
|
||||
}
|
||||
|
||||
function LogsRangeSelectorIntervalPickers({
|
||||
onSubmitFilterValues,
|
||||
}: LogsRangeSelectorProps) {
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const applicationCreationDate = new Date(currentProject.createdAt);
|
||||
|
||||
const { setValue, getValues } = useFormContext<LogsFilterFormValues>();
|
||||
const { from } = useWatch<LogsFilterFormValues>();
|
||||
|
||||
const { handleClose } = useDropdown();
|
||||
|
||||
const handleApply = () => {
|
||||
onSubmitFilterValues(getValues());
|
||||
handleClose();
|
||||
};
|
||||
|
||||
/**
|
||||
* Will subtract the `customInterval` time in minutes from the current date.
|
||||
*/
|
||||
function handleIntervalChange({
|
||||
minutesToDecreaseFromCurrentDate,
|
||||
}: LogsCustomInterval) {
|
||||
setValue('from', subMinutes(new Date(), minutesToDecreaseFromCurrentDate));
|
||||
setValue('to', new Date());
|
||||
}
|
||||
|
||||
return (
|
||||
<Box className="flex flex-col space-y-4">
|
||||
<div className="flex flex-col space-y-4">
|
||||
<LogsDatePicker
|
||||
label="From"
|
||||
value={from}
|
||||
onChange={(date) => setValue('from', date)}
|
||||
minDate={applicationCreationDate}
|
||||
maxDate={new Date()}
|
||||
/>
|
||||
|
||||
<LogsToDatePickerLiveButton />
|
||||
</div>
|
||||
|
||||
<Box className="grid grid-cols-2 gap-2">
|
||||
{LOGS_AVAILABLE_INTERVALS.map((logInterval) => (
|
||||
<Button
|
||||
key={logInterval.label}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
className="self-center"
|
||||
onClick={() => handleIntervalChange(logInterval)}
|
||||
>
|
||||
Last {logInterval.label}
|
||||
</Button>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
<Button color="primary" variant="contained" onClick={handleApply}>
|
||||
Apply
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default function LogsRangeSelector({
|
||||
onSubmitFilterValues,
|
||||
}: LogsRangeSelectorProps) {
|
||||
const { from, to } = useWatch<LogsFilterFormValues>();
|
||||
|
||||
return (
|
||||
<Dropdown.Root>
|
||||
<Dropdown.Trigger hideChevron className="flex w-full rounded-full">
|
||||
<Button
|
||||
component="a"
|
||||
className="h-10 w-full min-w-40 items-center justify-between"
|
||||
variant="outlined"
|
||||
>
|
||||
<span>
|
||||
{to === null
|
||||
? 'Live'
|
||||
: `${formatDistance(to.getTime(), from.getTime())}`}
|
||||
</span>
|
||||
<ChevronDownIcon className="h-3 w-3" />
|
||||
</Button>
|
||||
</Dropdown.Trigger>
|
||||
|
||||
<Dropdown.Content PaperProps={{ className: 'mt-1 max-w-xs w-full p-3' }}>
|
||||
<LogsRangeSelectorIntervalPickers
|
||||
onSubmitFilterValues={onSubmitFilterValues}
|
||||
/>
|
||||
</Dropdown.Content>
|
||||
</Dropdown.Root>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as LogsRangeSelector } from './LogsRangeSelector';
|
||||
@@ -89,7 +89,7 @@ function LogsTimePicker({
|
||||
</Button>
|
||||
|
||||
<Button variant="contained" color="primary" onClick={handleApply}>
|
||||
Apply
|
||||
Set
|
||||
</Button>
|
||||
</Box>
|
||||
</div>
|
||||
|
||||
@@ -26,4 +26,12 @@ export const LOGS_AVAILABLE_INTERVALS: LogsCustomInterval[] = [
|
||||
label: '60 min',
|
||||
minutesToDecreaseFromCurrentDate: 60,
|
||||
},
|
||||
{
|
||||
label: '12 hours',
|
||||
minutesToDecreaseFromCurrentDate: 720,
|
||||
},
|
||||
{
|
||||
label: '24 hours',
|
||||
minutesToDecreaseFromCurrentDate: 1440,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useIsCurrentUserOwner } from '@/features/projects/common/hooks/useIsCur
|
||||
import { PendingWorkspaceMemberInvitation } from '@/features/projects/workspaces/components/PendingWorkspaceMemberInvitation';
|
||||
import { WorkspaceMember } from '@/features/projects/workspaces/components/WorkspaceMember';
|
||||
import { discordAnnounce } from '@/utils/discordAnnounce';
|
||||
import { getErrorMessage } from '@/utils/getErrorMessage';
|
||||
import { execPromiseWithErrorToast } from '@/utils/execPromiseWithErrorToast';
|
||||
import { triggerToast } from '@/utils/toast';
|
||||
import {
|
||||
refetchGetWorkspaceMembersQuery,
|
||||
@@ -52,38 +52,42 @@ function WorkspaceMemberInviteForm({
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await insertWorkspaceMemberInvite({
|
||||
variables: {
|
||||
workspaceMemberInvite: {
|
||||
workspaceId: currentWorkspace.id,
|
||||
email,
|
||||
memberType: 'member',
|
||||
await execPromiseWithErrorToast(
|
||||
async () => {
|
||||
await insertWorkspaceMemberInvite({
|
||||
variables: {
|
||||
workspaceMemberInvite: {
|
||||
workspaceId: currentWorkspace.id,
|
||||
email,
|
||||
memberType: 'member',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
triggerToast(
|
||||
`Invite to join workspace ${currentWorkspace.name} sent to ${email}.`,
|
||||
);
|
||||
} catch (error) {
|
||||
await discordAnnounce(
|
||||
`Error trying to invite to ${email} to ${currentWorkspace.name} ${error.message}`,
|
||||
);
|
||||
if (
|
||||
error.message ===
|
||||
'Foreign key violation. insert or update on table "workspace_member_invites" violates foreign key constraint "workspace_member_invites_email_fkey"'
|
||||
) {
|
||||
setWorkspaceInviteError(
|
||||
'You can only invite users that are already registered at Nhost. Ask the person to register an account, then invite them again.',
|
||||
});
|
||||
|
||||
triggerToast(
|
||||
`Invite to join workspace ${currentWorkspace.name} sent to ${email}.`,
|
||||
);
|
||||
},
|
||||
{
|
||||
loadingMessage: 'Sending invite...',
|
||||
successMessage: 'The invite has been sent successfully.',
|
||||
errorMessage: `Error trying to invite to ${email} to ${currentWorkspace.name}`,
|
||||
onError: async (error) => {
|
||||
await discordAnnounce(
|
||||
`Error trying to invite to ${email} to ${currentWorkspace.name} ${error.message}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setWorkspaceInviteError(getErrorMessage(error, 'invite'));
|
||||
|
||||
return;
|
||||
}
|
||||
if (
|
||||
error.message ===
|
||||
'Foreign key violation. insert or update on table "workspace_member_invites" violates foreign key constraint "workspace_member_invites_email_fkey"'
|
||||
) {
|
||||
setWorkspaceInviteError(
|
||||
'You can only invite users that are already registered at Nhost. Ask the person to register an account, then invite them again.',
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
setEmail('');
|
||||
};
|
||||
@@ -130,8 +134,8 @@ export default function WorkspaceMembers() {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="mx-auto mt-18 max-w-3xl font-display">
|
||||
<div className="mb-2 grid grid-flow-row gap-1">
|
||||
<div className="max-w-3xl mx-auto mt-18 font-display">
|
||||
<div className="grid grid-flow-row gap-1 mb-2">
|
||||
<Text variant="h3">Members</Text>
|
||||
<Text color="secondary" className="text-sm">
|
||||
People in this workspace can manage all projects listed above.
|
||||
|
||||
@@ -3,8 +3,15 @@ query getProjectLogs(
|
||||
$service: String
|
||||
$from: Timestamp
|
||||
$to: Timestamp
|
||||
$regexFilter: String
|
||||
) {
|
||||
logs(appID: $appID, service: $service, from: $from, to: $to) {
|
||||
logs(
|
||||
appID: $appID
|
||||
service: $service
|
||||
from: $from
|
||||
to: $to
|
||||
regexFilter: $regexFilter
|
||||
) {
|
||||
log
|
||||
service
|
||||
timestamp
|
||||
@@ -15,8 +22,14 @@ subscription getLogsSubscription(
|
||||
$appID: String!
|
||||
$service: String
|
||||
$from: Timestamp
|
||||
$regexFilter: String
|
||||
) {
|
||||
logs(appID: $appID, service: $service, from: $from) {
|
||||
logs(
|
||||
appID: $appID
|
||||
service: $service
|
||||
from: $from
|
||||
regexFilter: $regexFilter
|
||||
) {
|
||||
log
|
||||
service
|
||||
timestamp
|
||||
|
||||
3
dashboard/src/gql/logs/getServiceLabelValues.gql
Normal file
3
dashboard/src/gql/logs/getServiceLabelValues.gql
Normal file
@@ -0,0 +1,3 @@
|
||||
query getServiceLabelValues($appID: String!) {
|
||||
getServiceLabelValues(appID: $appID)
|
||||
}
|
||||
@@ -35,7 +35,24 @@ export default function useRemoteApplicationGQLClientWithSubscriptions() {
|
||||
);
|
||||
|
||||
return new ApolloClient({
|
||||
cache: new InMemoryCache(),
|
||||
cache: new InMemoryCache({
|
||||
typePolicies: {
|
||||
Subscription: {
|
||||
fields: {
|
||||
logs: {
|
||||
keyArgs: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
Query: {
|
||||
fields: {
|
||||
logs: {
|
||||
keyArgs: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
connectToDevTools: true,
|
||||
link: split(
|
||||
({ query }) => {
|
||||
|
||||
@@ -2,49 +2,53 @@ import { ProjectLayout } from '@/components/layout/ProjectLayout';
|
||||
import { RetryableErrorBoundary } from '@/components/presentational/RetryableErrorBoundary';
|
||||
import { useCurrentWorkspaceAndProject } from '@/features/projects/common/hooks/useCurrentWorkspaceAndProject';
|
||||
import { LogsBody } from '@/features/projects/logs/components/LogsBody';
|
||||
import { LogsHeader } from '@/features/projects/logs/components/LogsHeader';
|
||||
import {
|
||||
LogsHeader,
|
||||
type LogsFilterFormValues,
|
||||
} from '@/features/projects/logs/components/LogsHeader';
|
||||
import { AvailableLogsService } from '@/features/projects/logs/utils/constants/services';
|
||||
import { useRemoteApplicationGQLClientWithSubscriptions } from '@/hooks/useRemoteApplicationGQLClientWithSubscriptions';
|
||||
import { MINUTES_TO_DECREASE_FROM_CURRENT_DATE } from '@/utils/constants/common';
|
||||
import {
|
||||
GetLogsSubscriptionDocument,
|
||||
useGetProjectLogsQuery,
|
||||
} from '@/utils/__generated__/graphql';
|
||||
import { subMinutes } from 'date-fns';
|
||||
import type { ReactElement } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
type ReactElement,
|
||||
} from 'react';
|
||||
|
||||
const MINUTES_TO_DECREASE_FROM_CURRENT_DATE = 20;
|
||||
interface LogsFilters {
|
||||
from: Date;
|
||||
to: Date | null;
|
||||
service: AvailableLogsService;
|
||||
regexFilter: string;
|
||||
}
|
||||
|
||||
export default function LogsPage() {
|
||||
const { currentProject } = useCurrentWorkspaceAndProject();
|
||||
const [fromDate, setFromDate] = useState<Date>(
|
||||
subMinutes(new Date(), MINUTES_TO_DECREASE_FROM_CURRENT_DATE),
|
||||
);
|
||||
const [toDate, setToDate] = useState<Date | null>(new Date());
|
||||
const [service, setService] = useState<AvailableLogsService>(
|
||||
AvailableLogsService.ALL,
|
||||
);
|
||||
|
||||
// create a client that sends http requests to Hasura but websocket requests to Bragi
|
||||
const clientWithSplit = useRemoteApplicationGQLClientWithSubscriptions();
|
||||
const subscriptionReturn = useRef(null);
|
||||
|
||||
/**
|
||||
* Will change the specific service from which we query logs.
|
||||
*/
|
||||
function handleServiceChange(value: AvailableLogsService) {
|
||||
setService(value);
|
||||
}
|
||||
const [filters, setFilters] = useState<LogsFilters>({
|
||||
from: subMinutes(new Date(), MINUTES_TO_DECREASE_FROM_CURRENT_DATE),
|
||||
to: new Date(),
|
||||
regexFilter: '',
|
||||
service: AvailableLogsService.ALL,
|
||||
});
|
||||
|
||||
const { data, loading, error, subscribeToMore, client } =
|
||||
const { data, error, subscribeToMore, client, loading, refetch } =
|
||||
useGetProjectLogsQuery({
|
||||
variables: {
|
||||
appID: currentProject.id,
|
||||
from: fromDate,
|
||||
to: toDate,
|
||||
service,
|
||||
},
|
||||
variables: { appID: currentProject.id, ...filters },
|
||||
client: clientWithSplit,
|
||||
fetchPolicy: 'cache-and-network',
|
||||
notifyOnNetworkStatusChange: true,
|
||||
});
|
||||
|
||||
const subscribeToMoreLogs = useCallback(
|
||||
@@ -53,8 +57,9 @@ export default function LogsPage() {
|
||||
document: GetLogsSubscriptionDocument,
|
||||
variables: {
|
||||
appID: currentProject.id,
|
||||
service,
|
||||
from: fromDate,
|
||||
service: filters.service,
|
||||
from: filters.from,
|
||||
regexFilter: filters.regexFilter,
|
||||
},
|
||||
updateQuery: (prev, { subscriptionData }) => {
|
||||
// if there is no new data, just return the previous data
|
||||
@@ -93,40 +98,47 @@ export default function LogsPage() {
|
||||
};
|
||||
},
|
||||
}),
|
||||
[subscribeToMore, currentProject.id, service, fromDate],
|
||||
[subscribeToMore, currentProject.id, filters],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (toDate && subscriptionReturn.current !== null) {
|
||||
if (filters.to && subscriptionReturn.current !== null) {
|
||||
subscriptionReturn.current();
|
||||
subscriptionReturn.current = null;
|
||||
|
||||
return () => {};
|
||||
}
|
||||
|
||||
if (toDate) {
|
||||
if (filters.to) {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
if (subscriptionReturn.current) {
|
||||
subscriptionReturn.current();
|
||||
subscriptionReturn.current = null;
|
||||
}
|
||||
|
||||
// This will open the websocket connection and it will return a function to close it.
|
||||
subscriptionReturn.current = subscribeToMoreLogs();
|
||||
|
||||
// get rid of the current apollo client instance (will also close the websocket if it's the live status)
|
||||
return () => client.stop();
|
||||
}, [subscribeToMoreLogs, toDate, client]);
|
||||
return () => {};
|
||||
}, [filters, subscribeToMoreLogs, client]);
|
||||
|
||||
const onSubmitFilterValues = useCallback(
|
||||
async (values: LogsFilterFormValues) => {
|
||||
setFilters({ ...(values as LogsFilters) });
|
||||
await refetch();
|
||||
},
|
||||
[setFilters, refetch],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col">
|
||||
<RetryableErrorBoundary>
|
||||
<LogsHeader
|
||||
fromDate={fromDate}
|
||||
toDate={toDate}
|
||||
service={service}
|
||||
onServiceChange={handleServiceChange}
|
||||
onFromDateChange={setFromDate}
|
||||
onToDateChange={setToDate}
|
||||
loading={loading}
|
||||
onSubmitFilterValues={onSubmitFilterValues}
|
||||
/>
|
||||
|
||||
<LogsBody error={error} loading={loading} logsData={data} />
|
||||
</RetryableErrorBoundary>
|
||||
</div>
|
||||
|
||||
98
dashboard/src/utils/__generated__/graphql.ts
generated
98
dashboard/src/utils/__generated__/graphql.ts
generated
@@ -196,6 +196,7 @@ export type ConfigAppSystemConfig = {
|
||||
*/
|
||||
export type ConfigAuth = {
|
||||
__typename?: 'ConfigAuth';
|
||||
elevatedPrivileges?: Maybe<ConfigAuthElevatedPrivileges>;
|
||||
method?: Maybe<ConfigAuthMethod>;
|
||||
redirections?: Maybe<ConfigAuthRedirections>;
|
||||
/** Resources for the service */
|
||||
@@ -219,6 +220,7 @@ export type ConfigAuthComparisonExp = {
|
||||
_and?: InputMaybe<Array<ConfigAuthComparisonExp>>;
|
||||
_not?: InputMaybe<ConfigAuthComparisonExp>;
|
||||
_or?: InputMaybe<Array<ConfigAuthComparisonExp>>;
|
||||
elevatedPrivileges?: InputMaybe<ConfigAuthElevatedPrivilegesComparisonExp>;
|
||||
method?: InputMaybe<ConfigAuthMethodComparisonExp>;
|
||||
redirections?: InputMaybe<ConfigAuthRedirectionsComparisonExp>;
|
||||
resources?: InputMaybe<ConfigResourcesComparisonExp>;
|
||||
@@ -229,7 +231,28 @@ export type ConfigAuthComparisonExp = {
|
||||
version?: InputMaybe<ConfigStringComparisonExp>;
|
||||
};
|
||||
|
||||
export type ConfigAuthElevatedPrivileges = {
|
||||
__typename?: 'ConfigAuthElevatedPrivileges';
|
||||
mode?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ConfigAuthElevatedPrivilegesComparisonExp = {
|
||||
_and?: InputMaybe<Array<ConfigAuthElevatedPrivilegesComparisonExp>>;
|
||||
_not?: InputMaybe<ConfigAuthElevatedPrivilegesComparisonExp>;
|
||||
_or?: InputMaybe<Array<ConfigAuthElevatedPrivilegesComparisonExp>>;
|
||||
mode?: InputMaybe<ConfigStringComparisonExp>;
|
||||
};
|
||||
|
||||
export type ConfigAuthElevatedPrivilegesInsertInput = {
|
||||
mode?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ConfigAuthElevatedPrivilegesUpdateInput = {
|
||||
mode?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type ConfigAuthInsertInput = {
|
||||
elevatedPrivileges?: InputMaybe<ConfigAuthElevatedPrivilegesInsertInput>;
|
||||
method?: InputMaybe<ConfigAuthMethodInsertInput>;
|
||||
redirections?: InputMaybe<ConfigAuthRedirectionsInsertInput>;
|
||||
resources?: InputMaybe<ConfigResourcesInsertInput>;
|
||||
@@ -808,6 +831,7 @@ export type ConfigAuthTotpUpdateInput = {
|
||||
};
|
||||
|
||||
export type ConfigAuthUpdateInput = {
|
||||
elevatedPrivileges?: InputMaybe<ConfigAuthElevatedPrivilegesUpdateInput>;
|
||||
method?: InputMaybe<ConfigAuthMethodUpdateInput>;
|
||||
redirections?: InputMaybe<ConfigAuthRedirectionsUpdateInput>;
|
||||
resources?: InputMaybe<ConfigResourcesUpdateInput>;
|
||||
@@ -5238,9 +5262,9 @@ export type AuthUserProviders_Bool_Exp = {
|
||||
export enum AuthUserProviders_Constraint {
|
||||
/** unique or primary key constraint on columns "id" */
|
||||
UserProvidersPkey = 'user_providers_pkey',
|
||||
/** unique or primary key constraint on columns "provider_id", "provider_user_id" */
|
||||
/** unique or primary key constraint on columns "provider_user_id", "provider_id" */
|
||||
UserProvidersProviderIdProviderUserIdKey = 'user_providers_provider_id_provider_user_id_key',
|
||||
/** unique or primary key constraint on columns "provider_id", "user_id" */
|
||||
/** unique or primary key constraint on columns "user_id", "provider_id" */
|
||||
UserProvidersUserIdProviderIdKey = 'user_providers_user_id_provider_id_key'
|
||||
}
|
||||
|
||||
@@ -15774,6 +15798,7 @@ export type Query_Root = {
|
||||
runServiceConfig?: Maybe<ConfigRunServiceConfig>;
|
||||
runServiceConfigRawJSON: Scalars['String'];
|
||||
runServiceConfigs: Array<ConfigRunServiceConfigWithId>;
|
||||
runServiceConfigsAll: Array<ConfigRunServiceConfigWithId>;
|
||||
/** An array relationship */
|
||||
runServices: Array<Run_Service>;
|
||||
/** fetch aggregated fields from the table: "run_service" */
|
||||
@@ -16755,6 +16780,12 @@ export type Query_RootRunServiceConfigRawJsonArgs = {
|
||||
|
||||
|
||||
export type Query_RootRunServiceConfigsArgs = {
|
||||
appID: Scalars['uuid'];
|
||||
resolve: Scalars['Boolean'];
|
||||
};
|
||||
|
||||
|
||||
export type Query_RootRunServiceConfigsAllArgs = {
|
||||
resolve: Scalars['Boolean'];
|
||||
where?: InputMaybe<ConfigRunServiceConfigComparisonExp>;
|
||||
};
|
||||
@@ -21355,7 +21386,7 @@ export type WorkspaceMemberInvites_Bool_Exp = {
|
||||
|
||||
/** unique or primary key constraints on table "workspace_member_invites" */
|
||||
export enum WorkspaceMemberInvites_Constraint {
|
||||
/** unique or primary key constraint on columns "email", "workspace_id" */
|
||||
/** unique or primary key constraint on columns "workspace_id", "email" */
|
||||
WorkspaceMemberInvitesEmailWorkspaceIdKey = 'workspace_member_invites_email_workspace_id_key',
|
||||
/** unique or primary key constraint on columns "id" */
|
||||
WorkspaceMemberInvitesPkey = 'workspace_member_invites_pkey'
|
||||
@@ -22772,6 +22803,7 @@ export type GetProjectLogsQueryVariables = Exact<{
|
||||
service?: InputMaybe<Scalars['String']>;
|
||||
from?: InputMaybe<Scalars['Timestamp']>;
|
||||
to?: InputMaybe<Scalars['Timestamp']>;
|
||||
regexFilter?: InputMaybe<Scalars['String']>;
|
||||
}>;
|
||||
|
||||
|
||||
@@ -22781,11 +22813,19 @@ export type GetLogsSubscriptionSubscriptionVariables = Exact<{
|
||||
appID: Scalars['String'];
|
||||
service?: InputMaybe<Scalars['String']>;
|
||||
from?: InputMaybe<Scalars['Timestamp']>;
|
||||
regexFilter?: InputMaybe<Scalars['String']>;
|
||||
}>;
|
||||
|
||||
|
||||
export type GetLogsSubscriptionSubscription = { __typename?: 'subscription_root', logs: Array<{ __typename?: 'Log', log: string, service: string, timestamp: any }> };
|
||||
|
||||
export type GetServiceLabelValuesQueryVariables = Exact<{
|
||||
appID: Scalars['String'];
|
||||
}>;
|
||||
|
||||
|
||||
export type GetServiceLabelValuesQuery = { __typename?: 'query_root', getServiceLabelValues: Array<string> };
|
||||
|
||||
export type DeletePaymentMethodMutationVariables = Exact<{
|
||||
paymentMethodId: Scalars['uuid'];
|
||||
}>;
|
||||
@@ -25558,8 +25598,14 @@ export function refetchGetGithubRepositoriesQuery(variables?: GetGithubRepositor
|
||||
return { query: GetGithubRepositoriesDocument, variables: variables }
|
||||
}
|
||||
export const GetProjectLogsDocument = gql`
|
||||
query getProjectLogs($appID: String!, $service: String, $from: Timestamp, $to: Timestamp) {
|
||||
logs(appID: $appID, service: $service, from: $from, to: $to) {
|
||||
query getProjectLogs($appID: String!, $service: String, $from: Timestamp, $to: Timestamp, $regexFilter: String) {
|
||||
logs(
|
||||
appID: $appID
|
||||
service: $service
|
||||
from: $from
|
||||
to: $to
|
||||
regexFilter: $regexFilter
|
||||
) {
|
||||
log
|
||||
service
|
||||
timestamp
|
||||
@@ -25583,6 +25629,7 @@ export const GetProjectLogsDocument = gql`
|
||||
* service: // value for 'service'
|
||||
* from: // value for 'from'
|
||||
* to: // value for 'to'
|
||||
* regexFilter: // value for 'regexFilter'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
@@ -25601,8 +25648,8 @@ export function refetchGetProjectLogsQuery(variables: GetProjectLogsQueryVariabl
|
||||
return { query: GetProjectLogsDocument, variables: variables }
|
||||
}
|
||||
export const GetLogsSubscriptionDocument = gql`
|
||||
subscription getLogsSubscription($appID: String!, $service: String, $from: Timestamp) {
|
||||
logs(appID: $appID, service: $service, from: $from) {
|
||||
subscription getLogsSubscription($appID: String!, $service: String, $from: Timestamp, $regexFilter: String) {
|
||||
logs(appID: $appID, service: $service, from: $from, regexFilter: $regexFilter) {
|
||||
log
|
||||
service
|
||||
timestamp
|
||||
@@ -25625,6 +25672,7 @@ export const GetLogsSubscriptionDocument = gql`
|
||||
* appID: // value for 'appID'
|
||||
* service: // value for 'service'
|
||||
* from: // value for 'from'
|
||||
* regexFilter: // value for 'regexFilter'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
@@ -25634,6 +25682,42 @@ export function useGetLogsSubscriptionSubscription(baseOptions: Apollo.Subscript
|
||||
}
|
||||
export type GetLogsSubscriptionSubscriptionHookResult = ReturnType<typeof useGetLogsSubscriptionSubscription>;
|
||||
export type GetLogsSubscriptionSubscriptionResult = Apollo.SubscriptionResult<GetLogsSubscriptionSubscription>;
|
||||
export const GetServiceLabelValuesDocument = gql`
|
||||
query getServiceLabelValues($appID: String!) {
|
||||
getServiceLabelValues(appID: $appID)
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useGetServiceLabelValuesQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useGetServiceLabelValuesQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useGetServiceLabelValuesQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useGetServiceLabelValuesQuery({
|
||||
* variables: {
|
||||
* appID: // value for 'appID'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useGetServiceLabelValuesQuery(baseOptions: Apollo.QueryHookOptions<GetServiceLabelValuesQuery, GetServiceLabelValuesQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<GetServiceLabelValuesQuery, GetServiceLabelValuesQueryVariables>(GetServiceLabelValuesDocument, options);
|
||||
}
|
||||
export function useGetServiceLabelValuesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetServiceLabelValuesQuery, GetServiceLabelValuesQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<GetServiceLabelValuesQuery, GetServiceLabelValuesQueryVariables>(GetServiceLabelValuesDocument, options);
|
||||
}
|
||||
export type GetServiceLabelValuesQueryHookResult = ReturnType<typeof useGetServiceLabelValuesQuery>;
|
||||
export type GetServiceLabelValuesLazyQueryHookResult = ReturnType<typeof useGetServiceLabelValuesLazyQuery>;
|
||||
export type GetServiceLabelValuesQueryResult = Apollo.QueryResult<GetServiceLabelValuesQuery, GetServiceLabelValuesQueryVariables>;
|
||||
export function refetchGetServiceLabelValuesQuery(variables: GetServiceLabelValuesQueryVariables) {
|
||||
return { query: GetServiceLabelValuesDocument, variables: variables }
|
||||
}
|
||||
export const DeletePaymentMethodDocument = gql`
|
||||
mutation deletePaymentMethod($paymentMethodId: uuid!) {
|
||||
deletePaymentMethod(id: $paymentMethodId) {
|
||||
|
||||
@@ -66,3 +66,8 @@ export const RESOURCE_VCPU_PRICE_PER_MINUTE = 0.0012;
|
||||
* Maximum number of free projects a user is allowed to have.
|
||||
*/
|
||||
export const MAX_FREE_PROJECTS = 1;
|
||||
|
||||
/**
|
||||
* Default value in minutes to use for querying the logs
|
||||
*/
|
||||
export const MINUTES_TO_DECREASE_FROM_CURRENT_DATE = 20;
|
||||
|
||||
@@ -29,6 +29,7 @@ export default async function execPromiseWithErrorToast(
|
||||
const result = await call();
|
||||
|
||||
toast.dismiss(loadingToastId);
|
||||
|
||||
toast.success(successMessage, {
|
||||
style: toastStyle.style,
|
||||
...toastStyle.success,
|
||||
|
||||
@@ -21,6 +21,9 @@ module.exports = {
|
||||
copper: '#DD792D',
|
||||
paper: '#171d26',
|
||||
divider: '#2f363d',
|
||||
'primary-main': '#0052cd',
|
||||
'primary-light': '#ebf3ff',
|
||||
'primary-dark': '#063799',
|
||||
},
|
||||
boxShadow: {
|
||||
outline: 'inset 0 0 0 2px rgba(0, 82, 205, 0.6)',
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @nhost/docs
|
||||
|
||||
## 2.6.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- dc23dc0: fix: docs run references
|
||||
|
||||
## 2.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -9,7 +9,7 @@ If you are using the Nhost CLI for local development, as of [v0.12.0](https://gi
|
||||
|
||||
<Steps>
|
||||
<Step title="Configuring the Service">
|
||||
Follow the steps highlighed in the ["Enabling Service"](enabling-service) guide and don't forget to add the relevant secrets to your `.secrets` file.
|
||||
Follow the steps highlighed in the [Enabling Service](enabling-service) guide and don't forget to add the relevant secrets to your `.secrets` file.
|
||||
</Step>
|
||||
<Step title="Start nhost">
|
||||
Run `nhost up`:
|
||||
|
||||
@@ -46,11 +46,11 @@ capacity=1
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Info>Head to [CLI & CI deployments](/run/ci) for more details on how to deploy using a configuration file.</Info>
|
||||
<Info>Head to [CLI & CI deployments](/guides/run/cli-deployments) for more details on how to deploy using a configuration file.</Info>
|
||||
|
||||
The `name` of the service is used as an identifier and to generate URLs when exposing the service to the Internet. You can use any container image publicly available or you can push your own to the [Nhost registry](/run/registry).
|
||||
The `name` of the service is used as an identifier and to generate URLs when exposing the service to the Internet. You can use any container image publicly available or you can push your own to the [Nhost registry](/guides/run/registry).
|
||||
|
||||
All environment variables set here are exclusive to this service and will not be shared with other services or with the Nhost stack. If you are using a configuration file secrets are supported.
|
||||
|
||||
For more details about the `Ports` section head to [networking](/run/networking). You can also head to [resources](/run/resources) for more information about replicas, compute, and storage.
|
||||
For more details about the `Ports` section head to [networking](/guides/run/networking). You can also head to [resources](/guides/run/resources) for more information about replicas, compute, and storage.
|
||||
|
||||
|
||||
@@ -12,11 +12,11 @@ Then on `New Service`:
|
||||
|
||||

|
||||
|
||||
Now you can fill your [service configuration](/run/configuration):
|
||||
Now you can fill your [service configuration](/guides/run/configuration):
|
||||
|
||||

|
||||
|
||||
As you configure the `Ports` section you can take note of the generated URL. You can find more information about this section under [Networking](/run/networking).
|
||||
As you configure the `Ports` section you can take note of the generated URL. You can find more information about this section under [Networking](/guides/run/networking).
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ To pause a service, simply set its number of replicas to `0`:
|
||||
<Tab title="dashboard">
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
</Tab>
|
||||
<Tab title="toml">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/docs",
|
||||
"version": "2.5.0",
|
||||
"version": "2.6.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "mintlify dev"
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @nhost-examples/cli
|
||||
|
||||
## 0.1.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.8
|
||||
|
||||
## 0.1.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.7
|
||||
|
||||
## 0.1.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/cli",
|
||||
"version": "0.1.7",
|
||||
"version": "0.1.9",
|
||||
"main": "src/index.mjs",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @nhost-examples/codegen-react-apollo
|
||||
|
||||
## 0.1.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.3
|
||||
- @nhost/react-apollo@9.0.3
|
||||
|
||||
## 0.1.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.2
|
||||
- @nhost/react-apollo@9.0.2
|
||||
|
||||
## 0.1.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/codegen-react-apollo",
|
||||
"version": "0.1.15",
|
||||
"version": "0.1.17",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"codegen": "graphql-codegen",
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @nhost-examples/codegen-react-query
|
||||
|
||||
## 0.1.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.3
|
||||
|
||||
## 0.1.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.2
|
||||
|
||||
## 0.1.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/codegen-react-query",
|
||||
"version": "0.1.16",
|
||||
"version": "0.1.18",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"codegen": "graphql-codegen",
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @nhost-examples/react-urql
|
||||
|
||||
## 0.0.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.3
|
||||
- @nhost/react-urql@6.0.3
|
||||
|
||||
## 0.0.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.2
|
||||
- @nhost/react-urql@6.0.2
|
||||
|
||||
## 0.0.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/codegen-react-urql",
|
||||
"private": true,
|
||||
"version": "0.0.12",
|
||||
"version": "0.0.14",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @nhost-examples/multi-tenant-one-to-many
|
||||
|
||||
## 2.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.8
|
||||
|
||||
## 2.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.7
|
||||
|
||||
## 2.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/multi-tenant-one-to-many",
|
||||
"private": true,
|
||||
"version": "2.0.5",
|
||||
"version": "2.0.7",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {},
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @nhost-examples/nextjs
|
||||
|
||||
## 0.1.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.3
|
||||
- @nhost/react-apollo@9.0.3
|
||||
- @nhost/nextjs@2.1.5
|
||||
|
||||
## 0.1.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.2
|
||||
- @nhost/react-apollo@9.0.2
|
||||
- @nhost/nextjs@2.1.4
|
||||
|
||||
## 0.1.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/nextjs",
|
||||
"version": "0.1.17",
|
||||
"version": "0.1.19",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @nhost-examples/node-storage
|
||||
|
||||
## 0.0.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.8
|
||||
|
||||
## 0.0.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.7
|
||||
|
||||
## 0.0.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/node-storage",
|
||||
"version": "0.0.9",
|
||||
"version": "0.0.11",
|
||||
"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.2.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.8
|
||||
|
||||
## 0.2.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.7
|
||||
|
||||
## 0.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/nextjs-server-components",
|
||||
"version": "0.2.3",
|
||||
"version": "0.2.5",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @nhost-examples/react-apollo
|
||||
|
||||
## 0.3.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.3
|
||||
- @nhost/react-apollo@9.0.3
|
||||
|
||||
## 0.3.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.2
|
||||
- @nhost/react-apollo@9.0.2
|
||||
|
||||
## 0.3.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/react-apollo",
|
||||
"version": "0.3.1",
|
||||
"version": "0.3.3",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.9.4",
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @nhost-examples/react-gqty
|
||||
|
||||
## 1.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.3
|
||||
|
||||
## 1.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.2
|
||||
|
||||
## 1.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/react-gqty",
|
||||
"private": true,
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.7",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @nhost-examples/vue-apollo
|
||||
|
||||
## 0.2.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.8
|
||||
- @nhost/apollo@6.0.8
|
||||
- @nhost/vue@2.2.3
|
||||
|
||||
## 0.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.7
|
||||
- @nhost/apollo@6.0.7
|
||||
- @nhost/vue@2.2.2
|
||||
|
||||
## 0.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@nhost-examples/vue-apollo",
|
||||
"private": true,
|
||||
"version": "0.2.2",
|
||||
"version": "0.2.4",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @nhost-examples/vue-quickstart
|
||||
|
||||
## 0.0.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/apollo@6.0.8
|
||||
- @nhost/vue@2.2.3
|
||||
|
||||
## 0.0.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/apollo@6.0.7
|
||||
- @nhost/vue@2.2.2
|
||||
|
||||
## 0.0.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost-examples/vue-quickstart",
|
||||
"version": "0.0.14",
|
||||
"version": "0.0.16",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @nhost/apollo
|
||||
|
||||
## 6.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.8
|
||||
|
||||
## 6.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.7
|
||||
|
||||
## 6.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/apollo",
|
||||
"version": "6.0.6",
|
||||
"version": "6.0.8",
|
||||
"description": "Nhost Apollo Client library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @nhost/react-apollo
|
||||
|
||||
## 9.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/apollo@6.0.8
|
||||
- @nhost/react@3.2.3
|
||||
|
||||
## 9.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/apollo@6.0.7
|
||||
- @nhost/react@3.2.2
|
||||
|
||||
## 9.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/react-apollo",
|
||||
"version": "9.0.1",
|
||||
"version": "9.0.3",
|
||||
"description": "Nhost React Apollo client",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @nhost/react-urql
|
||||
|
||||
## 6.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.3
|
||||
|
||||
## 6.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.2
|
||||
|
||||
## 6.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/react-urql",
|
||||
"version": "6.0.1",
|
||||
"version": "6.0.3",
|
||||
"description": "Nhost React URQL client",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @nhost/graphql-js
|
||||
|
||||
## 0.1.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 407feea: fix: replace `jwt-decode` with `jose` to decode access tokens in a non browser environment
|
||||
|
||||
## 0.1.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 2d68fee: fix: resolve an issue where unauthenticated graphql requests are not sent
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/graphql-js",
|
||||
"version": "0.1.6",
|
||||
"version": "0.1.8",
|
||||
"description": "Nhost GraphQL client",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
@@ -55,7 +55,7 @@
|
||||
"dependencies": {
|
||||
"@graphql-typed-document-node/core": "^3.2.0",
|
||||
"isomorphic-unfetch": "^3.1.0",
|
||||
"jwt-decode": "^3.1.2"
|
||||
"jose": "^5.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0"
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
Variables
|
||||
} from './types'
|
||||
|
||||
import jwtDecode, { JwtPayload } from 'jwt-decode'
|
||||
import * as jose from 'jose'
|
||||
|
||||
/**
|
||||
* @alias GraphQL
|
||||
@@ -30,13 +30,13 @@ export class NhostGraphqlClient {
|
||||
this.adminSecret = adminSecret
|
||||
}
|
||||
|
||||
private isAccessTokenValid = () => {
|
||||
private isAccessTokenValidOrNull = () => {
|
||||
if (!this.accessToken) {
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
try {
|
||||
const decodedToken: JwtPayload = jwtDecode(this.accessToken)
|
||||
const decodedToken = jose.decodeJwt<jose.JWTPayload>(this.accessToken)
|
||||
return decodedToken.exp != null && decodedToken.exp * 1000 > Date.now()
|
||||
} catch (error) {
|
||||
console.error('Error decoding token:', error)
|
||||
@@ -44,21 +44,21 @@ export class NhostGraphqlClient {
|
||||
}
|
||||
}
|
||||
|
||||
private awaitForValidAccessToken = async () => {
|
||||
if (this.isAccessTokenValid()) {
|
||||
private awaitForValidAccessTokenOrNull = async () => {
|
||||
if (this.isAccessTokenValidOrNull()) {
|
||||
return true
|
||||
}
|
||||
|
||||
const waitForValidToken = () => {
|
||||
if (this.isAccessTokenValid()) {
|
||||
const waitForValidTokenOrNull = () => {
|
||||
if (this.isAccessTokenValidOrNull()) {
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => waitForValidToken().then(resolve), 100)
|
||||
setTimeout(() => waitForValidTokenOrNull().then(resolve), 100)
|
||||
})
|
||||
}
|
||||
|
||||
return waitForValidToken()
|
||||
return waitForValidTokenOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,7 +106,7 @@ export class NhostGraphqlClient {
|
||||
|
||||
if (!process.env.TEST_MODE) {
|
||||
// We skip this while running unit tests because the accessToken is generated using faker
|
||||
await this.awaitForValidAccessToken()
|
||||
await this.awaitForValidAccessTokenOrNull()
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @nhost/nextjs
|
||||
|
||||
## 2.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.3
|
||||
|
||||
## 2.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/react@3.2.2
|
||||
|
||||
## 2.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/nextjs",
|
||||
"version": "2.1.3",
|
||||
"version": "2.1.5",
|
||||
"description": "Nhost NextJS library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @nhost/nhost-js
|
||||
|
||||
## 3.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [407feea]
|
||||
- @nhost/graphql-js@0.1.8
|
||||
|
||||
## 3.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2d68fee]
|
||||
- @nhost/graphql-js@0.1.7
|
||||
|
||||
## 3.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/nhost-js",
|
||||
"version": "3.0.6",
|
||||
"version": "3.0.8",
|
||||
"description": "Nhost JavaScript SDK",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @nhost/react
|
||||
|
||||
## 3.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.8
|
||||
|
||||
## 3.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.7
|
||||
|
||||
## 3.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/react",
|
||||
"version": "3.2.1",
|
||||
"version": "3.2.3",
|
||||
"description": "Nhost React library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @nhost/vue
|
||||
|
||||
## 2.2.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.8
|
||||
|
||||
## 2.2.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- @nhost/nhost-js@3.0.7
|
||||
|
||||
## 2.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nhost/vue",
|
||||
"version": "2.2.1",
|
||||
"version": "2.2.3",
|
||||
"description": "Nhost Vue library",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
||||
39
pnpm-lock.yaml
generated
39
pnpm-lock.yaml
generated
@@ -224,8 +224,8 @@ importers:
|
||||
specifier: ^8.11.7
|
||||
version: 8.11.7(react-dom@18.2.0)(react@18.2.0)
|
||||
'@tanstack/react-virtual':
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.2(react-dom@18.2.0)(react@18.2.0)
|
||||
specifier: ^3.1.2
|
||||
version: 3.1.2(react-dom@18.2.0)(react@18.2.0)
|
||||
'@uiw/codemirror-theme-github':
|
||||
specifier: ^4.21.21
|
||||
version: 4.21.21
|
||||
@@ -1578,9 +1578,9 @@ importers:
|
||||
isomorphic-unfetch:
|
||||
specifier: ^3.1.0
|
||||
version: 3.1.0
|
||||
jwt-decode:
|
||||
specifier: ^3.1.2
|
||||
version: 3.1.2
|
||||
jose:
|
||||
specifier: ^5.2.2
|
||||
version: 5.2.2
|
||||
devDependencies:
|
||||
'@nhost/docgen':
|
||||
specifier: workspace:*
|
||||
@@ -5944,7 +5944,7 @@ packages:
|
||||
/@graphql-tools/utils@8.13.1(graphql@16.8.1):
|
||||
resolution: {integrity: sha512-qIh9yYpdUFmctVqovwMdheVNJqFh+DQNWIhX87FJStfXYnmweBUDATok9fWPleKeFwxnW8IapKmY8m8toJEkAw==}
|
||||
peerDependencies:
|
||||
graphql: 16.8.1
|
||||
graphql: '>=16.8.1'
|
||||
dependencies:
|
||||
graphql: 16.8.1
|
||||
tslib: 2.6.2
|
||||
@@ -5982,7 +5982,7 @@ packages:
|
||||
/@graphql-typed-document-node/core@3.2.0(graphql@16.8.1):
|
||||
resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==}
|
||||
peerDependencies:
|
||||
graphql: 16.8.1
|
||||
graphql: '>=16.8.1'
|
||||
dependencies:
|
||||
graphql: 16.8.1
|
||||
|
||||
@@ -6085,7 +6085,7 @@ packages:
|
||||
react: ^16 || ^17 || ^18
|
||||
react-dom: ^16 || ^17 || ^18
|
||||
dependencies:
|
||||
'@tanstack/react-virtual': 3.0.2(react-dom@18.2.0)(react@18.2.0)
|
||||
'@tanstack/react-virtual': 3.1.2(react-dom@18.2.0)(react@18.2.0)
|
||||
client-only: 0.0.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
@@ -10248,13 +10248,13 @@ packages:
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@tanstack/react-virtual@3.0.2(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-9XbRLPKgnhMwwmuQMnJMv+5a9sitGNCSEtf/AZXzmJdesYk7XsjYHaEDny+IrJzvPNwZliIIDwCRiaUqR3zzCA==}
|
||||
/@tanstack/react-virtual@3.1.2(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-qibmxtctgOZo2I+3Rw5GR9kXgaa15U5r3/idDY1ItUKW15UK7GhCfyIfE6qYuJ1fxQF6dJDsD8SbpPyuJgpxuA==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
'@tanstack/virtual-core': 3.0.0
|
||||
'@tanstack/virtual-core': 3.1.2
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
@@ -10264,8 +10264,8 @@ packages:
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/@tanstack/virtual-core@3.0.0:
|
||||
resolution: {integrity: sha512-SYXOBTjJb05rXa2vl55TTwO40A6wKu0R5i1qQwhJYNDIqaIGF7D0HsLw+pJAyi2OvntlEIVusx3xtbbgSUi6zg==}
|
||||
/@tanstack/virtual-core@3.1.2:
|
||||
resolution: {integrity: sha512-DATZJs8iejkIUqXZe6ruDAnjFo78BKnIIgqQZrc7CmEFqfLEN/TPD91n4hRfo6hpRB6xC00bwKxv7vdjFNEmOg==}
|
||||
dev: false
|
||||
|
||||
/@testing-library/dom@9.3.4:
|
||||
@@ -19205,7 +19205,7 @@ packages:
|
||||
/isomorphic-unfetch@3.1.0:
|
||||
resolution: {integrity: sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==}
|
||||
dependencies:
|
||||
node-fetch: 2.7.0(encoding@0.1.13)
|
||||
node-fetch: 2.7.0
|
||||
unfetch: 4.2.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
@@ -22003,6 +22003,17 @@ packages:
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
|
||||
/node-fetch@2.7.0:
|
||||
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
peerDependencies:
|
||||
encoding: ^0.1.0
|
||||
peerDependenciesMeta:
|
||||
encoding:
|
||||
optional: true
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
|
||||
/node-fetch@2.7.0(encoding@0.1.13):
|
||||
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
|
||||
Reference in New Issue
Block a user