From 234b1b7893961db91bcd644e6e98b829fec60ee0 Mon Sep 17 00:00:00 2001 From: Nzai Kilonzo Date: Mon, 25 May 2026 18:42:03 +0300 Subject: [PATCH 1/3] feat: add logs to the frontend and small ui fixes --- web/app/routes/logs.tsx | 24 +++- web/components/logs-filter.tsx | 2 +- web/components/logs-list.tsx | 155 +++++++++++++++++--------- web/components/pages/edit-webhook.tsx | 13 ++- web/components/pages/logs.tsx | 4 +- web/components/ui/button.tsx | 22 ++-- web/lib/api.ts | 9 +- web/lib/types.ts | 11 +- 8 files changed, 160 insertions(+), 80 deletions(-) diff --git a/web/app/routes/logs.tsx b/web/app/routes/logs.tsx index a941fcf..fa5d69c 100644 --- a/web/app/routes/logs.tsx +++ b/web/app/routes/logs.tsx @@ -1,5 +1,5 @@ -import { getLogs } from "@/lib/api"; -import type { Log } from "@/lib/types"; +import { getLogs, getWebhooks } from "@/lib/api"; +import type { LogWithWebhook } from "@/lib/types"; import { LogsPage } from "@/components/pages/logs"; import { Skeleton } from "@/components/ui/skeleton"; @@ -9,8 +9,22 @@ export async function clientLoader({ request }: { request: Request }) { const url = new URL(request.url); const page = Number(url.searchParams.get("page")) || 0; const limit = Number(url.searchParams.get("limit")) || DEFAULT_LIMIT; - const logs = await getLogs({ page, limit }); - return { logs, page, limit }; + const [logs, webhooks] = await Promise.all([ + getLogs({ page, limit }), + getWebhooks({ limit: 200 }), + ]); + const webhookNames = new Map( + webhooks.map((webhook) => [webhook.id, webhook.name]) + ); + + const logsWithWebhooks: LogWithWebhook[] = logs.map((log) => ({ + ...log, + webhook_name: log.webhook_id + ? webhookNames.get(log.webhook_id) ?? "Deleted webhook" + : null, + })); + + return { logs: logsWithWebhooks, page, limit }; } export function HydrateFallback() { @@ -39,7 +53,7 @@ export function HydrateFallback() { export default function Logs({ loaderData, }: { - loaderData: { logs: Log[]; page: number; limit: number }; + loaderData: { logs: LogWithWebhook[]; page: number; limit: number }; }) { return ( - {["all", "info", "warn", "error"].map((level) => ( + {["all", "debug", "info", "warn", "error"].map((level) => ( = { - info: "bg-muted-foreground/70", - warn: "bg-yellow-500/80", - error: "bg-red-500/80", + trace: "bg-sky-500/80", + debug: "bg-blue-500/80", + info: "bg-muted-foreground/70", + warn: "bg-yellow-500/80", + error: "bg-red-500/80", +}; + +const levelText: Record = { + trace: "text-sky-500", + debug: "text-blue-500", + info: "text-muted-foreground", + warn: "text-yellow-500", + error: "text-red-500", }; interface LogsListProps { - logs: Log[]; + logs: LogWithWebhook[]; +} + +function truncate(value: string, maxLength = 32) { + return value.length > maxLength + ? `${value.slice(0, maxLength - 1)}...` + : value; } export function LogsList({ logs }: LogsListProps) { - if (logs.length === 0) { - return ( - -
- -

No logs

-

- Logs will appear here as activity occurs. -

-
-
- ); - } + if (logs.length === 0) { + return ( + +
+
+ +
+ +

No logs yet

+ +

+ Webhook activity and system events will appear here. +

+
+
+ ); + } + + return ( + + + + + + + {logs.map((log) => ( + + + + + + + {log.timestamp} + + + +
+ + {log.webhook_name ? truncate(log.webhook_name) : "System"} + - return ( - - -
- - - {logs.map((log) => ( - - - - + + {log.target} + + + - - {log.timestamp} - + +
+

+ {log.message} +

- - {log.message} - - - ))} - -
-
-
- ); + + {log.level} + + + + + ))} + + + + + ); } diff --git a/web/components/pages/edit-webhook.tsx b/web/components/pages/edit-webhook.tsx index abe5dc6..5b45424 100644 --- a/web/components/pages/edit-webhook.tsx +++ b/web/components/pages/edit-webhook.tsx @@ -1,5 +1,5 @@ import { Form, useActionData, useNavigate, useNavigation } from "react-router"; -import { ArrowLeft } from "lucide-react"; +import { ArrowLeft, Save } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -12,6 +12,7 @@ import { CardTitle, } from "@/components/ui/card"; import type { Webhook } from "@/lib/types"; +import { Spinner } from "../ui/spinner"; interface EditWebhookPageProps { webhook: Webhook; @@ -104,7 +105,15 @@ export function EditWebhookPage({ webhook }: EditWebhookPageProps) { Cancel diff --git a/web/components/pages/logs.tsx b/web/components/pages/logs.tsx index b3f3a41..8405591 100644 --- a/web/components/pages/logs.tsx +++ b/web/components/pages/logs.tsx @@ -1,6 +1,6 @@ import { useMemo, useState } from "react"; import { useNavigate } from "react-router"; -import type { Log } from "@/lib/types"; +import type { LogWithWebhook } from "@/lib/types"; import { LogsFilter } from "../logs-filter"; import { LogsList } from "../logs-list"; import { @@ -12,7 +12,7 @@ import { } from "@/components/ui/pagination"; interface LogsPageProps { - logs: Log[]; + logs: LogWithWebhook[]; page: number; limit: number; } diff --git a/web/components/ui/button.tsx b/web/components/ui/button.tsx index 4d38506..aa705db 100644 --- a/web/components/ui/button.tsx +++ b/web/components/ui/button.tsx @@ -1,19 +1,19 @@ -import * as React from "react" -import { cva, type VariantProps } from "class-variance-authority" -import { Slot } from "radix-ui" +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; +import { Slot } from "radix-ui"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const buttonVariants = cva( "inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", { variants: { variant: { - default: "bg-primary text-primary-foreground hover:bg-primary/90", + default: "bg-primary text-primary-foreground hover:bg-neutral-600", destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40", outline: - "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", + "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/90", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: @@ -36,7 +36,7 @@ const buttonVariants = cva( size: "default", }, } -) +); function Button({ className, @@ -46,9 +46,9 @@ function Button({ ...props }: React.ComponentProps<"button"> & VariantProps & { - asChild?: boolean + asChild?: boolean; }) { - const Comp = asChild ? Slot.Root : "button" + const Comp = asChild ? Slot.Root : "button"; return ( - ) + ); } -export { Button, buttonVariants } +export { Button, buttonVariants }; diff --git a/web/lib/api.ts b/web/lib/api.ts index afe2278..cd3657d 100644 --- a/web/lib/api.ts +++ b/web/lib/api.ts @@ -129,7 +129,14 @@ export const dispatch = (data: { webhook_name: string; event_type: string; paylo // Logs export const getLogs = (params?: PaginationParams) => get(`/logs${buildQuery(params)}`); -export const createLog = (data: { level: string; message: string }) => +export const getWebhookLogs = (webhookId: string, params?: PaginationParams) => + get(`/webhooks/${webhookId}/logs${buildQuery(params)}`); +export const createLog = (data: { + level: string; + message: string; + target: string; + webhook_id?: string | null; +}) => post("/logs", data); // Settings (per event type) diff --git a/web/lib/types.ts b/web/lib/types.ts index b7fa721..81d8736 100644 --- a/web/lib/types.ts +++ b/web/lib/types.ts @@ -1,9 +1,14 @@ export interface Log { - id: number; - webhook_id: string | undefined; + id: string; + webhook_id: string | null; level: string; - timestamp: string; message: string; + target: string; + timestamp: string; +} + +export interface LogWithWebhook extends Log { + webhook_name: string | null; } export interface Webhook { From 6f6f7d0c4ec8bef38d5d0033191b02fbfd51d406 Mon Sep 17 00:00:00 2001 From: Nzai Kilonzo Date: Mon, 25 May 2026 18:46:21 +0300 Subject: [PATCH 2/3] feat: change icon --- web/app/root.tsx | 84 ++++++++++++++++---------------- web/public/apple-icon.png | Bin 2626 -> 0 bytes web/public/icon-dark-32x32.png | Bin 585 -> 0 bytes web/public/icon-light-32x32.png | Bin 566 -> 0 bytes web/public/icon.png | Bin 0 -> 5999 bytes web/public/icon.svg | 26 ---------- web/public/placeholder-logo.png | Bin 568 -> 0 bytes 7 files changed, 42 insertions(+), 68 deletions(-) delete mode 100644 web/public/apple-icon.png delete mode 100644 web/public/icon-dark-32x32.png delete mode 100644 web/public/icon-light-32x32.png create mode 100644 web/public/icon.png delete mode 100644 web/public/icon.svg delete mode 100644 web/public/placeholder-logo.png diff --git a/web/app/root.tsx b/web/app/root.tsx index 036e6ba..2533a3a 100644 --- a/web/app/root.tsx +++ b/web/app/root.tsx @@ -4,55 +4,55 @@ import { Providers } from "./providers"; import stylesheet from "./globals.css?url"; export const links: LinksFunction = () => [ - { - rel: "stylesheet", - href: "https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@100..900&display=swap", - }, - { rel: "stylesheet", href: stylesheet }, - { - rel: "icon", - href: "/icon-light-32x32.png", - media: "(prefers-color-scheme: light)", - }, - { - rel: "icon", - href: "/icon-dark-32x32.png", - media: "(prefers-color-scheme: dark)", - }, - { - rel: "icon", - href: "/icon.svg", - type: "image/svg+xml", - }, - { - rel: "apple-touch-icon", - href: "/apple-icon.png", - }, + { + rel: "stylesheet", + href: "https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Geist+Mono:wght@100..900&display=swap", + }, + { rel: "stylesheet", href: stylesheet }, + { + rel: "icon", + href: "/icon.png", + media: "(prefers-color-scheme: light)", + }, + { + rel: "icon", + href: "/icon.png", + media: "(prefers-color-scheme: dark)", + }, + { + rel: "icon", + href: "/icon.png", + type: "image/png", + }, + { + rel: "apple-touch-icon", + href: "/apple-icon.png", + }, ]; export const meta: MetaFunction = () => [ - { title: "Webhook Dashboard" }, - { name: "description", content: "Manage webhook endpoints and deliveries" }, + { title: "Webhook Dashboard" }, + { name: "description", content: "Manage webhook endpoints and deliveries" }, ]; export function Layout({ children }: { children: React.ReactNode }) { - return ( - - - - - - - - - {children} - - - - - ); + return ( + + + + + + + + + {children} + + + + + ); } export default function App() { - return ; + return ; } diff --git a/web/public/apple-icon.png b/web/public/apple-icon.png deleted file mode 100644 index f9418b406bdb93a28dfc30c2ea61bef97b9d7c22..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2626 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 diff --git a/web/public/icon-dark-32x32.png b/web/public/icon-dark-32x32.png deleted file mode 100644 index 12c825a109e3c3b7941d957d8a2ac936d5717c5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 585 zcmV-P0=E5$P)F5K@I)Y9hkG8}6|W~i?1A`6NlJRKhy3zE`f!keoG%R0 zLjeJTG%_U3XczECJ+W9Ul%E@hL8fWaWHO=AXw-HPJ%Obz83z};-L7?CU4%tZlvbfq zEBjzx9^1A}Dr6sIBaY)TBdrV^4u?`@rFb@*wbXi^SI_f8Vm_bOV-;zd3J%)H1n!X; z$~%U-YUfcD(Q37#N6E6R%!<5wkR(aNuWGW6j0n4%|2!eXNTrO$5-O@GSb5HORaRJwL>*7H%p)jeV2su!r z*f@A&t;!dLE_1aH@a~o88UniJApD$%1pw?uB>;9y@TUdjc`mp(TLJ*rORM9l437xC z26nfAN`%~~s)g__*G88?fQq|*4;+uj@<@)yW8#!H=FnjF`+euV)9JL?f=~SaAo&vl z3erab0bit#2t{b|MNE2l9}WhCgfDLYy6X?0V`ueA*$pRw#5ZO?_MS6vLWbO% z$;=!eG9lP9wu((Y*{a{QLvkP)fYfDCmppnQxw1r&haSk4i4VSf(V24@8gKNI90B=3 z4FQro;+;IhyMRx660$6_KCkPV4u=D6w_7@$PFA0%l+-00k4HI~OnUFNNf-n{V+uC4 zx)0{{@n*9j8+0E;V>}+ye!uTWU^E&vDizl2bx&;^$L+ijLgsnij%`s1!%$XLWkiCH z%J6yDcMNrE=Nv9dl7#LBlQ&V(cMq^|!_TM!kWZ)6)_Jj5P!vVEk`@SLyjG)<)q5a61F@a{Ty3(#Ng3V>Zh{;&W(Q@PvidIB)VXKp+Pfz3D^*|nX!x6ubJS7JZlAMP9Jr_FRH=9>9Cn9I^!Tk>lm_{te$Bp65B|*17fEbMK8de4xt=<^cl$0A@Xe*5iw}@4o}0ySOV%E3*Lr zPI)~obtAvbbv&cD(S8>F_IwsuoWUOs@6d!jVudWZT8L&&Ypj}^KYEF-7|5=@L>_gk zA3xa3tf=tueJEfQFQD$Z6G($e8VZtW``-gZZm2d_ zRX*O7hZZ6psI%gzULfL+rmG{e}b;P@o|F{OdaY+c&sFA zI3`ir=e9r*_7q82p1jAP6q~Qpz~Z@>^{i^uheR{`e)KDX|BMr|)`Fea9J?dgX|z#TE*)6CM*pw_Xcsl|crXK~WAS(4TN{?=3W4Ej(jG)bPB8goLgSnU-f({^qFYK&gCmQ0)eG3oJgXiB*%##fURZi zxYB+>e?=5!%&IwfCAeffbv=0c>1J~YIL^Rk<$NUYp?asYdXSXYc8DWs>6q06`#R+ z9od-q+TE77K%C=57dTW^h-f4!VC0Jq!p;o`6`p)En&?77&8LNOg*uaw4z6P*!tb_o zhMJZCr2Gj`MCitFC)GGodW1;{@)B8cRxz=5w4%uG{hlCz1hPKjW+*D=-Hh{JmUWjv zm5GTC6kFBOC(S*NBpAc6AE4vWrSN<=(322*)u^5@c#5YX`ZgTWaYu^C-cwYSB)gc5bf*4lB3=#I8xhq(AYB+`>0SEVD&}nXF)u*Z^`?TzRCwGvxomU=5E*m z#SxdU;E-LcmpxAC+ZQ+9*>*87$ewh*HHyd!Ws9bVqvcWNMT8*yKm8~8rU>?mpSoD7 zomT-STuDO0%RPRiDZ!U zoxz-^*$j0CyiX4vo>gCW?{9+y7R!R^H7%q<+m?$oX~&bsBE! zz!K8OLYRxS&lD@TLSQzuYm6R|fiXA>%%1+ye_5hCTDPEK_x7G7RSZW04R;C7q;ZLP z8p^}yapxV>wtE@(Q7#70!Gr)7i2awyxNggp+%o!Iwn{F92R+6^)n%Z7d7cAzO_5I2 zu+f~x`#4~gyW~}=XUqg2@B{(jbr9Uf3PBQYz}el>5#xyHgoIl&YSU}M~e-U%cz}Ul6d_0{I}WzWfzt@stny8 zxhryOh&YZ&2?}=IsLA)Km4?z@ZyXeKsTXtVn4r&=uoAd}SXEU37IZ#I6uiwt#gk^N z{-QB^$C4)%=vHdl?laBFayEa0KAwJ+mC14p(yPnlV=#AB4Iukp7^-JMvlMbpM~x;J zjc<&M-_y--7Dn)IF2W_R2S>Z|Or#bzz%WH+Y$k^C>g1(bj|}21 zB=E9FwU&^M6j(OoU3kJJK6v%vRGhY+2u9X%!-cz+J$KB55h0=uk58jXh*;sFin~=7 zCr;uVlp{!(03m^53as+vr?f$j5M5K}%Lga<`If%bTo7p<);T4YhQ@z$>%!LxpAWSL z4E^gKSRp$8NKW9faAaM9u+-%^d*KbrAF=-!1<%drfyEL0fot_S^9_DVXB|H>)z%B` ztAuhDkfbWo{*>AfG3~v#dJ3TRmqi!ip9CH3;f5b^j!2Rpm($ya7X zM(@=lA-rm81gX4b`q$EP4!A?vn<&{lnW`JXT-ceNhczb2&o$Yk-N%EdLNC)3bV~&S z?nSd?I<$*A*OWPD*ezC!z*C)KCQ8wOSkLQQ%kT71Q|4DpC1y*!&X$XOw?V16-83h zB0~AU7h%Ikj9^@o=3WO2QbJz;y#7~g)) z8WV2ZJ19676Os8*NG;S8H#Ukm3>Rf_q00ShOD2#*cK2OhC3c}iw2<)1J{fvWSP~?r zVfD*`$LPH+pVWMz+qomAtCi_yHnc822Z2>x)w5h-BR4A4%v+t#r5bo6fsas*M8nxv zsa%MZ!d?-Y{Scv_20NaMlifBUyED3P+V&MQms8Upmms?}e6m2aOg@pqW%8=Mi?=ax zAMU1FJO@$vHS6$*M;U;dks{2%Q>svQEjn+asdZSYrg_t5P#0K8rkuBWasR9bEF{S5 z)#p5%V%B6W7s;+Abkqbg1&KL-{1?!`M=kW$=AupdyC%0cewT|S9RmAKa^}OCDjh5x z+|RLS4%k!%Bn9_Uf6zQVnCJkfXnyanqVMJXCk&`|Cwu~$B=H`RH{ve{pUusfDt%~| z<6mNjltY!af8^9jShXMrt;>0T*jI_{?fD{*736tt!v3K!otC2!(X_=y@-^BtN~?1L z8B4LBjyrubM3Y01h6)8BPl}myrnKDGdP0=jgfwWi#9*lRL!MlaWElO@eJ+-Rj%3SL zragz*AeIhwEXV^s_)wAe!@7AyzJr zy}TWVr&IIc;v83Uw`N5ZFpT!Jiz2Rd;7&o*MDI(pP)@48k)XtGa7R1qjOvB^Vf<1U zCkE?ji@PO<{ITreMf5Fdm@2z;H>pZ55GT`7JHbouwMvGZH)^ZORNeHjBuC1!>XotH z`vIq3kEk|zc@ZM<-TbV>)=Ho>9gMLIIs3Ro4G38FfYGo@zS!gwGa7zQ)6?sJQ*YZ| zM#pa>GBp%vmsG|J=+Y(ehCRjlx6bkd6)g*rpxsl<+41BA`XP`KKecScUVk#hr|f+F z0HM$(ar-p`n%*f_d^9y9F%+xjzK?{X&gF8*84*2CygO$AU2bq$4Npf4&-SO(mRJ>baRe{+=G zy>s35$*fLD0>YOpX=!!tr@5*m4b_|I;Z4UGzo!jV#1P5oEr`Ga=sK+bi2- z@>beVu+9sId{?boL~u9!zINo^9rPXnTX^h_=AAF09K6qF)p+=L57;OVU1|AZA;A$* zIrne_jw7 zagyxGk$XZy*s_q-ly8@`ivGGXZd@T*YI7!ilZg~rL_}+K{+gMOCL}N28J;|3_m_Oo zVLRY?a=GfkQzPJI9MWKU7uR-2;$Du0OwgN4d(7Vbug~f@$ca6b;o7(<^mA+KU>~uG z>Px7;BXLlf*t7O{FL&Bnb4oo<|GSI~Smoo;cN1-^1jKHL>U6F34=;{x>L*6^YiW1R z+(bLnxqYWs1yZJIV~ZLxg&JlF)$hWP$A?`ZvB86(TlQ8Y6Na3MKOHhMR38J(ui>qq zND4b>?uDS62vXIAAD$C0?Q?2#PXHHxGbrQ8qq2v0D!c=DJbu_8<6`&)yk&8p)_z;R z{z{M4Qk9BB#+`XGOLXv`&YqBS1`9#L)#CDT>?_ud*U60nv+(dFSJ(K3%4Pn(u z9HQmpd^oJ|N~~QuGSg;@w70S}I2_6dGFB?}D6FF%BQfut!Sxrskt^MwW^r1ot3i_5V8UkNJ)_FV&222Pu!{ZuI>-bP$jku6n9Id9}GSD2OgV|C1? zj>GSH&JPT74L*&%?Hisb^9o?tT;XJ{p+jk&c=Inc??*E8tsm+y#jE{BcBGH=vZWqj zIxe#()Lva9%iyaw8U1nze_m+Mf%8XrYy-xa4UJ`3*~`*n_glNQvl&g%lx z53w-+IVE?T=|#SLd)3ZRDTQIs)%Z2H!+$m-YXb=f7pIzZ+!m9*MhwN;?fCJ|TGOjg z(mfKz8}%J;-{GQ6)d_f?8Z%=a$9cH3HKqQ^ZJwJd$|WOz)zQPl zAthKxF%9j=Xc()vPcGKY2%0|(3LdxzyAtjJ3)+w8_Kz~WHs^PofzQAXmO5IYnn?B+ zZA|Wp(kbilhG(lIfj}j^+N|FSu$~GJq!x zT?}r$A@|Rku{!QYl)4B30HiiwjxGA8_ZR%`U(R!fB(g<*cwcM=`}CFOm5 zBhz59`L6KE=xuk&V&qX-X;wo__}$H`jlq>+Qrf2lHU8Pcv0c%fANhm- zRdq^5&EB3~sWY(oR}?93y-in(_VT0%vA=lfdA@y!K5%xGC(cQW2Esp#R@iI!G*UJc z6!>l+#`qeW9wbNA)-~`Sj?|1dBNbp*&2XmrBomoDtmVf+?CT*{5k_Cc4Oesw0W3&x1}@IA%(I?YS8sf^==Aq$ zZI)n%<%sEC59R{~&ghZPf22Eu(~Mm{0^g2>t2b5O_TC=j)M>3d?@pee%0BfsWL_$T z9$6~Mni;x(K8U};d8$YIOT^cLtq=eDcdgIFt?@IYLb#NkIHKigg2DCm;a};5LAA)x zolD}*Yi`n?Mt7Io-mfEm`!*^cj((>;vr%bynQy$g3=%Y>6}@QE^@3H}-qEj|4wPr~ zzoDh!BDy6SP^x#++k2-U27v$kEKZ?;T@1TD?@ zQ7ywEZ~i2PsI4h0PO^&hO`I;@hIap|ut#HcFf+%^5%XV41>eBWj5iNJ5I#QeSxa;G zF4`fmw;ZW(eFDjmWKS}wGFGv`e6esbQ^1USPyA8t-xLC*4d9E*5grDL!nzeIlmdYk z;_1v0IV|PXbTN$*1IUEsU=!AbnDdG-x&Gm^J!_sBfa|vYNc16y{AP<(4(rCiaB%uzpmq+ zGXQd1B8_N4RH&-}OZk_R4)rnQlrwv%{)N7|8rS^bpV0U5kjGT&|LqmnepgIWHcqUk zLh)jST1`09$qJ2b)Kl8ac^2AFS=LPiiwKtL7ku^|UqWq(@a}g$wC(+GV=kk?p{dmD z=yHs$_`!;jp{K*Tee_5L@-EqfFmNHl|3$xKnj&7T*DKK-ABh+HP%mo&Ddz=Q`B?qO z|4U8)4K-DQ95*D2UAR2FQupb9Sf}ES3*2awPkb#J2$x#m&0ZG9`!}D9X@CUzKUCAR zYQ%&C)uVuPG=g+okZ>+rIK&?HzXY5M008IbNkv2#2THE_#UdS`r~N>yQo}a%{{YjF BVxRy3 literal 0 HcmV?d00001 diff --git a/web/public/icon.svg b/web/public/icon.svg deleted file mode 100644 index 5c11e6c..0000000 --- a/web/public/icon.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/web/public/placeholder-logo.png b/web/public/placeholder-logo.png deleted file mode 100644 index 8a792ac2ddfbe047639f7907c82f14c73e3de3d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 568 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5Ca^FANm1Vwi-43tfKP}kNR$*1WiP`GRL@@$ z}KL6V^XjP|gFiXN9MWV@SoVw>S2B83zimJup|_^-FbrjeV3it>ba2M!&I{-9Oz_xPcYF3oFZ=eo>b ztd*bf%*2|<`prCX7bFlbJSYChhATpnZHm_gDmxOE$M3Ov^QCh;k9GZ#-G|g`wsZ1y zY-hCA6Y*4%kP@8PzH+9qgqG>UuX7G~Jv3yxb4X!L$WkVer)+Lao)20%*YwyPoNauk zS!$0_-Kr#3+3w{V4Ccu^47H4DY>jP zOx?nl+kWZD?x4qFw+?-2Jz{$0yTXmVj$U6an>Gc>YVI}tW1#2eCg2#H$RT;W`9jE4 z<(d!P-_Kw0oV0yf*RuH&47YRnuIuc%FZkt*pVIL&DpPv8{ Date: Mon, 25 May 2026 19:53:22 +0300 Subject: [PATCH 3/3] feat: make the logs list one liner --- web/components/logs-list.tsx | 61 ++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/web/components/logs-list.tsx b/web/components/logs-list.tsx index d136325..44b2f92 100644 --- a/web/components/logs-list.tsx +++ b/web/components/logs-list.tsx @@ -1,4 +1,5 @@ import { FileText } from "lucide-react"; +import { Link } from "react-router"; import { Card } from "@/components/ui/card"; import { ScrollArea } from "@/components/ui/scroll-area"; import { @@ -30,7 +31,7 @@ interface LogsListProps { logs: LogWithWebhook[]; } -function truncate(value: string, maxLength = 32) { +function truncate(value: string, maxLength = 28) { return value.length > maxLength ? `${value.slice(0, maxLength - 1)}...` : value; @@ -70,42 +71,54 @@ export function LogsList({ logs }: LogsListProps) { key={log.id} className="group border-border/60 transition-colors hover:bg-muted/35" > - + - - {log.timestamp} + + + {log.timestamp} + - -
- - {log.webhook_name ? truncate(log.webhook_name) : "System"} + + {log.webhook_id && log.webhook_name ? ( + + {truncate(log.webhook_name)} + + ) : ( + + System + )} + - - {log.target} - -
+ + + {log.target} + - -
-

- {log.message} -

+ + + {log.level} + + - - {log.level} - -
+ + + {log.message} + ))}