Skip to content

Commit 9fc5a7c

Browse files
fix: localize alert kinds across 15 languages; bootstrap generates split configs
- alert kinds are now stable codes (burst/degrade/both): UI translates per language, Feishu cards map to Chinese labels, json webhooks receive raw codes - first run generates the full split set: pingping.jsonc + webhooks.jsonc + alerts.jsonc + targets/ping.list - web_user/web_password documented inline (commented) in the generated main config
1 parent 3a28ec4 commit 9fc5a7c

5 files changed

Lines changed: 76 additions & 15 deletions

File tree

detect.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,11 +191,11 @@ func (d *Detector) AfterAppend(name string) {
191191
st.clearSince = 0
192192
switch {
193193
case degradeAlert && burstAlert:
194-
kind = "劣化+丢包突发"
194+
kind = "both"
195195
case degradeAlert:
196-
kind = "P99 劣化"
196+
kind = "degrade"
197197
default:
198-
kind = "丢包突发"
198+
kind = "burst"
199199
}
200200
cooldown := int64(d.cfg.CooldownMin) * 60
201201
if !st.alertActive || now.Unix()-st.lastNotify >= cooldown {

main.go

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"log"
99
"os"
1010
"os/signal"
11+
"path/filepath"
1112
"syscall"
1213
"time"
1314
)
@@ -16,14 +17,38 @@ var version = "dev"
1617

1718
// demoConfig 是首次运行自动生成的演示配置。fast 档让第一屏的烟雾几分钟内成形。
1819
const demoConfig = `{
19-
// pingping 演示配置 —— 首次运行自动生成
20-
// 探测目标写在 targets/ping.list 和 targets/tcp.list,一行一个,重启生效
21-
// 完整字段说明见仓库内 config.example.jsonc
20+
// pingping 主配置 —— 首次运行自动生成。配置已拆分为四处:
21+
// 本文件 监听 / 登录 / 全局参数
22+
// targets/*.list 探测目标(一行一个,重启生效)
23+
// webhooks.jsonc 推送渠道
24+
// alerts.jsonc 告警阈值
2225
"listen": "0.0.0.0:8517"
23-
// 飞书推送:去掉下面的注释,填入你的群机器人 webhook
24-
//,"webhooks": [
25-
// { "name": "运维群", "url": "https://open.feishu.cn/open-apis/bot/v2/hook/xxxx" }
26-
//]
26+
27+
// Web 登录:去掉下面两行的注释即启用(或运行 ./pingping --localhost 交给反代认证)
28+
//,"web_user": "admin"
29+
//,"web_password": "change-me"
30+
}
31+
`
32+
33+
const demoWebhooks = `{
34+
// 推送渠道 —— 填入 webhook 后重启生效
35+
// format: "feishu"(默认,交互卡片)| "json"(裸 JSON 事件,任意系统可接)
36+
// kinds: alert recovery heartbeat daily manual(不填 = 全收)
37+
"webhooks": [
38+
// { "name": "运维群", "url": "https://open.feishu.cn/open-apis/bot/v2/hook/xxxx",
39+
// "secret": "", "kinds": ["alert", "recovery", "heartbeat"] }
40+
]
41+
}
42+
`
43+
44+
const demoAlerts = `{
45+
// 告警阈值 —— 只需写想改的字段,未写的保持内置默认
46+
"alerts": {
47+
"cooldown_min": 30, // 同一告警重复推送冷却(分钟)
48+
"recover_min": 15, // 持续正常多久才发恢复(分钟)
49+
"degrade_ratio": 1.5, // P99 超基线倍数判劣化
50+
"burst_alert_n": 3 // 30 分钟内突发 N 次升级为告警
51+
}
2752
}
2853
`
2954

@@ -64,7 +89,14 @@ func main() {
6489
if _, serr := os.Stat("targets/ping.list"); os.IsNotExist(serr) {
6590
os.WriteFile("targets/ping.list", []byte(demoPingList), 0o644)
6691
}
67-
log.Printf("未找到配置文件,已生成演示配置 %s + targets/ping.list(探测 www.google.com)", *cfgPath)
92+
dir := filepath.Dir(*cfgPath)
93+
for f, c := range map[string]string{"webhooks.jsonc": demoWebhooks, "alerts.jsonc": demoAlerts} {
94+
p := filepath.Join(dir, f)
95+
if _, serr := os.Stat(p); os.IsNotExist(serr) {
96+
os.WriteFile(p, []byte(c), 0o644)
97+
}
98+
}
99+
log.Printf("未找到配置,已生成拆分配置:%s + webhooks.jsonc + alerts.jsonc + targets/ping.list(探测 www.google.com)", *cfgPath)
68100
log.Printf("加监控就一行:echo \"1.2.3.4 广州电信\" >> targets/ping.list,重启生效")
69101
cfg, err = LoadConfig(*cfgPath)
70102
}

notify.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,20 @@ type hook struct {
4343
skipUntil time.Time
4444
}
4545

46+
// kindLabel 把告警类型代码映射为卡片用的中文标签;
47+
// json 格式的 webhook 收到的是原始代码(burst/degrade/both),便于机器消费。
48+
func kindLabel(code string) string {
49+
switch code {
50+
case "burst":
51+
return "丢包突发"
52+
case "degrade":
53+
return "P99 劣化"
54+
case "both":
55+
return "劣化+丢包突发"
56+
}
57+
return code
58+
}
59+
4660
// AlertEvent 是一次告警/恢复的完整证据包。
4761
type AlertEvent struct {
4862
Target, Host, Kind string
@@ -83,11 +97,11 @@ func (n *Notifier) SendAlert(ev AlertEvent) {
8397
lines = append(lines, fmt.Sprintf("近 30 分钟丢包突发 **%d** 次,最差单轮丢包 **%.0f%%**", ev.Bursts, ev.WorstLoss))
8498
}
8599
n.push(note{kind: "alert", color: "red", title: "🔴 链路异常 · " + ev.Target,
86-
body: "**" + ev.Kind + "**\n" + join(lines), meta: n.meta(ev, false), event: &ev})
100+
body: "**" + kindLabel(ev.Kind) + "**\n" + join(lines), meta: n.meta(ev, false), event: &ev})
87101
}
88102

89103
func (n *Notifier) SendRecovery(ev AlertEvent) {
90-
body := fmt.Sprintf("**%s** 已恢复", ev.Kind)
104+
body := fmt.Sprintf("**%s** 已恢复", kindLabel(ev.Kind))
91105
n.push(note{kind: "recovery", color: "green", title: "🟢 链路恢复 · " + ev.Target,
92106
body: body, meta: n.meta(ev, true), event: &ev})
93107
}

static/i18n.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// pingping i18n —— 15 语言字典,与 README 表里如一。偏好存 localStorage,首次按浏览器语言猜。
22
const I18N = {
33
zh: {
4+
kind_burst: '丢包突发', kind_degrade: 'P99 劣化', kind_both: '劣化+丢包突发',
45
tagline: '链路质量示波器 · 监控告诉你挂没挂,pingping 告诉你活得好不好',
56
tabScope: '烟雾图', tabHosts: '主机', tabHooks: '通知',
67
push: '推送实时报告', pushed: '报告已推送至全部 webhook',
@@ -17,6 +18,7 @@ const I18N = {
1718
signin: '登 录', language: '语言', loginFailed: '用户名或密码错误', logout: '退出',
1819
},
1920
zht: {
21+
kind_burst: '丟包突發', kind_degrade: 'P99 劣化', kind_both: '劣化+丟包突發',
2022
tagline: '鏈路品質示波器 · 監控告訴你掛沒掛,pingping 告訴你活得好不好',
2123
tabScope: '煙霧圖', tabHosts: '主機', tabHooks: '通知',
2224
push: '推送即時報告', pushed: '報告已推送至全部 webhook',
@@ -33,6 +35,7 @@ const I18N = {
3335
signin: '登 入', language: '語言', loginFailed: '使用者名稱或密碼錯誤', logout: '登出',
3436
},
3537
en: {
38+
kind_burst: 'loss burst', kind_degrade: 'P99 degradation', kind_both: 'degradation + loss burst',
3639
tagline: 'Link quality oscilloscope · Monitoring tells you if it is down, pingping tells you how well it lives',
3740
tabScope: 'Smoke', tabHosts: 'Hosts', tabHooks: 'Notify',
3841
push: 'Push live report', pushed: 'Report pushed to all webhooks',
@@ -49,6 +52,7 @@ const I18N = {
4952
signin: 'Sign in', language: 'Language', loginFailed: 'Wrong username or password', logout: 'Sign out',
5053
},
5154
ja: {
55+
kind_burst: '損失バースト', kind_degrade: 'P99 劣化', kind_both: '劣化+損失バースト',
5256
tagline: 'リンク品質オシロスコープ · 死活は監視が、体調は pingping が教える',
5357
tabScope: 'スモーク', tabHosts: 'ホスト', tabHooks: '通知',
5458
push: 'レポートを送信', pushed: '全 webhook に送信しました',
@@ -65,6 +69,7 @@ const I18N = {
6569
signin: 'ログイン', language: '言語', loginFailed: 'ユーザー名またはパスワードが違います', logout: 'ログアウト',
6670
},
6771
ko: {
72+
kind_burst: '손실 버스트', kind_degrade: 'P99 저하', kind_both: '저하+손실 버스트',
6873
tagline: '링크 품질 오실로스코프 · 다운 여부는 모니터링이, 상태는 pingping이 알려줍니다',
6974
tabScope: '스모크', tabHosts: '호스트', tabHooks: '알림',
7075
push: '실시간 보고서 전송', pushed: '모든 webhook에 전송됨',
@@ -81,6 +86,7 @@ const I18N = {
8186
signin: '로그인', language: '언어', loginFailed: '사용자 이름 또는 비밀번호가 올바르지 않습니다', logout: '로그아웃',
8287
},
8388
de: {
89+
kind_burst: 'Verlust-Burst', kind_degrade: 'P99-Verschlechterung', kind_both: 'Verschlechterung + Burst',
8490
tagline: 'Link-Qualitäts-Oszilloskop · Monitoring sagt ob es down ist, pingping sagt wie gut es lebt',
8591
tabScope: 'Rauch', tabHosts: 'Hosts', tabHooks: 'Benachrichtigung',
8692
push: 'Bericht senden', pushed: 'Bericht an alle Webhooks gesendet',
@@ -97,6 +103,7 @@ const I18N = {
97103
signin: 'Anmelden', language: 'Sprache', loginFailed: 'Benutzername oder Passwort falsch', logout: 'Abmelden',
98104
},
99105
fr: {
106+
kind_burst: 'rafale de perte', kind_degrade: 'dégradation P99', kind_both: 'dégradation + rafale',
100107
tagline: 'Oscilloscope de qualité de lien · La supervision dit si c\'est tombé, pingping dit comment ça vit',
101108
tabScope: 'Fumée', tabHosts: 'Hôtes', tabHooks: 'Notifications',
102109
push: 'Envoyer le rapport', pushed: 'Rapport envoyé à tous les webhooks',
@@ -113,6 +120,7 @@ const I18N = {
113120
signin: 'Se connecter', language: 'Langue', loginFailed: 'Identifiants incorrects', logout: 'Déconnexion',
114121
},
115122
es: {
123+
kind_burst: 'ráfaga de pérdida', kind_degrade: 'degradación P99', kind_both: 'degradación + ráfaga',
116124
tagline: 'Osciloscopio de calidad de enlace · El monitoreo dice si está caído, pingping dice cómo vive',
117125
tabScope: 'Humo', tabHosts: 'Hosts', tabHooks: 'Avisos',
118126
push: 'Enviar informe', pushed: 'Informe enviado a todos los webhooks',
@@ -129,6 +137,7 @@ const I18N = {
129137
signin: 'Entrar', language: 'Idioma', loginFailed: 'Usuario o contraseña incorrectos', logout: 'Salir',
130138
},
131139
pt: {
140+
kind_burst: 'rajada de perda', kind_degrade: 'degradação P99', kind_both: 'degradação + rajada',
132141
tagline: 'Osciloscópio de qualidade de link · O monitoramento diz se caiu, o pingping diz como vive',
133142
tabScope: 'Fumaça', tabHosts: 'Hosts', tabHooks: 'Avisos',
134143
push: 'Enviar relatório', pushed: 'Relatório enviado a todos os webhooks',
@@ -145,6 +154,7 @@ const I18N = {
145154
signin: 'Entrar', language: 'Idioma', loginFailed: 'Usuário ou senha incorretos', logout: 'Sair',
146155
},
147156
it: {
157+
kind_burst: 'raffica di perdita', kind_degrade: 'degrado P99', kind_both: 'degrado + raffica',
148158
tagline: 'Oscilloscopio di qualità del collegamento · Il monitoraggio dice se è giù, pingping dice come vive',
149159
tabScope: 'Fumo', tabHosts: 'Host', tabHooks: 'Notifiche',
150160
push: 'Invia rapporto', pushed: 'Rapporto inviato a tutti i webhook',
@@ -161,6 +171,7 @@ const I18N = {
161171
signin: 'Accedi', language: 'Lingua', loginFailed: 'Nome utente o password errati', logout: 'Esci',
162172
},
163173
ru: {
174+
kind_burst: 'всплеск потерь', kind_degrade: 'деградация P99', kind_both: 'деградация + всплеск',
164175
tagline: 'Осциллограф качества канала · Мониторинг скажет, упал ли он, pingping — как он живёт',
165176
tabScope: 'Дым', tabHosts: 'Хосты', tabHooks: 'Уведомления',
166177
push: 'Отправить отчёт', pushed: 'Отчёт отправлен на все webhook',
@@ -177,6 +188,7 @@ const I18N = {
177188
signin: 'Войти', language: 'Язык', loginFailed: 'Неверное имя пользователя или пароль', logout: 'Выйти',
178189
},
179190
th: {
191+
kind_burst: 'เบิร์สต์สูญหาย', kind_degrade: 'P99 เสื่อมถอย', kind_both: 'เสื่อมถอย + เบิร์สต์',
180192
tagline: 'ออสซิลโลสโคปคุณภาพลิงก์ · มอนิเตอร์บอกว่าล่มไหม pingping บอกว่าเป็นอย่างไร',
181193
tabScope: 'ควัน', tabHosts: 'โฮสต์', tabHooks: 'การแจ้งเตือน',
182194
push: 'ส่งรายงานสด', pushed: 'ส่งรายงานไปยัง webhook ทั้งหมดแล้ว',
@@ -193,6 +205,7 @@ const I18N = {
193205
signin: 'เข้าสู่ระบบ', language: 'ภาษา', loginFailed: 'ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้อง', logout: 'ออกจากระบบ',
194206
},
195207
id: {
208+
kind_burst: 'burst loss', kind_degrade: 'degradasi P99', kind_both: 'degradasi + burst',
196209
tagline: 'Osiloskop kualitas tautan · Monitoring memberi tahu jika mati, pingping memberi tahu seberapa sehat',
197210
tabScope: 'Asap', tabHosts: 'Host', tabHooks: 'Notifikasi',
198211
push: 'Kirim laporan', pushed: 'Laporan terkirim ke semua webhook',
@@ -209,6 +222,7 @@ const I18N = {
209222
signin: 'Masuk', language: 'Bahasa', loginFailed: 'Nama pengguna atau kata sandi salah', logout: 'Keluar',
210223
},
211224
vi: {
225+
kind_burst: 'bùng phát mất gói', kind_degrade: 'suy giảm P99', kind_both: 'suy giảm + bùng phát',
212226
tagline: 'Máy hiện sóng chất lượng liên kết · Giám sát cho biết có sập không, pingping cho biết sống ra sao',
213227
tabScope: 'Khói', tabHosts: 'Máy chủ', tabHooks: 'Thông báo',
214228
push: 'Gửi báo cáo', pushed: 'Đã gửi báo cáo tới tất cả webhook',
@@ -225,6 +239,7 @@ const I18N = {
225239
signin: 'Đăng nhập', language: 'Ngôn ngữ', loginFailed: 'Tên đăng nhập hoặc mật khẩu sai', logout: 'Đăng xuất',
226240
},
227241
ar: {
242+
kind_burst: 'اندفاع فقد', kind_degrade: 'تدهور P99', kind_both: 'تدهور + اندفاع',
228243
tagline: 'راسم إشارة لجودة الوصلة · المراقبة تخبرك إن سقطت، وpingping يخبرك كيف تعيش',
229244
tabScope: 'الدخان', tabHosts: 'المضيفون', tabHooks: 'الإشعارات',
230245
push: 'إرسال التقرير', pushed: 'تم إرسال التقرير إلى جميع الـ webhook',

static/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@
218218
$('bursts').textContent = bursts;
219219
$('bursts').className = 'v' + (bursts ? ' bad' : '');
220220
$('conclusion').textContent = t.status.alert
221-
? T('concAlert', { name: t.name, kind: t.status.kind })
221+
? T('concAlert', { name: t.name, kind: T('kind_' + t.status.kind) })
222222
: T('concOK', { name: t.name, p99: t.last_24h.p99.toFixed(1),
223223
loss: t.last_24h.loss_pct.toFixed(2), bursts: t.last_24h.bursts });
224224
const phos = getComputedStyle(document.documentElement).getPropertyValue('--phos').trim();
@@ -268,7 +268,7 @@
268268
const body = $('hostsBody'); body.innerHTML = '';
269269
rows.forEach(t => {
270270
const st = t.alerts_off ? `<span class="st-obs">${T('stObserve')}</span>`
271-
: t.status.alert ? `<span class="st-bad">● ${T('stAlert')} · ${t.status.kind || ''}</span>`
271+
: t.status.alert ? `<span class="st-bad">● ${T('stAlert')} · ${t.status.kind ? T('kind_' + t.status.kind) : ''}</span>`
272272
: `<span class="st-ok">● ${T('stOK')}</span>`;
273273
const tr = document.createElement('tr');
274274
tr.innerHTML = `<td>${t.type === 'icmp' ? 'PING' : 'TCP'}</td><td>${t.name}</td>` +

0 commit comments

Comments
 (0)