diff --git a/main.go b/main.go
index 9e18fa5..3f9e972 100644
--- a/main.go
+++ b/main.go
@@ -1,4 +1,3 @@
-// main.go
package main
import (
@@ -11,6 +10,7 @@ import (
"math/rand"
"net/http"
"os"
+ "sort"
"strings"
"sync"
"time"
@@ -25,7 +25,9 @@ type Task struct {
InStock bool `json:"in_stock"`
LastCheck time.Time `json:"last_check"`
Status string `json:"status"`
- Notified bool `json:"notified"`
+ Notified bool `json:"notified"`
+ NotifyEnabled bool `json:"notify_enabled"`
+ Order int `json:"order"`
History []HistoryItem `json:"history"`
}
@@ -64,6 +66,7 @@ func main() {
http.HandleFunc("/", handleIndex)
http.HandleFunc("/api/tasks", auth(handleTasks))
http.HandleFunc("/api/task", auth(handleTask))
+ http.HandleFunc("/api/task/toggle-notify", auth(handleToggleNotify))
http.HandleFunc("/api/config", auth(handleConfig))
http.HandleFunc("/api/test-notification", auth(handleTestNotification))
@@ -139,6 +142,8 @@ func handleIndex(w http.ResponseWriter, r *http.Request) {
.stock-no{color:var(--gray)}
.btn{padding:5px 10px;margin:0 2px;cursor:pointer;border:none;border-radius:3px;background:var(--blue);color:#fff;font-size:12px}
.btn-del{background:var(--red)}
+ .btn-icon{background:transparent;color:var(--text);font-size:16px;padding:5px 8px}
+ .btn-icon.active{color:var(--green)}
.uptime-bar{display:flex;gap:2px;height:30px}
.uptime-item{flex:1;border-radius:2px;cursor:pointer;position:relative;transition:transform .2s}
.uptime-item:hover{transform:scaleY(1.2)}
@@ -181,7 +186,7 @@ func handleIndex(w http.ResponseWriter, r *http.Request) {
@@ -255,7 +264,6 @@ func handleIndex(w http.ResponseWriter, r *http.Request) {
if(token) localStorage.setItem('token', token);
const headers = {'Authorization': 'Bearer ' + token};
-
function loadTasks() {
fetch('/api/tasks', {headers})
.then(r => r.json())
@@ -265,6 +273,8 @@ func handleIndex(w http.ResponseWriter, r *http.Request) {
const statusClass = t.status || 'checking';
const stockText = t.in_stock ? '
有货' : '
无货';
const lastCheck = t.last_check ? new Date(t.last_check).toLocaleString('zh-CN') : '-';
+ const notifyIcon = t.notify_enabled ? '🔔' : '🔕';
+ const notifyClass = t.notify_enabled ? 'active' : '';
const history = t.history || [];
const uptimeBar = history.map(h => {
@@ -279,6 +289,8 @@ func handleIndex(w http.ResponseWriter, r *http.Request) {
'
' + stockText + ' | ' +
'
' + lastCheck + ' | ' +
'
' +
+ '' +
+ '🔗' +
'' +
'' +
' | ';
@@ -286,6 +298,11 @@ func handleIndex(w http.ResponseWriter, r *http.Request) {
});
}
+ function toggleNotify(id) {
+ fetch('/api/task/toggle-notify?id=' + id, {method: 'POST', headers})
+ .then(() => loadTasks());
+ }
+
function showModal(task) {
document.getElementById('modalTitle').textContent = task ? '编辑监控' : '添加监控';
document.getElementById('taskId').value = task?.id || '';
@@ -293,6 +310,7 @@ func handleIndex(w http.ResponseWriter, r *http.Request) {
document.getElementById('url').value = task?.url || '';
document.getElementById('pageLoaded').value = task?.page_loaded || '';
document.getElementById('outOfStock').value = task?.out_of_stock || '';
+ document.getElementById('order').value = task?.order || 0;
document.getElementById('modal').classList.add('show');
}
@@ -327,7 +345,7 @@ func handleIndex(w http.ResponseWriter, r *http.Request) {
method: 'POST',
headers: {...headers, 'Content-Type': 'application/json'},
body: JSON.stringify(testConfig)
- }).then(r => r.ok ? alert('测试通知已发送,请检查 Gotify') : alert('发送失败,查看后台日志'));
+ }).then(r => r.ok ? alert('测试通知已发送,请检查 Gotify') : alert('发送失败,查看后台日志'));
}
function editTask(id) {
@@ -342,9 +360,7 @@ func handleIndex(w http.ResponseWriter, r *http.Request) {
function delTask(id) {
if(!confirm('确认删除?')) return;
fetch('/api/task?id=' + id, {method: 'DELETE', headers})
- .then(() => {
- loadTasks();
- });
+ .then(() => loadTasks());
}
document.getElementById('taskForm').onsubmit = e => {
@@ -354,7 +370,8 @@ func handleIndex(w http.ResponseWriter, r *http.Request) {
name: document.getElementById('name').value,
url: document.getElementById('url').value,
page_loaded: document.getElementById('pageLoaded').value,
- out_of_stock: document.getElementById('outOfStock').value
+ out_of_stock: document.getElementById('outOfStock').value,
+ order: parseInt(document.getElementById('order').value) || 0
};
fetch('/api/task', {
method: 'POST',
@@ -396,11 +413,16 @@ func handleIndex(w http.ResponseWriter, r *http.Request) {
func handleTasks(w http.ResponseWriter, r *http.Request) {
mu.RLock()
- defer mu.RUnlock()
list := make([]*Task, 0, len(tasks))
for _, t := range tasks {
list = append(list, t)
}
+ mu.RUnlock()
+
+ sort.Slice(list, func(i, j int) bool {
+ return list[i].Order < list[j].Order
+ })
+
json.NewEncoder(w).Encode(list)
}
@@ -419,6 +441,7 @@ func handleTask(w http.ResponseWriter, r *http.Request) {
isNew := task.ID == ""
if isNew {
task.ID = time.Now().Format("20060102150405")
+ task.NotifyEnabled = true
task.History = make([]HistoryItem, 90)
now := time.Now()
for i := 0; i < 90; i++ {
@@ -435,6 +458,7 @@ func handleTask(w http.ResponseWriter, r *http.Request) {
task.LastCheck = existing.LastCheck
task.Status = existing.Status
task.Notified = existing.Notified
+ task.NotifyEnabled = existing.NotifyEnabled
}
mu.RUnlock()
}
@@ -445,6 +469,16 @@ func handleTask(w http.ResponseWriter, r *http.Request) {
saveTasks()
}
+func handleToggleNotify(w http.ResponseWriter, r *http.Request) {
+ id := r.URL.Query().Get("id")
+ mu.Lock()
+ if task, ok := tasks[id]; ok {
+ task.NotifyEnabled = !task.NotifyEnabled
+ }
+ mu.Unlock()
+ saveTasks()
+}
+
func handleConfig(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
var cfg Config
@@ -517,6 +551,7 @@ func checkTask(task *Task) {
mu.Lock()
wasInStock := task.InStock
wasNotified := task.Notified
+ taskNotifyEnabled := task.NotifyEnabled
task.Status = status
task.InStock = inStock
task.LastCheck = now
@@ -536,10 +571,10 @@ func checkTask(task *Task) {
saveTasks()
configMu.RLock()
- notifyEnabled := config.NotifyEnabled
+ globalNotifyEnabled := config.NotifyEnabled
configMu.RUnlock()
- if notifyEnabled && inStock && !wasInStock && !wasNotified {
+ if globalNotifyEnabled && taskNotifyEnabled && inStock && !wasInStock && !wasNotified {
notify(task.Name + " 有货了!", task.URL)
mu.Lock()
task.Notified = true
@@ -635,12 +670,12 @@ func updateClient() {
}
func saveJSON(filename string, v interface{}) {
+ os.MkdirAll("data", 0755)
data, _ := json.MarshalIndent(v, "", " ")
- os.WriteFile(filename, data, 0644)
+ os.WriteFile("data/"+filename, data, 0644)
}
-
func loadTasks() {
- data, err := os.ReadFile("tasks.json")
+ data, err := os.ReadFile("data/tasks.json")
if err != nil {
return
}
@@ -652,19 +687,8 @@ func loadTasks() {
}
mu.Unlock()
}
-
-func saveTasks() {
- mu.RLock()
- list := make([]*Task, 0, len(tasks))
- for _, t := range tasks {
- list = append(list, t)
- }
- mu.RUnlock()
- saveJSON("tasks.json", list)
-}
-
func loadConfig() {
- data, err := os.ReadFile("config.json")
+ data, err := os.ReadFile("data/config.json")
if err != nil {
return
}