Merge pull request #13 from d0zingcat/feautre/ui_improve

This commit is contained in:
2026-01-15 23:14:14 +08:00
committed by GitHub
10 changed files with 561 additions and 25 deletions

1
.gitignore vendored
View File

@@ -34,7 +34,6 @@ pnpm-debug.log*
# Database
*.sqlite
*.db
apps/server/drizzle/meta
# Bun
.bun/

View File

@@ -4,6 +4,17 @@
本文件的格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/)
并且本项目遵循 [语义化版本 (Semantic Versioning)](https://semver.org/lang/zh-CN/spec/v2.0.0.html)。
## [1.3.0] - 2026-01-15
### 新增
- **视觉品牌**: 引入了自定义图标和 Favicon。
- 为 "Alert Message Center" 专门设计的现代 Indigo 主题 Logo。
- 将 Logo 集成到登录界面和顶部导航栏,替换了通用的 Activity 图标。
### 修复
- **部署可靠性**: 修复了 Docker 环境下数据库迁移失败的问题。
-`.gitignore` 中移除了对 `apps/server/drizzle/meta` 的忽略,确保 Drizzle 迁移日志 (`_journal.json`) 能正确打包进 Docker 镜像。
## [1.2.7] - 2026-01-15

View File

@@ -0,0 +1,514 @@
{
"id": "bc7589bc-5cb6-40c9-ab16-2076a0a2133f",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.alert_logs": {
"name": "alert_logs",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"task_id": {
"name": "task_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true
},
"error": {
"name": "error",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"alert_logs_task_id_alert_tasks_id_fk": {
"name": "alert_logs_task_id_alert_tasks_id_fk",
"tableFrom": "alert_logs",
"tableTo": "alert_tasks",
"columnsFrom": [
"task_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.alert_tasks": {
"name": "alert_tasks",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"topic_slug": {
"name": "topic_slug",
"type": "text",
"primaryKey": false,
"notNull": false
},
"sender_id": {
"name": "sender_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'pending'"
},
"recipient_count": {
"name": "recipient_count",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"success_count": {
"name": "success_count",
"type": "integer",
"primaryKey": false,
"notNull": false,
"default": 0
},
"payload": {
"name": "payload",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"error": {
"name": "error",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"alert_tasks_sender_id_users_id_fk": {
"name": "alert_tasks_sender_id_users_id_fk",
"tableFrom": "alert_tasks",
"tableTo": "users",
"columnsFrom": [
"sender_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.known_group_chats": {
"name": "known_group_chats",
"schema": "",
"columns": {
"chat_id": {
"name": "chat_id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"last_active_at": {
"name": "last_active_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.subscriptions": {
"name": "subscriptions",
"schema": "",
"columns": {
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"topic_id": {
"name": "topic_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"subscriptions_user_id_users_id_fk": {
"name": "subscriptions_user_id_users_id_fk",
"tableFrom": "subscriptions",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"subscriptions_topic_id_topics_id_fk": {
"name": "subscriptions_topic_id_topics_id_fk",
"tableFrom": "subscriptions",
"tableTo": "topics",
"columnsFrom": [
"topic_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"subscriptions_user_id_topic_id_pk": {
"name": "subscriptions_user_id_topic_id_pk",
"columns": [
"user_id",
"topic_id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.topic_group_chats": {
"name": "topic_group_chats",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"topic_id": {
"name": "topic_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"chat_id": {
"name": "chat_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"created_by": {
"name": "created_by",
"type": "text",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"topic_group_chats_topic_id_topics_id_fk": {
"name": "topic_group_chats_topic_id_topics_id_fk",
"tableFrom": "topic_group_chats",
"tableTo": "topics",
"columnsFrom": [
"topic_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"topic_group_chats_created_by_users_id_fk": {
"name": "topic_group_chats_created_by_users_id_fk",
"tableFrom": "topic_group_chats",
"tableTo": "users",
"columnsFrom": [
"created_by"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.topics": {
"name": "topics",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"slug": {
"name": "slug",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false
},
"status": {
"name": "status",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'approved'"
},
"created_by": {
"name": "created_by",
"type": "text",
"primaryKey": false,
"notNull": false
},
"approved_by": {
"name": "approved_by",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"topics_created_by_users_id_fk": {
"name": "topics_created_by_users_id_fk",
"tableFrom": "topics",
"tableTo": "users",
"columnsFrom": [
"created_by"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"topics_approved_by_users_id_fk": {
"name": "topics_approved_by_users_id_fk",
"tableFrom": "topics",
"tableTo": "users",
"columnsFrom": [
"approved_by"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"topics_slug_unique": {
"name": "topics_slug_unique",
"nullsNotDistinct": false,
"columns": [
"slug"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"feishu_user_id": {
"name": "feishu_user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": false
},
"is_admin": {
"name": "is_admin",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"personal_token": {
"name": "personal_token",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"users_email_unique": {
"name": "users_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
},
"users_personal_token_unique": {
"name": "users_personal_token_unique",
"nullsNotDistinct": false,
"columns": [
"personal_token"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1768448660268,
"tag": "0000_stiff_electro",
"breakpoints": true
}
]
}

View File

@@ -2,7 +2,8 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" type="image/png" href="/favicon.png" />
<link rel="apple-touch-icon" href="/icon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Alert Message Center</title>
</head>

BIN
apps/web/public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 KiB

BIN
apps/web/public/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 KiB

View File

@@ -1,5 +1,4 @@
import {
Activity,
Hash,
LogIn,
LogOut,
@@ -38,7 +37,7 @@ function App() {
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="max-w-md w-full bg-white shadow-lg rounded-lg p-8">
<div className="text-center">
<Activity className="h-16 w-16 text-indigo-600 mx-auto mb-4" />
<img src="/icon.png" alt="Logo" className="h-16 w-16 mx-auto mb-4" />
<h1 className="text-3xl font-bold text-gray-900 mb-2">
Alert Message Center
</h1>
@@ -66,7 +65,7 @@ function App() {
<div className="flex justify-between h-16">
<div className="flex">
<div className="flex-shrink-0 flex items-center">
<Activity className="h-8 w-8 text-indigo-600" />
<img src="/icon.png" alt="Logo" className="h-8 w-8" />
<span className="ml-2 text-xl font-bold text-gray-900">
Alert Message Center
</span>
@@ -76,11 +75,10 @@ function App() {
<button
type="button"
onClick={() => setActiveTab("admin")}
className={`${
activeTab === "admin"
? "border-indigo-500 text-gray-900"
: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700"
} inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium`}
className={`${activeTab === "admin"
? "border-indigo-500 text-gray-900"
: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700"
} inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium`}
>
<Settings className="mr-2 h-4 w-4" />
Admin
@@ -89,11 +87,10 @@ function App() {
<button
type="button"
onClick={() => setActiveTab("topics")}
className={`${
activeTab === "topics"
? "border-indigo-500 text-gray-900"
: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700"
} inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium`}
className={`${activeTab === "topics"
? "border-indigo-500 text-gray-900"
: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700"
} inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium`}
>
<Hash className="mr-2 h-4 w-4" />
Topics
@@ -102,11 +99,10 @@ function App() {
<button
type="button"
onClick={() => setActiveTab("users")}
className={`${
activeTab === "users"
? "border-indigo-500 text-gray-900"
: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700"
} inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium`}
className={`${activeTab === "users"
? "border-indigo-500 text-gray-900"
: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700"
} inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium`}
>
<Users className="mr-2 h-4 w-4" />
Users

View File

@@ -223,11 +223,10 @@ The database schema is defined in `apps/server/src/db/schema.ts`.
- Deployment Architecture: A single container runs the Bun server, which serves API requests and static frontend assets (via `hono/bun`'s `serveStatic`).
- **Database Initialization**: The Docker entrypoint automatically runs `bun run db:migrate:deploy` before starting the server to ensure the schema is up-to-date in new environments.
- **Token Migration**: The `db:migrate:deploy` script (defined in `src/db/migrate.ts`) also handles legacy user token shortening to maintain consistency with the 8-character token logic introduced in v1.2.6.
- **Drizzle Meta**: The `apps/server/drizzle/meta` directory MUST NOT be ignored by git (it was previously explicitly excluded in `.gitignore` but has been restored). This directory contains `_journal.json`, which is essential for `drizzle-kit` and the runtime migrator to verify migration integrity. Without it, migrations will fail in clean environments like Docker containers.
## 8. Core Documents
- **[README.md](file:///Users/lilithgames/Workspace/dap/alert-message-center/README.md)**: Main project documentation, including quick start, tech stack overview, and Webhook usage guide.
- **[CHANGELOG.md](file:///Users/lilithgames/Workspace/dap/alert-message-center/CHANGELOG.md)**: Record of version changes, following the Keep a Changelog specification.
- **[todo.md](file:///Users/lilithgames/Workspace/dap/alert-message-center/todo.md)**: Task tracking and upcoming features.
- **[README.md](file:///Users/lilithgames/Workspace/play/alert-message-center/README.md)**: Main project documentation, including quick start, tech stack overview, and Webhook usage guide.
- **[CHANGELOG.md](file:///Users/lilithgames/Workspace/play/alert-message-center/CHANGELOG.md)**: Record of version changes, following the Keep a Changelog specification.
- **[todo.md](file:///Users/lilithgames/Workspace/play/alert-message-center/todo.md)**: Task tracking and upcoming features.

View File

@@ -31,3 +31,6 @@
- [x] **Frontend Resilience**: Hardened API calls to prevent crashes on empty data or env access errors.
- [x] **CI & Type Safety**: Resolved all TypeScript errors and Biome formatting issues to ensure a healthy CI pipeline.
- [x] **User Token Shortening**: Shortened `personalToken` to 8 characters and integrated automated migration into the deployment script.
- [x] **Visual Identity**: Added custom logo, favicon and integrated them into the UI (login/navbar).
- [x] **Migration Robustness**: Fixed migration failures in Docker by un-ignoring the drizzle meta directory.