From c060e294f947fb76570e0f625db44f1b0f055ab8 Mon Sep 17 00:00:00 2001 From: Henry Dollman Date: Mon, 22 Jul 2024 16:14:55 -0400 Subject: [PATCH] alerts for cpu, memory, and disk --- hub/alerts.go | 142 ++++++++++++++++ hub/main.go | 67 +------- .../1720568457_collections_snapshot.go | 41 ++++- hub/site/bun.lockb | Bin 141750 -> 142590 bytes hub/site/package.json | 1 + hub/site/src/components/table-alerts.tsx | 152 +++++++++++++++--- hub/site/src/components/ui/slider.tsx | 23 +++ hub/site/src/lib/utils.ts | 2 +- 8 files changed, 335 insertions(+), 93 deletions(-) create mode 100644 hub/alerts.go create mode 100644 hub/site/src/components/ui/slider.tsx diff --git a/hub/alerts.go b/hub/alerts.go new file mode 100644 index 0000000..c8da900 --- /dev/null +++ b/hub/alerts.go @@ -0,0 +1,142 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/mail" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/models" + "github.com/pocketbase/pocketbase/tools/mailer" + "github.com/pocketbase/pocketbase/tools/types" +) + +func handleSystemAlerts(newStatus string, newRecord *models.Record, oldRecord *models.Record) { + alertRecords, err := app.Dao().FindRecordsByExpr("alerts", + dbx.NewExp("system = {:system}", dbx.Params{"system": oldRecord.Get("id")}), + ) + if err != nil || len(alertRecords) == 0 { + // log.Println("no alerts found for system") + return + } + // log.Println("found alerts", len(alertRecords)) + var systemInfo *SystemInfo + for _, alertRecord := range alertRecords { + name := alertRecord.Get("name").(string) + switch name { + case "Status": + handleStatusAlerts(newStatus, oldRecord, alertRecord) + case "CPU", "Memory", "Disk": + if newStatus != "up" { + continue + } + if systemInfo == nil { + systemInfo = getSystemInfo(newRecord) + } + if name == "CPU" { + handleSlidingValueAlert(newRecord, alertRecord, name, systemInfo.Cpu) + } else if name == "Memory" { + handleSlidingValueAlert(newRecord, alertRecord, name, systemInfo.MemPct) + } else if name == "Disk" { + handleSlidingValueAlert(newRecord, alertRecord, name, systemInfo.DiskPct) + } + } + } +} + +func getSystemInfo(record *models.Record) *SystemInfo { + var SystemInfo SystemInfo + json.Unmarshal([]byte(record.Get("info").(types.JsonRaw)), &SystemInfo) + return &SystemInfo +} + +func handleSlidingValueAlert(newRecord *models.Record, alertRecord *models.Record, name string, curValue float64) { + triggered := alertRecord.Get("triggered").(bool) + threshold := alertRecord.Get("value").(float64) + // fmt.Println(name, curValue, "threshold", threshold, "triggered", triggered) + var subject string + var body string + if !triggered && curValue > threshold { + alertRecord.Set("triggered", true) + systemName := newRecord.Get("name").(string) + subject = fmt.Sprintf("%s usage threshold exceeded on %s", name, systemName) + body = fmt.Sprintf("%s usage on %s is %.1f%%.\n\n- Beszel", name, systemName, curValue) + } else if triggered && curValue <= threshold { + alertRecord.Set("triggered", false) + systemName := newRecord.Get("name").(string) + subject = fmt.Sprintf("%s usage returned below threshold on %s", name, systemName) + body = fmt.Sprintf("%s usage on %s is below threshold at %.1f%%.\n\n%s\n\n- Beszel", name, systemName, curValue, app.Settings().Meta.AppUrl+"/system/"+systemName) + } else { + // fmt.Println(name, "not triggered") + return + } + if err := app.Dao().SaveRecord(alertRecord); err != nil { + // app.Logger().Error("failed to save alert record", "err", err.Error()) + return + } + // expand the user relation and send the alert + if errs := app.Dao().ExpandRecord(alertRecord, []string{"user"}, nil); len(errs) > 0 { + // app.Logger().Error("failed to expand user relation", "errs", errs) + return + } + if user := alertRecord.ExpandedOne("user"); user != nil { + sendAlert(EmailData{ + to: user.Get("email").(string), + subj: subject, + body: body, + }) + } +} + +func handleStatusAlerts(newStatus string, oldRecord *models.Record, alertRecord *models.Record) error { + var alertStatus string + switch newStatus { + case "up": + if oldRecord.Get("status") == "down" { + alertStatus = "up" + } + case "down": + if oldRecord.Get("status") == "up" { + alertStatus = "down" + } + } + if alertStatus == "" { + return nil + } + // expand the user relation + if errs := app.Dao().ExpandRecord(alertRecord, []string{"user"}, nil); len(errs) > 0 { + return fmt.Errorf("failed to expand: %v", errs) + } + user := alertRecord.ExpandedOne("user") + if user == nil { + return nil + } + emoji := "\U0001F534" + if alertStatus == "up" { + emoji = "\u2705" + } + // send alert + systemName := oldRecord.Get("name").(string) + sendAlert(EmailData{ + to: user.Get("email").(string), + subj: fmt.Sprintf("Connection to %s is %s %v", systemName, alertStatus, emoji), + body: fmt.Sprintf("Connection to %s is %s\n\n- Beszel", systemName, alertStatus), + }) + return nil +} + +func sendAlert(data EmailData) { + // fmt.Println("sending alert", "to", data.to, "subj", data.subj, "body", data.body) + message := &mailer.Message{ + From: mail.Address{ + Address: app.Settings().Meta.SenderAddress, + Name: app.Settings().Meta.SenderName, + }, + To: []mail.Address{{Address: data.to}}, + Subject: data.subj, + Text: data.body, + } + if err := app.NewMailClient().Send(message); err != nil { + app.Logger().Error("Failed to send alert: ", "err", err.Error()) + } +} diff --git a/hub/main.go b/hub/main.go index cffb738..ab65676 100644 --- a/hub/main.go +++ b/hub/main.go @@ -12,7 +12,6 @@ import ( "log" "net/http" "net/http/httputil" - "net/mail" "net/url" "os" "strings" @@ -26,7 +25,6 @@ import ( "github.com/pocketbase/pocketbase/models" "github.com/pocketbase/pocketbase/plugins/migratecmd" "github.com/pocketbase/pocketbase/tools/cron" - "github.com/pocketbase/pocketbase/tools/mailer" "github.com/spf13/cobra" "golang.org/x/crypto/ssh" ) @@ -175,7 +173,7 @@ func main() { } // alerts - handleStatusAlerts(newStatus, oldRecord) + handleSystemAlerts(newStatus, newRecord, oldRecord) return nil }) @@ -378,69 +376,6 @@ func requestJson(server *Server) (SystemData, error) { return systemData, nil } -func sendAlert(data EmailData) { - message := &mailer.Message{ - From: mail.Address{ - Address: app.Settings().Meta.SenderAddress, - Name: app.Settings().Meta.SenderName, - }, - To: []mail.Address{{Address: data.to}}, - Subject: data.subj, - Text: data.body, - } - if err := app.NewMailClient().Send(message); err != nil { - app.Logger().Error("Failed to send alert: ", "err", err.Error()) - } -} - -func handleStatusAlerts(newStatus string, oldRecord *models.Record) error { - var alertStatus string - switch newStatus { - case "up": - if oldRecord.Get("status") == "down" { - alertStatus = "up" - } - case "down": - if oldRecord.Get("status") == "up" { - alertStatus = "down" - } - } - if alertStatus == "" { - return nil - } - alerts, err := app.Dao().FindRecordsByFilter("alerts", "name = 'status' && system = {:system}", "-created", -1, 0, dbx.Params{ - "system": oldRecord.Get("id")}) - if err != nil { - log.Println("failed to get users", "err", err.Error()) - return nil - } - if len(alerts) == 0 { - return nil - } - // expand the user relation - if errs := app.Dao().ExpandRecords(alerts, []string{"user"}, nil); len(errs) > 0 { - return fmt.Errorf("failed to expand: %v", errs) - } - systemName := oldRecord.Get("name").(string) - emoji := "\U0001F534" - if alertStatus == "up" { - emoji = "\u2705" - } - for _, alert := range alerts { - user := alert.ExpandedOne("user") - if user == nil { - continue - } - // send alert - sendAlert(EmailData{ - to: user.Get("email").(string), - subj: fmt.Sprintf("Connection to %s is %s %v", systemName, alertStatus, emoji), - body: fmt.Sprintf("Connection to %s is %s\n\n- Beszel", systemName, alertStatus), - }) - } - return nil -} - func getSSHKey() ([]byte, error) { dataDir := app.DataDir() // check if the key pair already exists diff --git a/hub/migrations/1720568457_collections_snapshot.go b/hub/migrations/1720568457_collections_snapshot.go index 131d348..047c16c 100644 --- a/hub/migrations/1720568457_collections_snapshot.go +++ b/hub/migrations/1720568457_collections_snapshot.go @@ -15,7 +15,7 @@ func init() { { "id": "2hz5ncl8tizk5nx", "created": "2024-07-07 16:08:20.979Z", - "updated": "2024-07-17 15:27:00.429Z", + "updated": "2024-07-22 19:39:17.434Z", "name": "systems", "type": "base", "system": false, @@ -102,7 +102,7 @@ func init() { "unique": false, "options": { "collectionId": "_pb_users_auth_", - "cascadeDelete": false, + "cascadeDelete": true, "minSelect": null, "maxSelect": null, "displayFields": null @@ -250,7 +250,7 @@ func init() { { "id": "_pb_users_auth_", "created": "2024-07-14 16:25:18.226Z", - "updated": "2024-07-20 00:55:02.071Z", + "updated": "2024-07-22 20:10:20.670Z", "name": "users", "type": "auth", "system": false, @@ -304,7 +304,7 @@ func init() { "options": { "allowEmailAuth": true, "allowOAuth2Auth": true, - "allowUsernameAuth": true, + "allowUsernameAuth": false, "exceptEmailDomains": null, "manageRule": null, "minPasswordLength": 8, @@ -316,7 +316,7 @@ func init() { { "id": "elngm8x1l60zi2v", "created": "2024-07-15 01:16:04.044Z", - "updated": "2024-07-15 22:44:12.297Z", + "updated": "2024-07-22 19:13:16.498Z", "name": "alerts", "type": "base", "system": false, @@ -364,16 +364,43 @@ func init() { "options": { "maxSelect": 1, "values": [ - "status" + "Status", + "CPU", + "Memory", + "Disk" ] } + }, + { + "system": false, + "id": "o2ablxvn", + "name": "value", + "type": "number", + "required": false, + "presentable": false, + "unique": false, + "options": { + "min": null, + "max": null, + "noDecimal": false + } + }, + { + "system": false, + "id": "6hgdf6hs", + "name": "triggered", + "type": "bool", + "required": false, + "presentable": false, + "unique": false, + "options": {} } ], "indexes": [], "listRule": "@request.auth.id != \"\" && user.id = @request.auth.id", "viewRule": "", "createRule": "@request.auth.id != \"\" && user.id = @request.auth.id", - "updateRule": null, + "updateRule": "@request.auth.id != \"\" && user.id = @request.auth.id", "deleteRule": "@request.auth.id != \"\" && user.id = @request.auth.id", "options": {} } diff --git a/hub/site/bun.lockb b/hub/site/bun.lockb index 7f1670ca67ad472ae6de40e6e9168e6f197fa497..ad7893fbb1c4f7d912a599a7778ccb2222201a48 100755 GIT binary patch delta 21608 zcmeHPd0dr6_kZTXMIIFu5fBj(cSLsasw_7*+_w~Q$pr!BB8!0KmU2x?+;W+6+_lsa zTFunl(lUEXGc(^-TeNz6MbjcPE4SbGJTniV;nMs5-v4?&oO|Y+IdkUBnKS2^=i$!X zal-xjXYNbFf@dCIy}W(bFZ!Nr^2D-p4Vq>5{`DW*AOB%zOrPm2tovsTCg!xUXmlkn zO%5!aK8P8FrWH6QrBm^tlq|>C+)3I`sPNJ>XB2offSoRy<_mltxE63~K|y*!s-|Uw zCb@~Z1(U$jELKhP0Ih>x9XKZ?C)bgtX+NM>9rV@4bMBD4qX5d1t~Qj`izav3QFxs!7=ZM(bd$XC@*bc~xyid{h0gIu)I*9w>h5TLN5 zAas0I?pSRHlxCA+FH{h24=J+z1}r7|AZW69Y<@~=dV!{OtSR|UU^3)GV5+~QW=i(H2gVJa;9dd zOiIltcrZ0Tr664!#0Oe@3!|WbhBPT7J%>i!K+(;CX#}0?%GSm>$tT`YKHHJwn3z&f z&Khy6fP$$twp)sN!Au7T`6-qSU(uwM(QV_`Fi)1pID zbF*^uqroRx(CIY8Gf;0>tmMWhx_c|xZlI#$fH95EhQU%kOvwkT`T?+kZ0;;^p);qo zbXhxPY|9Wi2gV>m9R;4CDHvL|kuJqzo-}o0y5)l*`i=Xgo^!x7;IDy6j`iXx?!JW{ zXht(~HA%+yNnncU{lE=@cMOoLxj0!y$m76d_Opur5wIud>qBLTA|GYZVp={-hAwW7 zq2sbr#yCzk9fyw+$z~m)!f!;22#H#^8hq2(1u8hfYAf0()$v97pmmX*dLF#CPLr&8* z2ol{gQ!ZFv;5wkc1D_V~`>Ni|G-hftQ`Iii(*V{02LNl?(xbzHDGZDh$d6VJiF`3v z2E;~Sibpgsvg$(t2oJKM~QeeAk$(X=5@;l{V7wdcXUcGiremuPJ(JVAoe+(>4wq#`An_R?VH%X8XjlI9`g{S)kjZ z)U9z~o&3XV-SDx(~Yjwp0Cs0Z6OeILT z8Dnzt-~c;2z#Rd0{VHlmpDQOzyTP~Am&Kswf>L!pHufHOG_vb%@H*M%!XpE0dRI^+ zQH|$;dK?su+G?~u2?{HT>U?b$Zx3D)n4k|t(jlgWmqFzM9^BZ@{>2@Q?YbXsMP#Ct zW0)+Nm!hVa2M5{N7u*qKx74l8OM()tsn{&s$3yGKvR8O;6Fd8jJMd}a#rT}TOYym# z%KCZ4C)r|jWbKUQ(Tkt=k7WyaX;V8p$Ag>M*?8{2=WDzepBH#(GrJy-Buz#zzO{)> zp9`uTC>NgB$foZruPgJn=~qCJr{K2$8;j?`E$mEiM+>{9hBq&1k)UTI;Bf=R@M%1o zd2ma+ehR$7hI+U}Z;K^GzR-CdI-3D15tQM2%W)sx+BZS>M!q2tSkTzUvUqVTyZ)l$ zxf{`ukAzX~Wql_onh!T)2CgYe&PNBV)ca6}C~0D|t_C&4h>bFoXw2?3PkML*Nx5QIE!y>XwFbMl@rEB!Z$|b-p#w z#^&(SHg^3@@Opsf!pqv&^v1{(BmqmHd6c568F6tRrx zjd8w?5={tviCFy=6pcd8Uj%oA+u1>0j87k48g92f+}Mbi`my@kDAB|)vWofg;t0Fm zBS_Q2B(JGWe@IbS`{;iYC>eQ?Ep6-!FOIb9^O|T{EadANk@Oj;=yIxNQ|TqfA+M){ zQg$KxtmmascJ?_Bjz-RCR&GC5>W83YY?61}o11l!Eo^#EP^1Ii!ANIU)S=xTP!;XU zR2_WWs=a#)X}EOw21UVLX!ac_>Ac8s?cG~S{g@~8Jp`20NP-JN+0i%h1AK7=RJ^1D zY!>%c{A{}fJ*kz+M02)`J8X9SIC#Ac!Mq@wH6$2H`1z)>dL~Lr0n|N9d_#%?&J^b*pYKeQFACnu0$*bKbs!aMur*!wT;a> z5mY8IcTkBu6zO{eM6g5=f?HAQCFQYn z86rQE_|^p|jWQ%kQ5s`P=o1iKW6L!j1%(>|L9y07DD^XHTgO0$ zQJRlZAEWd!O1(&qHRg^^c6~c$I>FFf29Gx4rQi*)mCpmh;R#T*h%uVRHvJ1w_k%(j z#*E#E5K@8J!e*UD6!pSh<&G|PYjrpjOBHtZK`DWUwusf|qcjxlHD1=trhfy9{DvF} zztx2*>IwcvdOi<|>M(~*Y-|V*?q;{nMCgp*q1|GwpP@9&C`G}1$yOgk)_RO5+XQ5tP!6QUvb$qm9zy^3nyAWQ*Pi2w7W#lC;333*JUi z3w=0BloqfJfXy3taZkJTscy!6^ow&vp$~tdXRI|g(Jaj^FP)I3zHzSIX@*G5fl?2n zw6(nSdwHo7TqSGgm6wi}m;8E~wJGJLVw4hjHY_m;)I|)UFwy8@dU@#$l)4%`D^{>9 z^(-$fDlZ*3OBU}wyrh2u8_G-j+u0vHc!1s7voFQM`Hr#H4^bM&vxmgFViPu$So#u_ z`k2#B+kt_0y>)+0gCuURTd~*{f{HVmzk^b~Q5rk|Da$DJ#ZyR{Q96&(D5KOHQJF|3 zwv;;t+4Uxa%v&>(lC=;N+9EV}^5DT_qywLQc=2Gn&T$8)AjVx4+4U$WmHj}~Ns_xG z-2WQctX)Bk;V%q`a~+~-IYzfjP?Dzng3@q<*Do1a$|${!(gV~0zBxO9Z#+Sw-V7;3kk0k=J;onGVNW->uY=@UWGay9O#>_zSvX4iiLkM7t= zn@w%jHX}@ZlX>uP+HyID+x64nBBRjK3b*NA4`>>4A}wOVEW?bgenyYtYIv3kO2sSE}p z)1C(f?PM;pOHU*@%&W=(D?wo$$U5uSppe~0$GN8N!vdW>A<}>m{OSr>SpAl`}3)_y$zD25UqbE!WV_aqcLP3#8CtP?Vbt%33#_#zyx7 z2QN*r>;C|a^n$F0+qK1Q9M2q^pm!N3Zy!>@qo7R7+Pi~*HA;8)@s;!K0#K?aWZ3hd zIzzIqv3(2AkcNXoewoG{X?A@-c$AT4H=Yi2$mHG0pb(bI>Z4R=IN&~ble{k5ECEG9 zC8_r-*I64)pf(t(C1V0Vo1UQW00T=z30Kdm)PX(*6qzpf=vzPyrKZ+;*%Vh)dI%*< z>6kco6y(%w9pBzP$JBy(qFr%@oxRRWGwc@kT;AG|U>TgtGaU)~zFZj|xOpIuTqa7h zUHMi}oj~Ea)c%aMUXEPHlz4CbL2^|ZYYnnzt7hwiy0q_CD0W1JG z!?-GAS8y;9nhT(tQTx|0=|%1?)JPFBr=bX8XIzyr8HAi?7=}4AF2ck|wll6fVUj~U z81l$^#zmM6NTQckbRm%$4CE(G<1?~+3#l0riE&kht*F7uH7>#=KL$W*#{!7PLNYGG z#7_qhAJJ}Hm9aZGn0Dg=E;Ix&zXrx!8W&+wln z@aF(X?_&VE9z*}CP=QI|Tma!B$u=-GoUdrYq;Ls<4B-Hhe*!=kVd4uSa8<@6w*o+T zC6<(Nt;8oaSOcJnwE(&Z6MqAMt_{ZL&6o^(TJZ_H@a!BpvCk@QRhYDvsCvSr<#|Q_ z6F|q z6R&Bd^nvR?!PM(n040J80Mh=8qJIOX-md^?sn&up$<+lWzBfaplBWYzMN?q>Y0Z=X zz3HQ+6%JetxSis6P&f{lI_#|KdjeAjeSzt!jH$x`;FDXDfJtNsQ!8Z%C~7!d;ZaIp zw8CS6=^{)Wjsva%oCjPNc($tl%hN%TzEHu|8afc9WAA~T91*? zU^d``=5V7LSyh;-HsOO-{WD6AFwt9pYXk2DCcS%&dd$BpNK7$s02S2HK||oqm_*)E za+NW~2>oA@s*Wi99xxg5J}?FSr^KiD`%G1w1g4I^QuHZcGUz+S|2HuHv@`T^3no^n zsz0mh2~+zEimr@_^&>uv_@@F{R;KVps>MZ^_?Hx26{h;js3*h!P;!LpfUb^8a&>Km z>j9ITFEIWzKMVRNMnlDD1Wc}Lrsx)mZUamSwj(fIgsEM;!uJ7FRcCxqhusywm*V%K zN?e4=#=fe4fCb~%!5F3(4=6#xG=eN(YLE>~Rk`$`FeRqRpeYYLqUz@WQ;=~*KM711 zVKQtLupjU?U`lAa3svC_VEkzZ@j-&`0F&Yqz_ghw1@-{`6_^r^JBCj|R2P^Gq_0h& zO7kYSC`=tRRCHxb^7J(bk}nKIff$XIAYr2EYOcU66Tb!E|8aL&SmpMUEb9)S=>6aB zF2nD%y+rT0{@z_0k@A@I#^1Y3<%beriqpTctE8qRPb>HD-6eSg*PV8qlyS}ijN2G6 z-5mbjUEcF9^Y7i|owl!ZpZ$Ax`SfA-+#LG|Vxeop3RK&}0`H|xtw zL9Mvx!TVn9&HD3|7n6C9Up)9VPy>0-Uy}K6pkDZ;Hygx%2esvr2OoKiqPzLA*W(Jmbvx#SJ&R(_QBxZEj~<@riiHf(F2ZvV^D3nMK^XCI zC8*|<-r*SIvMV0#A80>8P45G*CNMoFom0F%N{*iGt^r7%ULxVocusUdfq4BCk2Z2v zdW%KO{)$P@ZSG2t-Z)Wb)RC@u^i4hd8P9V+0+Rvc5#rHP?&koy1}h#tP|h|u6>qNS z;dFuG8E>(u6HmY*#T=>{)5GH9iZ@L0=v~}m#TyPDJ^IxHJOLoZBbA&t%1ZzwZ@v+u zXTPP2H(JRVZ!(uD<`~7S51L*Oladt0J4G+vaAk}TuMcLg#5Y5ko=oZSItM^cuRZ`@ zKz)E8paGyW8sIvBq0Rzu0lWvA9?;hS)&n*Hwg5^1&jFqXJPx4O9W+WB)mT6(1wa}K^oI9+ zz%jsa0KGV-ZT<^@7XdE;UIuIhyaLz;pa=0rfPHBHI$%HGe(>qBw-2B%ARmB*sZ9br z2zUrE888Jf4lthn|LH&>3oslo0x%LV1TYBD13>@&p_LY|37=#(+`1AS<%tKAnLX?g z6bEBqG{^L2;#I&7z)rv^0No%%0nvaMi})s)b&RAhlGFR3HGs8%b%3i-e*|y>up2;c zp+5sqKz}0o4rTt$-$d~(z&`-*0_X;D08RU${48J#pja##%6toFqc{~X4KN*$49EqH z0i*y100yEdEhl>MOD}!s71sv9Q-G%d#{mBXd<6Iy@Ckrki+v5C_jQK=uK{)g_5jX6 z=V`#lWYIrScn{DYPy*NhcnVMim837y#XbtcKGyv2FTteN~fH?qK{7(WnU?qSSJ}q=w*Tkc> zY}T(pc?BuO)sU7Pst*okLGd&f9RTeCln&idgBdpR9JF zcZahRI=fG7O=DgjcCdl~%>XUM)HG(RT+rtQqSeW~H+b zqkVy~K=hEIf)L zjgmQKd_MqLv2P)Nj&!RcV}ZOYU0J08O+b0eC!K>?$xqekL?(ETnR)W z1qe?E^J6nb2%%-7n}f~rFM)VXh`*(k4*Ge~_z;Lkgtd*Zg+=TTr%CRBxZ+^Z?63&W zWFbvYK)g1@zX7fV{9?MOb#2jyL1-2g)|L$XNzBM(-B^)0n8`k2--^`}VAKy7nGW$O z4ILkxE*kZ-A#RN4k~ju|hUPi0bJ=gL9{TTrM<4)gVKJ1Z#lwQtw|KdTMp>-Bzj;Dy z-D7*YY`IqJ8A!H+B*xcD49tST(V~Aoy6l9&A`2sOkF8wlJ0k{ikuio3`-}CEix~-l zIuK|WUAKMn51OroKulN^4Z%Dn*Z=o}+xNPTU8RhPgs>>fW`4pgn>AqPMNl>z^Mgpr zX5%d|^Yv_)XTiS01A-wRZOlJD;gtudTdCD(8|`l%dD}d1L(|s%o=#B8(XFqr=divk zRV>PZ{pL}+*)OLbYu%y$E<;g7m<`!Zype;Bw~I><_ot(A_;XfYd;0S^)xM2}Toi^0 zW7~_7xzKk|JeLc7=83rYiwCwIt`n01Ih&MoimxFMV;+%f9p2;WZzJl_93cwGABQj? z^3;N~C${eW{N1mOE)17{Btj>`|p@%DaEw$1gS-+N(NRQ5?dXQ6js5h3M10>aU(nwf|14m4=)wrBL1Q)m{A zL87zM;>Q9O-OxO}S5oui>o>Z5)D{8=e{!zoEfOZN`bZ?nlc0Z)w>-s=BNj}8V)Jxg z^1y%g9r^RSrz_g^7yBX5-#qTOW$N|Z=nejzTxpn*w6x6geb=(s%M+V){Txjx{$W#% z`f@&h`(t{>n?s-2Y+=73V^GrY7pEUWuN@&kYbR~k!8g~{-e9$`=HS3GXAd#rK?tOS zq$rQ9UUsf^Vm((28wd_%tmnmI2*gy;d0TXj@tVgF1Dc#}dO&YNaZT$NH5bK~r294k zn1ZRo6V|agB7}@I&nJ$}YZiZBjoR&@2& z-T|W5WERENh#8X+ZMoveWY|6}P`dN<^`kGgYU{BA?I=q?|6;*bGGCXr+7{7w3iAz! z(9#>rxf#fXTj;|OiDtoK))c62Cyq_Qs5>{2bGPZoo&TD?EbleLq_$yL z5#}kw!_4#Z{?DEH!{`*Mu(J~ZQ<2=W&=il8&cjIVl&z=NkNv|jcksA6%&d7!Oqhxp zI4V+RuzF(URNM?@xv^o;15IVaGh61@{`g}zxUCO3b)c%UlzHkg%(f(^ck$pfV?bD& zct#UJ)1W?1!~*%7rzbbRJpROEmhU@5j@BXqc#Oy;f!tGpin3{JH8uu|rn7E(FrE)6Q$~xE)8Q!daA)ZL?E&LMoJgE;5TJ46c;_TXcDgpT zU7bD49^H^+Vck?sKN0*ea;|y4Gv*JUoYbhASe32SjXPH?eHbHXD4qjqXddId?7ZG% zzqU6=;WYzu5*c#4IWcPt*ZQ&M zV)_hZH9z4wo7HO^W2nHszXry$a8UB56)&ya59KP%RhyRL8nrnrB4@&A*;I_4iCO(l z6wYKJEJ!>zlLaB#gXW_8`G9+C?Yr%RZ(8~mmz zll;w-YM&Gbe!H*ja|m~8gLR+{7M+TjS2d|%f{3351#*aNw#XyOFwoE6JOcLY_SE|h z)_T9VqTAi#sa1vw>hYwA+|0bh53>;N=9up4g9##h&Ru+%4XZ>Mf}NF!8bw$Ek)muK^V7}6c2Nu~V%;pvCN>n|rX4AE z6`_d~l2nktdGxr^$}dh$+2dV`9w_pVmTaQSyg%zaDwd)PW^^AW7SCr^@zp$5_tviE zgeW(5wHhN8`--rCdSX7-<#6%Rd}KBA{O6HvfxYaRJ!hkHTUZoj6!Wxfi^e{-z=xB* zG;1QYZQ>f5V#DUU0M>mdx-4M1UglxNrQyRDwb<p&mLqv;fHL$ zikeSE{NvDVo_TicirdoGE&F>{h8tESCF3h14+0jCu43WiNc`rZ>2`hI+ws)0SEp47 zMvDCqj4@AHKV3XC^MZTx4=V)BbJ#_$&u5JG>^P;OCR2DUh7sn$?RTmT5Q%59->eW= zDmp=cZ5AU)&OG^ja`w2y$mO5EQ6YC!EQUZk^Qib(pR0Q9zhKDQDB-yTM)VLNK>p_O_2&{DBlGPo*Hp+&5~E4L zJnw$|sX75~wJPmjA+S!=Ue3I&^iWhHW-VjhfmAe4iPv1#ZLsH@PrFy#oR5mr)bY3C z3h6bEo%?uw-=|ekk2j3hFy{U*cT% zTAlJoTjl$kk>TOKa(c~qxvRhZ&Ex-veg5#SUc2n03e$?k(&ZJVNt;aVvgbQY_s!8E z=a&3LFGbjO@ua}h<)8Q6#!ea8*A$fg@UP;L2#>_Hk$OZ#u7HipMeh}u`D{^0)LyZY zC}rbqo~V)AjE5Fej;R2>i#R>w+;#9btr{#`>nfOIY*&3;+tcQ+_C}a#Y%sipa}{%A z#@^148T&Ioi(91Fy9$1qFWz_wf(hcsRR{<3Hw~6GU%um`o}H3uTW37K&=Y0}!Vz1@ zgw<$fZ0!8l29dWKo2>n#<+<5EZ|%$)*<4KA!V;UAyxU8wUc}>Tuy?+@c%@N_I%`=Q zwqNXD%c6C&-Ufze%DuHp1U-ezO%B0^fAYqAip5IdP(Em-{e(!_RJHTvvbooFq9#c# z6LVB*x!f_fzTV7moJ{yXQ?(f?+Wz7Kslsjb zzN6)|I+pK+<*$Vm6FUjO^?r~q0g78hf5 z#?!Qy*zgi_t2z~{@s}rDx$18Ts5`jB$jp9By(}oT{d!JeaD;!77d3Hn2^n`kTK=vp4i&r*>-(7Fn43AAq`}DUXVr7w_p-bFb0+ z7iasfH{OQkZ|WRyWpAH)aNMUA3(CADs|dMi#_!#Qx6gcXJ(>=!m{+k0Anq980{t5~qT%14Ifx$===qu39DhUTv<&4>yMeACON32w4@lSi+G&3_fT zR1>v!;GO$JqSFq{-fl4h$p6+C8WuJ)3Js&e+FuikA<*p40_I!Y-*#bWxL(^W-rK0hd|e3NS3sMq$ZlVa9RW^Zs6&LH-j z_A&3VDYdSjp>Gx78rNp$=Q~*pb8)1l@4WgN`*%izEEMy@L&N3I_Td-rK!(yk2qHw^ vCYByz%o$cktZiy}TznH`$raBYVg6#tx6DoK|A~2sEln(!c7`;wEU^9`$G!h2 delta 21092 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 diff --git a/hub/site/package.json b/hub/site/package.json index d81f089..d8fe2b7 100644 --- a/hub/site/package.json +++ b/hub/site/package.json @@ -17,6 +17,7 @@ "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-separator": "^1.1.0", + "@radix-ui/react-slider": "^1.2.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.1.0", "@radix-ui/react-toast": "^1.2.1", diff --git a/hub/site/src/components/table-alerts.tsx b/hub/site/src/components/table-alerts.tsx index 376697f..0b5ffbf 100644 --- a/hub/site/src/components/table-alerts.tsx +++ b/hub/site/src/components/table-alerts.tsx @@ -13,9 +13,18 @@ import { cn, isAdmin } from '@/lib/utils' import { Button } from '@/components/ui/button' import { Switch } from '@/components/ui/switch' import { AlertRecord, SystemRecord } from '@/types' -import { useMemo, useState } from 'react' +import { lazy, Suspense, useMemo, useState } from 'react' import { toast } from './ui/use-toast' +const Slider = lazy(() => import('./ui/slider')) + +const failedUpdateToast = () => + toast({ + title: 'Failed to update alert', + description: 'Please check logs for more details.', + variant: 'destructive', + }) + export default function AlertsButton({ system }: { system: SystemRecord }) { const alerts = useStore($alerts) @@ -38,7 +47,7 @@ export default function AlertsButton({ system }: { system: SystemRecord }) { /> - + Alerts for {system.name} @@ -54,38 +63,57 @@ export default function AlertsButton({ system }: { system: SystemRecord }) { to ensure alerts are delivered.{' '} )} - Webhook delivery and more alert options will be added in the future. - +
+ + + + +
) } -function Alert({ system, alerts }: { system: SystemRecord; alerts: AlertRecord[] }) { +function AlertStatus({ system, alerts }: { system: SystemRecord; alerts: AlertRecord[] }) { const [pendingChange, setPendingChange] = useState(false) const alert = useMemo(() => { - return alerts.find((alert) => alert.name === 'status') + return alerts.find((alert) => alert.name === 'Status') }, [alerts]) return (