234 lines
13 KiB
HTML
234 lines
13 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Apprise Notify Center</title>
|
|
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
<link rel="stylesheet" href="/css/style.css">
|
|
</head>
|
|
<body class="bg-gray-100">
|
|
<div id="app">
|
|
<!-- 导航栏 -->
|
|
<nav class="bg-blue-600 text-white shadow-lg">
|
|
<div class="max-w-7xl mx-auto px-4">
|
|
<div class="flex justify-between h-16">
|
|
<div class="flex items-center">
|
|
<i class="fas fa-bell text-2xl mr-3"></i>
|
|
<span class="font-bold text-xl">Notify Center</span>
|
|
</div>
|
|
<div class="flex items-center space-x-6">
|
|
<a href="#" @click.prevent="currentView = 'channels'"
|
|
:class="{'text-blue-200': currentView !== 'channels'}"
|
|
class="hover:text-blue-200">
|
|
<i class="fas fa-broadcast-tower mr-1"></i>通道管理
|
|
</a>
|
|
<a href="#" @click.prevent="currentView = 'send'"
|
|
:class="{'text-blue-200': currentView !== 'send'}"
|
|
class="hover:text-blue-200">
|
|
<i class="fas fa-paper-plane mr-1"></i>手动发送
|
|
</a>
|
|
<a href="#" @click.prevent="currentView = 'history'"
|
|
:class="{'text-blue-200': currentView !== 'history'}"
|
|
class="hover:text-blue-200">
|
|
<i class="fas fa-history mr-1"></i>历史记录
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- 主内容区 -->
|
|
<main class="max-w-7xl mx-auto px-4 py-8">
|
|
<!-- 通道管理 -->
|
|
<div v-if="currentView === 'channels'" class="space-y-6">
|
|
<div class="flex justify-between items-center">
|
|
<h2 class="text-2xl font-bold text-gray-800">通道管理</h2>
|
|
<button @click="showChannelModal = true"
|
|
class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700">
|
|
<i class="fas fa-plus mr-2"></i>添加通道
|
|
</button>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-lg shadow overflow-hidden">
|
|
<table class="min-w-full">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">名称</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">类型</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">标签</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">状态</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-200">
|
|
<tr v-for="channel in channels" :key="channel.id">
|
|
<td class="px-6 py-4">{{ channel.name }}</td>
|
|
<td class="px-6 py-4">
|
|
<span class="px-2 py-1 bg-gray-100 rounded text-sm">{{ channel.type }}</span>
|
|
</td>
|
|
<td class="px-6 py-4">
|
|
<span v-for="tag in channel.tags" :key="tag"
|
|
class="px-2 py-1 bg-blue-100 text-blue-800 rounded text-xs mr-1">
|
|
{{ tag }}
|
|
</span>
|
|
</td>
|
|
<td class="px-6 py-4">
|
|
<span :class="channel.is_active ? 'text-green-600' : 'text-gray-400'">
|
|
{{ channel.is_active ? '活跃' : '禁用' }}
|
|
</span>
|
|
</td>
|
|
<td class="px-6 py-4 space-x-2">
|
|
<button @click="testChannel(channel.id)"
|
|
class="text-blue-600 hover:text-blue-800">
|
|
<i class="fas fa-vial"></i> 测试
|
|
</button>
|
|
<button @click="deleteChannel(channel.id)"
|
|
class="text-red-600 hover:text-red-800">
|
|
<i class="fas fa-trash"></i> 删除
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 手动发送 -->
|
|
<div v-if="currentView === 'send'" class="max-w-2xl">
|
|
<h2 class="text-2xl font-bold text-gray-800 mb-6">手动发送通知</h2>
|
|
<div class="bg-white rounded-lg shadow p-6 space-y-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">选择通道</label>
|
|
<select v-model="sendForm.channels" multiple
|
|
class="w-full border rounded-lg px-3 py-2 h-32">
|
|
<option v-for="channel in channels" :key="channel.id" :value="channel.name">
|
|
{{ channel.name }} ({{ channel.type }})
|
|
</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">优先级</label>
|
|
<select v-model="sendForm.priority" class="w-full border rounded-lg px-3 py-2">
|
|
<option value="low">低</option>
|
|
<option value="normal">普通</option>
|
|
<option value="high">高</option>
|
|
<option value="urgent">紧急</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">标题(可选)</label>
|
|
<input v-model="sendForm.title" type="text"
|
|
class="w-full border rounded-lg px-3 py-2">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">内容</label>
|
|
<textarea v-model="sendForm.body" rows="4"
|
|
class="w-full border rounded-lg px-3 py-2" required></textarea>
|
|
</div>
|
|
<button @click="sendNotification"
|
|
:disabled="sending"
|
|
class="w-full bg-blue-600 text-white py-3 rounded-lg hover:bg-blue-700 disabled:bg-gray-400">
|
|
<i v-if="!sending" class="fas fa-paper-plane mr-2"></i>
|
|
<i v-else class="fas fa-spinner fa-spin mr-2"></i>
|
|
{{ sending ? '发送中...' : '发送通知' }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 历史记录 -->
|
|
<div v-if="currentView === 'history'" class="space-y-6">
|
|
<h2 class="text-2xl font-bold text-gray-800">发送历史</h2>
|
|
<div class="bg-white rounded-lg shadow overflow-hidden">
|
|
<table class="min-w-full">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">时间</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">通道</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">标题</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">状态</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-200">
|
|
<tr v-for="n in notifications" :key="n.id">
|
|
<td class="px-6 py-4 text-sm">{{ formatDate(n.created_at) }}</td>
|
|
<td class="px-6 py-4">{{ n.channel?.name || '-' }}</td>
|
|
<td class="px-6 py-4">{{ n.title || '-' }}</td>
|
|
<td class="px-6 py-4">
|
|
<span :class="getStatusClass(n.status)">
|
|
{{ n.status }}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<!-- 通道编辑模态框 -->
|
|
<div v-if="showChannelModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|
<div class="bg-white rounded-lg shadow-xl max-w-lg w-full mx-4 max-h-screen overflow-y-auto">
|
|
<div class="p-6">
|
|
<h3 class="text-lg font-bold mb-4">添加通道</h3>
|
|
<form @submit.prevent="saveChannel" class="space-y-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">名称</label>
|
|
<input v-model="channelForm.name" type="text" required
|
|
class="w-full border rounded-lg px-3 py-2">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">类型</label>
|
|
<select v-model="channelForm.type" required
|
|
class="w-full border rounded-lg px-3 py-2">
|
|
<option value="discord">Discord</option>
|
|
<option value="telegram">Telegram</option>
|
|
<option value="email">Email</option>
|
|
<option value="slack">Slack</option>
|
|
<option value="apprise">Apprise URL</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">配置 (JSON)</label>
|
|
<textarea v-model="channelForm.configJson" rows="4" required
|
|
class="w-full border rounded-lg px-3 py-2 font-mono text-sm"
|
|
placeholder='{"webhook_url": "https://..."}'></textarea>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">标签(逗号分隔)</label>
|
|
<input v-model="channelForm.tags" type="text"
|
|
class="w-full border rounded-lg px-3 py-2"
|
|
placeholder="alerts, production">
|
|
</div>
|
|
<div class="flex items-center">
|
|
<input v-model="channelForm.is_active" type="checkbox" id="is_active"
|
|
class="mr-2">
|
|
<label for="is_active">启用</label>
|
|
</div>
|
|
<div class="flex justify-end space-x-3 mt-6">
|
|
<button type="button" @click="showChannelModal = false"
|
|
class="px-4 py-2 text-gray-600 hover:text-gray-800">
|
|
取消
|
|
</button>
|
|
<button type="submit"
|
|
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
|
|
保存
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 消息提示 -->
|
|
<div v-if="message" :class="`fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg ${message.type === 'success' ? 'bg-green-500' : 'bg-red-500'} text-white`">
|
|
{{ message.text }}
|
|
</div>
|
|
</div>
|
|
|
|
<script src="/js/app.js"></script>
|
|
</body>
|
|
</html>
|