mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Working on implementation of alert policies. Some refactoring in user profile and patching a bug in status endpoint api.
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const LoginSchema = z.object({
|
||||
email: z.string(),
|
||||
password: z.string(),
|
||||
});
|
||||
email: z.string().email({message: "Email is invalid"}),
|
||||
password: z.string().nonempty({message: "Password could not be empty"}),
|
||||
|
||||
})
|
||||
|
||||
export type LoginType = z.infer<typeof LoginSchema>;
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
import {cn} from "@/lib/utils";
|
||||
|
||||
export type ConnectionCircleProps = {
|
||||
date?: Date | null;
|
||||
};
|
||||
|
||||
export const ConnectionCircle = ({ date }: ConnectionCircleProps) => {
|
||||
export const ConnectionCircle = ({date}: ConnectionCircleProps) => {
|
||||
let style = "bg-gray-300 border-gray-400";
|
||||
|
||||
if (date instanceof Date && !isNaN(date.getTime())) {
|
||||
const now = Date.now();
|
||||
const timestamp = date.getTime();
|
||||
const interval = now - timestamp;
|
||||
const interval_seconds = (now - timestamp) / 1000;
|
||||
|
||||
console.log({ now, timestamp, interval });
|
||||
console.log({now, timestamp, interval_seconds});
|
||||
|
||||
if (interval < 10000) {
|
||||
if (interval_seconds < 55) {
|
||||
style = "bg-green-400 border-green-600";
|
||||
} else if (interval <= 20000) {
|
||||
} else if (interval_seconds <= 60) {
|
||||
style = "bg-orange-400 border-orange-600";
|
||||
} else {
|
||||
style = "bg-red-400 border-red-600";
|
||||
@@ -27,5 +27,5 @@ export const ConnectionCircle = ({ date }: ConnectionCircleProps) => {
|
||||
|
||||
console.log(style);
|
||||
|
||||
return <div className={cn("w-5 h-5 rounded-full border-4", style)} />;
|
||||
return <div className={cn("w-5 h-5 rounded-full border-4", style)}/>;
|
||||
};
|
||||
|
||||
@@ -1,28 +1,36 @@
|
||||
import {Icon} from "@iconify/react";
|
||||
import {CircleHelp, KeyRound} from "lucide-react";
|
||||
import {cn} from "@/lib/utils";
|
||||
|
||||
|
||||
export const providerSwitch = (provider: string) => {
|
||||
export const providerSwitch = (provider: string, small?: boolean) => {
|
||||
switch (provider) {
|
||||
case "google":
|
||||
return (
|
||||
<div className="p-4">
|
||||
<Icon icon={"logos:google"} height="24" />
|
||||
<div className={cn(small ? "p-0" : "p-4")}>
|
||||
{small ?
|
||||
<Icon icon={"flat-color-icons:google"} height="24"/>
|
||||
:
|
||||
<Icon icon={"logos:google"} height="24"/>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
case "credential":
|
||||
return (
|
||||
<div className="flex flex-row gap-x-2 items-center p-4">
|
||||
<KeyRound height="24" />
|
||||
<span>Email and Password</span>
|
||||
<div className={cn("flex flex-row gap-x-2 items-center", small ? "p-0" : "p-4")}>
|
||||
<KeyRound height="24"/>
|
||||
{!small && (<span>Email and Password</span>)}
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<div className="flex flex-row gap-x-2 items-center p-4">
|
||||
<CircleHelp height="24" />
|
||||
<span>No credentials</span>
|
||||
<div className={cn("flex flex-row gap-x-2 items-center", small ? "p-0" : "p-4")}>
|
||||
<CircleHelp height="24"/>
|
||||
{!small && (<span>No credentials</span>)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
+7
-12
@@ -6,22 +6,17 @@ import {toast} from "sonner";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {unlinkUserProviderAction} from "@/components/wrappers/dashboard/profile/user-form/user-form.action";
|
||||
import {providerSwitch} from "@/components/wrappers/common/provider-switch";
|
||||
import {Account} from "better-auth";
|
||||
|
||||
export const accountsColumns: ColumnDef<{
|
||||
id: string;
|
||||
provider: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
accountId: string;
|
||||
scopes: string[];
|
||||
}>[] = [
|
||||
|
||||
export const accountsColumns: ColumnDef<Account>[] = [
|
||||
{
|
||||
id: "provider",
|
||||
header: "Provider",
|
||||
cell: ({row}) => {
|
||||
return (
|
||||
<div>
|
||||
{providerSwitch(row.original.provider)}
|
||||
{providerSwitch(row.original.providerId)}
|
||||
</div>
|
||||
|
||||
)
|
||||
@@ -36,7 +31,7 @@ export const accountsColumns: ColumnDef<{
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
if (row.original.provider === "credential") {
|
||||
if (row.original.providerId === "credential") {
|
||||
toast.error(`This provider cannot be unlinked.`);
|
||||
router.refresh();
|
||||
return;
|
||||
@@ -49,7 +44,7 @@ export const accountsColumns: ColumnDef<{
|
||||
}
|
||||
|
||||
const status = await unlinkUserProviderAction({
|
||||
provider: row.original.provider,
|
||||
provider: row.original.providerId,
|
||||
account: row.original.accountId,
|
||||
});
|
||||
|
||||
@@ -66,7 +61,7 @@ export const accountsColumns: ColumnDef<{
|
||||
<div className="flex items-center gap-2">
|
||||
<ButtonWithLoading
|
||||
variant="outline"
|
||||
disabled={row.original.provider === "credential" || table.getRowModel().rows.length <= 1}
|
||||
disabled={row.original.providerId === "credential" || table.getRowModel().rows.length <= 1}
|
||||
icon={<Unlink color="red" size={15}/>}
|
||||
onClick={async () => {
|
||||
await mutation.mutateAsync();
|
||||
|
||||
@@ -68,13 +68,13 @@ export const usersColumnsAdmin: ColumnDef<UserWithAccounts>[] = [
|
||||
},
|
||||
{
|
||||
accessorKey: "accounts",
|
||||
header: "Provider ID",
|
||||
header: "Provider(s)",
|
||||
cell: ({row}) => {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-row items-center gap-x-2">
|
||||
{row.original.accounts.map((item) => (
|
||||
<div key={item.id}>
|
||||
{providerSwitch(item.providerId)}
|
||||
{providerSwitch(item.providerId, true)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -93,9 +93,11 @@ export const usersColumnsAdmin: ColumnDef<UserWithAccounts>[] = [
|
||||
id: "actions",
|
||||
cell: ({row}) => {
|
||||
const router = useRouter();
|
||||
const {data: session, isPending} = useSession();
|
||||
const {data: session, isPending, error} = useSession();
|
||||
const isSuperAdmin = session?.user.role == "superadmin";
|
||||
|
||||
if (isPending || error) return null;
|
||||
|
||||
return (
|
||||
<ButtonDeleteUser
|
||||
disabled={!isSuperAdmin || !session || session?.user.email === row.original.email}
|
||||
|
||||
+23
-16
@@ -1,33 +1,34 @@
|
||||
import { ButtonWithLoading } from "@/components/wrappers/common/button/button-with-loading";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { Session } from "better-auth";
|
||||
import { Unlink } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {ColumnDef} from "@tanstack/react-table";
|
||||
import {Session} from "better-auth";
|
||||
import {Unlink} from "lucide-react";
|
||||
import {toast} from "sonner";
|
||||
import {useRouter} from "next/navigation";
|
||||
import detectOSWithUA from "@/utils/os-parser";
|
||||
import { Icon } from "@iconify/react";
|
||||
import { authClient } from "@/lib/auth/auth-client";
|
||||
import { timeAgo } from "@/utils/date-formatting";
|
||||
import {Icon} from "@iconify/react";
|
||||
import {authClient} from "@/lib/auth/auth-client";
|
||||
import {timeAgo} from "@/utils/date-formatting";
|
||||
import {deleteUserSessionAction} from "@/components/wrappers/dashboard/profile/user-form/user-form.action";
|
||||
|
||||
export const sessionsColumns: ColumnDef<Session>[] = [
|
||||
{
|
||||
accessorKey: "expiresAt",
|
||||
header: "Expires At",
|
||||
cell: ({ row }) => {
|
||||
cell: ({row}) => {
|
||||
return timeAgo(row.original.expiresAt);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "device",
|
||||
header: "Device",
|
||||
cell: ({ row }) => {
|
||||
cell: ({row}) => {
|
||||
const os = detectOSWithUA(row.original.userAgent!);
|
||||
|
||||
return (
|
||||
<div className="flex flex-row gap-x-2 items-center pt-4 pb-4">
|
||||
{os.icon && <Icon icon={`logos:${os.icon.name}`} height={os.icon.size.height} width={os.icon.size.width} />}
|
||||
{os.icon &&
|
||||
<Icon icon={`logos:${os.icon.name}`} height={os.icon.size.height} width={os.icon.size.width}/>}
|
||||
{os.showText && <span>{os.name}</span>}
|
||||
</div>
|
||||
);
|
||||
@@ -40,10 +41,11 @@ export const sessionsColumns: ColumnDef<Session>[] = [
|
||||
{
|
||||
header: "Action",
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
cell: ({row}) => {
|
||||
const router = useRouter();
|
||||
|
||||
const { data: session } = authClient.useSession();
|
||||
const {data: session, isPending, error} = authClient.useSession();
|
||||
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
@@ -64,12 +66,17 @@ export const sessionsColumns: ColumnDef<Session>[] = [
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
if (isPending || error) return null;
|
||||
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<ButtonWithLoading
|
||||
isPending={mutation.isPending}
|
||||
variant="outline"
|
||||
disabled={session?.session.id === row.original.id}
|
||||
icon={<Unlink color="red" size={15} />}
|
||||
icon={<Unlink color="red" size={15}/>}
|
||||
onClick={async () => {
|
||||
await mutation.mutateAsync();
|
||||
}}
|
||||
|
||||
@@ -43,7 +43,7 @@ export const NotifierAddEditModal = ({
|
||||
</Button>
|
||||
}
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogContent onOpenAutoFocus={(e) => e.preventDefault()}>
|
||||
<DialogHeader>
|
||||
<DialogTitle> {isCreate ? "Add" : "Edit"} Notification Channel</DialogTitle>
|
||||
<DialogDescription>
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ export const DeleteNotifierButton = ({notificationChannelId, organizationId}: De
|
||||
return (
|
||||
<ButtonWithConfirm
|
||||
title="Delete Notifier"
|
||||
description="Are you sure you want to remove this notifier? This action cannot be undone."
|
||||
description="Are you sure you want to remove this notifier ? This action cannot be undone and will delete all alert policies related to this channel !"
|
||||
button={{
|
||||
main: {
|
||||
size: "icon",
|
||||
|
||||
+1
-1
@@ -153,7 +153,7 @@ export const updateNotificationChannelAction = userAction.schema(
|
||||
config: channel.config as JSON
|
||||
},
|
||||
actionSuccess: {
|
||||
message: "Notification channel has been successfully updated.",
|
||||
message: `Notification channel "${channel.name}" has been successfully updated.`,
|
||||
messageParams: {notificationChannelId: channel.id},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@ import {NotificationChannel} from "@/db/schema/09_notification-channel";
|
||||
import {
|
||||
NotifierTestChannelButton
|
||||
} from "@/components/wrappers/dashboard/common/notifier/notifier-form/notifier-test-channel-button";
|
||||
import {useEffect} from "react";
|
||||
|
||||
type NotifierFormProps = {
|
||||
onSuccessAction?: () => void;
|
||||
@@ -35,16 +36,17 @@ type NotifierFormProps = {
|
||||
export const NotifierForm = ({onSuccessAction, organization, defaultValues}: NotifierFormProps) => {
|
||||
|
||||
const isCreate = !Boolean(defaultValues);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const form = useZodForm({
|
||||
schema: NotificationChannelFormSchema,
|
||||
// @ts-ignore
|
||||
defaultValues: {...defaultValues},
|
||||
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
form.reset(defaultValues ? {...defaultValues} : {});
|
||||
}, [defaultValues]);
|
||||
|
||||
const mutationCreateOrganisation = useMutation({
|
||||
mutationFn: async (values: NotificationChannelFormType) => {
|
||||
|
||||
@@ -60,11 +62,11 @@ export const NotifierForm = ({onSuccessAction, organization, defaultValues}: Not
|
||||
|
||||
if (inner?.success) {
|
||||
toast.success(inner.actionSuccess?.message);
|
||||
onSuccessAction?.();
|
||||
isCreate && onSuccessAction?.();
|
||||
router.refresh();
|
||||
} else {
|
||||
toast.error(inner?.actionError?.message);
|
||||
onSuccessAction?.();
|
||||
isCreate && onSuccessAction?.();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -112,8 +114,8 @@ export const NotifierForm = ({onSuccessAction, organization, defaultValues}: Not
|
||||
<SelectContent>
|
||||
<SelectItem value="smtp">SMTP (Email)</SelectItem>
|
||||
<SelectItem value="slack">Slack</SelectItem>
|
||||
<SelectItem value="curl">Curl</SelectItem>
|
||||
<SelectItem value="webhook">Webhook</SelectItem>
|
||||
{/*<SelectItem value="curl">Curl</SelectItem>*/}
|
||||
{/*<SelectItem value="webhook">Webhook</SelectItem>*/}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
@@ -137,7 +139,7 @@ export const NotifierForm = ({onSuccessAction, organization, defaultValues}: Not
|
||||
<NotifierTestChannelButton notificationChannel={defaultValues}/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2 ">
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
@@ -149,7 +151,7 @@ export const NotifierForm = ({onSuccessAction, organization, defaultValues}: Not
|
||||
Cancel
|
||||
</Button>
|
||||
<ButtonWithLoading isPending={mutationCreateOrganisation.isPending}>
|
||||
Add Channel
|
||||
{isCreate ? "Add" : "Save"} Channel
|
||||
</ButtonWithLoading>
|
||||
</div>
|
||||
|
||||
|
||||
+2
@@ -2,6 +2,7 @@
|
||||
import {UseFormReturn} from "react-hook-form";
|
||||
import {FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form";
|
||||
import {Input} from "@/components/ui/input";
|
||||
import {Separator} from "@/components/ui/separator";
|
||||
|
||||
|
||||
type NotifierSmtpFormProps = {
|
||||
@@ -11,6 +12,7 @@ type NotifierSmtpFormProps = {
|
||||
export const NotifierSlackForm = ({form}: NotifierSmtpFormProps) => {
|
||||
return(
|
||||
<>
|
||||
<Separator className="my-1"/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="config.slackWebhook"
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ type NotifierSmtpFormProps = {
|
||||
export const NotifierSmtpForm = ({form}: NotifierSmtpFormProps) => {
|
||||
return (
|
||||
<>
|
||||
<Separator className="my-4"/>
|
||||
<Separator className="my-1"/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="config.host"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"use client"
|
||||
import {Megaphone} from "lucide-react";
|
||||
import {Filter, Megaphone} from "lucide-react";
|
||||
|
||||
import {useState} from "react";
|
||||
import {
|
||||
@@ -12,12 +12,13 @@ import {
|
||||
} from "@/components/ui/dialog";
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {AlertPolicyForm} from "@/components/wrappers/dashboard/database/alert-policy/alert-policy-form";
|
||||
import {Database} from "@/db/schema/07_database";
|
||||
import {DatabaseWith} from "@/db/schema/07_database";
|
||||
import {NotificationChannel} from "@/db/schema/09_notification-channel";
|
||||
import {Separator} from "@/components/ui/separator";
|
||||
import {Badge} from "@/components/ui/badge";
|
||||
|
||||
type AlertPolicyModalProps = {
|
||||
database: Database;
|
||||
database: DatabaseWith;
|
||||
notificationChannels: NotificationChannel[];
|
||||
organizationId: string;
|
||||
}
|
||||
@@ -28,8 +29,15 @@ export const AlertPolicyModal = ({database, notificationChannels, organizationId
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline">
|
||||
<Button variant="outline" onClick={() => setOpen(true)} className="relative">
|
||||
<Megaphone/>
|
||||
{database.alertPolicies && database.alertPolicies.length > 0 && (
|
||||
<Badge
|
||||
className="absolute -top-1.5 -right-1.5 h-4 w-4 rounded-full p-0 text-[10px] flex items-center justify-center"
|
||||
>
|
||||
{database.alertPolicies.length}
|
||||
</Badge>
|
||||
)}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
|
||||
+1
-1
@@ -61,7 +61,6 @@ export const OrganizationNotifiersTab = ({
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-6 h-full py-4">
|
||||
|
||||
<div className="h-full flex flex-col gap-y-6">
|
||||
<div className={cn("hidden flex-row justify-between items-start", hasNotifiers && "flex")}>
|
||||
<div className="max-w-2xl">
|
||||
@@ -74,6 +73,7 @@ export const OrganizationNotifiersTab = ({
|
||||
</p>
|
||||
</div>
|
||||
<NotifierAddEditModal
|
||||
|
||||
organization={organization}
|
||||
open={isAddModalOpen}
|
||||
onOpenChangeAction={setIsAddModalOpen}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { db } from "@/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import * as drizzleDb from "@/db";
|
||||
import {revokeSession, unlinkAccount} from "@/lib/auth/auth";
|
||||
import {withUpdatedAt} from "@/db/utils";
|
||||
|
||||
export const updateUserAction = userAction
|
||||
.schema(
|
||||
@@ -15,7 +16,7 @@ export const updateUserAction = userAction
|
||||
})
|
||||
)
|
||||
.action(async ({ parsedInput }) => {
|
||||
const [updatedUser] = await db.update(drizzleDb.schemas.user).set(parsedInput.data).where(eq(drizzleDb.schemas.user.id, parsedInput.id)).returning();
|
||||
const [updatedUser] = await db.update(drizzleDb.schemas.user).set(withUpdatedAt(parsedInput.data)).where(eq(drizzleDb.schemas.user.id, parsedInput.id)).returning();
|
||||
return {
|
||||
data: updatedUser,
|
||||
};
|
||||
|
||||
@@ -14,20 +14,13 @@ import { updateUserAction } from "@/components/wrappers/dashboard/profile/user-f
|
||||
import {DataTable} from "@/components/wrappers/common/table/data-table";
|
||||
import {sessionsColumns} from "@/components/wrappers/dashboard/admin/tabs/admin-user-tab/sessions/table-columns";
|
||||
import {accountsColumns} from "@/components/wrappers/dashboard/admin/tabs/admin-user-tab/accounts/table-columns";
|
||||
import {Session} from "better-auth";
|
||||
import {Account, Session} from "better-auth";
|
||||
|
||||
export type UserFormProps = {
|
||||
defaultValues?: UserType;
|
||||
userId?: string;
|
||||
sessions: Session[];
|
||||
accounts: {
|
||||
id: string;
|
||||
provider: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
accountId: string;
|
||||
scopes: string[];
|
||||
}[];
|
||||
accounts?: Account[];
|
||||
};
|
||||
|
||||
export const UserForm = (props: UserFormProps) => {
|
||||
@@ -120,7 +113,7 @@ export const UserForm = (props: UserFormProps) => {
|
||||
<CardDescription>Manage your active auth providers</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<DataTable columns={accountsColumns} data={props.accounts} enableSelect={false}/>
|
||||
<DataTable columns={accountsColumns} data={props.accounts ?? []} enableSelect={false}/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -110,15 +110,14 @@ export const DatabaseBackupList = (props: DatabaseBackupListProps) => {
|
||||
selectedActions={(rows) => (
|
||||
<>
|
||||
|
||||
<div className="flex justify-start md:justify-between gap-3 md:gap-0 items-center w-full ml-0">
|
||||
<div className="flex gap-2">
|
||||
{!isMember && (
|
||||
<div className="flex justify-start md:justify-between gap-3 md:gap-0 items-center w-full ml-0">
|
||||
<div className="flex gap-2">
|
||||
{!isMember && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<ButtonWithLoading
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
|
||||
}}
|
||||
disabled={rows.length === 0 || mutationDeleteBackups.isPending}
|
||||
icon={<MoreHorizontal/>}
|
||||
@@ -139,15 +138,15 @@ export const DatabaseBackupList = (props: DatabaseBackupListProps) => {
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
<FiltersDropdown
|
||||
items={items}
|
||||
selectedItems={selectedFilters}
|
||||
onSelect={handleSelectFilter}
|
||||
clearFilters={clearFilters}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<FiltersDropdown
|
||||
items={items}
|
||||
selectedItems={selectedFilters}
|
||||
onSelect={handleSelectFilter}
|
||||
clearFilters={clearFilters}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
import { ConnectionCircle } from "@/components/wrappers/common/connection-circle";
|
||||
import { formatDateLastContact } from "@/utils/date-formatting";
|
||||
import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
||||
import {ConnectionCircle} from "@/components/wrappers/common/connection-circle";
|
||||
import {formatDateLastContact} from "@/utils/date-formatting";
|
||||
import {Database} from "@/db/schema/07_database";
|
||||
import {Avatar, AvatarFallback, AvatarImage} from "@/components/ui/avatar";
|
||||
|
||||
@@ -15,11 +15,12 @@ export type projectDatabaseCardProps = {
|
||||
};
|
||||
|
||||
export const ProjectDatabaseCard = (props: projectDatabaseCardProps) => {
|
||||
const { organizationSlug, data: database, extendedProps: extendedProps } = props;
|
||||
const {organizationSlug, data: database, extendedProps: extendedProps} = props;
|
||||
|
||||
return (
|
||||
<Link className="block transition-all duration-200 hover:scale-[1.01] hover:shadow-md rounded-xl" href={`/dashboard/projects/${extendedProps.id}/database/${database.id}`}>
|
||||
<DatabaseCard data={database} />
|
||||
<Link className="block transition-all duration-200 hover:scale-[1.01] hover:shadow-md rounded-xl"
|
||||
href={`/dashboard/projects/${extendedProps.id}/database/${database.id}`}>
|
||||
<DatabaseCard data={database}/>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
@@ -29,12 +30,13 @@ export type databaseCardProps = {
|
||||
};
|
||||
|
||||
export const DatabaseCard = (props: databaseCardProps) => {
|
||||
const { data: database } = props;
|
||||
const {data: database} = props;
|
||||
|
||||
return (
|
||||
<Card className="flex flex-row justify-between">
|
||||
<div className="flex items-center space-x-4 px-4">
|
||||
<Image src={`/images/${database.dbms}.png`} alt="Database type Icon" width={60} height={60} className="object-cover ml-4" />
|
||||
<Card className="flex flex-row justify-between space-x-2 ">
|
||||
<div className="flex items-center justify-start w-full space-x-5 ">
|
||||
<Image src={`/images/${database.dbms}.png`} alt="Database type Icon" width={60} height={60}
|
||||
className="object-cover ml-4"/>
|
||||
<div className="justify-between">
|
||||
<div className="font-medium">Name: {database.name}</div>
|
||||
<div className="text-sm text-muted-foreground">Generated Id: {database.agentDatabaseId}</div>
|
||||
@@ -43,9 +45,8 @@ export const DatabaseCard = (props: databaseCardProps) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center px-4">
|
||||
<ConnectionCircle date={database.lastContact} />
|
||||
<div className="flex items-center px-5 justify-center">
|
||||
<ConnectionCircle date={database.lastContact}/>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE "alert_policy" DROP CONSTRAINT "alert_policy_notification_channel_id_notification_channel_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "alert_policy" ADD CONSTRAINT "alert_policy_notification_channel_id_notification_channel_id_fk" FOREIGN KEY ("notification_channel_id") REFERENCES "public"."notification_channel"("id") ON DELETE cascade ON UPDATE no action;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -64,6 +64,13 @@
|
||||
"when": 1763914207236,
|
||||
"tag": "0008_aberrant_scorpion",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 9,
|
||||
"version": "7",
|
||||
"when": 1764364481615,
|
||||
"tag": "0009_lucky_edwin_jarvis",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -12,7 +12,7 @@ export const alertPolicy = pgTable('alert_policy', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
notificationChannelId: uuid('notification_channel_id')
|
||||
.notNull()
|
||||
.references(() => notificationChannel.id, {onDelete: 'restrict'}),
|
||||
.references(() => notificationChannel.id, {onDelete: 'cascade'}),
|
||||
eventKinds: eventKindEnum("event_kind").array().notNull(),
|
||||
enabled: boolean('enabled').default(true).notNull(),
|
||||
databaseId: uuid('database_id')
|
||||
|
||||
Reference in New Issue
Block a user