Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
759c324
Merge pull request #22 from operacle/develop
tolaleng May 26, 2025
aec7118
Merge pull request #25 from operacle/develop
tolaleng May 28, 2025
dd758b9
Merge pull request #27 from operacle/develop
tolaleng May 31, 2025
4ec0c7a
Merge pull request #32 from operacle/develop
tolaleng Jun 4, 2025
4dc005c
Merge pull request #33 from operacle/develop
tolaleng Jun 5, 2025
1dc25cc
Merge pull request #35 from operacle/develop
tolaleng Jun 6, 2025
9b1918e
Merge pull request #36 from operacle/develop
tolaleng Jun 6, 2025
68a4ad5
Merge pull request #38 from operacle/develop
tolaleng Jun 8, 2025
9d5fa80
Merge pull request #42 from operacle/develop
tolaleng Jun 12, 2025
cfdedb9
Merge pull request #45 from operacle/develop
tolaleng Jun 15, 2025
9a001f6
Merge pull request #49 from operacle/develop
tolaleng Jun 18, 2025
9ed4d4c
Merge pull request #50 from operacle/develop
tolaleng Jun 19, 2025
9f4e759
Merge pull request #51 from operacle/develop
tolaleng Jun 19, 2025
f88cc32
Merge pull request #52 from operacle/develop
tolaleng Jun 20, 2025
23f3c5b
Merge pull request #53 from operacle/develop
tolaleng Jun 21, 2025
9a4cc7c
Merge pull request #57 from operacle/develop
tolaleng Jun 26, 2025
474a439
Merge pull request #59 from operacle/develop
tolaleng Jun 30, 2025
eddc120
Merge pull request #60 from operacle/develop
tolaleng Jul 1, 2025
8bd8ae2
Merge pull request #61 from operacle/develop
tolaleng Jul 2, 2025
cb6946b
Create FUNDING.yml
tolaleng Jul 3, 2025
6702bfa
Merge pull request #62 from operacle/develop
tolaleng Jul 3, 2025
ad869c1
add codeowners and pull request workflow
tolaleng Jul 3, 2025
e29f604
Merge pull request #64 from operacle/develop
tolaleng Jul 8, 2025
547a4c4
Merge pull request #65 from operacle/develop
tolaleng Jul 10, 2025
3f975db
Merge pull request #68 from operacle/develop
tolaleng Jul 13, 2025
cc132a6
Merge pull request #70 from operacle/develop
tolaleng Jul 15, 2025
72984f5
Merge pull request #79 from operacle/develop
tolaleng Jul 22, 2025
af64ac3
Merge pull request #83 from operacle/develop
tolaleng Jul 23, 2025
27ee133
Merge pull request #84 from operacle/develop
tolaleng Jul 23, 2025
b151f89
add i18n for zh-CN
chennqqi Jul 25, 2025
1a72392
notification: add wecom
chennqqi Jul 25, 2025
59ba0f0
language fix
chennqqi Jul 29, 2025
ed1dffa
Merge remote-tracking branch 'remotes/official/develop'
chennqqi Jul 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified application/bun.lockb
Binary file not shown.
111 changes: 111 additions & 0 deletions application/src/api/realtime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,117 @@ export default async function handler(req) {
description: "Signal message sent successfully (simulated)"
}
};
}
// Handle WeChat Work (WeCom) notifications
else if (type === "wecom") {
const { webhookUrl, message } = body;

if (!webhookUrl || !message) {
console.error("Missing required parameters for WeChat Work notification", {
hasWebhookUrl: !!webhookUrl,
hasMessage: !!message
});

return {
status: 400,
json: {
ok: false,
error_code: 400,
description: "Missing required WeChat Work parameters"
}
};
}

try {
console.log("Attempting to call WeChat Work webhook API");
console.log("Calling WeChat Work webhook URL: [REDACTED]");

// Parse the message to get the JSON payload
let messagePayload;
try {
messagePayload = JSON.parse(message);
} catch (e) {
console.error("Error parsing WeChat Work message payload:", e);
return {
status: 400,
json: {
ok: false,
error_code: 400,
description: "Invalid WeChat Work message format"
}
};
}

const response = await fetch(webhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(messagePayload),
});

if (!response.ok) {
const errorText = await response.text();
console.error(`WeChat Work API error (${response.status}):`, errorText);

try {
// Try to parse error as JSON if possible
const errorJson = JSON.parse(errorText);
return {
status: response.status,
json: errorJson
};
} catch (e) {
// If parsing fails, return the raw error
return {
status: response.status,
json: {
ok: false,
error_code: response.status,
description: `WeChat Work API error: ${errorText}`
}
};
}
}

const result = await response.json();
console.log("WeChat Work API response:", JSON.stringify(result, null, 2));

// WeChat Work API returns errcode: 0 for success
if (result.errcode !== 0) {
console.error("WeChat Work API error:", result);
return {
status: 400,
json: {
ok: false,
error_code: result.errcode,
description: result.errmsg || "Unknown WeChat Work API error"
}
};
}

console.log("Successfully sent message to WeChat Work!");
return {
status: 200,
json: {
ok: true,
result: result,
description: "Message sent successfully to WeChat Work"
}
};
} catch (error) {
console.error("Error calling WeChat Work API:", error);

// Return detailed error information
return {
status: 500,
json: {
ok: false,
error_code: 500,
description: `Error sending WeChat Work message: ${error instanceof Error ? error.message : "Unknown error"}`
}
};
}
} else {
// Return error for unsupported notification type
console.error("Unsupported notification type:", type);
Expand Down
3 changes: 3 additions & 0 deletions application/src/components/dashboard/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ export const Header = ({
<DropdownMenuItem onClick={() => setLanguage("ja")} className={language === "ja" ? "bg-accent" : ""}>
{t("japanese")}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setLanguage("zh-CN")} className={language === "zh-CN" ? "bg-accent" : ""}>
{t("simplifiedChinese")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

import React, { useEffect } from "react";
import React, { useEffect, useState } from "react";
import {
Dialog,
DialogContent,
Expand All @@ -24,7 +24,9 @@ import {
FormMessage,
} from "@/components/ui/form";
import { Switch } from "@/components/ui/switch";
import { Loader2 } from "lucide-react";
import { Loader2, MessageSquare } from "lucide-react";
import TestWecomDialog from "./TestWecomDialog";
import { useLanguage } from "@/contexts/LanguageContext";

interface NotificationChannelDialogProps {
open: boolean;
Expand All @@ -34,7 +36,7 @@ interface NotificationChannelDialogProps {

const baseSchema = z.object({
notify_name: z.string().min(1, "Name is required"),
notification_type: z.enum(["telegram", "discord", "slack", "signal", "email"]),
notification_type: z.enum(["telegram", "discord", "slack", "signal", "email", "wecom"]),
enabled: z.boolean().default(true),
service_id: z.string().default("global"), // Assuming global for now, could be linked to specific services
template_id: z.string().optional(),
Expand Down Expand Up @@ -66,12 +68,18 @@ const emailSchema = baseSchema.extend({
// Email specific fields could be added here
});

const wecomSchema = baseSchema.extend({
notification_type: z.literal("wecom"),
wecom_webhook_url: z.string().url("Must be a valid URL"),
});

const formSchema = z.discriminatedUnion("notification_type", [
telegramSchema,
discordSchema,
slackSchema,
signalSchema,
emailSchema
emailSchema,
wecomSchema
]);

type FormValues = z.infer<typeof formSchema>;
Expand All @@ -96,6 +104,8 @@ export const NotificationChannelDialog = ({
const { watch, reset } = form;
const notificationType = watch("notification_type");
const [isSubmitting, setIsSubmitting] = React.useState(false);
const [showTestWecomDialog, setShowTestWecomDialog] = useState(false);
const { language } = useLanguage();

useEffect(() => {
if (editingConfig) {
Expand Down Expand Up @@ -224,6 +234,14 @@ export const NotificationChannelDialog = ({
Email
</FormLabel>
</FormItem>
<FormItem className="flex items-center space-x-3 space-y-0">
<FormControl>
<RadioGroupItem value="wecom" />
</FormControl>
<FormLabel className="font-normal">
{language === "zh-CN" ? "企业微信" : "Wecom"}
</FormLabel>
</FormItem>
</RadioGroup>
</FormControl>
<FormMessage />
Expand Down Expand Up @@ -311,6 +329,38 @@ export const NotificationChannelDialog = ({
/>
)}

{notificationType === "wecom" && (
<>
<FormField
control={form.control}
name="wecom_webhook_url"
render={({ field }) => (
<FormItem>
<FormLabel>Webhook URL</FormLabel>
<FormControl>
<Input placeholder={language === "zh-CN" ? "企业微信机器人Webhook URL" : "Wecom Bot Webhook URL"} {...field} />
</FormControl>
<FormDescription>
{language === "zh-CN" ? "在企业微信群聊中添加机器人,获取Webhook URL" : "Add a bot in Wecom group chat to get the Webhook URL"}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
{isEditing && editingConfig && editingConfig.wecom_webhook_url && (
<Button
type="button"
variant="outline"
className="flex items-center gap-2 mt-2"
onClick={() => setShowTestWecomDialog(true)}
>
<MessageSquare className="h-4 w-4" />
{language === "zh-CN" ? "发送测试消息" : "Send Test Message"}
</Button>
)}
</>
)}

<FormField
control={form.control}
name="enabled"
Expand Down Expand Up @@ -344,6 +394,13 @@ export const NotificationChannelDialog = ({
</form>
</Form>
</DialogContent>

{/* 测试企业微信通知对话框 */}
<TestWecomDialog
open={showTestWecomDialog}
onOpenChange={setShowTestWecomDialog}
config={editingConfig}
/>
</Dialog>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Switch } from "@/components/ui/switch";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { alertConfigService } from "@/services/alertConfigService";
import { useLanguage } from "@/contexts/LanguageContext";

interface NotificationChannelListProps {
channels: AlertConfiguration[];
Expand All @@ -25,6 +26,7 @@ export const NotificationChannelList = ({
onEdit,
onDelete
}: NotificationChannelListProps) => {
const { language } = useLanguage();
const toggleEnabled = async (config: AlertConfiguration) => {
if (!config.id) return;

Expand All @@ -43,6 +45,7 @@ export const NotificationChannelList = ({
case "slack": return "Slack";
case "signal": return "Signal";
case "email": return "Email";
case "wecom": return language === "zh-CN" ? "企业微信" : "Wecom";
default: return type;
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import { Plus, Loader2 } from "lucide-react";
import { AlertConfiguration, alertConfigService } from "@/services/alertConfigService";
import { NotificationChannelDialog } from "./NotificationChannelDialog";
import { NotificationChannelList } from "./NotificationChannelList";
import { useLanguage } from "@/contexts/LanguageContext";

const NotificationSettings = () => {
const [isLoading, setIsLoading] = useState(true);
const [alertConfigs, setAlertConfigs] = useState<AlertConfiguration[]>([]);
const [dialogOpen, setDialogOpen] = useState(false);
const [currentTab, setCurrentTab] = useState<string>("all");
const [editingConfig, setEditingConfig] = useState<AlertConfiguration | null>(null);
const { language } = useLanguage();

const fetchAlertConfigurations = async () => {
setIsLoading(true);
Expand Down Expand Up @@ -87,6 +89,7 @@ const NotificationSettings = () => {
<TabsTrigger value="slack">Slack</TabsTrigger>
<TabsTrigger value="signal">Signal</TabsTrigger>
<TabsTrigger value="email">Email</TabsTrigger>
<TabsTrigger value="wecom">{language === "zh-CN" ? "企业微信" : "Wecom"}</TabsTrigger>
</TabsList>

<TabsContent value={currentTab} className="mt-0">
Expand Down
Loading