Merge pull request #73 from gujiangjiang/main

feat: 为新用户添加默认的通知渠道和消息模板、完善PWA应用
This commit is contained in:
阿杰鲁 authored and GitHub committed 2025-10-10 05:05:45 +08:00
commit 3b7da479aa
14 files changed
+1786 -1855

No files matched your search

+13 -1
View File
@@ -72,9 +72,21 @@ jobs:
D1_DATABASE_ID: ${{ secrets.D1_DATABASE_ID }}
D1_DATABASE_NAME: ${{ secrets.D1_DATABASE_NAME }}
# 新增步骤:检查 Cloudflare 密钥是否存在
- name: Check for Cloudflare credentials
id: cf_creds
run: |
if [ -z "${{ secrets.CF_API_TOKEN }}" ] || [ -z "${{ secrets.CF_ACCOUNT_ID }}" ]; then
echo "Cloudflare credentials not found, skipping deployment."
echo "has_creds=false" >> $GITHUB_ENV
else
echo "has_creds=true" >> $GITHUB_ENV
fi
# 第八步:部署到 Cloudflare
# 使用 Cloudflare 官方的 'wrangler-action' 来执行部署
# 仅在检测到密钥存在时执行
- name: Deploy to Cloudflare
if: env.has_creds == 'true'
uses: cloudflare/wrangler-action@v3
with:
# 传入 API 令牌用于身份验证
+12
View File
@@ -18,14 +18,26 @@ jobs:
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
-
name: Check for Docker Hub credentials
id: docker_creds
run: |
if [ -z "${{ secrets.DOCKERHUB_USERNAME }}" ] || [ -z "${{ secrets.DOCKERHUB_TOKEN }}" ]; then
echo "Docker Hub credentials not found, skipping login and push."
echo "has_creds=false" >> $GITHUB_ENV
else
echo "has_creds=true" >> $GITHUB_ENV
fi
-
name: Login to Docker Hub
if: env.has_creds == 'true'
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
if: env.has_creds == 'true'
uses: docker/build-push-action@v3
with:
context: .
+2 -37
View File
@@ -182,41 +182,6 @@ export const updateNotificationTemplate = async (
Omit<NotificationTemplate, "id" | "created_at" | "updated_at">
>
): Promise<boolean> => {
const sets: string[] = [];
const values: any[] = [];
if (template.name !== undefined) {
sets.push("name = ?");
values.push(template.name);
}
if (template.type !== undefined) {
sets.push("type = ?");
values.push(template.type);
}
if (template.subject !== undefined) {
sets.push("subject = ?");
values.push(template.subject);
}
if (template.content !== undefined) {
sets.push("content = ?");
values.push(template.content);
}
if (template.is_default !== undefined) {
sets.push("is_default = ?");
values.push(template.is_default ? 1 : 0);
}
if (sets.length === 0) {
return false;
}
sets.push("updated_at = CURRENT_TIMESTAMP");
values.push(id);
const result = await db
.update(notificationTemplates)
.set({
@@ -228,7 +193,7 @@ export const updateNotificationTemplate = async (
})
.where(and(eq(notificationTemplates.id, id), eq(notificationTemplates.created_by, userId)));
return result.length > 0;
return result.success; // fix: 返回 success 布尔值
};
// 删除通知模板
@@ -240,7 +205,7 @@ export const deleteNotificationTemplate = async (
.delete(notificationTemplates)
.where(and(eq(notificationTemplates.id, id), eq(notificationTemplates.created_by, userId)));
return result.length > 0;
return result.success; // fix: 返回 success 布尔值
};
+6
View File
@@ -9,6 +9,7 @@ import * as repositories from "../repositories";
import { getJwtSecret } from "../utils/jwt";
import { Bindings } from "../models/db";
import * as SettingsService from "./SettingsService";
import * as NotificationService from "./NotificationService";
/**
* 用户登录
@@ -143,6 +144,11 @@ export async function registerUser(
"user"
);
// 为新用户创建默认的通知设置
if (newUser && newUser.id) {
await NotificationService.createDefaultNotificationSettingsForUser(newUser.id);
}
return {
success: true,
message: "注册成功",
@@ -998,3 +998,85 @@ export async function deleteNotificationSettings(
message: `${type}通知设置删除成功`,
};
}
/**
* 为新用户创建默认的通知设置
* @param userId 新用户的ID
*/
export async function createDefaultNotificationSettingsForUser(userId: number): Promise<void> {
try {
console.log(`为新用户 ${userId} 创建默认通知设置...`);
const now = new Date().toISOString();
// 创建默认通知模板
await repositories.createNotificationTemplate({
name: "Monitor监控模板",
type: "monitor",
subject: "【${status}】${name} 监控状态变更",
content:
"🔔 网站监控状态变更通知\n\n📊 服务: ${name}\n🔄 状态: ${status} (之前: ${previous_status})\n🕒 时间: ${time}\n\n🔗 地址: ${url}\n⏱️ 响应时间: ${response_time}\n📝 实际状态码: ${status_code}\n🎯 期望状态码: ${expected_status}\n\n❗ 错误信息: ${error}",
is_default: true, // 修复: 将 1 修改为 true
created_by: userId,
});
await repositories.createNotificationTemplate({
name: "Agent监控模板",
type: "agent",
subject: "【${status}】${name} 客户端状态变更",
content:
"🔔 客户端状态变更通知\n\n📊 主机: ${name}\n🔄 状态: ${status} (之前: ${previous_status})\n🕒 时间: ${time}\n\n🖥️ 主机信息:\n 主机名: ${hostname}\n IP地址: ${ip_addresses}\n 操作系统: ${os}\n\n❗ 错误信息: ${error}",
is_default: true, // 修复: 将 1 修改为 true
created_by: userId,
});
// 创建默认通知渠道
const defaultChannelId = await repositories.createNotificationChannel({
name: "默认Telegram通知渠道",
type: "telegram",
config:
'{"botToken": "8163201319:AAGyY7FtdaRb6o8NCVXSbBUb6ofDK45cNJU", "chatId": "-1002608818360"}',
enabled: true,
created_by: userId,
});
// 创建默认通知设置
await repositories.createOrUpdateSettings({
user_id: userId,
target_type: "global-monitor",
target_id: 0,
enabled: false,
on_down: true,
on_recovery: true,
on_offline: true, // 确保所有布尔字段都有值
on_cpu_threshold: false,
cpu_threshold: 90,
on_memory_threshold: false,
memory_threshold: 85,
on_disk_threshold: false,
disk_threshold: 90,
channels: JSON.stringify([defaultChannelId]),
});
await repositories.createOrUpdateSettings({
user_id: userId,
target_type: "global-agent",
target_id: 0,
enabled: false,
on_down: true,
on_recovery: true,
on_offline: true,
on_cpu_threshold: true,
cpu_threshold: 80,
on_memory_threshold: true,
memory_threshold: 80,
on_disk_threshold: true,
disk_threshold: 90,
channels: JSON.stringify([defaultChannelId]),
});
console.log(`为新用户 ${userId} 创建默认通知设置成功`);
} catch (error) {
console.error(`为新用户 ${userId} 创建默认通知设置失败:`, error);
// 此处不向上抛出异常,以免影响用户创建的主流程
}
}
+6
View File
@@ -1,5 +1,6 @@
import { compare, hash } from "bcryptjs";
import * as repositories from "../repositories";
import * as NotificationService from "./NotificationService"; // 导入通知服务
export async function getAllUsersService(userRole: string) {
try {
@@ -91,6 +92,11 @@ export async function createUserService(
userData.role
);
// 为新用户创建默认的通知设置
if (newUser && newUser.id) {
await NotificationService.createDefaultNotificationSettingsForUser(newUser.id);
}
return { success: true, user: newUser, status: 201 };
} catch (error) {
console.error("创建用户错误:", error);
+1 -1
View File
@@ -6,7 +6,7 @@
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/manifest.webmanifest" />
<!-- 移除了 manifest 链接,插件会自动注入 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>XUGOU - 轻量化监控平台</title>
</head>
+2 -1
View File
@@ -61,6 +61,7 @@
"globals": "^13.24.0",
"tw-animate-css": "^1.3.0",
"typescript": "~5.1.6",
"vite": "^6.0.0"
"vite": "^6.0.0",
"vite-plugin-pwa": "^0.20.1"
}
}
+16 -7
View File
@@ -1,11 +1,20 @@
self.addEventListener("install", (event) => {
// 这个文件可以保留为空,或者删除。
// vite-plugin-pwa 会在构建时自动生成新的 sw.js 文件到输出目录(dist)。
// 开发环境中,为了避免旧的 Service Worker 缓存导致问题,我们最好确保它被正确地注销。
// 如果您在开发时遇到缓存问题,可以取消下面代码的注释来强制注销。
/*
self.addEventListener('install', (e) => {
self.skipWaiting();
});
self.addEventListener("activate", (event) => {
self.clients.claim();
self.addEventListener('activate', (e) => {
e.waitUntil(
self.registration.unregister().then(() => {
return self.clients.matchAll();
}).then(clients => {
clients.forEach(client => client.navigate(client.url));
})
);
});
self.addEventListener("fetch", (event) => {
event.respondWith(fetch(event.request));
});
*/
+24 -1
View File
@@ -516,6 +516,21 @@ const enUS = {
"notifications.save.error": "Failed to save notification settings",
"notifications.globalSettings.description": "Configure global notification settings, including notification channels, monitor settings, and message templates. These settings will apply to all monitors and agents unless overridden by specific configurations.",
// Notification Variables
"notifications.variables.name": "Name",
"notifications.variables.status": "Status",
"notifications.variables.previous_status": "Previous Status",
"notifications.variables.time": "Time",
"notifications.variables.url": "URL",
"notifications.variables.response_time": "Response Time",
"notifications.variables.status_code": "Status Code",
"notifications.variables.expected_status": "Expected Status",
"notifications.variables.error": "Error",
"notifications.variables.details": "Details",
"notifications.variables.hostname": "Hostname",
"notifications.variables.ip_addresses": "IP Addresses",
"notifications.variables.os": "Operating System",
// Notification Event Types
"notifications.events.onDownOnly": "Send notification only when service goes down",
"notifications.events.onRecovery": "Send notification when service recovers",
@@ -565,7 +580,15 @@ const enUS = {
// StatusSummaryCard Component
"statusSummary.title": "Status Summary",
//补全
"notifications.fetch.error": "Failed to fetch notification configuration.",
"notifications.templates.createSuccess": "Template created successfully.",
"notifications.templates.updateSuccess": "Template updated successfully.",
"notifications.templates.deleteConfirmTitle": "Confirm Delete",
"notifications.templates.deleteConfirmMessage": "Are you sure you want to delete this template?",
"notifications.templates.deleteSuccess": "Template deleted successfully."
},
};
export default enUS;
export default enUS;
+24 -1
View File
@@ -513,6 +513,21 @@ const zhCN = {
"notifications.globalSettings.description":
"配置全局通知设置,包括通知渠道、监控设置和消息模板。这些设置将应用于所有监控和客户端,除非被特定的配置覆盖。",
// 通知变量
"notifications.variables.name": "名称",
"notifications.variables.status": "状态",
"notifications.variables.previous_status": "先前状态",
"notifications.variables.time": "时间",
"notifications.variables.url": "URL地址",
"notifications.variables.response_time": "响应时间",
"notifications.variables.status_code": "状态码",
"notifications.variables.expected_status": "预期状态码",
"notifications.variables.error": "错误信息",
"notifications.variables.details": "详情",
"notifications.variables.hostname": "主机名",
"notifications.variables.ip_addresses": "IP地址",
"notifications.variables.os": "操作系统",
// 通知事件类型
"notifications.events.onDownOnly": "仅服务故障时发送通知",
"notifications.events.onRecovery": "服务恢复时发送通知",
@@ -562,7 +577,15 @@ const zhCN = {
// StatusSummaryCard 组件
"statusSummary.title": "状态摘要",
//补全
"notifications.fetch.error": "获取通知配置失败。",
"notifications.templates.createSuccess": "模板创建成功。",
"notifications.templates.updateSuccess": "模板更新成功。",
"notifications.templates.deleteConfirmTitle": "确认删除",
"notifications.templates.deleteConfirmMessage": "你确定要删除这个模板吗?",
"notifications.templates.deleteSuccess": "模板删除成功。"
},
};
export default zhCN;
export default zhCN;
+2 -12
View File
@@ -8,18 +8,8 @@ import "./styles/global.css";
import "./i18n/config";
import router from "./router";
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker
.register("/sw.js")
.then((registration) => {
console.log("Service Worker 注册成功:", registration);
})
.catch((registrationError) => {
console.log("Service Worker 注册失败:", registrationError);
});
});
}
// 删除了手动的 Service Worker 注册代码
// vite-plugin-pwa 会自动处理
createRoot(document.getElementById("root")!).render(
<StrictMode>
File diff suppressed because it is too large. Load diff
+45 -1
View File
@@ -2,10 +2,54 @@ import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";
import tailwindcss from "@tailwindcss/vite";
import { VitePWA } from 'vite-plugin-pwa'; // 导入 PWA 插件
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss()],
plugins: [
react(),
tailwindcss(),
// 添加 PWA 插件配置
VitePWA({
registerType: 'autoUpdate', // 自动更新 Service Worker
injectRegister: 'auto', // 自动注入注册 Service Worker 的代码
workbox: {
// 配置需要缓存的文件类型
globPatterns: ['**/*.{js,css,html,ico,png,svg,webmanifest}']
},
// 包含在 public 目录中的其他需要缓存的资源
includeAssets: ['favicon.svg', 'apple-touch-icon.png'],
// PWA 应用清单配置
manifest: {
name: 'XUGOU',
short_name: 'XUGOU',
description: 'XUGOU 是一个基于 CloudFlare 的轻量化系统监控平台,提供系统监控和状态页面功能。',
theme_color: '#4F46E5',
background_color: '#ffffff',
display: 'standalone',
scope: '/',
start_url: '/',
icons: [
{
src: 'android-chrome-192x192.png',
sizes: '192x192',
type: 'image/png'
},
{
src: 'android-chrome-512x512.png',
sizes: '512x512',
type: 'image/png'
},
{
src: 'android-chrome-512x512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'maskable' // 添加 'maskable' 图标, 优化 Android 上的显示效果
}
]
}
})
],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"), // __dirname 指向 vite.config.ts 文件所在的目录