feature(dashboard): add ability to filter out events by profile id and ip (#101)

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-12-07 21:34:32 +01:00
committed by GitHub
parent 27ee623584
commit f4ad97d87d
39 changed files with 1148 additions and 542 deletions

View File

@@ -27,31 +27,21 @@ import { ModalContent, ModalHeader } from './Modal/Container';
const validation = z.object({
name: z.string().min(1),
cors: z.string().min(1).or(z.literal('')),
projectId: z.string(),
type: z.enum(['read', 'write', 'root']),
crossDomain: z.boolean().optional(),
});
type IForm = z.infer<typeof validation>;
interface Props {
projectId: string;
}
export default function AddClient(props: Props) {
export default function AddClient() {
const { organizationId, projectId } = useAppParams();
const router = useRouter();
const form = useForm<IForm>({
resolver: zodResolver(validation),
defaultValues: {
name: '',
cors: '',
projectId: props.projectId ?? projectId,
type: 'write',
crossDomain: undefined,
},
});
const [hasDomain, setHasDomain] = useState(true);
const mutation = api.client.create.useMutation({
onError: handleError,
onSuccess() {
@@ -63,19 +53,11 @@ export default function AddClient(props: Props) {
organizationId,
});
const onSubmit: SubmitHandler<IForm> = (values) => {
if (hasDomain && values.cors === '') {
return form.setError('cors', {
type: 'required',
message: 'Please add a domain',
});
}
mutation.mutate({
name: values.name,
cors: hasDomain ? values.cors : null,
projectId: values.projectId,
organizationId,
type: values.type,
crossDomain: values.crossDomain,
projectId,
organizationId,
});
};
@@ -106,33 +88,6 @@ export default function AddClient(props: Props) {
className="flex flex-col gap-4"
onSubmit={form.handleSubmit(onSubmit)}
>
<div>
<Controller
control={form.control}
name="projectId"
render={({ field }) => {
return (
<div>
<Label>Project</Label>
<Combobox
{...field}
className="w-full"
onChange={(value) => {
field.onChange(value);
}}
items={
query.data?.map((item) => ({
value: item.id,
label: item.name,
})) ?? []
}
placeholder="Select a project"
/>
</div>
);
}}
/>
</div>
<div>
<Label>Client name</Label>
<Input
@@ -142,67 +97,6 @@ export default function AddClient(props: Props) {
/>
</div>
<div>
<Label className="flex items-center justify-between">
<span>Domain(s)</span>
<Switch checked={hasDomain} onCheckedChange={setHasDomain} />
</Label>
<AnimateHeight open={hasDomain}>
<Controller
name="cors"
control={form.control}
render={({ field }) => (
<TagInput
{...field}
error={form.formState.errors.cors?.message}
placeholder="Add a domain"
value={field.value?.split(',') ?? []}
renderTag={(tag) =>
tag === '*' ? 'Allow all domains' : tag
}
onChange={(newValue) => {
field.onChange(
newValue
.map((item) => {
const trimmed = item.trim();
if (
trimmed.startsWith('http://') ||
trimmed.startsWith('https://') ||
trimmed === '*'
) {
return trimmed;
}
return `https://${trimmed}`;
})
.join(','),
);
}}
/>
)}
/>
<Controller
name="crossDomain"
control={form.control}
render={({ field }) => {
return (
<CheckboxInput
className="mt-4"
ref={field.ref}
onBlur={field.onBlur}
defaultChecked={field.value}
onCheckedChange={field.onChange}
>
<div>Enable cross domain support</div>
<div className="font-normal text-muted-foreground">
This will let you track users across multiple domains
</div>
</CheckboxInput>
);
}}
/>
</AnimateHeight>
</div>
<div>
<Controller
control={form.control}
@@ -233,7 +127,7 @@ export default function AddClient(props: Props) {
]}
placeholder="Select a project"
/>
<p className="mt-1 text-sm text-muted-foreground">
<p className="mt-2 text-sm text-muted-foreground">
{field.value === 'write' &&
'Write: Is the default client type and is used for ingestion of data'}
{field.value === 'read' &&

View File

@@ -1,67 +1,164 @@
'use client';
import AnimateHeight from '@/components/animate-height';
import { ButtonContainer } from '@/components/button-container';
import { InputWithLabel } from '@/components/forms/input-with-label';
import { InputWithLabel, WithLabel } from '@/components/forms/input-with-label';
import TagInput from '@/components/forms/tag-input';
import { Button } from '@/components/ui/button';
import { CheckboxInput } from '@/components/ui/checkbox';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch';
import { useAppParams } from '@/hooks/useAppParams';
import { api, handleError } from '@/trpc/client';
import { zodResolver } from '@hookform/resolvers/zod';
import { useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form';
import type { IServiceProjectWithClients } from '@openpanel/db';
import { zProject } from '@openpanel/validation';
import { SaveIcon } from 'lucide-react';
import { useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { toast } from 'sonner';
import { z } from 'zod';
import { popModal } from '.';
import type { z } from 'zod';
import { ModalContent, ModalHeader } from './Modal/Container';
const validator = z.object({
name: z.string().min(1),
});
type Props = { project: IServiceProjectWithClients };
const validator = zProject.pick({
name: true,
domain: true,
cors: true,
crossDomain: true,
});
type IForm = z.infer<typeof validator>;
export default function AddProject() {
const { organizationId } = useAppParams();
const router = useRouter();
const mutation = api.project.create.useMutation({
onError: handleError,
onSuccess() {
router.refresh();
toast('Success', {
description: 'Project created! Lets create a client for it 🤘',
});
popModal();
},
});
const { register, handleSubmit, formState } = useForm<IForm>({
const [hasDomain, setHasDomain] = useState(true);
const form = useForm<IForm>({
resolver: zodResolver(validator),
defaultValues: {
name: '',
domain: '',
cors: [],
crossDomain: false,
},
});
const mutation = api.project.create.useMutation({
onError: handleError,
onSuccess: () => {
toast.success('Project created');
},
});
const onSubmit = (values: IForm) => {
if (hasDomain) {
let error = false;
if (values.cors.length === 0) {
form.setError('cors', {
type: 'required',
message: 'Please add at least one cors domain',
});
error = true;
}
if (!values.domain) {
form.setError('domain', {
type: 'required',
message: 'Please add a domain',
});
error = true;
}
if (error) {
return;
}
}
mutation.mutate({
...(hasDomain ? values : { ...values, cors: [], domain: null }),
organizationId,
});
};
return (
<ModalContent>
<ModalHeader title="Create project" />
<form
onSubmit={handleSubmit((values) => {
mutation.mutate({
...values,
organizationId,
});
onSubmit={form.handleSubmit(onSubmit, (errors) => {
console.log(errors);
})}
className="col gap-4"
>
<div className="flex flex-col gap-4">
<InputWithLabel
label="Name"
placeholder="Name"
{...register('name')}
/>
<InputWithLabel label="Name" {...form.register('name')} />
<div className="-mb-2 flex gap-2 items-center justify-between">
<Label className="mb-0">Domain</Label>
<Switch checked={hasDomain} onCheckedChange={setHasDomain} />
</div>
<AnimateHeight open={hasDomain}>
<Input
placeholder="Domain"
{...form.register('domain')}
className="mb-4"
error={form.formState.errors.domain?.message}
/>
<Controller
name="cors"
control={form.control}
render={({ field }) => (
<WithLabel label="Cors">
<TagInput
{...field}
id="Cors"
error={form.formState.errors.cors?.message}
placeholder="Add a domain"
value={field.value ?? []}
renderTag={(tag) => (tag === '*' ? 'Allow all domains' : tag)}
onChange={(newValue) => {
field.onChange(
newValue.map((item) => {
const trimmed = item.trim();
if (
trimmed.startsWith('http://') ||
trimmed.startsWith('https://') ||
trimmed === '*'
) {
return trimmed;
}
return `https://${trimmed}`;
}),
);
}}
/>
</WithLabel>
)}
/>
<Controller
name="crossDomain"
control={form.control}
render={({ field }) => {
return (
<CheckboxInput
className="mt-4"
ref={field.ref}
onBlur={field.onBlur}
defaultChecked={field.value}
onCheckedChange={field.onChange}
>
<div>Enable cross domain support</div>
<div className="font-normal text-muted-foreground">
This will let you track users across multiple domains
</div>
</CheckboxInput>
);
}}
/>
</AnimateHeight>
<ButtonContainer>
<Button type="button" variant="outline" onClick={() => popModal()}>
Cancel
</Button>
<Button type="submit" disabled={!formState.isDirty}>
Create
<Button loading={mutation.isLoading} type="submit" icon={SaveIcon}>
Save
</Button>
</ButtonContainer>
</form>