tambahan
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -164,6 +164,18 @@ exports.Prisma.WaHookScalarFieldEnum = {
|
||||
updatedAt: 'updatedAt'
|
||||
};
|
||||
|
||||
exports.Prisma.ChatFlowsScalarFieldEnum = {
|
||||
id: 'id',
|
||||
flows: 'flows',
|
||||
defaultFlow: 'defaultFlow',
|
||||
defaultData: 'defaultData',
|
||||
active: 'active',
|
||||
flowUrl: 'flowUrl',
|
||||
flowToken: 'flowToken',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
};
|
||||
|
||||
exports.Prisma.SortOrder = {
|
||||
asc: 'asc',
|
||||
desc: 'desc'
|
||||
@@ -195,7 +207,8 @@ exports.Prisma.ModelName = {
|
||||
User: 'User',
|
||||
ApiKey: 'ApiKey',
|
||||
WebHook: 'WebHook',
|
||||
WaHook: 'WaHook'
|
||||
WaHook: 'WaHook',
|
||||
ChatFlows: 'ChatFlows'
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
1337
generated/prisma/index.d.ts
vendored
1337
generated/prisma/index.d.ts
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "prisma-client-b7159a5a3af13766d165b3bf5d09869b273a4102920455b6b9bd965ee512be7e",
|
||||
"name": "prisma-client-0c787597671925c5dbd90480bfc5d45c3aa9b494d0bff63e2482cd62c5107b7b",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"browser": "default.js",
|
||||
|
||||
@@ -53,3 +53,15 @@ model WaHook {
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model ChatFlows {
|
||||
id String @id @default(cuid())
|
||||
flows Json?
|
||||
defaultFlow String?
|
||||
defaultData Json?
|
||||
active Boolean @default(true)
|
||||
flowUrl String? @unique
|
||||
flowToken String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
@@ -136,6 +136,18 @@ exports.Prisma.WaHookScalarFieldEnum = {
|
||||
updatedAt: 'updatedAt'
|
||||
};
|
||||
|
||||
exports.Prisma.ChatFlowsScalarFieldEnum = {
|
||||
id: 'id',
|
||||
flows: 'flows',
|
||||
defaultFlow: 'defaultFlow',
|
||||
defaultData: 'defaultData',
|
||||
active: 'active',
|
||||
flowUrl: 'flowUrl',
|
||||
flowToken: 'flowToken',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
};
|
||||
|
||||
exports.Prisma.SortOrder = {
|
||||
asc: 'asc',
|
||||
desc: 'desc'
|
||||
@@ -167,7 +179,8 @@ exports.Prisma.ModelName = {
|
||||
User: 'User',
|
||||
ApiKey: 'ApiKey',
|
||||
WebHook: 'WebHook',
|
||||
WaHook: 'WaHook'
|
||||
WaHook: 'WaHook',
|
||||
ChatFlows: 'ChatFlows'
|
||||
};
|
||||
/**
|
||||
* Create the Client
|
||||
@@ -216,13 +229,13 @@ const config = {
|
||||
}
|
||||
}
|
||||
},
|
||||
"inlineSchema": "generator client {\n provider = \"prisma-client-js\"\n output = \"../generated/prisma\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n url = env(\"DATABASE_URL\")\n}\n\nmodel User {\n id String @id @default(cuid())\n name String?\n email String? @unique\n password String?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n ApiKey ApiKey[]\n}\n\nmodel ApiKey {\n id String @id @default(cuid())\n User User? @relation(fields: [userId], references: [id])\n userId String\n name String\n key String @unique @db.Text\n description String?\n expiredAt DateTime?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel WebHook {\n id String @id @default(cuid())\n name String?\n description String?\n url String\n payload String? @default(\"{}\")\n method String @default(\"POST\")\n headers String? @default(\"{}\")\n apiToken String?\n retries Int? @default(3)\n enabled Boolean @default(true)\n replay Boolean @default(false)\n replayKey String?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel WaHook {\n id String @id @default(cuid())\n data Json? @db.Json\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n",
|
||||
"inlineSchemaHash": "f672201c199f0f043f3266324775af184d82ebd00379f5c23166510b25c889d0",
|
||||
"inlineSchema": "generator client {\n provider = \"prisma-client-js\"\n output = \"../generated/prisma\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n url = env(\"DATABASE_URL\")\n}\n\nmodel User {\n id String @id @default(cuid())\n name String?\n email String? @unique\n password String?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n ApiKey ApiKey[]\n}\n\nmodel ApiKey {\n id String @id @default(cuid())\n User User? @relation(fields: [userId], references: [id])\n userId String\n name String\n key String @unique @db.Text\n description String?\n expiredAt DateTime?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel WebHook {\n id String @id @default(cuid())\n name String?\n description String?\n url String\n payload String? @default(\"{}\")\n method String @default(\"POST\")\n headers String? @default(\"{}\")\n apiToken String?\n retries Int? @default(3)\n enabled Boolean @default(true)\n replay Boolean @default(false)\n replayKey String?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel WaHook {\n id String @id @default(cuid())\n data Json? @db.Json\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel ChatFlows {\n id String @id @default(cuid())\n flows Json?\n defaultFlow String?\n defaultData Json?\n active Boolean @default(true)\n flowUrl String? @unique\n flowToken String?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n",
|
||||
"inlineSchemaHash": "853d49417068ff1819c3dbb60ac7aeea59288f307f7939669c83fdd63bb495e3",
|
||||
"copyEngine": true
|
||||
}
|
||||
config.dirname = '/'
|
||||
|
||||
config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"ApiKey\",\"kind\":\"object\",\"type\":\"ApiKey\",\"relationName\":\"ApiKeyToUser\"}],\"dbName\":null},\"ApiKey\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"User\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ApiKeyToUser\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"key\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiredAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"WebHook\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"url\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"payload\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"method\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"headers\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"apiToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"retries\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"enabled\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"replay\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"replayKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"WaHook\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"data\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}")
|
||||
config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"ApiKey\",\"kind\":\"object\",\"type\":\"ApiKey\",\"relationName\":\"ApiKeyToUser\"}],\"dbName\":null},\"ApiKey\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"User\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ApiKeyToUser\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"key\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"expiredAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"WebHook\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"url\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"payload\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"method\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"headers\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"apiToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"retries\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"enabled\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"replay\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"replayKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"WaHook\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"data\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"ChatFlows\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"flows\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"defaultFlow\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"defaultData\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"active\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"flowUrl\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"flowToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}")
|
||||
defineDmmfProperty(exports.Prisma, config.runtimeDataModel)
|
||||
config.engineWasm = {
|
||||
getRuntime: async () => require('./query_engine_bg.js'),
|
||||
|
||||
@@ -48,8 +48,20 @@ model WebHook {
|
||||
}
|
||||
|
||||
model WaHook {
|
||||
id String @id @default(cuid())
|
||||
data Json? @db.Json
|
||||
id String @id @default(cuid())
|
||||
data Json? @db.Json
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model ChatFlows {
|
||||
id String @id @default(cuid())
|
||||
flows Json?
|
||||
defaultFlow String?
|
||||
defaultData Json?
|
||||
active Boolean @default(true)
|
||||
flowUrl String? @unique
|
||||
flowToken String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import AppRoutes from "./AppRoutes";
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<MantineProvider>
|
||||
<MantineProvider defaultColorScheme="dark">
|
||||
<Notifications />
|
||||
<ModalsProvider>
|
||||
<AppRoutes />
|
||||
|
||||
@@ -7,6 +7,9 @@ import WebhookHome from "./pages/sq/dashboard/webhook/webhook_home";
|
||||
import WebhookLayout from "./pages/sq/dashboard/webhook/webhook_layout";
|
||||
import WajsHome from "./pages/sq/dashboard/wajs/wajs_home";
|
||||
import WajsLayout from "./pages/sq/dashboard/wajs/wajs_layout";
|
||||
import WaHookHome from "./pages/sq/dashboard/wa-hook/wa_hook_home";
|
||||
import FlowWaHook from "./pages/sq/dashboard/wa-hook/flow_wa_hook";
|
||||
import WaHookLayout from "./pages/sq/dashboard/wa-hook/wa_hook_layout";
|
||||
import ApikeyPage from "./pages/sq/dashboard/apikey/apikey_page";
|
||||
import DashboardPage from "./pages/sq/dashboard/dashboard_page";
|
||||
import DashboardLayout from "./pages/sq/dashboard/dashboard_layout";
|
||||
@@ -48,6 +51,19 @@ export default function AppRoutes() {
|
||||
element={<WajsHome />}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
<Route path="/sq/dashboard/wa-hook" element={<WaHookLayout />}>
|
||||
<Route index element={<WaHookHome />} />
|
||||
|
||||
<Route
|
||||
path="/sq/dashboard/wa-hook/wa-hook-home"
|
||||
element={<WaHookHome />}
|
||||
/>
|
||||
<Route
|
||||
path="/sq/dashboard/wa-hook/flow-wa-hook"
|
||||
element={<FlowWaHook />}
|
||||
/>
|
||||
</Route>
|
||||
<Route
|
||||
path="/sq/dashboard/apikey/apikey"
|
||||
element={<ApikeyPage />}
|
||||
|
||||
@@ -9,6 +9,9 @@ const clientRoutes = {
|
||||
"/sq/dashboard/webhook/webhook-home": "/sq/dashboard/webhook/webhook-home",
|
||||
"/sq/dashboard/wajs": "/sq/dashboard/wajs",
|
||||
"/sq/dashboard/wajs/wajs-home": "/sq/dashboard/wajs/wajs-home",
|
||||
"/sq/dashboard/wa-hook": "/sq/dashboard/wa-hook",
|
||||
"/sq/dashboard/wa-hook/wa-hook-home": "/sq/dashboard/wa-hook/wa-hook-home",
|
||||
"/sq/dashboard/wa-hook/flow-wa-hook": "/sq/dashboard/wa-hook/flow-wa-hook",
|
||||
"/sq/dashboard/apikey/apikey": "/sq/dashboard/apikey/apikey",
|
||||
"/sq/dashboard/dashboard": "/sq/dashboard/dashboard",
|
||||
"/login": "/login",
|
||||
|
||||
@@ -10,6 +10,7 @@ import WaRoute from "./server/routes/wa_route";
|
||||
import WebhookRoute from "./server/routes/webhook_route";
|
||||
import cors from "@elysiajs/cors";
|
||||
import WaHookRoute from "./server/routes/wa_hook_route";
|
||||
import FlowRoute from "./server/routes/flow_route";
|
||||
|
||||
const Docs = new Elysia().use(
|
||||
Swagger({
|
||||
@@ -35,7 +36,7 @@ const Api = new Elysia({
|
||||
.use(ApiUser)
|
||||
.use(WaRoute)
|
||||
.use(WebhookRoute)
|
||||
|
||||
.use(FlowRoute);
|
||||
|
||||
const app = new Elysia()
|
||||
.use(cors())
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import clientRoutes from "@/clientRoutes";
|
||||
import { Button, Container } from "@mantine/core";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export default function Home() {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<div>
|
||||
<Container>
|
||||
<h1>Home</h1>
|
||||
</div>
|
||||
<Button onClick={() => navigate(clientRoutes["/sq/dashboard"])}>
|
||||
Go to SQ
|
||||
</Button>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -254,25 +254,17 @@ function ListApiKey({ refresh }: { refresh: boolean }) {
|
||||
}}
|
||||
>
|
||||
<Table.Td>{apiKey.name}</Table.Td>
|
||||
<Table.Td c="#9A9A9A">
|
||||
{apiKey.description || "—"}
|
||||
</Table.Td>
|
||||
<Table.Td c="#9A9A9A">{apiKey.description || "—"}</Table.Td>
|
||||
<Table.Td>
|
||||
{apiKey.expiredAt
|
||||
? new Date(apiKey.expiredAt)
|
||||
.toISOString()
|
||||
.split("T")[0]
|
||||
? new Date(apiKey.expiredAt).toISOString().split("T")[0]
|
||||
: "—"}
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
{new Date(apiKey.createdAt)
|
||||
.toISOString()
|
||||
.split("T")[0]}
|
||||
{new Date(apiKey.createdAt).toISOString().split("T")[0]}
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
{new Date(apiKey.updatedAt)
|
||||
.toISOString()
|
||||
.split("T")[0]}
|
||||
{new Date(apiKey.updatedAt).toISOString().split("T")[0]}
|
||||
</Table.Td>
|
||||
<Table.Td align="right">
|
||||
<Group gap={4} justify="right">
|
||||
@@ -301,7 +293,7 @@ function ListApiKey({ refresh }: { refresh: boolean }) {
|
||||
id: apiKey.id,
|
||||
});
|
||||
setApiKeys((prev) =>
|
||||
prev.filter((a) => a.id !== apiKey.id)
|
||||
prev.filter((a) => a.id !== apiKey.id),
|
||||
);
|
||||
showNotification({
|
||||
title: "Deleted",
|
||||
|
||||
@@ -258,6 +258,12 @@ function NavigationDashboard() {
|
||||
icon: <IconWebhook size={20} color="#00FFFF" />,
|
||||
desc: "Incoming and outgoing event handlers",
|
||||
},
|
||||
{
|
||||
path: clientRoutes["/sq/dashboard/wa-hook/wa-hook-home"],
|
||||
label: "WA Hook",
|
||||
icon: <IconWebhook size={20} color="#00FFFF" />,
|
||||
desc: "WA Hook",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
195
src/pages/sq/dashboard/wa-hook/flow_wa_hook.tsx
Normal file
195
src/pages/sq/dashboard/wa-hook/flow_wa_hook.tsx
Normal file
@@ -0,0 +1,195 @@
|
||||
import { useState, useCallback } from "react";
|
||||
import {
|
||||
Container,
|
||||
Stack,
|
||||
Card,
|
||||
Group,
|
||||
Button,
|
||||
Table,
|
||||
TextInput,
|
||||
PasswordInput,
|
||||
ActionIcon,
|
||||
Checkbox,
|
||||
Text,
|
||||
Title,
|
||||
Flex,
|
||||
Loader,
|
||||
} from "@mantine/core";
|
||||
import { IconReload, IconCheck, IconCopy } from "@tabler/icons-react";
|
||||
import { showNotification } from "@mantine/notifications";
|
||||
import { useShallowEffect } from "@mantine/hooks";
|
||||
import apiFetch from "@/lib/apiFetch";
|
||||
|
||||
export default function FlowWaHook() {
|
||||
return (
|
||||
<Container size="xl" px="md">
|
||||
<Stack gap="xl">
|
||||
<FlowWaHookForm />
|
||||
<FlowWaHookList />
|
||||
</Stack>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
function FlowWaHookList() {
|
||||
const [flows, setFlows] = useState<{ id: string; name: string; type: string }[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [defaultFlow, setDefaultFlow] = useState("");
|
||||
|
||||
const loadFlows = useCallback(async () => {
|
||||
setLoading(true);
|
||||
const { data, error } = await apiFetch.api.chatflows.find.get();
|
||||
if (error) {
|
||||
showNotification({ title: "Error", message: "Failed to load flows", color: "red" });
|
||||
} else {
|
||||
setFlows(data?.flows || []);
|
||||
setDefaultFlow(data?.defaultFlow || "");
|
||||
}
|
||||
setLoading(false);
|
||||
}, []);
|
||||
|
||||
useShallowEffect(() => {
|
||||
loadFlows();
|
||||
}, [loadFlows]);
|
||||
|
||||
const syncFlows = async () => {
|
||||
setLoading(true);
|
||||
const { error } = await apiFetch.api.chatflows.sync.get();
|
||||
if (error) {
|
||||
showNotification({ title: "Error", message: "Sync failed", color: "red" });
|
||||
} else {
|
||||
await loadFlows();
|
||||
showNotification({ title: "Success", message: "Flows synchronized", color: "green" });
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const setAsDefault = async (id: string) => {
|
||||
setLoading(true);
|
||||
const { error } = await apiFetch.api.chatflows.default.put({ id, defaultData: {} });
|
||||
if (error) {
|
||||
showNotification({ title: "Error", message: "Failed to set default flow", color: "red" });
|
||||
} else {
|
||||
await loadFlows();
|
||||
showNotification({ title: "Success", message: "Default flow updated", color: "green" });
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card radius="md" p="lg" withBorder>
|
||||
<Stack gap="lg">
|
||||
<Flex justify="space-between" align="center">
|
||||
<Title order={3}>Flow Management</Title>
|
||||
<Button leftSection={<IconReload size={16} />} onClick={syncFlows} loading={loading}>
|
||||
Sync Flows
|
||||
</Button>
|
||||
</Flex>
|
||||
<Table striped highlightOnHover withTableBorder withColumnBorders>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th>Name</Table.Th>
|
||||
<Table.Th>Type</Table.Th>
|
||||
<Table.Th>Default</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{flows.map((flow) => (
|
||||
<Table.Tr key={flow.id} bg={defaultFlow === flow.id ? "dark.4" : undefined}>
|
||||
<Table.Td>{flow.name}</Table.Td>
|
||||
<Table.Td>{flow.type}</Table.Td>
|
||||
<Table.Td>
|
||||
<Checkbox checked={defaultFlow === flow.id} onChange={() => setAsDefault(flow.id)} />
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
))}
|
||||
{!loading && flows.length === 0 && (
|
||||
<Table.Tr>
|
||||
<Table.Td colSpan={3}>
|
||||
<Text ta="center" c="dimmed">
|
||||
No flows available
|
||||
</Text>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
)}
|
||||
{loading && (
|
||||
<Table.Tr>
|
||||
<Table.Td colSpan={3}>
|
||||
<Flex justify="center" align="center" py="md">
|
||||
<Loader size="sm" />
|
||||
</Flex>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
)}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</Stack>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function FlowWaHookForm() {
|
||||
const [flowUrl, setFlowUrl] = useState("");
|
||||
const [flowToken, setFlowToken] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useShallowEffect(() => {
|
||||
const loadCredentials = async () => {
|
||||
const { data, error } = await apiFetch.api.chatflows["url-token"].get();
|
||||
if (error) {
|
||||
showNotification({ title: "Error", message: "Failed to load credentials", color: "red" });
|
||||
} else {
|
||||
setFlowUrl(data?.data?.flowUrl || "");
|
||||
setFlowToken(data?.data?.flowToken || "");
|
||||
}
|
||||
};
|
||||
loadCredentials();
|
||||
}, []);
|
||||
|
||||
const saveCredentials = async () => {
|
||||
if (!flowUrl || !flowToken) {
|
||||
showNotification({ title: "Error", message: "URL and token are required", color: "red" });
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
const { error } = await apiFetch.api.chatflows["url-token"].put({ flowUrl, flowToken });
|
||||
if (error) {
|
||||
showNotification({ title: "Error", message: "Failed to update credentials", color: "red" });
|
||||
} else {
|
||||
showNotification({ title: "Success", message: "Credentials updated", color: "green" });
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const copyToken = () => {
|
||||
navigator.clipboard.writeText(flowToken);
|
||||
showNotification({ title: "Copied", message: "Token copied to clipboard", color: "green" });
|
||||
};
|
||||
|
||||
return (
|
||||
<Card radius="md" p="lg" withBorder>
|
||||
<Stack gap="lg">
|
||||
<Title order={3}>Flow Credentials</Title>
|
||||
<Stack gap="md">
|
||||
<TextInput label="Flow URL" placeholder="Enter flow URL" value={flowUrl} onChange={(e) => setFlowUrl(e.currentTarget.value)} />
|
||||
<PasswordInput
|
||||
label="Flow Token"
|
||||
placeholder="Enter flow token"
|
||||
value={flowToken}
|
||||
onChange={(e) => setFlowToken(e.currentTarget.value)}
|
||||
rightSection={
|
||||
<ActionIcon onClick={copyToken}>
|
||||
<IconCopy size={16} />
|
||||
</ActionIcon>
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
<Group justify="flex-end">
|
||||
<Button leftSection={<IconCheck size={16} />} onClick={saveCredentials} loading={loading}>
|
||||
Save Changes
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
29
src/pages/sq/dashboard/wa-hook/wa_hook_home.tsx
Normal file
29
src/pages/sq/dashboard/wa-hook/wa_hook_home.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import apiFetch from "@/lib/apiFetch";
|
||||
import { Skeleton, Stack, Text, Title } from "@mantine/core";
|
||||
import { useShallowEffect } from "@mantine/hooks";
|
||||
import useSWR from "swr";
|
||||
|
||||
export default function WaHookHome() {
|
||||
const { data, error, isLoading, mutate } = useSWR("/wa-hook", apiFetch["wa-hook"].list.get,{
|
||||
refreshInterval: 3000,
|
||||
revalidateOnFocus: true,
|
||||
revalidateOnReconnect: true,
|
||||
revalidateIfStale: true,
|
||||
refreshWhenHidden: true,
|
||||
refreshWhenOffline: true,
|
||||
dedupingInterval: 3000,
|
||||
})
|
||||
|
||||
useShallowEffect(() => {
|
||||
mutate()
|
||||
}, [])
|
||||
|
||||
if (isLoading) return <Skeleton height={500} />
|
||||
if (error) return <div>Error: {error.message}</div>
|
||||
return (
|
||||
<Stack>
|
||||
<Title order={2}>WaHookHome</Title>
|
||||
<pre>{JSON.stringify(data?.data?.list, null, 2)}</pre>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
23
src/pages/sq/dashboard/wa-hook/wa_hook_layout.tsx
Normal file
23
src/pages/sq/dashboard/wa-hook/wa_hook_layout.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import clientRoutes from "@/clientRoutes";
|
||||
import { Button, Container, Group, Stack } from "@mantine/core";
|
||||
import { Outlet, useNavigate } from "react-router-dom";
|
||||
|
||||
export default function WaHookLayout() {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<Container size="xl" w={"100%"}>
|
||||
<Group justify="flex-start" p={"md"}>
|
||||
<Button
|
||||
size="compact-xs"
|
||||
radius={"lg"}
|
||||
onClick={() =>
|
||||
navigate(clientRoutes["/sq/dashboard/wa-hook/flow-wa-hook"])
|
||||
}
|
||||
>
|
||||
Flow WA Hook
|
||||
</Button>
|
||||
</Group>
|
||||
<Outlet />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
import { useState, useMemo } from "react";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Checkbox,
|
||||
Group,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Select,
|
||||
Divider,
|
||||
Title,
|
||||
Button,
|
||||
Card,
|
||||
Checkbox,
|
||||
Group,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Select,
|
||||
Divider,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { IconCode, IconCheck, IconX } from "@tabler/icons-react";
|
||||
@@ -38,279 +38,273 @@ Available variables:
|
||||
`;
|
||||
|
||||
export default function WebhookCreate() {
|
||||
const navigate = useNavigate();
|
||||
const [name, setName] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const [url, setUrl] = useState("");
|
||||
const [method, setMethod] = useState("POST");
|
||||
const [headers, setHeaders] = useState(
|
||||
JSON.stringify({ "Content-Type": "application/json" }, null, 2),
|
||||
);
|
||||
const [payload, setPayload] = useState("{}");
|
||||
const [apiToken, setApiToken] = useState("");
|
||||
const [enabled, setEnabled] = useState(true);
|
||||
const [replay, setReplay] = useState(false);
|
||||
const [replayKey, setReplayKey] = useState("");
|
||||
const navigate = useNavigate();
|
||||
const [name, setName] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const [url, setUrl] = useState("");
|
||||
const [method, setMethod] = useState("POST");
|
||||
const [headers, setHeaders] = useState(
|
||||
JSON.stringify({ "Content-Type": "application/json" }, null, 2),
|
||||
);
|
||||
const [payload, setPayload] = useState("{}");
|
||||
const [apiToken, setApiToken] = useState("");
|
||||
const [enabled, setEnabled] = useState(true);
|
||||
const [replay, setReplay] = useState(false);
|
||||
const [replayKey, setReplayKey] = useState("");
|
||||
|
||||
const safeJson = (value: string) => {
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(value || "{}"), null, 2);
|
||||
} catch {
|
||||
return value || "{}";
|
||||
}
|
||||
};
|
||||
const safeJson = (value: string) => {
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(value || "{}"), null, 2);
|
||||
} catch {
|
||||
return value || "{}";
|
||||
}
|
||||
};
|
||||
|
||||
const previewCode = useMemo(() => {
|
||||
let headerObj: Record<string, string> = {};
|
||||
try {
|
||||
headerObj = JSON.parse(headers);
|
||||
} catch { }
|
||||
if (apiToken) headerObj["Authorization"] = `Bearer ${apiToken}`;
|
||||
const prettyHeaders = safeJson(JSON.stringify(headerObj));
|
||||
const prettyPayload = safeJson(payload);
|
||||
const includeBody = ["POST", "PUT", "PATCH"].includes(method.toUpperCase());
|
||||
const previewCode = useMemo(() => {
|
||||
let headerObj: Record<string, string> = {};
|
||||
try {
|
||||
headerObj = JSON.parse(headers);
|
||||
} catch {}
|
||||
if (apiToken) headerObj["Authorization"] = `Bearer ${apiToken}`;
|
||||
const prettyHeaders = safeJson(JSON.stringify(headerObj));
|
||||
const prettyPayload = safeJson(payload);
|
||||
const includeBody = ["POST", "PUT", "PATCH"].includes(method.toUpperCase());
|
||||
|
||||
return `fetch("${url || "https://example.com/webhook"}", {
|
||||
return `fetch("${url || "https://example.com/webhook"}", {
|
||||
method: "${method}",
|
||||
headers: ${prettyHeaders},${includeBody ? `\n body: ${prettyPayload},` : ""}
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(console.log)
|
||||
.catch(console.error);`;
|
||||
}, [url, method, headers, payload, apiToken]);
|
||||
}, [url, method, headers, payload, apiToken]);
|
||||
|
||||
async function onSubmit() {
|
||||
const { data } = await apiFetch.api.webhook.create.post({
|
||||
name,
|
||||
description,
|
||||
apiToken,
|
||||
url,
|
||||
method,
|
||||
headers,
|
||||
payload,
|
||||
enabled,
|
||||
replay,
|
||||
replayKey,
|
||||
});
|
||||
async function onSubmit() {
|
||||
const { data } = await apiFetch.api.webhook.create.post({
|
||||
name,
|
||||
description,
|
||||
apiToken,
|
||||
url,
|
||||
method,
|
||||
headers,
|
||||
payload,
|
||||
enabled,
|
||||
replay,
|
||||
replayKey,
|
||||
});
|
||||
|
||||
if (data?.success) {
|
||||
notifications.show({
|
||||
title: "Webhook Created",
|
||||
message: data.message,
|
||||
color: "teal",
|
||||
icon: <IconCheck />,
|
||||
});
|
||||
if (data?.success) {
|
||||
notifications.show({
|
||||
title: "Webhook Created",
|
||||
message: data.message,
|
||||
color: "teal",
|
||||
icon: <IconCheck />,
|
||||
});
|
||||
|
||||
navigate(clientRoutes["/sq/dashboard/webhook"]);
|
||||
} else {
|
||||
notifications.show({
|
||||
title: "Creation Failed",
|
||||
message: data?.message || "Unable to create webhook",
|
||||
color: "red",
|
||||
icon: <IconX />,
|
||||
});
|
||||
}
|
||||
navigate(clientRoutes["/sq/dashboard/webhook"]);
|
||||
} else {
|
||||
notifications.show({
|
||||
title: "Creation Failed",
|
||||
message: data?.message || "Unable to create webhook",
|
||||
color: "red",
|
||||
icon: <IconX />,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack style={{ backgroundColor: "#191919" }} p="xl">
|
||||
<Stack
|
||||
gap="md"
|
||||
maw={900}
|
||||
mx="auto"
|
||||
bg="rgba(45,45,45,0.6)"
|
||||
p="xl"
|
||||
style={{
|
||||
borderRadius: "20px",
|
||||
backdropFilter: "blur(12px)",
|
||||
border: "1px solid rgba(0,255,200,0.2)",
|
||||
// boxShadow: "0 0 25px rgba(0,255,200,0.15)",
|
||||
}}
|
||||
>
|
||||
<Group justify="space-between">
|
||||
<Title order={2} c="#EAEAEA" fw={600}>
|
||||
Create Webhook
|
||||
</Title>
|
||||
<IconCode color="#00FFFF" size={28} />
|
||||
</Group>
|
||||
return (
|
||||
<Stack style={{ backgroundColor: "#191919" }} p="xl">
|
||||
<Stack
|
||||
gap="md"
|
||||
maw={900}
|
||||
mx="auto"
|
||||
bg="rgba(45,45,45,0.6)"
|
||||
p="xl"
|
||||
style={{
|
||||
borderRadius: "20px",
|
||||
backdropFilter: "blur(12px)",
|
||||
border: "1px solid rgba(0,255,200,0.2)",
|
||||
// boxShadow: "0 0 25px rgba(0,255,200,0.15)",
|
||||
}}
|
||||
>
|
||||
<Group justify="space-between">
|
||||
<Title order={2} c="#EAEAEA" fw={600}>
|
||||
Create Webhook
|
||||
</Title>
|
||||
<IconCode color="#00FFFF" size={28} />
|
||||
</Group>
|
||||
|
||||
<Divider color="rgba(0,255,200,0.2)" />
|
||||
<Divider color="rgba(0,255,200,0.2)" />
|
||||
|
||||
<TextInput
|
||||
label="Name"
|
||||
placeholder="Name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
|
||||
/>
|
||||
<TextInput
|
||||
label="Name"
|
||||
placeholder="Name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Description"
|
||||
placeholder="Description"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
|
||||
/>
|
||||
<TextInput
|
||||
label="Description"
|
||||
placeholder="Description"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Webhook URL"
|
||||
placeholder="https://example.com/webhook"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
|
||||
/>
|
||||
<TextInput
|
||||
label="Webhook URL"
|
||||
placeholder="https://example.com/webhook"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
/>
|
||||
|
||||
<Select
|
||||
label="HTTP Method"
|
||||
placeholder="Select method"
|
||||
value={method}
|
||||
onChange={(v) => setMethod(v || "POST")}
|
||||
data={["POST", "GET", "PUT", "PATCH", "DELETE"].map((v) => ({
|
||||
value: v,
|
||||
label: v,
|
||||
}))}
|
||||
|
||||
/>
|
||||
<Select
|
||||
label="HTTP Method"
|
||||
placeholder="Select method"
|
||||
value={method}
|
||||
onChange={(v) => setMethod(v || "POST")}
|
||||
data={["POST", "GET", "PUT", "PATCH", "DELETE"].map((v) => ({
|
||||
value: v,
|
||||
label: v,
|
||||
}))}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="API Token"
|
||||
placeholder="Bearer ..."
|
||||
value={apiToken}
|
||||
onChange={(e) => {
|
||||
setApiToken(e.target.value);
|
||||
try {
|
||||
const current = JSON.parse(headers);
|
||||
if (!e.target.value) {
|
||||
delete current["Authorization"];
|
||||
} else {
|
||||
current["Authorization"] = `Bearer ${e.target.value}`;
|
||||
}
|
||||
setHeaders(JSON.stringify(current, null, 2));
|
||||
} catch { }
|
||||
}}
|
||||
|
||||
/>
|
||||
<TextInput
|
||||
label="API Token"
|
||||
placeholder="Bearer ..."
|
||||
value={apiToken}
|
||||
onChange={(e) => {
|
||||
setApiToken(e.target.value);
|
||||
try {
|
||||
const current = JSON.parse(headers);
|
||||
if (!e.target.value) {
|
||||
delete current["Authorization"];
|
||||
} else {
|
||||
current["Authorization"] = `Bearer ${e.target.value}`;
|
||||
}
|
||||
setHeaders(JSON.stringify(current, null, 2));
|
||||
} catch {}
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack gap="xs">
|
||||
<Text fw={600} c="#EAEAEA">
|
||||
Headers (JSON)
|
||||
</Text>
|
||||
<Editor
|
||||
theme="vs-dark"
|
||||
height="20vh"
|
||||
language="json"
|
||||
value={headers}
|
||||
onChange={(val) => setHeaders(val ?? "{}")}
|
||||
options={{
|
||||
minimap: { enabled: false },
|
||||
fontSize: 13,
|
||||
scrollBeyondLastLine: false,
|
||||
lineNumbers: "off",
|
||||
automaticLayout: true,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Stack gap="xs">
|
||||
<Text fw={600} c="#EAEAEA">
|
||||
Payload
|
||||
</Text>
|
||||
<Text size="xs" c="#9A9A9A" mb="xs">
|
||||
{templateData}
|
||||
</Text>
|
||||
<Editor
|
||||
theme="vs-dark"
|
||||
height="35vh"
|
||||
language="json"
|
||||
value={payload}
|
||||
onChange={(val) => setPayload(val ?? "{}")}
|
||||
options={{
|
||||
minimap: { enabled: false },
|
||||
fontSize: 13,
|
||||
scrollBeyondLastLine: false,
|
||||
automaticLayout: true,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Checkbox
|
||||
label="Enable Webhook"
|
||||
checked={enabled}
|
||||
onChange={(e) => setEnabled(e.currentTarget.checked)}
|
||||
color="teal"
|
||||
styles={{
|
||||
label: { color: "#EAEAEA" },
|
||||
}}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Enable Replay"
|
||||
checked={replay}
|
||||
onChange={(e) => setReplay(e.currentTarget.checked)}
|
||||
color="teal"
|
||||
styles={{
|
||||
label: { color: "#EAEAEA" },
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
description="Replay Key is used to identify the webhook example: data.text"
|
||||
label="Replay Key"
|
||||
placeholder="Replay Key"
|
||||
value={replayKey}
|
||||
onChange={(e) => setReplayKey(e.target.value)}
|
||||
|
||||
/>
|
||||
|
||||
<Card
|
||||
radius="xl"
|
||||
p="md"
|
||||
style={{
|
||||
background: "rgba(25,25,25,0.6)",
|
||||
border: "1px solid rgba(0,255,200,0.3)",
|
||||
// boxShadow: "0 0 15px rgba(0,255,200,0.15)",
|
||||
}}
|
||||
>
|
||||
<Stack gap="xs">
|
||||
<Text fw={600} c="#EAEAEA">
|
||||
Request Preview
|
||||
</Text>
|
||||
<Editor
|
||||
theme="vs-dark"
|
||||
height="35vh"
|
||||
language="javascript"
|
||||
value={previewCode}
|
||||
options={{
|
||||
readOnly: true,
|
||||
minimap: { enabled: false },
|
||||
fontSize: 13,
|
||||
scrollBeyondLastLine: false,
|
||||
automaticLayout: true,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
<Group justify="flex-end" mt="md">
|
||||
<Button
|
||||
onClick={() => navigate(clientRoutes["/sq/dashboard/webhook"])}
|
||||
variant="subtle"
|
||||
c="#EAEAEA"
|
||||
styles={{
|
||||
root: { backgroundColor: "#2D2D2D", borderColor: "#00FFC8" },
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onSubmit}
|
||||
style={{
|
||||
background: "linear-gradient(90deg, #00FFC8, #00FFFF)",
|
||||
color: "#191919",
|
||||
}}
|
||||
>
|
||||
Save Webhook
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
<Stack gap="xs">
|
||||
<Text fw={600} c="#EAEAEA">
|
||||
Headers (JSON)
|
||||
</Text>
|
||||
<Editor
|
||||
theme="vs-dark"
|
||||
height="20vh"
|
||||
language="json"
|
||||
value={headers}
|
||||
onChange={(val) => setHeaders(val ?? "{}")}
|
||||
options={{
|
||||
minimap: { enabled: false },
|
||||
fontSize: 13,
|
||||
scrollBeyondLastLine: false,
|
||||
lineNumbers: "off",
|
||||
automaticLayout: true,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
<Stack gap="xs">
|
||||
<Text fw={600} c="#EAEAEA">
|
||||
Payload
|
||||
</Text>
|
||||
<Text size="xs" c="#9A9A9A" mb="xs">
|
||||
{templateData}
|
||||
</Text>
|
||||
<Editor
|
||||
theme="vs-dark"
|
||||
height="35vh"
|
||||
language="json"
|
||||
value={payload}
|
||||
onChange={(val) => setPayload(val ?? "{}")}
|
||||
options={{
|
||||
minimap: { enabled: false },
|
||||
fontSize: 13,
|
||||
scrollBeyondLastLine: false,
|
||||
automaticLayout: true,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Checkbox
|
||||
label="Enable Webhook"
|
||||
checked={enabled}
|
||||
onChange={(e) => setEnabled(e.currentTarget.checked)}
|
||||
color="teal"
|
||||
styles={{
|
||||
label: { color: "#EAEAEA" },
|
||||
}}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Enable Replay"
|
||||
checked={replay}
|
||||
onChange={(e) => setReplay(e.currentTarget.checked)}
|
||||
color="teal"
|
||||
styles={{
|
||||
label: { color: "#EAEAEA" },
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
description="Replay Key is used to identify the webhook example: data.text"
|
||||
label="Replay Key"
|
||||
placeholder="Replay Key"
|
||||
value={replayKey}
|
||||
onChange={(e) => setReplayKey(e.target.value)}
|
||||
/>
|
||||
|
||||
<Card
|
||||
radius="xl"
|
||||
p="md"
|
||||
style={{
|
||||
background: "rgba(25,25,25,0.6)",
|
||||
border: "1px solid rgba(0,255,200,0.3)",
|
||||
// boxShadow: "0 0 15px rgba(0,255,200,0.15)",
|
||||
}}
|
||||
>
|
||||
<Stack gap="xs">
|
||||
<Text fw={600} c="#EAEAEA">
|
||||
Request Preview
|
||||
</Text>
|
||||
<Editor
|
||||
theme="vs-dark"
|
||||
height="35vh"
|
||||
language="javascript"
|
||||
value={previewCode}
|
||||
options={{
|
||||
readOnly: true,
|
||||
minimap: { enabled: false },
|
||||
fontSize: 13,
|
||||
scrollBeyondLastLine: false,
|
||||
automaticLayout: true,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
<Group justify="flex-end" mt="md">
|
||||
<Button
|
||||
onClick={() => navigate(clientRoutes["/sq/dashboard/webhook"])}
|
||||
variant="subtle"
|
||||
c="#EAEAEA"
|
||||
styles={{
|
||||
root: { backgroundColor: "#2D2D2D", borderColor: "#00FFC8" },
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onSubmit}
|
||||
style={{
|
||||
background: "linear-gradient(90deg, #00FFC8, #00FFFF)",
|
||||
color: "#191919",
|
||||
}}
|
||||
>
|
||||
Save Webhook
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,18 @@ import { useMemo } from "react";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { IconCode, IconCheck, IconX } from "@tabler/icons-react";
|
||||
import Editor from "@monaco-editor/react";
|
||||
import { Stack, Group, Title, Divider, TextInput, Select, Checkbox, Card, Button, Text } from "@mantine/core";
|
||||
import {
|
||||
Stack,
|
||||
Group,
|
||||
Title,
|
||||
Divider,
|
||||
TextInput,
|
||||
Select,
|
||||
Checkbox,
|
||||
Card,
|
||||
Button,
|
||||
Text,
|
||||
} from "@mantine/core";
|
||||
import { modals } from "@mantine/modals";
|
||||
import clientRoutes from "@/clientRoutes";
|
||||
import { useShallowEffect } from "@mantine/hooks";
|
||||
@@ -18,344 +29,359 @@ Available variables:
|
||||
`;
|
||||
|
||||
export default function WebhookEdit() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const id = searchParams.get("id");
|
||||
const { data, error, isLoading, mutate } = useSWR("/", () => apiFetch.api.webhook.find({
|
||||
id: id!
|
||||
}).get(), {dedupingInterval: 3000})
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const id = searchParams.get("id");
|
||||
const { data, error, isLoading, mutate } = useSWR(
|
||||
"/",
|
||||
() =>
|
||||
apiFetch.api.webhook
|
||||
.find({
|
||||
id: id!,
|
||||
})
|
||||
.get(),
|
||||
{ dedupingInterval: 3000 },
|
||||
);
|
||||
const navigate = useNavigate();
|
||||
|
||||
useShallowEffect(() => {
|
||||
mutate();
|
||||
}, [data]);
|
||||
useShallowEffect(() => {
|
||||
mutate();
|
||||
}, [data]);
|
||||
|
||||
if (isLoading) return <div>Loading...</div>;
|
||||
if (error) return <div>Error: {error}</div>;
|
||||
if (!data?.data?.webhook) return <div>No data</div>;
|
||||
if (isLoading) return <div>Loading...</div>;
|
||||
if (error) return <div>Error: {error}</div>;
|
||||
if (!data?.data?.webhook) return <div>No data</div>;
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
|
||||
<Group justify="space-between">
|
||||
<Title order={2}>Edit Webhook</Title>
|
||||
<Button variant="outline" onClick={() => {
|
||||
modals.openConfirmModal({
|
||||
title: "Remove Webhook",
|
||||
children: <Text>Are you sure you want to remove this webhook?</Text>,
|
||||
confirmProps: { color: "red" },
|
||||
labels: {
|
||||
cancel: "Cancel",
|
||||
confirm: "Remove",
|
||||
},
|
||||
onConfirm: () => {
|
||||
apiFetch.api.webhook.remove({
|
||||
id: id!
|
||||
}).delete()
|
||||
navigate(clientRoutes["/sq/dashboard/webhook"]);
|
||||
},
|
||||
onCancel: () => {
|
||||
navigate(clientRoutes["/sq/dashboard/webhook/webhook-edit"] + "?id=" + id);
|
||||
},
|
||||
})
|
||||
}}>Remove</Button>
|
||||
</Group>
|
||||
<EditView webhook={data.data?.webhook || null} />
|
||||
</Stack>
|
||||
);
|
||||
return (
|
||||
<Stack>
|
||||
<Group justify="space-between">
|
||||
<Title order={2}>Edit Webhook</Title>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
modals.openConfirmModal({
|
||||
title: "Remove Webhook",
|
||||
children: (
|
||||
<Text>Are you sure you want to remove this webhook?</Text>
|
||||
),
|
||||
confirmProps: { color: "red" },
|
||||
labels: {
|
||||
cancel: "Cancel",
|
||||
confirm: "Remove",
|
||||
},
|
||||
onConfirm: () => {
|
||||
apiFetch.api.webhook
|
||||
.remove({
|
||||
id: id!,
|
||||
})
|
||||
.delete();
|
||||
navigate(clientRoutes["/sq/dashboard/webhook"]);
|
||||
},
|
||||
onCancel: () => {
|
||||
navigate(
|
||||
clientRoutes["/sq/dashboard/webhook/webhook-edit"] +
|
||||
"?id=" +
|
||||
id,
|
||||
);
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
</Group>
|
||||
<EditView webhook={data.data?.webhook || null} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function EditView({ webhook }: { webhook: WebHook | null }) {
|
||||
const navigate = useNavigate();
|
||||
const [name, setName] = useState(webhook?.name || "");
|
||||
const [description, setDescription] = useState(webhook?.description || "");
|
||||
const [url, setUrl] = useState(webhook?.url || "");
|
||||
const [method, setMethod] = useState(webhook?.method || "POST");
|
||||
const [headers, setHeaders] = useState(webhook?.headers || "{}");
|
||||
const [payload, setPayload] = useState(webhook?.payload || "{}");
|
||||
const [apiToken, setApiToken] = useState(webhook?.apiToken || "");
|
||||
const [enabled, setEnabled] = useState(webhook?.enabled || true);
|
||||
const [replay, setReplay] = useState(webhook?.replay || false);
|
||||
const [replayKey, setReplayKey] = useState(webhook?.replayKey || "");
|
||||
const navigate = useNavigate();
|
||||
const [name, setName] = useState(webhook?.name || "");
|
||||
const [description, setDescription] = useState(webhook?.description || "");
|
||||
const [url, setUrl] = useState(webhook?.url || "");
|
||||
const [method, setMethod] = useState(webhook?.method || "POST");
|
||||
const [headers, setHeaders] = useState(webhook?.headers || "{}");
|
||||
const [payload, setPayload] = useState(webhook?.payload || "{}");
|
||||
const [apiToken, setApiToken] = useState(webhook?.apiToken || "");
|
||||
const [enabled, setEnabled] = useState(webhook?.enabled || true);
|
||||
const [replay, setReplay] = useState(webhook?.replay || false);
|
||||
const [replayKey, setReplayKey] = useState(webhook?.replayKey || "");
|
||||
|
||||
const safeJson = (value: string) => {
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(value || "{}"), null, 2);
|
||||
} catch {
|
||||
return value || "{}";
|
||||
}
|
||||
};
|
||||
const safeJson = (value: string) => {
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(value || "{}"), null, 2);
|
||||
} catch {
|
||||
return value || "{}";
|
||||
}
|
||||
};
|
||||
|
||||
// useShallowEffect(() => {
|
||||
// let headerObj: Record<string, string> = {};
|
||||
// try {
|
||||
// headerObj = JSON.parse(headers);
|
||||
// } catch { }
|
||||
// if (apiToken) headerObj["Authorization"] = `Bearer ${apiToken}`;
|
||||
// setHeaders(JSON.stringify(headerObj, null, 2));
|
||||
// }, [apiToken]);
|
||||
// useShallowEffect(() => {
|
||||
// let headerObj: Record<string, string> = {};
|
||||
// try {
|
||||
// headerObj = JSON.parse(headers);
|
||||
// } catch { }
|
||||
// if (apiToken) headerObj["Authorization"] = `Bearer ${apiToken}`;
|
||||
// setHeaders(JSON.stringify(headerObj, null, 2));
|
||||
// }, [apiToken]);
|
||||
|
||||
const previewCode = useMemo(() => {
|
||||
let headerObj: Record<string, string> = {};
|
||||
try {
|
||||
headerObj = JSON.parse(headers);
|
||||
} catch { }
|
||||
if (apiToken) headerObj["Authorization"] = `Bearer ${apiToken}`;
|
||||
const prettyHeaders = safeJson(JSON.stringify(headerObj));
|
||||
const prettyPayload = safeJson(payload);
|
||||
const includeBody = ["POST", "PUT", "PATCH"].includes(method.toUpperCase());
|
||||
const previewCode = useMemo(() => {
|
||||
let headerObj: Record<string, string> = {};
|
||||
try {
|
||||
headerObj = JSON.parse(headers);
|
||||
} catch {}
|
||||
if (apiToken) headerObj["Authorization"] = `Bearer ${apiToken}`;
|
||||
const prettyHeaders = safeJson(JSON.stringify(headerObj));
|
||||
const prettyPayload = safeJson(payload);
|
||||
const includeBody = ["POST", "PUT", "PATCH"].includes(method.toUpperCase());
|
||||
|
||||
return `fetch("${url || "https://example.com/webhook"}", {
|
||||
return `fetch("${url || "https://example.com/webhook"}", {
|
||||
method: "${method}",
|
||||
headers: ${prettyHeaders},${includeBody ? `\n body: ${prettyPayload},` : ""}
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(console.log)
|
||||
.catch(console.error);`;
|
||||
}, [url, method, headers, payload, apiToken]);
|
||||
}, [url, method, headers, payload, apiToken]);
|
||||
|
||||
async function onSubmit() {
|
||||
if (!webhook?.id) {
|
||||
return notifications.show({
|
||||
title: "Webhook ID Not Found",
|
||||
message: "Unable to update webhook",
|
||||
color: "red",
|
||||
icon: <IconX />,
|
||||
});
|
||||
}
|
||||
const { data } = await apiFetch.api.webhook.update({
|
||||
id: webhook?.id,
|
||||
}).put({
|
||||
name,
|
||||
description,
|
||||
apiToken,
|
||||
url,
|
||||
method,
|
||||
headers,
|
||||
payload,
|
||||
enabled,
|
||||
replay,
|
||||
replayKey,
|
||||
});
|
||||
|
||||
if (data?.success) {
|
||||
notifications.show({
|
||||
title: "Webhook Created",
|
||||
message: data.message,
|
||||
color: "teal",
|
||||
icon: <IconCheck />,
|
||||
});
|
||||
navigate(clientRoutes["/sq/dashboard/webhook"]);
|
||||
} else {
|
||||
notifications.show({
|
||||
title: "Creation Failed",
|
||||
message: data?.message || "Unable to create webhook",
|
||||
color: "red",
|
||||
icon: <IconX />,
|
||||
});
|
||||
}
|
||||
async function onSubmit() {
|
||||
if (!webhook?.id) {
|
||||
return notifications.show({
|
||||
title: "Webhook ID Not Found",
|
||||
message: "Unable to update webhook",
|
||||
color: "red",
|
||||
icon: <IconX />,
|
||||
});
|
||||
}
|
||||
const { data } = await apiFetch.api.webhook
|
||||
.update({
|
||||
id: webhook?.id,
|
||||
})
|
||||
.put({
|
||||
name,
|
||||
description,
|
||||
apiToken,
|
||||
url,
|
||||
method,
|
||||
headers,
|
||||
payload,
|
||||
enabled,
|
||||
replay,
|
||||
replayKey,
|
||||
});
|
||||
|
||||
return (
|
||||
<Stack style={{ backgroundColor: "#191919" }} p="xl">
|
||||
<Stack
|
||||
gap="md"
|
||||
maw={900}
|
||||
mx="auto"
|
||||
bg="rgba(45,45,45,0.6)"
|
||||
p="xl"
|
||||
style={{
|
||||
borderRadius: "20px",
|
||||
backdropFilter: "blur(12px)",
|
||||
border: "1px solid rgba(0,255,200,0.2)",
|
||||
// boxShadow: "0 0 25px rgba(0,255,200,0.15)",
|
||||
}}
|
||||
>
|
||||
<Group justify="space-between">
|
||||
<Title order={2} c="#EAEAEA" fw={600}>
|
||||
Create Webhook
|
||||
</Title>
|
||||
<IconCode color="#00FFFF" size={28} />
|
||||
</Group>
|
||||
if (data?.success) {
|
||||
notifications.show({
|
||||
title: "Webhook Created",
|
||||
message: data.message,
|
||||
color: "teal",
|
||||
icon: <IconCheck />,
|
||||
});
|
||||
navigate(clientRoutes["/sq/dashboard/webhook"]);
|
||||
} else {
|
||||
notifications.show({
|
||||
title: "Creation Failed",
|
||||
message: data?.message || "Unable to create webhook",
|
||||
color: "red",
|
||||
icon: <IconX />,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
<Divider color="rgba(0,255,200,0.2)" />
|
||||
return (
|
||||
<Stack style={{ backgroundColor: "#191919" }} p="xl">
|
||||
<Stack
|
||||
gap="md"
|
||||
maw={900}
|
||||
mx="auto"
|
||||
bg="rgba(45,45,45,0.6)"
|
||||
p="xl"
|
||||
style={{
|
||||
borderRadius: "20px",
|
||||
backdropFilter: "blur(12px)",
|
||||
border: "1px solid rgba(0,255,200,0.2)",
|
||||
// boxShadow: "0 0 25px rgba(0,255,200,0.15)",
|
||||
}}
|
||||
>
|
||||
<Group justify="space-between">
|
||||
<Title order={2} c="#EAEAEA" fw={600}>
|
||||
Create Webhook
|
||||
</Title>
|
||||
<IconCode color="#00FFFF" size={28} />
|
||||
</Group>
|
||||
|
||||
<TextInput
|
||||
label="Name"
|
||||
placeholder="Name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
|
||||
/>
|
||||
<Divider color="rgba(0,255,200,0.2)" />
|
||||
|
||||
<TextInput
|
||||
label="Description"
|
||||
placeholder="Description"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
|
||||
/>
|
||||
<TextInput
|
||||
label="Name"
|
||||
placeholder="Name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="Webhook URL"
|
||||
placeholder="https://example.com/webhook"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
|
||||
/>
|
||||
<TextInput
|
||||
label="Description"
|
||||
placeholder="Description"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
/>
|
||||
|
||||
<Select
|
||||
label="HTTP Method"
|
||||
placeholder="Select method"
|
||||
value={method}
|
||||
onChange={(v) => setMethod(v || "POST")}
|
||||
data={["POST", "GET", "PUT", "PATCH", "DELETE"].map((v) => ({
|
||||
value: v,
|
||||
label: v,
|
||||
}))}
|
||||
|
||||
/>
|
||||
<TextInput
|
||||
label="Webhook URL"
|
||||
placeholder="https://example.com/webhook"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label="API Token"
|
||||
placeholder="Bearer ..."
|
||||
value={apiToken}
|
||||
onChange={(e) => {
|
||||
setApiToken(e.target.value);
|
||||
try {
|
||||
const current = JSON.parse(headers);
|
||||
if (!e.target.value) {
|
||||
delete current["Authorization"];
|
||||
} else {
|
||||
current["Authorization"] = `Bearer ${e.target.value}`;
|
||||
}
|
||||
setHeaders(JSON.stringify(current, null, 2));
|
||||
} catch { }
|
||||
}}
|
||||
|
||||
/>
|
||||
<Select
|
||||
label="HTTP Method"
|
||||
placeholder="Select method"
|
||||
value={method}
|
||||
onChange={(v) => setMethod(v || "POST")}
|
||||
data={["POST", "GET", "PUT", "PATCH", "DELETE"].map((v) => ({
|
||||
value: v,
|
||||
label: v,
|
||||
}))}
|
||||
/>
|
||||
|
||||
<Stack gap="xs">
|
||||
<Text fw={600} c="#EAEAEA">
|
||||
Headers (JSON)
|
||||
</Text>
|
||||
<Editor
|
||||
theme="vs-dark"
|
||||
height="20vh"
|
||||
language="json"
|
||||
value={headers}
|
||||
onChange={(val) => setHeaders(val ?? "{}")}
|
||||
options={{
|
||||
minimap: { enabled: false },
|
||||
fontSize: 13,
|
||||
scrollBeyondLastLine: false,
|
||||
lineNumbers: "off",
|
||||
automaticLayout: true,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
<TextInput
|
||||
label="API Token"
|
||||
placeholder="Bearer ..."
|
||||
value={apiToken}
|
||||
onChange={(e) => {
|
||||
setApiToken(e.target.value);
|
||||
try {
|
||||
const current = JSON.parse(headers);
|
||||
if (!e.target.value) {
|
||||
delete current["Authorization"];
|
||||
} else {
|
||||
current["Authorization"] = `Bearer ${e.target.value}`;
|
||||
}
|
||||
setHeaders(JSON.stringify(current, null, 2));
|
||||
} catch {}
|
||||
}}
|
||||
/>
|
||||
|
||||
<Stack gap="xs">
|
||||
<Text fw={600} c="#EAEAEA">
|
||||
Payload
|
||||
</Text>
|
||||
<Text size="xs" c="#9A9A9A" mb="xs">
|
||||
{templateData}
|
||||
</Text>
|
||||
<Editor
|
||||
theme="vs-dark"
|
||||
height="35vh"
|
||||
language="json"
|
||||
value={payload}
|
||||
onChange={(val) => setPayload(val ?? "{}")}
|
||||
options={{
|
||||
minimap: { enabled: false },
|
||||
fontSize: 13,
|
||||
scrollBeyondLastLine: false,
|
||||
automaticLayout: true,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Checkbox
|
||||
label="Enable Webhook"
|
||||
checked={enabled}
|
||||
onChange={(e) => setEnabled(e.target.checked as any)}
|
||||
color="teal"
|
||||
styles={{
|
||||
label: { color: "#EAEAEA" },
|
||||
}}
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
label="Enable Replay"
|
||||
checked={replay}
|
||||
onChange={(e) => setReplay(e.target.checked as any)}
|
||||
color="teal"
|
||||
styles={{
|
||||
label: { color: "#EAEAEA" },
|
||||
}}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
description="Replay Key is used to identify the webhook example: data.text"
|
||||
label="Replay Key"
|
||||
placeholder="Replay Key"
|
||||
value={replayKey}
|
||||
onChange={(e) => setReplayKey(e.target.value)}
|
||||
|
||||
/>
|
||||
|
||||
<Card
|
||||
radius="xl"
|
||||
p="md"
|
||||
style={{
|
||||
background: "rgba(25,25,25,0.6)",
|
||||
border: "1px solid rgba(0,255,200,0.3)",
|
||||
// boxShadow: "0 0 15px rgba(0,255,200,0.15)",
|
||||
}}
|
||||
>
|
||||
<Stack gap="xs">
|
||||
<Text fw={600} c="#EAEAEA">
|
||||
Request Preview
|
||||
</Text>
|
||||
<Editor
|
||||
theme="vs-dark"
|
||||
height="35vh"
|
||||
language="javascript"
|
||||
value={previewCode}
|
||||
options={{
|
||||
readOnly: true,
|
||||
minimap: { enabled: false },
|
||||
fontSize: 13,
|
||||
scrollBeyondLastLine: false,
|
||||
automaticLayout: true,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
<Group justify="flex-end" mt="md">
|
||||
<Button
|
||||
onClick={() => navigate(clientRoutes["/sq/dashboard/webhook"])}
|
||||
variant="subtle"
|
||||
c="#EAEAEA"
|
||||
styles={{
|
||||
root: { backgroundColor: "#2D2D2D", borderColor: "#00FFC8" },
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onSubmit}
|
||||
style={{
|
||||
background: "linear-gradient(90deg, #00FFC8, #00FFFF)",
|
||||
color: "#191919",
|
||||
}}
|
||||
>
|
||||
Save Webhook
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
<Stack gap="xs">
|
||||
<Text fw={600} c="#EAEAEA">
|
||||
Headers (JSON)
|
||||
</Text>
|
||||
<Editor
|
||||
theme="vs-dark"
|
||||
height="20vh"
|
||||
language="json"
|
||||
value={headers}
|
||||
onChange={(val) => setHeaders(val ?? "{}")}
|
||||
options={{
|
||||
minimap: { enabled: false },
|
||||
fontSize: 13,
|
||||
scrollBeyondLastLine: false,
|
||||
lineNumbers: "off",
|
||||
automaticLayout: true,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
<Stack gap="xs">
|
||||
<Text fw={600} c="#EAEAEA">
|
||||
Payload
|
||||
</Text>
|
||||
<Text size="xs" c="#9A9A9A" mb="xs">
|
||||
{templateData}
|
||||
</Text>
|
||||
<Editor
|
||||
theme="vs-dark"
|
||||
height="35vh"
|
||||
language="json"
|
||||
value={payload}
|
||||
onChange={(val) => setPayload(val ?? "{}")}
|
||||
options={{
|
||||
minimap: { enabled: false },
|
||||
fontSize: 13,
|
||||
scrollBeyondLastLine: false,
|
||||
automaticLayout: true,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Checkbox
|
||||
label="Enable Webhook"
|
||||
checked={enabled}
|
||||
onChange={(e) => setEnabled(e.target.checked as any)}
|
||||
color="teal"
|
||||
styles={{
|
||||
label: { color: "#EAEAEA" },
|
||||
}}
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
label="Enable Replay"
|
||||
checked={replay}
|
||||
onChange={(e) => setReplay(e.target.checked as any)}
|
||||
color="teal"
|
||||
styles={{
|
||||
label: { color: "#EAEAEA" },
|
||||
}}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
description="Replay Key is used to identify the webhook example: data.text"
|
||||
label="Replay Key"
|
||||
placeholder="Replay Key"
|
||||
value={replayKey}
|
||||
onChange={(e) => setReplayKey(e.target.value)}
|
||||
/>
|
||||
|
||||
<Card
|
||||
radius="xl"
|
||||
p="md"
|
||||
style={{
|
||||
background: "rgba(25,25,25,0.6)",
|
||||
border: "1px solid rgba(0,255,200,0.3)",
|
||||
// boxShadow: "0 0 15px rgba(0,255,200,0.15)",
|
||||
}}
|
||||
>
|
||||
<Stack gap="xs">
|
||||
<Text fw={600} c="#EAEAEA">
|
||||
Request Preview
|
||||
</Text>
|
||||
<Editor
|
||||
theme="vs-dark"
|
||||
height="35vh"
|
||||
language="javascript"
|
||||
value={previewCode}
|
||||
options={{
|
||||
readOnly: true,
|
||||
minimap: { enabled: false },
|
||||
fontSize: 13,
|
||||
scrollBeyondLastLine: false,
|
||||
automaticLayout: true,
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
<Group justify="flex-end" mt="md">
|
||||
<Button
|
||||
onClick={() => navigate(clientRoutes["/sq/dashboard/webhook"])}
|
||||
variant="subtle"
|
||||
c="#EAEAEA"
|
||||
styles={{
|
||||
root: { backgroundColor: "#2D2D2D", borderColor: "#00FFC8" },
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onSubmit}
|
||||
style={{
|
||||
background: "linear-gradient(90deg, #00FFC8, #00FFFF)",
|
||||
color: "#191919",
|
||||
}}
|
||||
>
|
||||
Save Webhook
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
Card,
|
||||
Group,
|
||||
Text,
|
||||
Title,
|
||||
Badge,
|
||||
Loader,
|
||||
Center,
|
||||
Tooltip,
|
||||
ActionIcon,
|
||||
Stack,
|
||||
Divider,
|
||||
Button,
|
||||
Card,
|
||||
Group,
|
||||
Text,
|
||||
Title,
|
||||
Badge,
|
||||
Loader,
|
||||
Center,
|
||||
Tooltip,
|
||||
ActionIcon,
|
||||
Stack,
|
||||
Divider,
|
||||
Button,
|
||||
} from "@mantine/core";
|
||||
import {
|
||||
IconLink,
|
||||
IconCode,
|
||||
IconKey,
|
||||
IconCheck,
|
||||
IconX,
|
||||
IconRefresh,
|
||||
IconEdit,
|
||||
IconPlus,
|
||||
IconMessageReply,
|
||||
IconLink,
|
||||
IconCode,
|
||||
IconKey,
|
||||
IconCheck,
|
||||
IconX,
|
||||
IconRefresh,
|
||||
IconEdit,
|
||||
IconPlus,
|
||||
IconMessageReply,
|
||||
} from "@tabler/icons-react";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import useSWR from "swr";
|
||||
@@ -32,218 +32,228 @@ import clientRoutes from "@/clientRoutes";
|
||||
import { useShallowEffect } from "@mantine/hooks";
|
||||
|
||||
export default function WebhookHome() {
|
||||
const navigate = useNavigate();
|
||||
const { data, error, isLoading, mutate } = useSWR(
|
||||
"/",
|
||||
apiFetch.api.webhook.list.get, { dedupingInterval: 3000, refreshInterval: 3000 });
|
||||
const navigate = useNavigate();
|
||||
const { data, error, isLoading, mutate } = useSWR(
|
||||
"/",
|
||||
apiFetch.api.webhook.list.get,
|
||||
{ dedupingInterval: 3000, refreshInterval: 3000 },
|
||||
);
|
||||
|
||||
const webhooks = useMemo(() => data?.data?.list ?? [], [data]);
|
||||
const webhooks = useMemo(() => data?.data?.list ?? [], [data]);
|
||||
|
||||
useShallowEffect(() => {
|
||||
mutate();
|
||||
}, []);
|
||||
|
||||
|
||||
function ButtonCreate() {
|
||||
return <Tooltip label="Create new webhook" withArrow color="teal">
|
||||
<Button
|
||||
radius="xl"
|
||||
size="md"
|
||||
leftSection={<IconPlus size={18} />}
|
||||
variant="gradient"
|
||||
gradient={{ from: "#00FFC8", to: "#00FFFF", deg: 135 }}
|
||||
style={{
|
||||
color: "#191919",
|
||||
fontWeight: 600,
|
||||
// boxShadow: "0 0 12px rgba(0,255,200,0.25)",
|
||||
transition: "transform 0.2s ease, box-shadow 0.2s ease",
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.transform = "translateY(-2px)";
|
||||
e.currentTarget.style.boxShadow =
|
||||
"0 0 20px rgba(0,255,200,0.4)";
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.transform = "translateY(0)";
|
||||
e.currentTarget.style.boxShadow =
|
||||
"0 0 12px rgba(0,255,200,0.25)";
|
||||
}}
|
||||
onClick={() => navigate("/sq/dashboard/webhook/webhook-create")}
|
||||
>
|
||||
Create Webhook
|
||||
</Button>
|
||||
</Tooltip>
|
||||
}
|
||||
|
||||
if (isLoading)
|
||||
return (
|
||||
<Center h="100vh" bg="#191919">
|
||||
<Loader color="teal" size="lg" />
|
||||
</Center>
|
||||
);
|
||||
|
||||
if (error)
|
||||
return (
|
||||
<Center h="100vh" bg="#191919">
|
||||
<Text c="#FF4B4B" fw={500}>
|
||||
Failed to load webhooks. Please try again.
|
||||
</Text>
|
||||
</Center>
|
||||
);
|
||||
|
||||
if (!webhooks.length)
|
||||
return (
|
||||
<Center h="100vh" bg="#191919">
|
||||
<Stack align="center" gap="sm">
|
||||
<Text c="#9A9A9A" size="lg">
|
||||
No webhooks found
|
||||
</Text>
|
||||
<Text c="#00FFC8" size="sm">
|
||||
Connect your first webhook to start managing events
|
||||
</Text>
|
||||
<ButtonCreate />
|
||||
</Stack>
|
||||
</Center>
|
||||
);
|
||||
useShallowEffect(() => {
|
||||
mutate();
|
||||
}, []);
|
||||
|
||||
function ButtonCreate() {
|
||||
return (
|
||||
<Stack style={{ backgroundColor: "#191919" }} p="xl">
|
||||
<Group justify="space-between" mb="lg">
|
||||
<Title order={2} c="#EAEAEA" fw={600}>
|
||||
Webhook Manager
|
||||
</Title>
|
||||
<ButtonCreate />
|
||||
<Tooltip label="Refresh webhooks" withArrow color="cyan">
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
size="lg"
|
||||
radius="xl"
|
||||
onClick={() => {
|
||||
mutate();
|
||||
notifications.show({
|
||||
title: "Refreshing data",
|
||||
message: "Webhook list is being updated...",
|
||||
color: "teal",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<IconRefresh color="#00FFFF" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Create new webhook" withArrow color="teal">
|
||||
<Button
|
||||
radius="xl"
|
||||
size="md"
|
||||
leftSection={<IconPlus size={18} />}
|
||||
variant="gradient"
|
||||
gradient={{ from: "#00FFC8", to: "#00FFFF", deg: 135 }}
|
||||
style={{
|
||||
color: "#191919",
|
||||
fontWeight: 600,
|
||||
// boxShadow: "0 0 12px rgba(0,255,200,0.25)",
|
||||
transition: "transform 0.2s ease, box-shadow 0.2s ease",
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.transform = "translateY(-2px)";
|
||||
e.currentTarget.style.boxShadow = "0 0 20px rgba(0,255,200,0.4)";
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.transform = "translateY(0)";
|
||||
e.currentTarget.style.boxShadow = "0 0 12px rgba(0,255,200,0.25)";
|
||||
}}
|
||||
onClick={() => navigate("/sq/dashboard/webhook/webhook-create")}
|
||||
>
|
||||
Create Webhook
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoading)
|
||||
return (
|
||||
<Center h="100vh" bg="#191919">
|
||||
<Loader color="teal" size="lg" />
|
||||
</Center>
|
||||
);
|
||||
|
||||
if (error)
|
||||
return (
|
||||
<Center h="100vh" bg="#191919">
|
||||
<Text c="#FF4B4B" fw={500}>
|
||||
Failed to load webhooks. Please try again.
|
||||
</Text>
|
||||
</Center>
|
||||
);
|
||||
|
||||
if (!webhooks.length)
|
||||
return (
|
||||
<Center h="100vh" bg="#191919">
|
||||
<Stack align="center" gap="sm">
|
||||
<Text c="#9A9A9A" size="lg">
|
||||
No webhooks found
|
||||
</Text>
|
||||
<Text c="#00FFC8" size="sm">
|
||||
Connect your first webhook to start managing events
|
||||
</Text>
|
||||
<ButtonCreate />
|
||||
</Stack>
|
||||
</Center>
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack style={{ backgroundColor: "#191919" }} p="xl">
|
||||
<Group justify="space-between" mb="lg">
|
||||
<Title order={2} c="#EAEAEA" fw={600}>
|
||||
Webhook Manager
|
||||
</Title>
|
||||
<ButtonCreate />
|
||||
<Tooltip label="Refresh webhooks" withArrow color="cyan">
|
||||
<ActionIcon
|
||||
variant="light"
|
||||
size="lg"
|
||||
radius="xl"
|
||||
onClick={() => {
|
||||
mutate();
|
||||
notifications.show({
|
||||
title: "Refreshing data",
|
||||
message: "Webhook list is being updated...",
|
||||
color: "teal",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<IconRefresh color="#00FFFF" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Group>
|
||||
|
||||
<Stack gap="md">
|
||||
{webhooks.map((webhook) => (
|
||||
<Card
|
||||
key={webhook.id}
|
||||
p="lg"
|
||||
radius="xl"
|
||||
style={{
|
||||
background: "rgba(45,45,45,0.6)",
|
||||
backdropFilter: "blur(12px)",
|
||||
border: "1px solid rgba(0,255,200,0.2)",
|
||||
// boxShadow: "0 0 12px rgba(0,255,200,0.15)",
|
||||
transition: "transform 0.2s ease, box-shadow 0.2s ease",
|
||||
}}
|
||||
>
|
||||
<Group justify="end" mb="sm">
|
||||
<Group>
|
||||
<IconLink color="#00FFFF" />
|
||||
<Text c="#EAEAEA" fw={500} size="lg">
|
||||
{webhook.name}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
<ActionIcon
|
||||
c={"teal"}
|
||||
variant="light"
|
||||
size="lg"
|
||||
radius="xl"
|
||||
onClick={() =>
|
||||
navigate(
|
||||
`${clientRoutes["/sq/dashboard/webhook/webhook-edit"]}?id=${webhook.id}`,
|
||||
)
|
||||
}
|
||||
>
|
||||
<IconEdit />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
|
||||
<Stack gap="md">
|
||||
{webhooks.map((webhook) => (
|
||||
<Card
|
||||
key={webhook.id}
|
||||
p="lg"
|
||||
radius="xl"
|
||||
style={{
|
||||
background: "rgba(45,45,45,0.6)",
|
||||
backdropFilter: "blur(12px)",
|
||||
border: "1px solid rgba(0,255,200,0.2)",
|
||||
// boxShadow: "0 0 12px rgba(0,255,200,0.15)",
|
||||
transition: "transform 0.2s ease, box-shadow 0.2s ease",
|
||||
}}
|
||||
>
|
||||
<Group justify="end" mb="sm">
|
||||
<Group>
|
||||
<IconLink color="#00FFFF" />
|
||||
<Text c="#EAEAEA" fw={500} size="lg">
|
||||
{webhook.name}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
<ActionIcon
|
||||
c={"teal"}
|
||||
variant="light"
|
||||
size="lg"
|
||||
radius="xl"
|
||||
onClick={() => navigate(`${clientRoutes["/sq/dashboard/webhook/webhook-edit"]}?id=${webhook.id}`)}
|
||||
>
|
||||
<IconEdit />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
|
||||
<Stack gap={"md"}>
|
||||
<Group>
|
||||
<Badge
|
||||
color={webhook.enabled ? "teal" : "red"}
|
||||
radius="xl"
|
||||
leftSection={
|
||||
webhook.enabled ? (
|
||||
<IconCheck size={14} />
|
||||
) : (
|
||||
<IconX size={14} />
|
||||
)
|
||||
}
|
||||
>
|
||||
{webhook.enabled ? "Active" : "Disabled"}
|
||||
</Badge>
|
||||
<Badge bg={"teal"} leftSection={<IconMessageReply size={16} color="#00FFC8" />}>
|
||||
{webhook.replay ? "Replay" : "Not Replay"}
|
||||
</Badge>
|
||||
</Group>
|
||||
<Text c="#9A9A9A" size="sm">{webhook.description}</Text>
|
||||
</Stack>
|
||||
<Divider color="rgba(0,255,200,0.2)" my="sm" />
|
||||
|
||||
<Stack gap="xs">
|
||||
<Group gap="xs">
|
||||
<IconCode size={16} color="#00FFC8" />
|
||||
<Text c="#9A9A9A" size="sm">
|
||||
Method:
|
||||
</Text>
|
||||
<Text c="#EAEAEA" size="sm" fw={500}>
|
||||
{webhook.method}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
<Group gap="xs">
|
||||
<IconLink size={16} color="#00FFC8" />
|
||||
<Text c="#9A9A9A" size="sm">
|
||||
URL:
|
||||
</Text>
|
||||
<Text c="#EAEAEA" size="sm" fw={500}>
|
||||
{webhook.url}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
<Group gap="xs">
|
||||
<IconKey size={16} color="#00FFC8" />
|
||||
<Text c="#9A9A9A" size="sm">
|
||||
API Token:
|
||||
</Text>
|
||||
<Text c="#EAEAEA" size="sm" fw={500}>
|
||||
{webhook.apiToken?.slice(0, 6) + "..." || "—"}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
<Group gap="xs">
|
||||
<Text c="#9A9A9A" size="sm">
|
||||
Headers:
|
||||
</Text>
|
||||
<Text c="#EAEAEA" size="sm" fw={500}>
|
||||
{Object.keys(webhook.headers || {}).length
|
||||
? webhook.headers
|
||||
: "No headers configured"}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
<Group gap="xs">
|
||||
<Text c="#9A9A9A" size="sm">
|
||||
Payload:
|
||||
</Text>
|
||||
<Text c="#EAEAEA" size="sm" fw={500}>
|
||||
{Object.keys(webhook.payload || {}).length
|
||||
? webhook.payload
|
||||
: "Empty payload"}
|
||||
</Text>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Card>
|
||||
))}
|
||||
<Stack gap={"md"}>
|
||||
<Group>
|
||||
<Badge
|
||||
color={webhook.enabled ? "teal" : "red"}
|
||||
radius="xl"
|
||||
leftSection={
|
||||
webhook.enabled ? (
|
||||
<IconCheck size={14} />
|
||||
) : (
|
||||
<IconX size={14} />
|
||||
)
|
||||
}
|
||||
>
|
||||
{webhook.enabled ? "Active" : "Disabled"}
|
||||
</Badge>
|
||||
<Badge
|
||||
bg={"teal"}
|
||||
leftSection={<IconMessageReply size={16} color="#00FFC8" />}
|
||||
>
|
||||
{webhook.replay ? "Replay" : "Not Replay"}
|
||||
</Badge>
|
||||
</Group>
|
||||
<Text c="#9A9A9A" size="sm">
|
||||
{webhook.description}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
<Divider color="rgba(0,255,200,0.2)" my="sm" />
|
||||
|
||||
<Stack gap="xs">
|
||||
<Group gap="xs">
|
||||
<IconCode size={16} color="#00FFC8" />
|
||||
<Text c="#9A9A9A" size="sm">
|
||||
Method:
|
||||
</Text>
|
||||
<Text c="#EAEAEA" size="sm" fw={500}>
|
||||
{webhook.method}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
<Group gap="xs">
|
||||
<IconLink size={16} color="#00FFC8" />
|
||||
<Text c="#9A9A9A" size="sm">
|
||||
URL:
|
||||
</Text>
|
||||
<Text c="#EAEAEA" size="sm" fw={500}>
|
||||
{webhook.url}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
<Group gap="xs">
|
||||
<IconKey size={16} color="#00FFC8" />
|
||||
<Text c="#9A9A9A" size="sm">
|
||||
API Token:
|
||||
</Text>
|
||||
<Text c="#EAEAEA" size="sm" fw={500}>
|
||||
{webhook.apiToken?.slice(0, 6) + "..." || "—"}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
<Group gap="xs">
|
||||
<Text c="#9A9A9A" size="sm">
|
||||
Headers:
|
||||
</Text>
|
||||
<Text c="#EAEAEA" size="sm" fw={500}>
|
||||
{Object.keys(webhook.headers || {}).length
|
||||
? webhook.headers
|
||||
: "No headers configured"}
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
<Group gap="xs">
|
||||
<Text c="#9A9A9A" size="sm">
|
||||
Payload:
|
||||
</Text>
|
||||
<Text c="#EAEAEA" size="sm" fw={500}>
|
||||
{Object.keys(webhook.payload || {}).length
|
||||
? webhook.payload
|
||||
: "Empty payload"}
|
||||
</Text>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Card>
|
||||
))}
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
import {
|
||||
Button,
|
||||
Group,
|
||||
Stack,
|
||||
Title,
|
||||
Tooltip,
|
||||
Divider,
|
||||
Container,
|
||||
Paper,
|
||||
Button,
|
||||
Group,
|
||||
Stack,
|
||||
Title,
|
||||
Tooltip,
|
||||
Divider,
|
||||
Container,
|
||||
Paper,
|
||||
} from "@mantine/core";
|
||||
import { IconPlus } from "@tabler/icons-react";
|
||||
import { useNavigate, Outlet } from "react-router-dom";
|
||||
|
||||
export default function WebhookLayout() {
|
||||
const navigate = useNavigate();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<Outlet />
|
||||
);
|
||||
return <Outlet />;
|
||||
}
|
||||
|
||||
247
src/server/routes/flow_route.ts
Normal file
247
src/server/routes/flow_route.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import Elysia, { t } from 'elysia'
|
||||
import { prisma } from '../lib/prisma'
|
||||
import _ from 'lodash'
|
||||
|
||||
const getUrlToken = async () => await prisma.chatFlows.findUnique({ where: { id: "1" }, select: { flowUrl: true, flowToken: true } })
|
||||
|
||||
const FlowRoute = new Elysia({
|
||||
prefix: '/chatflows',
|
||||
detail: { tags: ['chatflows'] },
|
||||
})
|
||||
.get('/sync', async ctx => {
|
||||
const result = await getUrlToken()
|
||||
if (!result) {
|
||||
return { error: 'Flow URL and Token not found' }
|
||||
}
|
||||
const { flowUrl, flowToken } = result
|
||||
const response = await fetch(flowUrl + '/chatflows', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + flowToken,
|
||||
Accept: '*/*',
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
return { error: 'Failed to fetch flows' }
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
const chatflows = await prisma.chatFlows.upsert({
|
||||
where: {
|
||||
id: "1",
|
||||
},
|
||||
update: {
|
||||
flows: data,
|
||||
},
|
||||
create: {
|
||||
flows: data,
|
||||
},
|
||||
})
|
||||
return { data: chatflows }
|
||||
}, {
|
||||
detail: {
|
||||
summary: "Sync chatflows",
|
||||
description: "Sync chatflows",
|
||||
}
|
||||
})
|
||||
.get('/find', async ctx => {
|
||||
const result = await prisma.chatFlows.findUnique({
|
||||
where: { id: "1" },
|
||||
})
|
||||
if (!result) {
|
||||
return { flows: [], defaultFlow: null, flowUrl: null, flowToken: null }
|
||||
}
|
||||
const flows = _.orderBy(result?.flows as any[], ['type'], ['asc'])
|
||||
const defaultFlow = result?.defaultFlow
|
||||
const flowUrl = result?.flowUrl
|
||||
const flowToken = result?.flowToken
|
||||
return { flows, defaultFlow, flowUrl, flowToken }
|
||||
}, {
|
||||
detail: {
|
||||
summary: "Find chatflows",
|
||||
description: "Find chatflows",
|
||||
}
|
||||
})
|
||||
.get("/default", async ctx => {
|
||||
const result = await prisma.chatFlows.findUnique({
|
||||
where: { id: "1" },
|
||||
})
|
||||
if (!result) {
|
||||
return { defaultFlow: null, defaultData: null }
|
||||
}
|
||||
const defaultFlow = result?.defaultFlow
|
||||
const defaultData = result?.defaultData
|
||||
return { defaultFlow, defaultData }
|
||||
}, {
|
||||
detail: {
|
||||
summary: "Get default chatflows",
|
||||
description: "Get default chatflows",
|
||||
}
|
||||
})
|
||||
.put(
|
||||
'/default',
|
||||
async ctx => {
|
||||
const { id, defaultData } = ctx.body
|
||||
|
||||
const result = await prisma.chatFlows.update({
|
||||
where: {
|
||||
id: "1",
|
||||
},
|
||||
data: {
|
||||
defaultFlow: id,
|
||||
defaultData: defaultData,
|
||||
},
|
||||
})
|
||||
return { data: result }
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
id: t.String(),
|
||||
defaultData: t.Optional(t.Any()),
|
||||
}),
|
||||
detail: {
|
||||
summary: "Update default chatflows",
|
||||
description: "Update default chatflows",
|
||||
}
|
||||
}
|
||||
)
|
||||
.post(
|
||||
'/query',
|
||||
async ctx => {
|
||||
const { flowId, question } = ctx.body
|
||||
const result = await chatFlowQuery({ flowId, question })
|
||||
return { data: result }
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
flowId: t.String(),
|
||||
question: t.String(),
|
||||
}),
|
||||
detail: {
|
||||
summary: "Query chatflows",
|
||||
description: "Query chatflows",
|
||||
}
|
||||
}
|
||||
)
|
||||
.put(
|
||||
'/flow-active',
|
||||
async ctx => {
|
||||
const { active } = ctx.body
|
||||
const result = await prisma.chatFlows.upsert({
|
||||
where: {
|
||||
id: "1",
|
||||
},
|
||||
update: {
|
||||
active: active,
|
||||
},
|
||||
create: {
|
||||
active: active,
|
||||
},
|
||||
})
|
||||
return { data: result }
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
active: t.Boolean(),
|
||||
}),
|
||||
detail: {
|
||||
summary: "Update flow active",
|
||||
description: "Update flow active",
|
||||
}
|
||||
}
|
||||
)
|
||||
.get('/url-token', async ctx => {
|
||||
const result = await prisma.chatFlows.findUnique({
|
||||
where: { id: "1" },
|
||||
select: {
|
||||
flowUrl: true,
|
||||
flowToken: true,
|
||||
},
|
||||
})
|
||||
if (!result) {
|
||||
return { data: { flowUrl: null, flowToken: null } }
|
||||
}
|
||||
return { data: { flowUrl: result.flowUrl, flowToken: result.flowToken } }
|
||||
}, {
|
||||
detail: {
|
||||
summary: "Get flow url and token",
|
||||
description: "Get flow url and token",
|
||||
}
|
||||
})
|
||||
.put(
|
||||
'/url-token',
|
||||
async ctx => {
|
||||
const { flowUrl, flowToken } = ctx.body
|
||||
const result = await prisma.chatFlows.upsert({
|
||||
where: {
|
||||
id: "1",
|
||||
},
|
||||
update: {
|
||||
flowUrl: flowUrl,
|
||||
flowToken: flowToken,
|
||||
},
|
||||
create: {
|
||||
id: "1",
|
||||
flowUrl: flowUrl,
|
||||
flowToken: flowToken,
|
||||
},
|
||||
})
|
||||
return { data: result }
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
flowUrl: t.String(),
|
||||
flowToken: t.String(),
|
||||
}),
|
||||
detail: {
|
||||
summary: "Update flow url and token",
|
||||
description: "Update flow url and token",
|
||||
}
|
||||
}
|
||||
)
|
||||
.on('error', ctx => {
|
||||
console.log(ctx.error)
|
||||
return { error: ctx.error }
|
||||
})
|
||||
|
||||
export default FlowRoute
|
||||
|
||||
async function chatFlowQuery({
|
||||
flowId,
|
||||
question,
|
||||
}: {
|
||||
flowId: string
|
||||
question: string
|
||||
}) {
|
||||
try {
|
||||
const resultUrlToken = await prisma.chatFlows.findUnique({ where: { id: "1" }, select: { flowUrl: true, flowToken: true } })
|
||||
if (!resultUrlToken) {
|
||||
return { error: 'Flow URL and Token not found' }
|
||||
}
|
||||
const { flowUrl, flowToken } = resultUrlToken
|
||||
if (!flowUrl || !flowToken) {
|
||||
return { error: 'Flow URL and Token not found' }
|
||||
}
|
||||
const response = await fetch(`${flowUrl}/prediction/${flowId}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${flowToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
question,
|
||||
overrideConfig: {
|
||||
sessionId: "1",
|
||||
},
|
||||
}),
|
||||
})
|
||||
const result = await response.text()
|
||||
return JSON.parse(result).text
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
return 'Failed to fetch response'
|
||||
}
|
||||
}
|
||||
13
x.sh
13
x.sh
@@ -1,8 +1,7 @@
|
||||
TOKEN="EAALP22EWyC4BPnZCfcPQNmD5pGLKV6Ao3GIeWZCc81aPivDFc2FXGA1ZBgrRGcB60LaZCdAr1sbnfP1ufrH3dGthxQzpf18BTjDZBkgG3vBiYZAMpHa7MEZBiRIUZCBe4BDXe8KV0r7DsDmQHJqhA3yZBDKPOL1PKJPEqIq40tLxPwMqWYg4o7xf0sBmZCzx2wI1KtJL8I20MV1ggldngHZCIcnOKDL0uPzDAhc2LAQuI7ZBsgZDZD"
|
||||
MEDIA_ID="24893686766920074"
|
||||
BUSINESS_PHONE_NUMBER_ID="783866307805501"
|
||||
|
||||
#!/bin/bash
|
||||
TOKEN="EAALP22EWyC4BPv7XnK1xSaZCWccblEoJFbHzPZAf5mlp4678lSM7cqhQl1ExATf8abrOpinvvFF6U6ruK2FsJqIk8wg6DiUz2fc0NYfcwjon3ng7I3C5HSDQHecgTiJLUBxfZAcvE4IIlhks722jakXaJpojlByo8QJ0CEURtzwEU1guFq7YTX3Et0ZCkbhkdftZCOGmpUKFjL5w5nUdd26Nd58YrLVZCoT8NKhxpWFQZDZD"
|
||||
curl -i -X POST \
|
||||
https://graph.facebook.com/v22.0/838757782652201/messages \
|
||||
-H 'Authorization: Bearer $TOKEN' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{ "messaging_product": "whatsapp", "to": "6289505046093", "type": "template", "template": { "name": "hello_world", "language": { "code": "en_US" } } }'
|
||||
curl 'https://graph.facebook.com/v19.0/$MEDIA_ID?phone_number_id=$BUSINESS_PHONE_NUMBER_ID' \
|
||||
-H 'Authorization: Bearer $TOKEN' \
|
||||
-H 'Content-Type: application/json'
|
||||
24
xx.ts
Normal file
24
xx.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import fetch from "node-fetch";
|
||||
import fs from "fs";
|
||||
|
||||
const token = "EAALP22EWyC4BPrjshjjYBbPVKWp4Gp2ljkb7hCmgpZArLigB8XNmRoXBomDJm6aWnjpKpqehdVatbfFAHeGaQftGkNBp4Oyds9apr4lOQjG2YWYEzZC05ZAo7MARnfXn7FVua0iaeNMh2gunMZBd6pO58wjAUP3gqLiUrwASeOnJu5pW3tKg6fHubALBlQZDZD"; // dari Meta Developer > App > Access Token
|
||||
const mediaId = "838467435201133"; // dari webhook
|
||||
|
||||
// 1. Dapatkan URL file asli
|
||||
const mediaInfo = await fetch(
|
||||
`https://graph.facebook.com/v19.0/${mediaId}?access_token=${token}`
|
||||
).then(res => res.json()) as any;
|
||||
|
||||
// mediaInfo.url berisi link unduhan sementara
|
||||
const fileUrl = mediaInfo.url;
|
||||
|
||||
const fileResponse = await fetch(fileUrl, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`, // wajib!
|
||||
},
|
||||
});
|
||||
|
||||
const buffer = await fileResponse.arrayBuffer();
|
||||
fs.writeFileSync("sticker.webp", Buffer.from(buffer));
|
||||
|
||||
console.log("Sticker berhasil diunduh!");
|
||||
Reference in New Issue
Block a user