fix: feature-base-refactoring (#297)

* docs: add AGENTS.md with feature-based architecture conventions

Defines the target src/ structure, feature list, naming conventions
(kebab-case, file type patterns), import direction rules, and
feature boundary guidelines for the planned big-bang refactor.

* docs: add features/channel/ to architecture conventions

Generic channel UI (card, modal, form) goes to features/channel/.
Provider-specific sub-folders: channel/notifications/ and channel/storages/.

* refactor: move event emitter to lib/, project service to db/services/, dedup use-mobile hook

* fix: remove unused auth import in db/services/project.ts

* refactor: migrate wrappers/common to components/common (flat)

* fix: remove dead commented-out code from button-with-loading.tsx

* refactor: merge features/browser into features/theme, flatten upload/updates, migrate statistics

* refactor: migrate wrappers/auth to features/auth

Move all auth components and forms from src/components/wrappers/auth/
into src/features/auth/ with flat kebab-case naming. Handle extra
reset-password-form.tsx and differing reset-password-schema.ts by
copying them as reset-password-page-form.tsx and reset-password-page.schema.ts.

* refactor: remove dead auth files and fix zPassword schema

Delete reset-password-section.tsx and reset-password-page-form.tsx (zero consumers).
Fix reset-password-page.schema.ts to import zPassword from @/lib/zod.

* refactor: migrate wrappers/dashboard/common to features/layout, rename Header.tsx to header.tsx

* fix: decouple layout from theme/updates cross-feature imports

Use slot props (actions, updateNotification) so app/ composes
ModeToggle and UpdateNotification into Header and AppSidebar.
Delete dead side-bar-logo.tsx (zero consumers).

* refactor: merge features/keys into features/agents, flatten sub-dirs, migrate agent wrappers

- Merge src/features/keys/keys.action.ts into src/features/agents/
- Flatten src/features/agents/components/ and hooks/ into feature root
- Migrate all src/components/wrappers/dashboard/agent/ files into src/features/agents/
- Update all import paths across app/ and src/ accordingly
- Fix broken code-snippet import in agent-key-modal.tsx (was pointing to non-existent wrappers path)

* fix: move github-releases to lib/, remove dead code and unused imports

Move GitHub release fetch logic from features/updates/github.ts to
lib/github-releases.ts so both features/updates and features/agents
can import it without cross-feature imports.
Remove commented-out impersonation block from agent-database-card.tsx.
Remove unused imports (CopyButton, Terminal, Key, Info).

* refactor: flatten features/projects/components, migrate project wrappers

Move project.dialog/form out of sub-dir, migrate project-card, project-database-card, and delete-project button/action from wrappers into features/projects/. Update all import paths across src/ and app/. Leave wrappers/dashboard/projects/database/ untouched for Task 10.

* fix: extract DatabaseCard to components/common, remove cross-feature import

DatabaseCard was imported by both features/projects and features/agents,
violating the no-cross-feature-import rule. Extract to components/common/
database-card.tsx so both features import from the shared layer.
Also tighten extendedProps type and remove commented-out maxCount prop.

* refactor: migrate all database/backup/restore/health UI to features/database

Consolidates ~40 files from 5 source locations (features/dashboard/backup,
features/dashboard/restore, wrappers/dashboard/backup, wrappers/dashboard/database,
wrappers/dashboard/health, wrappers/dashboard/projects/database) into the new
flat features/database directory with kebab-case filenames and updated @/ imports.

* fix: remove dead restore-form files and console.log from health-grid

Delete restore-form.tsx (186 lines all commented-out, zero consumers)
and restore-form.schema.ts (only a commented import).
Remove console.log(logs) from getOldestLog helper in health-grid.tsx.

Cross-feature imports (database→storages for dispatchStorage/storeBackupFiles)
are accepted as known architectural coupling — storage dispatch is called from
database server actions at runtime and cannot route through app/ props.

* refactor: migrate wrappers/admin/users to features/users, fix table-colums typo

* fix: fix relative imports, string interpolation, and console.error in users feature

Replace relative imports with @/ aliases in user-actions-cell.tsx and
admin-user-edit-modal.tsx. Fix string interpolation bug in dialog title
(was regular string, now template literal). Remove console.error from
admin-user-change-role-modal.tsx.

* refactor: migrate wrappers/profile to features/profile, fix profile-apperance typo

* fix: remove console.error calls and ts-ignore in profile feature

Remove console.error from profile-account.tsx, 2fa-form.tsx (x2).
Replace @ts-ignore on fileInput.onchange with typed cast in avatar-with-upload.tsx.

* refactor: rename features/organization to features/organizations, migrate all org wrappers

Moves 37 org-related files from features/organization/, features/dashboard/,
hooks/, wrappers/admin/organizations/, wrappers/admin/channels/organization/,
and wrappers/dashboard/organization/ into a flat features/organizations/ feature
directory. Updates all import paths codebase-wide. Fixes @ts-ignore and removes
unused @better-fetch/fetch import in admin-organization-form.

* fix: remove console.error, @ts-ignore, and unused destructures in features/organizations

* refactor: migrate wrappers/organization/migration to features/migration

Move all 5 migration files to src/features/migration/, update internal
imports to @/features/migration/ absolute paths, remove console.log,
and delete src/components/wrappers/dashboard/organization/ entirely.

* refactor: migrate wrappers/admin/settings to features/settings

Move all 14 settings files (email, notification, storage, s3) from
wrappers/dashboard/admin/settings/ into features/settings/, rewriting
imports, replacing @ts-ignore with @ts-expect-error, and renaming error
params to _error where applicable.

* refactor: rename notifications files to kebab convention, migrate notification logs

- Rename dispatch.ts/helpers.ts/types.ts to notifications.*.ts
- Migrate notification logs components from wrappers/dashboard/admin/notifications/logs/ to features/notifications/
- Fix all import paths across codebase and in providers/
- Remove unused useRouter import, replace @ts-ignore with @ts-expect-error
- Delete src/components/wrappers/dashboard/admin/notifications/

* refactor: rename storages dispatch/helpers/types to kebab convention

- Rename dispatch.ts/helpers.ts/types.ts to storages.*.ts
- Fix all import paths across codebase and in providers/
- Fix relative ../types and ../../types imports in all storage providers to absolute @/ paths
- Replace @ts-ignore with @ts-expect-error, remove console.debug/console.error

* refactor: create features/channel with generic UI and notifications/storages provider sub-folders

Consolidates 4 source locations into src/features/channel/:
- components/wrappers/dashboard/admin/channels/ → features/channel/ (11 generic UI files)
- features/notifications/providers/ → features/channel/notifications/ (9 backend + 18 form files)
- features/storages/providers/ → features/channel/storages/ (backend + forms)

The two conflicting google-drive/helpers.ts files (client helpers vs OAuth action)
are kept separate: helpers.ts and google-drive-refresh.action.ts.

Rewrites all imports codebase-wide to point to new locations.
Removes console.log/console.error calls, upgrades @ts-ignore to @ts-expect-error.

* fix: remove console.error from local storage provider

* fix: multi-select.tsx scroll issue

* fix: multi-select.tsx scroll issue
This commit is contained in:
Charles GTE
2026-05-24 17:09:12 +02:00
committed by GitHub
parent 4fadfcfaf2
commit 3fa0f9b76d
330 changed files with 627 additions and 1120 deletions
@@ -1,62 +1,4 @@
// "use client";
//
// import { Button } from "@/components/ui/button";
// import { ButtonHTMLAttributes } from "react";
// import { Loader2 } from "lucide-react";
//
// export type VariantButton = {
// secondary: string;
// default: string;
// outline: string;
// ghost: string;
// link: string;
// destructive: string;
// };
// export type sizeButton = {
// default: string;
// icon: string;
// sm: string;
// lg: string;
// };
//
// export type ButtonWithConfirmProps = {
// icon?: any;
// text: string;
// variant?: keyof VariantButton;
// className?: string;
// onClick: () => void;
// isPending?: boolean;
// size: keyof sizeButton;
// };
//
// export const ButtonWithLoading = ({
// icon,
// text,
// variant,
// className,
// onClick,
// isPending,
// size,
// ...props // catch all remaining props
// }: ButtonWithConfirmProps & ButtonHTMLAttributes<HTMLButtonElement>) => {
// return (
// <Button
// onClick={() => {
// onClick();
// }}
// variant={variant ? variant : "default"}
// className={className}
// {...props} // forward the remaining props to the Button component
// size={size || "default"}
// >
// {isPending && <Loader2 className="animate-spin mr-4" size={16} />}
// {text}
// <>{icon ? icon : null}</>
// </Button>
// );
// };
'use client'
"use client"
import { ButtonHTMLAttributes, ReactNode } from "react";
import { Loader2 } from "lucide-react";
@@ -3,8 +3,8 @@
import React, { ComponentType, useState } from "react";
import { cn } from "@/lib/utils";
import { PaginationNavigation } from "@/components/wrappers/common/pagination/pagination-navigation";
import { PaginationSize } from "@/components/wrappers/common/pagination/pagination-size";
import { PaginationNavigation } from "@/components/common/pagination-navigation";
import { PaginationSize } from "@/components/common/pagination-size";
interface CardsWithPaginationProps<T> {
className?: string;
@@ -3,7 +3,7 @@
import {PropsWithChildren, useState} from "react";
import {Button} from "@/components/ui/button";
import {Check, Copy, Terminal} from "lucide-react";
import {copyToClipboardWithMeta} from "@/components/wrappers/common/button/copy-button";
import {copyToClipboardWithMeta} from "@/components/common/copy-button";
import {cn} from "@/lib/utils";
export type CodeSnippetProps = PropsWithChildren<{
@@ -4,35 +4,17 @@ import Link from "next/link";
import Image from "next/image";
import {useState} from "react";
import {Card} from "@/components/ui/card";
import {ConnectionIndicator} from "@/components/wrappers/common/connection-indicator";
import {ConnectionIndicator} from "@/components/common/connection-indicator";
import {formatDateLastContact} from "@/utils/date-formatting";
import {Database} from "@/db/schema/07_database";
import {ChevronRight, Activity, Fingerprint, Copy, Check} from "lucide-react";
export type projectDatabaseCardProps = {
data: Database;
extendedProps: any;
organizationSlug: string;
};
export const ProjectDatabaseCard = (props: projectDatabaseCardProps) => {
const {data: database, extendedProps: extendedProps} = props;
return (
<Link
className="group block transition-all duration-200 outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-xl"
href={`/dashboard/projects/${extendedProps.id}/database/${database.id}`}>
<DatabaseCard data={database}/>
</Link>
);
};
export type databaseCardProps = {
export type DatabaseCardProps = {
data: Database;
withDetails?: boolean;
};
export const DatabaseCard = (props: databaseCardProps) => {
export const DatabaseCard = (props: DatabaseCardProps) => {
const {data: database, withDetails = true} = props;
const [isCopied, setIsCopied] = useState(false);
@@ -1,5 +1,3 @@
//https://github.com/sersavan/shadcn-multi-select-component
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { CheckIcon, XCircle, ChevronDown, XIcon, WandSparkles } from "lucide-react";
@@ -233,53 +231,87 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start" onEscapeKeyDown={() => setIsPopoverOpen(false)}>
<PopoverContent
className="w-auto p-0"
align="start"
onEscapeKeyDown={() => setIsPopoverOpen(false)}
>
<Command>
<CommandInput placeholder="Search..." onKeyDown={handleInputKeyDown} />
<CommandList>
<CommandList className="max-h-none overflow-visible">
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup>
<CommandItem key="all" onSelect={toggleAll} className="cursor-pointer">
<div
className={cn(
"mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
selectedValues.length === options.length ? "bg-primary text-primary-foreground" : "opacity-50 [&_svg]:invisible"
)}
>
<CheckIcon className="h-4 w-4" />
</div>
<span>(Select All)</span>
</CommandItem>
{options.map((option) => {
const isSelected = selectedValues.includes(option.value);
return (
<CommandItem key={option.value} onSelect={() => toggleOption(option.value)} className="cursor-pointer">
<div
className={cn(
"mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
isSelected ? "bg-primary text-primary-foreground" : "opacity-50 [&_svg]:invisible"
)}
<div
className="max-h-[18rem] overflow-y-auto overflow-x-hidden overscroll-contain touch-pan-y pr-1"
style={{ WebkitOverflowScrolling: "touch" }}
onWheelCapture={(event) => event.stopPropagation()}
onTouchMoveCapture={(event) => event.stopPropagation()}
>
<CommandGroup>
<CommandItem key="all" onSelect={toggleAll} className="cursor-pointer">
<div
className={cn(
"mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
selectedValues.length === options.length
? "bg-primary text-primary-foreground"
: "opacity-50 [&_svg]:invisible"
)}
>
<CheckIcon className="h-4 w-4" />
</div>
<span>(Select All)</span>
</CommandItem>
{options.map((option) => {
const isSelected = selectedValues.includes(option.value);
return (
<CommandItem
key={option.value}
onSelect={() => toggleOption(option.value)}
className="cursor-pointer"
>
<CheckIcon className="h-4 w-4" />
</div>
{option.icon && <option.icon className="mr-2 h-4 w-4 text-muted-foreground" />}
<span>{option.label}</span>
</CommandItem>
);
})}
</CommandGroup>
<div
className={cn(
"mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
isSelected
? "bg-primary text-primary-foreground"
: "opacity-50 [&_svg]:invisible"
)}
>
<CheckIcon className="h-4 w-4" />
</div>
{option.icon && (
<option.icon className="mr-2 h-4 w-4 text-muted-foreground" />
)}
<span>{option.label}</span>
</CommandItem>
);
})}
</CommandGroup>
</div>
<CommandSeparator />
<CommandGroup>
<div className="flex items-center justify-between">
{selectedValues.length > 0 && (
<>
<CommandItem onSelect={handleClear} className="flex-1 justify-center cursor-pointer">
<CommandItem
onSelect={handleClear}
className="flex-1 justify-center cursor-pointer"
>
Clear
</CommandItem>
<Separator orientation="vertical" className="flex min-h-6 h-full" />
</>
)}
<CommandItem onSelect={() => setIsPopoverOpen(false)} className="flex-1 justify-center cursor-pointer max-w-full">
<CommandItem
onSelect={() => setIsPopoverOpen(false)}
className="flex-1 justify-center cursor-pointer max-w-full"
>
Close
</CommandItem>
</div>
@@ -1,5 +1,5 @@
import { Pagination, PaginationContent, PaginationItem, PaginationNext, PaginationPrevious } from "@/components/ui/pagination";
import { PaginationIndexes } from "@/components/wrappers/common/pagination/pagination-indexes";
import { PaginationIndexes } from "@/components/common/pagination-indexes";
import { cn } from "@/lib/utils";
export type paginationNavigationProps = {
@@ -1,4 +1,4 @@
import { PaginationNavigation } from "@/components/wrappers/common/pagination/pagination-navigation";
import { PaginationNavigation } from "@/components/common/pagination-navigation";
export type paginationNavigationProps = {
className?: string;
@@ -1,7 +1,7 @@
"use client";
import {TablePaginationNavigation} from "@/components/wrappers/common/table/table-pagination-navigation";
import {TablePaginationSize} from "@/components/wrappers/common/table/table-pagination-size";
import {TablePaginationNavigation} from "@/components/common/table-pagination-navigation";
import {TablePaginationSize} from "@/components/common/table-pagination-size";
import {cn} from "@/lib/utils";
interface tablePaginationProps {
+2 -2
View File
@@ -1,7 +1,7 @@
// import * as React from "react";
// import EmailLayout from "./email-layout";
// import {Text, Section, Button} from "@react-email/components";
// import type {EventPayload} from "@/features/notifications/types";
// import type {EventPayload} from "@/features/notifications/notifications.types";
//
// export interface EmailNotificationProps {
// payload: EventPayload
@@ -48,7 +48,7 @@
import * as React from "react";
import EmailLayout from "./email-layout";
import {Text, Section, Button} from "@react-email/components";
import type {EventPayload} from "@/features/notifications/types";
import type {EventPayload} from "@/features/notifications/notifications.types";
export interface EmailNotificationProps {
payload: EventPayload;
@@ -1,94 +0,0 @@
"use client";
import {useRouter} from "next/navigation";
import {useMutation} from "@tanstack/react-query";
import {Form, FormControl, FormField, FormItem, FormLabel, useZodForm} from "@/components/ui/form";
import {PasswordStrengthInput} from "@/components/ui/password-input-indicator";
import {ResetPasswordSchema, ResetPasswordType} from "@/components/wrappers/auth/reset-password/reset-password-schema";
import {PasswordInput} from "@/components/ui/password-input";
import {CardContent, CardHeader} from "@/components/ui/card";
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading";
import {authClient} from "@/lib/auth/auth-client";
import {toast} from "sonner";
import {CardAuth} from "@/features/layout/card-auth";
type ResetPasswordFormProps = {
token: string;
};
export const ResetPasswordForm = ({token}: ResetPasswordFormProps) => {
const router = useRouter();
const form = useZodForm({
schema: ResetPasswordSchema,
});
const mutationResetPassword = useMutation({
mutationFn: async (data: ResetPasswordType) => {
await authClient.resetPassword({
newPassword: data.password,
token,
}, {
onSuccess: (response) => {
toast.success("Password changed successfully.");
setTimeout(() => router.push("/"), 1000);
},
onError: (error) => {
console.error(error);
toast.error(error.error.message);
},
});
},
});
return (
<CardAuth>
<CardHeader>
<div className="grid gap-2 text-center mb-2">
<h1 className="text-3xl font-bold">Reset Password</h1>
<p className="text-balance text-muted-foreground">Fill information below to change your
password</p>
</div>
</CardHeader>
<CardContent>
<Form
form={form}
className="flex flex-col gap-4"
onSubmit={async (values) => {
await mutationResetPassword.mutateAsync(values);
}}
>
<FormField
control={form.control}
name="password"
defaultValue=""
render={({field}) => (
<FormItem>
<PasswordStrengthInput label={"Enter new password"} field={field}/>
</FormItem>
)}
/>
<FormField
control={form.control}
name="confirmPassword"
defaultValue=""
render={({field}) => (
<FormItem>
<FormLabel>Confirmation Password</FormLabel>
<FormControl>
<PasswordInput placeholder={"Confirm password"} {...field}
value={field.value ?? ""}/>
</FormControl>
</FormItem>
)}
/>
<ButtonWithLoading type="submit" isPending={mutationResetPassword.isPending}>
Validate
</ButtonWithLoading>
</Form>
</CardContent>
</CardAuth>
);
};
@@ -1,37 +0,0 @@
"use client"
import {useRouter, useSearchParams} from "next/navigation";
import {ResetPasswordForm} from "@/components/wrappers/auth/reset-password/reset-password-form";
import {toast} from "sonner";
import {useEffect} from "react";
import {LoadingSpinner} from "@/components/wrappers/common/loading/loading-spinner";
export const ResetPasswordSection = () => {
const router = useRouter();
const searchParams = useSearchParams();
const token = searchParams.get("token");
const error = searchParams.get("error");
useEffect(() => {
if (error || !token) {
if (error == "INVALID_TOKEN") {
toast.error("Invalid reset token");
} else {
toast.error("An error occurred.");
}
setTimeout(() => router.push("/"), 1000);
}
}, [error, token, router]);
if (!token) return (
<div className="w-full h-full flex flex-col items-center justify-center">
<LoadingSpinner size={50}/>
</div>
);
return (
<ResetPasswordForm token={token}/>
)
}
@@ -1,26 +0,0 @@
"use client";
import {Database} from "@/db/schema/07_database";
import {DatabaseCard} from "@/components/wrappers/dashboard/projects/project-card/project-database-card";
export type agentDatabaseCardProps = {
data: Database;
};
export const AgentDatabaseCard = (props: agentDatabaseCardProps) => {
const {data: database} = props;
return <DatabaseCard withDetails={false} data={database}/>;
//Todo: Add again when impersonation system will be implemented
/* if (!database.projectId) {
return <DatabaseCard data={database}/>;
}
return (
<Link className="group block transition-all duration-200 outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-xl"
href={`/dashboard/projects/${database.projectId}/database/${database.id}`}>
<DatabaseCard data={database}/>
</Link>
);*/
};
@@ -1,41 +0,0 @@
"use client";
import {useSidebar} from "@/components/ui/sidebar";
import {useTheme} from "next-themes";
import Image from "next/image";
import Link from "next/link";
export type SideBarLogoProps = {};
export const SideBarLogo = (props: SideBarLogoProps) => {
const {state, isMobile} = useSidebar();
const {resolvedTheme} = useTheme();
return state === "collapsed" && !isMobile ? (
<Link href={"/dashboard"}>
<div className="flex items-center justify-center">
<Image
src={"/images/logo/picto.png"}
alt="Logo"
width={32}
height={32}
loading="eager"
className="object-contain"
/>
</div>
</Link>
) : (
<Link href={"/dashboard"}>
<div className="w-full h-auto p-4 flex items-center justify-center">
<Image
loading="eager"
src={"/images/logo/logo.png"}
alt="Logo"
width={190}
height={190}
className="object-contain"
/>
</div>
</Link>
);
};
@@ -1,39 +0,0 @@
import { z } from "zod";
const ImmediateExecutionSchema = z.object({
executionMode: z.literal("immediate"),
});
const ScheduledExecutionSchema = z.object({
executionMode: z.literal("scheduled"),
scheduledDatetime: z.date(),
});
const RemoteImmediateSchema = z
.object({
backupLocation: z.literal("remote-file"),
})
.merge(ImmediateExecutionSchema);
const RemoteScheduledSchema = z
.object({
backupLocation: z.literal("remote-file"),
})
.merge(ScheduledExecutionSchema);
const DesktopImmediateSchema = z
.object({
backupLocation: z.literal("desktop-file"),
uploadedBackupFile: z.instanceof(File),
})
.merge(ImmediateExecutionSchema);
const DesktopScheduledSchema = z
.object({
backupLocation: z.literal("desktop-file"),
uploadedBackupFile: z.instanceof(File),
})
.merge(ScheduledExecutionSchema);
export const RestoreSchema = z.union([RemoteImmediateSchema, RemoteScheduledSchema, DesktopImmediateSchema, DesktopScheduledSchema]);
export type RestoreType = z.infer<typeof RestoreSchema>;
@@ -1,186 +0,0 @@
// "use client";
//
// import { useState } from "react";
//
// import { DateTimePicker } from "@/components/wrappers/common/day-time-picker";
// import { Button } from "@/components/ui/button";
// import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm } from "@/components/ui/form";
// import { Input } from "@/components/ui/input";
// import { RestoreSchema } from "@/components/wrappers/dashboard/database/restore-form.schema";
// import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
// import { ComboBox } from "@/components/wrappers/common/combobox";
// import { Label } from "@/components/ui/label";
//
// export type restoreFormProps = {
// databaseToRestore: any;
// databases: any[];
// backups: any[];
// };
//
// export const RestoreForm = (props: restoreFormProps) => {
// const { databaseToRestore, databases, backups } = props;
//
// const backupLocations = [
// {
// value: "remote-file",
// label: "Remote File",
// },
// {
// value: "desktop-file",
// label: "Desktop File",
// },
// ];
//
// const executionModes = [
// {
// value: "immediate",
// label: "Immediate",
// },
// {
// value: "scheduled",
// label: "Scheduled",
// },
// ];
//
// const [selectedDatabaseId, setSelectedDatabaseId] = useState(databaseToRestore.id);
//
// const filteredBackups = backups.filter((backup) => backup.databaseId == selectedDatabaseId);
//
// const defaultValues = {
// executionMode: "immediate" as const,
// backupLocation: "remote-file" as const,
// };
//
// const form = useZodForm({
// schema: RestoreSchema,
// defaultValues: defaultValues,
// });
//
// const values = form.getValues();
//
// return (
// <div>
// <Form
// form={form}
// onSubmit={async (values) => {
// console.log(values);
// }}
// >
// <FormField
// control={form.control}
// name="backupLocation"
// render={({ field }) => (
// <FormItem>
// <FormLabel>Backup location</FormLabel>
// <Select onValueChange={field.onChange} defaultValue={field.value}>
// <FormControl>
// <SelectTrigger>
// <SelectValue />
// </SelectTrigger>
// </FormControl>
// <SelectContent>
// {backupLocations.map((backupLocation, key) => (
// <SelectItem key={key} value={backupLocation.value}>
// {backupLocation.label}
// </SelectItem>
// ))}
// </SelectContent>
// </Select>
// <FormMessage />
// </FormItem>
// )}
// />
//
// {values.backupLocation == "remote-file" ? (
// <>
// <div className="flex flex-col">
// <Label>Database</Label>
// <ComboBox
// values={databases.map((database) => ({ value: database.id, label: database.name }))}
// onValueChange={setSelectedDatabaseId}
// defaultValue={selectedDatabaseId}
// searchField
// />
// </div>
// <FormField
// control={form.control}
// name="remoteBackup"
// render={({ field }) => (
// <FormItem className="flex flex-col">
// <FormLabel>Remote backup</FormLabel>
// <ComboBoxFormItem
// values={filteredBackups.map((backup) => ({
// value: backup.id,
// label: backup.createdAt.toString(),
// }))}
// {...field}
// />
// <FormMessage />
// </FormItem>
// )}
// />
// </>
// ) : null}
//
// {values.backupLocation == "desktop-file" ? (
// <FormField
// control={form.control}
// name="uploadedBackupFile"
// render={({ field }) => (
// <FormItem>
// <FormLabel>File</FormLabel>
// <FormControl>
// <Input id="uploadedBackupFile" type="file" {...field} />
// </FormControl>
// <FormMessage />
// </FormItem>
// )}
// />
// ) : null}
//
// <FormField
// control={form.control}
// name="executionMode"
// render={({ field }) => (
// <FormItem>
// <FormLabel>Execution mode</FormLabel>
// <Select onValueChange={field.onChange} defaultValue={field.value}>
// <FormControl>
// <SelectTrigger>
// <SelectValue />
// </SelectTrigger>
// </FormControl>
// <SelectContent>
// {executionModes.map((executionMode, key) => (
// <SelectItem key={key} value={executionMode.value}>
// {executionMode.label}
// </SelectItem>
// ))}
// </SelectContent>
// </Select>
// <FormMessage />
// </FormItem>
// )}
// />
//
// {values.executionMode == "scheduled" ? (
// <FormField
// control={form.control}
// name="scheduledDatetime"
// render={({ field }) => (
// <FormItem>
// <FormLabel>Scheduled date</FormLabel>
// <FormControl>
// <DateTimePicker value={field.value} onSelect={field.onChange} />
// </FormControl>
// <FormMessage />
// </FormItem>
// )}
// />
// ) : null}
//
// <Button type="submit">Launch restore</Button>
// </Form>
// </div>
// );
// };
+2 -2
View File
@@ -1,8 +1,8 @@
import {db} from "@/db";
import * as drizzleDb from "@/db";
import {and, eq, gte, isNotNull, lt} from "drizzle-orm";
import {dispatchNotification} from "@/features/notifications/dispatch";
import {EventPayload} from "@/features/notifications/types";
import {dispatchNotification} from "@/features/notifications/notifications.dispatch";
import {EventPayload} from "@/features/notifications/notifications.types";
import {logger} from "@/lib/logger";
const log = logger.child({module: "tasks/healthcheck"});
@@ -1,4 +1,4 @@
import {auth, getOrganization} from "@/lib/auth/auth";
import {getOrganization} from "@/lib/auth/auth";
import {db} from "@/db";
import {and, eq} from "drizzle-orm";
import * as drizzleDb from "@/db";
@@ -59,4 +59,3 @@ export const getOrganizationProjectDatabases = async ({organizationSlug, project
};
}
};
@@ -2,9 +2,9 @@
import {Input} from "@/components/ui/input";
import {Button} from "@/components/ui/button";
import {Copy, Check, Eye, EyeOff, Terminal, Key, Info} from "lucide-react";
import {Copy, Check, Eye, EyeOff} from "lucide-react";
import {useState} from "react";
import {copyToClipboardWithMeta} from "@/components/wrappers/common/button/copy-button";
import {copyToClipboardWithMeta} from "@/components/common/copy-button";
import {Card, CardContent, CardDescription, CardHeader, CardTitle} from "@/components/ui/card";
export type AgentCardKeyProps = {
@@ -3,7 +3,7 @@
import {useState} from "react";
import Link from "next/link";
import {Card} from "@/components/ui/card";
import {ConnectionIndicator} from "@/components/wrappers/common/connection-indicator";
import {ConnectionIndicator} from "@/components/common/connection-indicator";
import {formatDateLastContact} from "@/utils/date-formatting";
import {AgentWith} from "@/db/schema/08_agent";
import {Activity, ChevronRight, Copy, Check, Fingerprint, Server, Database, AlertCircle} from "lucide-react";
@@ -11,7 +11,7 @@ import {Badge} from "@/components/ui/badge";
import {truncateWords} from "@/utils/text";
import {useIsMobile} from "@/hooks/use-mobile";
import {Tooltip, TooltipContent, TooltipTrigger} from "@/components/ui/tooltip";
import {useAgentUpdateCheck} from "@/features/agents/hooks/use-agent-update-check";
import {useAgentUpdateCheck} from "@/features/agents/use-agent-update-check";
export type agentCardProps = {
data: AgentWith;
@@ -3,7 +3,7 @@
import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card";
import {Server} from "lucide-react";
import {formatDateLastContact} from "@/utils/date-formatting";
import {AgentCardKey} from "@/components/wrappers/dashboard/agent/agent-card-key/agent-card-key";
import {AgentCardKey} from "@/features/agents/agent-card-key";
import {AgentWithDatabases} from "@/db/schema/08_agent";
import {useQuery} from "@tanstack/react-query";
import {getAgentAction} from "@/features/agents/agents.action";
@@ -15,9 +15,9 @@ import {
} from "@/components/ui/accordion";
import {Separator} from "@/components/ui/separator";
import {Badge} from "@/components/ui/badge";
import {CardsWithPagination} from "@/components/wrappers/common/cards-with-pagination";
import {AgentDatabaseCard} from "@/components/wrappers/dashboard/agent/agent-database-card";
import {HealthCheckGraph} from "@/components/wrappers/dashboard/health/heath-grid";
import {CardsWithPagination} from "@/components/common/cards-with-pagination";
import {AgentDatabaseCard} from "@/features/agents/agent-database-card";
import {HealthCheckGraph} from "@/features/database/health-grid";
import {HealthcheckLog} from "@/db/schema/15_healthcheck-log";
type AgentContentPageProps = {
@@ -0,0 +1,14 @@
"use client";
import {Database} from "@/db/schema/07_database";
import {DatabaseCard} from "@/components/common/database-card";
export type agentDatabaseCardProps = {
data: Database;
};
export const AgentDatabaseCard = (props: agentDatabaseCardProps) => {
const {data: database} = props;
return <DatabaseCard withDetails={false} data={database}/>;
};
@@ -3,7 +3,7 @@
import {ColumnDef} from "@tanstack/react-table"
import {Database} from "@/db/schema/07_database"
import Image from "next/image"
import {ConnectionIndicator} from "@/components/wrappers/common/connection-indicator"
import {ConnectionIndicator} from "@/components/common/connection-indicator"
import {formatDateLastContact} from "@/utils/date-formatting"
import Link from "next/link"
import {Button} from "@/components/ui/button"
@@ -1,11 +1,11 @@
"use client";
import {Trash2} from "lucide-react";
import {ButtonWithConfirm} from "@/components/wrappers/common/button/button-with-confirm";
import {ButtonWithConfirm} from "@/components/common/button-with-confirm";
import {useMutation} from "@tanstack/react-query";
import {useRouter} from "next/navigation";
import {toast} from "sonner";
import {deleteAgentAction} from "@/components/wrappers/dashboard/agent/button-delete-agent/delete-agent.action";
import {deleteAgentAction} from "@/features/agents/agent-delete.action";
import {useIsMobile} from "@/hooks/use-mobile";
export type ButtonDeleteAgentProps = {
@@ -7,16 +7,16 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {AgentForm} from "@/features/agents/components/agent.form";
import {AgentForm} from "@/features/agents/agent-form";
import {Button, buttonVariants} from "@/components/ui/button";
import {Plus} from "lucide-react";
import {GearIcon} from "@radix-ui/react-icons";
import {EmptyStatePlaceholder} from "@/components/wrappers/common/empty-state-placeholder";
import {EmptyStatePlaceholder} from "@/components/common/empty-state-placeholder";
import {useState} from "react";
import {useRouter} from "next/navigation";
import {OrganizationWithMembers} from "@/db/schema/03_organization";
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs";
import {AgentOrganisationForm} from "@/features/agents/components/agent-organizations.form";
import {AgentOrganisationForm} from "@/features/agents/agent-organizations.form";
import {AgentWith} from "@/db/schema/08_agent";
type AgentDialogProps = {
@@ -5,9 +5,8 @@ import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogT
import { generateEdgeKey } from "@/utils/edge_key";
import { PropsWithChildren } from "react";
import { CopyButton } from "@/components/wrappers/common/button/copy-button";
import { getServerUrl } from "@/utils/get-server-url";
import { CodeSnippet } from "@/components/wrappers/code-snippet/code-snippet";
import { CodeSnippet } from "@/components/common/code-snippet";
import {Agent} from "@/db/schema/08_agent";
export type agentRegistrationDialogProps = PropsWithChildren<{
@@ -3,13 +3,13 @@
import {useRouter} from "next/navigation";
import {useMutation} from "@tanstack/react-query";
import {Form, FormControl, FormField, FormItem, useZodForm} from "@/components/ui/form";
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading";
import {ButtonWithLoading} from "@/components/common/button-with-loading";
import {OrganizationWithMembers} from "@/db/schema/03_organization";
import {MultiSelect} from "@/components/wrappers/common/multiselect/multi-select";
import {MultiSelect} from "@/components/common/multi-select";
import {toast} from "sonner";
import {AgentWith} from "@/db/schema/08_agent";
import {AgentOrganizationSchema, AgentOrganizationType} from "@/features/agents/components/agent-organizations.schema";
import {updateAgentOrganizationsAction} from "@/features/agents/components/agent-organizations.action";
import {AgentOrganizationSchema, AgentOrganizationType} from "@/features/agents/agent-organizations.schema";
import {updateAgentOrganizationsAction} from "@/features/agents/agent-organizations.action";
type AgentOrganisationFormProps = {
@@ -1,7 +1,7 @@
"use client";
import {useQuery} from "@tanstack/react-query";
import {getNewAgentRelease} from "@/features/updates/services/github";
import {getNewAgentRelease} from "@/lib/github-releases";
export const useAgentUpdateCheck = (currentVersion?: string | null) => {
const {data: newRelease, isLoading} = useQuery({
@@ -7,7 +7,7 @@ import { FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm }
import { Input } from "@/components/ui/input";
import { Form } from "@/components/ui/form";
import { requestPasswordReset } from "@/lib/auth/auth-client";
import { ButtonWithLoading } from "@/components/wrappers/common/button/button-with-loading";
import { ButtonWithLoading } from "@/components/common/button-with-loading";
import Link from "next/link";
import { ForgotPasswordSchema, ForgotPasswordType } from "./forgot-password-form.schema";
import { ArrowLeft } from "lucide-react";
@@ -2,7 +2,7 @@
import { toast } from "sonner";
import { useRouter, useSearchParams } from "next/navigation";
import TwoFactorForm from "@/components/wrappers/dashboard/profile/form/2fa-form";
import TwoFactorForm from "@/features/profile/2fa-form";
export const GuardForm = () => {
const router = useRouter();
@@ -19,8 +19,8 @@ import { PasswordInput } from "@/components/ui/password-input";
import {
LoginSchema,
type LoginType,
} from "@/components/wrappers/auth/login/login-form/login-form.schema";
import { ButtonWithLoading } from "@/components/wrappers/common/button/button-with-loading";
} from "@/features/auth/login-form.schema";
import { ButtonWithLoading } from "@/components/common/button-with-loading";
import { signIn } from "@/lib/auth/auth-client";
export type loginFormProps = {
@@ -14,7 +14,7 @@ import {
import {
RegisterSchema,
RegisterType,
} from "@/components/wrappers/auth/register/register-form/register-form.schema";
} from "@/features/auth/register-form.schema";
import { signUp } from "@/lib/auth/auth-client";
import { useZodForm } from "@/components/ui/form";
import { CardAuth } from "@/features/layout/card-auth";
@@ -5,7 +5,7 @@ import {toast} from "sonner";
import {FormControl, FormField, FormItem, FormLabel, useZodForm} from "@/components/ui/form";
import {Form} from "@/components/ui/form";
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading";
import {ButtonWithLoading} from "@/components/common/button-with-loading";
import Link from "next/link";
import {ResetPasswordSchema, ResetPasswordType} from "./reset-password-form.schema";
import {PasswordStrengthInput} from "@/components/ui/password-input-indicator";
@@ -1,8 +1,5 @@
import {z} from "zod";
const passwordRegex = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-])/;
const zPassword = () => z.string().min(8, {message: "New Password too short"}).regex(passwordRegex, {message: "New Password too weak"});
import {zPassword} from "@/lib/zod";
export const ResetPasswordSchema = z
.object({
@@ -12,11 +12,11 @@ import {useIsMobile} from "@/hooks/use-mobile";
import {useEffect, useState} from "react";
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs";
import {StorageChannelWith} from "@/db/schema/12_storage-channel";
import {ChannelForm} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/channel-form";
import {ChannelKind, getChannelTextBasedOnKind} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
import {ChannelForm} from "@/features/channel/channel-form";
import {ChannelKind, getChannelTextBasedOnKind} from "@/features/channel/channels-helpers";
import {
ChannelOrganisationForm
} from "@/components/wrappers/dashboard/admin/channels/organization/channels-organization-form";
} from "@/features/organizations/channels-organization-form";
type ChannelAddModalProps = {
@@ -9,11 +9,11 @@ import {useIsMobile} from "@/hooks/use-mobile";
import {StorageChannelWith} from "@/db/schema/12_storage-channel";
import {
EditChannelButton
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-card/button-edit-channel";
import {ChannelKind, getChannelIcon} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
} from "@/features/channel/channel-edit-button";
import {ChannelKind, getChannelIcon} from "@/features/channel/channels-helpers";
import {
DeleteChannelButton
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-card/button-delete-channel";
} from "@/features/channel/channel-delete-button";
export type ChannelCardProps = {
data: NotificationChannelWith | StorageChannelWith;
@@ -1,16 +1,16 @@
"use client";
import {ButtonWithConfirm} from "@/components/wrappers/common/button/button-with-confirm";
import {ButtonWithConfirm} from "@/components/common/button-with-confirm";
import {useMutation} from "@tanstack/react-query";
import {useRouter} from "next/navigation";
import {Trash2} from "lucide-react";
import {
removeNotificationChannelAction,
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/action";
} from "@/features/channel/notifications/channel.action";
import {toast} from "sonner";
import {ChannelKind, getChannelTextBasedOnKind} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
import {ChannelKind, getChannelTextBasedOnKind} from "@/features/channel/channels-helpers";
import {
removeStorageChannelAction
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/storages/action";
} from "@/features/channel/storages/channel.action";
export type DeleteChannelButtonProps = {
channelId: string;
@@ -7,14 +7,14 @@ import {toast} from "sonner";
import {useState} from "react";
import {OrganizationWithMembers} from "@/db/schema/03_organization";
import {StorageChannelWith} from "@/db/schema/12_storage-channel";
import {ChannelKind} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
import {ChannelKind} from "@/features/channel/channels-helpers";
import {
updateNotificationChannelAction
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/action";
import {ChannelAddEditModal} from "@/components/wrappers/dashboard/admin/channels/channel/channel-add-edit-modal";
} from "@/features/channel/notifications/channel.action";
import {ChannelAddEditModal} from "@/features/channel/channel-add-edit-modal";
import {
updateStorageChannelAction
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/storages/action";
} from "@/features/channel/storages/channel.action";
export type EditChannelButtonProps = {
channel: NotificationChannelWith | StorageChannelWith;
@@ -52,10 +52,10 @@ export const EditChannelButton = ({
let result: any;
if (kind == "notification") {
// @ts-ignore
// @ts-expect-error — payload type varies between notification and storage
result = await updateNotificationChannelAction(payload)
} else if (kind == "storage") {
// @ts-ignore
// @ts-expect-error — payload type varies between notification and storage
result = await updateStorageChannelAction(payload)
} else {
toast.error("An error occurred while updating storage channel")
@@ -1,15 +1,15 @@
import {z} from "zod";
import {SlackChannelConfigSchema} from "./providers/notifications/forms/slack.schema";
import {SmtpChannelConfigSchema} from "./providers/notifications/forms/smtp.schema";
import {DiscordChannelConfigSchema} from "./providers/notifications/forms/discord.schema";
import {TelegramChannelConfigSchema} from "./providers/notifications/forms/telegram.schema";
import {GotifyChannelConfigSchema} from "./providers/notifications/forms/gotify.schema";
import {NtfyChannelConfigSchema} from "./providers/notifications/forms/ntfy.schema";
import {WebhookChannelConfigSchema} from "./providers/notifications/forms/webhook.schema";
import {NextcloudChannelConfigSchema} from "./providers/notifications/forms/nextcloud.schema";
import {S3ChannelConfigSchema} from "./providers/storages/forms/s3.schema";
import {GoogleDriveChannelConfigSchema} from "./providers/storages/forms/google-drive.schema";
import {LocalChannelConfigSchema} from "./providers/storages/forms/local.schema";
import {SlackChannelConfigSchema} from "@/features/channel/notifications/slack.schema";
import {SmtpChannelConfigSchema} from "@/features/channel/notifications/smtp.schema";
import {DiscordChannelConfigSchema} from "@/features/channel/notifications/discord.schema";
import {TelegramChannelConfigSchema} from "@/features/channel/notifications/telegram.schema";
import {GotifyChannelConfigSchema} from "@/features/channel/notifications/gotify.schema";
import {NtfyChannelConfigSchema} from "@/features/channel/notifications/ntfy.schema";
import {WebhookChannelConfigSchema} from "@/features/channel/notifications/webhook.schema";
import {NextcloudChannelConfigSchema} from "@/features/channel/notifications/nextcloud.schema";
import {S3ChannelConfigSchema} from "@/features/channel/storages/s3.schema";
import {GoogleDriveChannelConfigSchema} from "@/features/channel/storages/google-drive/google-drive.schema";
import {LocalChannelConfigSchema} from "@/features/channel/storages/local.schema";
const BaseChannelFormSchema = z.object({
@@ -3,12 +3,12 @@
import {useRouter} from "next/navigation";
import {useMutation} from "@tanstack/react-query";
import {Form, FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm} from "@/components/ui/form";
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading";
import {ButtonWithLoading} from "@/components/common/button-with-loading";
import {Input} from "@/components/ui/input";
import {
addStorageChannelAction, updateStorageChannelAction
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/storages/action";
} from "@/features/channel/storages/channel.action";
import {toast} from "sonner";
import {OrganizationWithMembers} from "@/db/schema/03_organization";
import {Button} from "@/components/ui/button";
@@ -20,19 +20,19 @@ import {ArrowLeft} from "lucide-react";
import {StorageChannelWith} from "@/db/schema/12_storage-channel";
import {
NotificationChannelFormSchema, NotificationChannelFormType, StorageChannelFormSchema, StorageChannelFormType
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/channel-form.schema";
} from "@/features/channel/channel-form.schema";
import {
ChannelKind,
renderChannelForm
} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
import {storageProviders} from "@/components/wrappers/dashboard/admin/channels/helpers/storage";
import {notificationProviders} from "@/components/wrappers/dashboard/admin/channels/helpers/notification";
} from "@/features/channel/channels-helpers";
import {storageProviders} from "@/features/channel/channels-storage-helper";
import {notificationProviders} from "@/features/channel/channels-notification-helper";
import {
ChannelTestButton
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/channel-test-button";
} from "@/features/channel/channel-test-button";
import {
addNotificationChannelAction, updateNotificationChannelAction
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/action";
} from "@/features/channel/notifications/channel.action";
type NotifierFormProps = {
@@ -50,7 +50,7 @@ export const ChannelForm = ({onSuccessAction, organization, defaultValues, kind}
const form = useZodForm({
schema: kind == "notification" ? NotificationChannelFormSchema : StorageChannelFormSchema,
// @ts-ignore
// @ts-expect-error — defaultValues type is a union of notification/storage channel types
defaultValues: {...defaultValues},
});
@@ -71,10 +71,10 @@ export const ChannelForm = ({onSuccessAction, organization, defaultValues, kind}
let result: any;
if (kind === "notification") {
// @ts-ignore
// @ts-expect-error — payload type varies between notification and storage
result = isCreate ? await addNotificationChannelAction(payload) : await updateNotificationChannelAction(payload);
} else if (kind === "storage") {
// @ts-ignore
// @ts-expect-error — payload type varies between notification and storage
result = isCreate ? await addStorageChannelAction(payload) : await updateStorageChannelAction(payload);
} else {
toast.error("An error occurred");
@@ -6,14 +6,14 @@ import {toast} from "sonner";
import {Button} from "@/components/ui/button";
import {NotificationChannel} from "@/db/schema/09_notification-channel";
import {dispatchNotification} from "@/features/notifications/dispatch";
import {EventPayload} from "@/features/notifications/types";
import {dispatchNotification} from "@/features/notifications/notifications.dispatch";
import {EventPayload} from "@/features/notifications/notifications.types";
import {useIsMobile} from "@/hooks/use-mobile";
import {cn} from "@/lib/utils";
import {StorageChannel} from "@/db/schema/12_storage-channel";
import {ChannelKind} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
import type {StorageInput} from "@/features/storages/types";
import {dispatchStorage} from "@/features/storages/dispatch";
import {ChannelKind} from "@/features/channel/channels-helpers";
import type {StorageInput} from "@/features/storages/storages.types";
import {dispatchStorage} from "@/features/storages/storages.dispatch";
type NotifierTestChannelButtonProps = {
@@ -50,7 +50,6 @@ export const ChannelTestButton = ({channel, organizationId, kind}: NotifierTestC
if (result.success) {
toast.success("Successfully connected to storage channel");
} else {
console.error(result);
const responseText = result.response ? `(${result.response})` : "";
toast.error(`An error occurred while testing the storage channel, check your configuration ${responseText}`);
}
@@ -1,40 +1,40 @@
import {UseFormReturn} from "react-hook-form";
import {
NotifierSmtpForm
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/forms/smtp.form";
} from "@/features/channel/notifications/smtp.form";
import {
NotifierSlackForm
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/forms/slack.form";
} from "@/features/channel/notifications/slack.form";
import {
NotifierDiscordForm
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/forms/discord.form";
} from "@/features/channel/notifications/discord.form";
import {
NotifierTelegramForm
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/forms/telegram.form";
} from "@/features/channel/notifications/telegram.form";
import {
NotifierGotifyForm
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/forms/gotify.form";
} from "@/features/channel/notifications/gotify.form";
import {
NotifierNtfyForm
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/forms/ntfy.form";
} from "@/features/channel/notifications/ntfy.form";
import {
NotifierWebhookForm
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/forms/webhook.form";
} from "@/features/channel/notifications/webhook.form";
import {
NotifierNextcloudForm
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/notifications/forms/nextcloud.form";
} from "@/features/channel/notifications/nextcloud.form";
import {
notificationProviders,
} from "@/components/wrappers/dashboard/admin/channels/helpers/notification";
import {storageProviders} from "@/components/wrappers/dashboard/admin/channels/helpers/storage";
} from "@/features/channel/channels-notification-helper";
import {storageProviders} from "@/features/channel/channels-storage-helper";
import {ForwardRefExoticComponent, JSX, RefAttributes, SVGProps} from "react";
import {LucideProps} from "lucide-react";
import {
StorageS3Form
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/storages/forms/s3.form";
} from "@/features/channel/storages/s3.form";
import {
StorageGoogleDriveForm
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/providers/storages/forms/google-drive.form";
} from "@/features/channel/storages/google-drive/google-drive.form";
export type ChannelKind = "notification" | "storage";
@@ -1,6 +1,6 @@
import {Mail} from "lucide-react";
import type { SVGProps } from 'react';
import {ProviderIconTypes} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
import {ProviderIconTypes} from "@/features/channel/channels-helpers";
@@ -1,16 +1,16 @@
"use client";
import { NotificationChannelWith } from "@/db/schema/09_notification-channel";
import { CardsWithPagination } from "@/components/wrappers/common/cards-with-pagination";
import { CardsWithPagination } from "@/components/common/cards-with-pagination";
import { useState } from "react";
import { EmptyStatePlaceholder } from "@/components/wrappers/common/empty-state-placeholder";
import { EmptyStatePlaceholder } from "@/components/common/empty-state-placeholder";
import { OrganizationWithMembers } from "@/db/schema/03_organization";
import { StorageChannelWith } from "@/db/schema/12_storage-channel";
import { ChannelCard } from "@/components/wrappers/dashboard/admin/channels/channel/channel-card/channel-card";
import { ChannelAddEditModal } from "@/components/wrappers/dashboard/admin/channels/channel/channel-add-edit-modal";
import { ChannelCard } from "@/features/channel/channel-card";
import { ChannelAddEditModal } from "@/features/channel/channel-add-edit-modal";
import {
ChannelKind,
getChannelTextBasedOnKind,
} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
} from "@/features/channel/channels-helpers";
type ChannelsSectionProps = {
channels: NotificationChannelWith[] | StorageChannelWith[];
@@ -1,6 +1,6 @@
import {Server} from "lucide-react";
import type {SVGProps} from "react";
import {ProviderIconTypes} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
import {ProviderIconTypes} from "@/features/channel/channels-helpers";
export const storageProviders: ProviderIconTypes[] = [
{value: "local", label: "Local", icon: Server},
@@ -10,7 +10,7 @@ import {and, eq} from "drizzle-orm";
import {withUpdatedAt} from "@/db/utils";
import {
NotificationChannelFormSchema
} from "@/components/wrappers/dashboard/admin/channels/channel/channel-form/channel-form.schema";
} from "@/features/channel/channel-form.schema";
export const addNotificationChannelAction = userAction.schema(
@@ -50,8 +50,8 @@ export const addNotificationChannelAction = userAction.schema(
messageParams: {notificationChannelId: channel.id},
},
};
} catch (error) {
console.error("Error:", error);
} catch (_error) {
const error = _error;
return {
success: false,
actionError: {
@@ -111,8 +111,8 @@ export const removeNotificationChannelAction = userAction.schema(
messageParams: {notificationChannelId: notificationChannelId},
},
};
} catch (error) {
console.error("Error:", error);
} catch (_error) {
const error = _error;
return {
success: false,
actionError: {
@@ -157,8 +157,8 @@ export const updateNotificationChannelAction = userAction.schema(
messageParams: {id: channel.id},
},
};
} catch (error) {
console.error("Error:", error);
} catch (_error) {
const error = _error;
return {
success: false,
actionError: {
@@ -1,4 +1,4 @@
import type {EventPayload, DispatchResult} from '../types';
import type {EventPayload, DispatchResult} from '@/features/notifications/notifications.types';
export async function sendDiscord(
config: { discordWebhook: string },
@@ -1,4 +1,4 @@
import type {EventPayload, DispatchResult} from '../types';
import type {EventPayload, DispatchResult} from '@/features/notifications/notifications.types';
export async function sendGotify(
config: { gotifyServerUrl: string; gotifyAppToken: string },
@@ -1,13 +1,13 @@
"use server"
import type {ProviderKind, EventPayload, DispatchResult} from '../types';
import type {ProviderKind, EventPayload, DispatchResult} from '@/features/notifications/notifications.types';
import {sendSlack} from './slack';
import {sendSmtp} from './smtp';
import {sendDiscord} from "@/features/notifications/providers/discord";
import {sendTelegram} from "@/features/notifications/providers/telegram";
import {sendGotify} from "@/features/notifications/providers/gotify";
import {sendNtfy} from "@/features/notifications/providers/ntfy";
import {sendWebhook} from "@/features/notifications/providers/webhook";
import {sendNextcloud} from "@/features/notifications/providers/nextcloud";
import {sendDiscord} from "@/features/channel/notifications/discord";
import {sendTelegram} from "@/features/channel/notifications/telegram";
import {sendGotify} from "@/features/channel/notifications/gotify";
import {sendNtfy} from "@/features/channel/notifications/ntfy";
import {sendWebhook} from "@/features/channel/notifications/webhook";
import {sendNextcloud} from "@/features/channel/notifications/nextcloud";
const handlers: Record<
ProviderKind,
@@ -1,5 +1,5 @@
import { createHmac, randomBytes } from "crypto";
import type { EventPayload, DispatchResult } from "../types";
import type { EventPayload, DispatchResult } from '@/features/notifications/notifications.types';
type NextcloudConfig = {
nextcloudUrl: string;
@@ -1,4 +1,4 @@
import type {EventPayload, DispatchResult} from '../types';
import type {EventPayload, DispatchResult} from '@/features/notifications/notifications.types';
const getPriority = (level?: string): number => {
switch (level) {
@@ -1,4 +1,4 @@
import type {EventPayload, DispatchResult} from '../types';
import type {EventPayload, DispatchResult} from '@/features/notifications/notifications.types';
export async function sendSlack(
config: { slackWebhook: string },

Some files were not shown because too many files have changed in this diff Show More