watch-tail 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +157 -3
- package/build/client/_app/immutable/assets/0.BQQ1YLPc.css +2 -0
- package/build/client/_app/immutable/assets/0.BQQ1YLPc.css.br +0 -0
- package/build/client/_app/immutable/assets/0.BQQ1YLPc.css.gz +0 -0
- package/build/client/_app/immutable/assets/BrushContext.2tkUc39H.css +1 -0
- package/build/client/_app/immutable/assets/BrushContext.2tkUc39H.css.br +0 -0
- package/build/client/_app/immutable/assets/BrushContext.2tkUc39H.css.gz +0 -0
- package/build/client/_app/immutable/assets/ChartAnnotations.Bw8CGteN.css +1 -0
- package/build/client/_app/immutable/assets/ChartAnnotations.Bw8CGteN.css.br +0 -0
- package/build/client/_app/immutable/assets/ChartAnnotations.Bw8CGteN.css.gz +0 -0
- package/build/client/_app/immutable/assets/Circle.C5MKzgk2.css +1 -0
- package/build/client/_app/immutable/assets/Circle.C5MKzgk2.css.br +1 -0
- package/build/client/_app/immutable/assets/Circle.C5MKzgk2.css.gz +0 -0
- package/build/client/_app/immutable/assets/DefaultTooltip.CGOVIm3D.css +1 -0
- package/build/client/_app/immutable/assets/DefaultTooltip.CGOVIm3D.css.br +0 -0
- package/build/client/_app/immutable/assets/DefaultTooltip.CGOVIm3D.css.gz +0 -0
- package/build/client/_app/immutable/assets/EventScatter.DA5sZ1c0.css +1 -0
- package/build/client/_app/immutable/assets/EventScatter.DA5sZ1c0.css.br +0 -0
- package/build/client/_app/immutable/assets/EventScatter.DA5sZ1c0.css.gz +0 -0
- package/build/client/_app/immutable/assets/Labels.sEaJtb97.css +1 -0
- package/build/client/_app/immutable/assets/Labels.sEaJtb97.css.br +0 -0
- package/build/client/_app/immutable/assets/Labels.sEaJtb97.css.gz +0 -0
- package/build/client/_app/immutable/assets/Legend.DnhxhJ2I.css +1 -0
- package/build/client/_app/immutable/assets/Legend.DnhxhJ2I.css.br +0 -0
- package/build/client/_app/immutable/assets/Legend.DnhxhJ2I.css.gz +0 -0
- package/build/client/_app/immutable/assets/Path.B0wDzTrn.css +1 -0
- package/build/client/_app/immutable/assets/Path.B0wDzTrn.css.br +0 -0
- package/build/client/_app/immutable/assets/Path.B0wDzTrn.css.gz +0 -0
- package/build/client/_app/immutable/assets/TransformContext.1cjyViIk.css +1 -0
- package/build/client/_app/immutable/assets/TransformContext.1cjyViIk.css.br +0 -0
- package/build/client/_app/immutable/assets/TransformContext.1cjyViIk.css.gz +0 -0
- package/build/client/_app/immutable/assets/Voronoi.ce7atosu.css +1 -0
- package/build/client/_app/immutable/assets/Voronoi.ce7atosu.css.br +0 -0
- package/build/client/_app/immutable/assets/Voronoi.ce7atosu.css.gz +0 -0
- package/build/client/_app/immutable/chunks/AUlz4-kZ.js +1 -0
- package/build/client/_app/immutable/chunks/AUlz4-kZ.js.br +0 -0
- package/build/client/_app/immutable/chunks/AUlz4-kZ.js.gz +0 -0
- package/build/client/_app/immutable/chunks/B1t8v2wP.js +1 -0
- package/build/client/_app/immutable/chunks/B1t8v2wP.js.br +0 -0
- package/build/client/_app/immutable/chunks/B1t8v2wP.js.gz +0 -0
- package/build/client/_app/immutable/chunks/B6nyufJR.js +1 -0
- package/build/client/_app/immutable/chunks/B6nyufJR.js.br +0 -0
- package/build/client/_app/immutable/chunks/B6nyufJR.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BR2LaRms.js +1 -0
- package/build/client/_app/immutable/chunks/BR2LaRms.js.br +1 -0
- package/build/client/_app/immutable/chunks/BR2LaRms.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BR7qH0F5.js +1 -0
- package/build/client/_app/immutable/chunks/BR7qH0F5.js.br +0 -0
- package/build/client/_app/immutable/chunks/BR7qH0F5.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BkXArOra.js +1 -0
- package/build/client/_app/immutable/chunks/BkXArOra.js.br +0 -0
- package/build/client/_app/immutable/chunks/BkXArOra.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Bl052uUt.js +1 -0
- package/build/client/_app/immutable/chunks/Bl052uUt.js.br +0 -0
- package/build/client/_app/immutable/chunks/Bl052uUt.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Bmk_Voz6.js +1 -0
- package/build/client/_app/immutable/chunks/Bmk_Voz6.js.br +0 -0
- package/build/client/_app/immutable/chunks/Bmk_Voz6.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Bp63j1zN.js +1 -0
- package/build/client/_app/immutable/chunks/Bp63j1zN.js.br +0 -0
- package/build/client/_app/immutable/chunks/Bp63j1zN.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BwntJtbK.js +1 -0
- package/build/client/_app/immutable/chunks/BwntJtbK.js.br +0 -0
- package/build/client/_app/immutable/chunks/BwntJtbK.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C20WvNRq.js +1 -0
- package/build/client/_app/immutable/chunks/C20WvNRq.js.br +0 -0
- package/build/client/_app/immutable/chunks/C20WvNRq.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C58PZtCD.js +4 -0
- package/build/client/_app/immutable/chunks/C58PZtCD.js.br +0 -0
- package/build/client/_app/immutable/chunks/C58PZtCD.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CCch3uox.js +1 -0
- package/build/client/_app/immutable/chunks/CCch3uox.js.br +0 -0
- package/build/client/_app/immutable/chunks/CCch3uox.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CDbeYGlD.js +1 -0
- package/build/client/_app/immutable/chunks/CDbeYGlD.js.br +0 -0
- package/build/client/_app/immutable/chunks/CDbeYGlD.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CJFHBop_.js +1 -0
- package/build/client/_app/immutable/chunks/CJFHBop_.js.br +0 -0
- package/build/client/_app/immutable/chunks/CJFHBop_.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CYw9ugj1.js +1 -0
- package/build/client/_app/immutable/chunks/CYw9ugj1.js.br +0 -0
- package/build/client/_app/immutable/chunks/CYw9ugj1.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CjJd0uRB.js +1 -0
- package/build/client/_app/immutable/chunks/CjJd0uRB.js.br +1 -0
- package/build/client/_app/immutable/chunks/CjJd0uRB.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CoEFQJcr.js +1 -0
- package/build/client/_app/immutable/chunks/CoEFQJcr.js.br +0 -0
- package/build/client/_app/immutable/chunks/CoEFQJcr.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D-JtVVAo.js +1 -0
- package/build/client/_app/immutable/chunks/D-JtVVAo.js.br +0 -0
- package/build/client/_app/immutable/chunks/D-JtVVAo.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DJDlf6lC.js +1 -0
- package/build/client/_app/immutable/chunks/DJDlf6lC.js.br +0 -0
- package/build/client/_app/immutable/chunks/DJDlf6lC.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DJLYkl5w.js +1 -0
- package/build/client/_app/immutable/chunks/DJLYkl5w.js.br +0 -0
- package/build/client/_app/immutable/chunks/DJLYkl5w.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DPKdNiYi.js +1 -0
- package/build/client/_app/immutable/chunks/DPKdNiYi.js.br +0 -0
- package/build/client/_app/immutable/chunks/DPKdNiYi.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DQi7bc16.js +1 -0
- package/build/client/_app/immutable/chunks/DQi7bc16.js.br +0 -0
- package/build/client/_app/immutable/chunks/DQi7bc16.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DVTgeA2y.js +2 -0
- package/build/client/_app/immutable/chunks/DVTgeA2y.js.br +0 -0
- package/build/client/_app/immutable/chunks/DVTgeA2y.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Db0q5_zr.js +1 -0
- package/build/client/_app/immutable/chunks/Db0q5_zr.js.br +0 -0
- package/build/client/_app/immutable/chunks/Db0q5_zr.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Dfvzj6n2.js +1 -0
- package/build/client/_app/immutable/chunks/Dfvzj6n2.js.br +0 -0
- package/build/client/_app/immutable/chunks/Dfvzj6n2.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Di37PX72.js +1 -0
- package/build/client/_app/immutable/chunks/Di37PX72.js.br +0 -0
- package/build/client/_app/immutable/chunks/Di37PX72.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{DyIYFD7i.js → DkyYNO4U.js} +1 -1
- package/build/client/_app/immutable/chunks/DkyYNO4U.js.br +0 -0
- package/build/client/_app/immutable/chunks/DkyYNO4U.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DmRhEzIa.js +1 -0
- package/build/client/_app/immutable/chunks/DmRhEzIa.js.br +0 -0
- package/build/client/_app/immutable/chunks/DmRhEzIa.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Dp8S9ymS.js +1 -0
- package/build/client/_app/immutable/chunks/Dp8S9ymS.js.br +0 -0
- package/build/client/_app/immutable/chunks/Dp8S9ymS.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DslUcEd-.js +1 -0
- package/build/client/_app/immutable/chunks/DslUcEd-.js.br +0 -0
- package/build/client/_app/immutable/chunks/DslUcEd-.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Dt_Zd6U2.js +1 -0
- package/build/client/_app/immutable/chunks/Dt_Zd6U2.js.br +0 -0
- package/build/client/_app/immutable/chunks/Dt_Zd6U2.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DwezeuNE.js +5 -0
- package/build/client/_app/immutable/chunks/DwezeuNE.js.br +0 -0
- package/build/client/_app/immutable/chunks/DwezeuNE.js.gz +0 -0
- package/build/client/_app/immutable/chunks/HclGiUj8.js +1 -0
- package/build/client/_app/immutable/chunks/HclGiUj8.js.br +0 -0
- package/build/client/_app/immutable/chunks/HclGiUj8.js.gz +0 -0
- package/build/client/_app/immutable/chunks/PYzEcuyd.js +10 -0
- package/build/client/_app/immutable/chunks/PYzEcuyd.js.br +0 -0
- package/build/client/_app/immutable/chunks/PYzEcuyd.js.gz +0 -0
- package/build/client/_app/immutable/chunks/RobXhXPM.js +1 -0
- package/build/client/_app/immutable/chunks/RobXhXPM.js.br +1 -0
- package/build/client/_app/immutable/chunks/RobXhXPM.js.gz +0 -0
- package/build/client/_app/immutable/chunks/UySfXNq7.js +1 -0
- package/build/client/_app/immutable/chunks/UySfXNq7.js.br +0 -0
- package/build/client/_app/immutable/chunks/UySfXNq7.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Y5urAfNy.js +1 -0
- package/build/client/_app/immutable/chunks/Y5urAfNy.js.br +0 -0
- package/build/client/_app/immutable/chunks/Y5urAfNy.js.gz +0 -0
- package/build/client/_app/immutable/chunks/_Zq76xnA.js +15 -0
- package/build/client/_app/immutable/chunks/_Zq76xnA.js.br +0 -0
- package/build/client/_app/immutable/chunks/_Zq76xnA.js.gz +0 -0
- package/build/client/_app/immutable/chunks/dbhDrJmm.js +1 -0
- package/build/client/_app/immutable/chunks/dbhDrJmm.js.br +0 -0
- package/build/client/_app/immutable/chunks/dbhDrJmm.js.gz +0 -0
- package/build/client/_app/immutable/chunks/pqiPC7Y8.js +3 -0
- package/build/client/_app/immutable/chunks/pqiPC7Y8.js.br +0 -0
- package/build/client/_app/immutable/chunks/pqiPC7Y8.js.gz +0 -0
- package/build/client/_app/immutable/chunks/yCfI4PCa.js +1 -0
- package/build/client/_app/immutable/chunks/yCfI4PCa.js.br +0 -0
- package/build/client/_app/immutable/chunks/yCfI4PCa.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.vMZB8_Zr.js +2 -0
- package/build/client/_app/immutable/entry/app.vMZB8_Zr.js.br +0 -0
- package/build/client/_app/immutable/entry/app.vMZB8_Zr.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.pM5lNk_5.js +1 -0
- package/build/client/_app/immutable/entry/start.pM5lNk_5.js.br +0 -0
- package/build/client/_app/immutable/entry/start.pM5lNk_5.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.CZO0sIxX.js +1 -0
- package/build/client/_app/immutable/nodes/0.CZO0sIxX.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.CZO0sIxX.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.zcN3R_zF.js +1 -0
- package/build/client/_app/immutable/nodes/1.zcN3R_zF.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.zcN3R_zF.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.DxevVqut.js +2 -0
- package/build/client/_app/immutable/nodes/2.DxevVqut.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.DxevVqut.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/handler.js +9 -7
- package/build/handler.js.map +1 -1
- package/build/index.js +9 -7
- package/build/index.js.map +1 -1
- package/build/server/chunks/chunks/Arc.canvas.js-D6g58nSp.js +701 -0
- package/build/server/chunks/chunks/Arc.canvas.js-D6g58nSp.js.map +1 -0
- package/build/server/chunks/chunks/Arc.js-D78Cf_4M.js +31 -0
- package/build/server/chunks/chunks/Arc.js-D78Cf_4M.js.map +1 -0
- package/build/server/chunks/chunks/Bar.js-Cv73T8_D.js +448 -0
- package/build/server/chunks/chunks/Bar.js-Cv73T8_D.js.map +1 -0
- package/build/server/chunks/chunks/ChartAnnotations.js-Ci22VB_6.js +983 -0
- package/build/server/chunks/chunks/ChartAnnotations.js-Ci22VB_6.js.map +1 -0
- package/build/server/chunks/chunks/Circle.html.js-BjwyNRG3.js +355 -0
- package/build/server/chunks/chunks/Circle.html.js-BjwyNRG3.js.map +1 -0
- package/build/server/chunks/chunks/ClipPath.canvas.js-CFTBDWVU.js +170 -0
- package/build/server/chunks/chunks/ClipPath.canvas.js-CFTBDWVU.js.map +1 -0
- package/build/server/chunks/chunks/DefaultTooltip.js-0r5rbE-1.js +352 -0
- package/build/server/chunks/chunks/DefaultTooltip.js-0r5rbE-1.js.map +1 -0
- package/build/server/chunks/chunks/EventScatter.js-DqCMPeHG.js +7304 -0
- package/build/server/chunks/chunks/EventScatter.js-DqCMPeHG.js.map +1 -0
- package/build/server/chunks/chunks/Labels.js-DpagpV6d.js +497 -0
- package/build/server/chunks/chunks/Labels.js-DpagpV6d.js.map +1 -0
- package/build/server/chunks/chunks/Legend.js-BOpwpe5s.js +343 -0
- package/build/server/chunks/chunks/Legend.js-BOpwpe5s.js.map +1 -0
- package/build/server/chunks/chunks/Line.html.js-BM5tR11G.js +423 -0
- package/build/server/chunks/chunks/Line.html.js-BM5tR11G.js.map +1 -0
- package/build/server/chunks/chunks/Link.canvas.js-P8ySzEAw.js +756 -0
- package/build/server/chunks/chunks/Link.canvas.js-P8ySzEAw.js.map +1 -0
- package/build/server/chunks/chunks/Path.canvas.js-DNAtnEBt.js +1259 -0
- package/build/server/chunks/chunks/Path.canvas.js-DNAtnEBt.js.map +1 -0
- package/build/server/chunks/chunks/Points.html.js-CLbHD6OP.js +228 -0
- package/build/server/chunks/chunks/Points.html.js-CLbHD6OP.js.map +1 -0
- package/build/server/chunks/chunks/Points.js-e6g0Re6H.js +14 -0
- package/build/server/chunks/chunks/Points.js-e6g0Re6H.js.map +1 -0
- package/build/server/chunks/chunks/Points2.js-BMGdkzTE.js +25 -0
- package/build/server/chunks/chunks/Points2.js-BMGdkzTE.js.map +1 -0
- package/build/server/chunks/chunks/Rect.html.js-Q3UvRwNn.js +547 -0
- package/build/server/chunks/chunks/Rect.html.js-Q3UvRwNn.js.map +1 -0
- package/build/server/chunks/chunks/Spline.js-BuZPz2PJ.js +516 -0
- package/build/server/chunks/chunks/Spline.js-BuZPz2PJ.js.map +1 -0
- package/build/server/chunks/chunks/Text.html.js-C8xO5Nz6.js +847 -0
- package/build/server/chunks/chunks/Text.html.js-C8xO5Nz6.js.map +1 -0
- package/build/server/chunks/chunks/TooltipItem.js-T0cKLjLu.js +314 -0
- package/build/server/chunks/chunks/TooltipItem.js-T0cKLjLu.js.map +1 -0
- package/build/server/chunks/chunks/Voronoi.js-DevTsXQE.js +2052 -0
- package/build/server/chunks/chunks/Voronoi.js-DevTsXQE.js.map +1 -0
- package/build/server/chunks/chunks/archive.js-C6rz09rJ.js +584 -0
- package/build/server/chunks/chunks/archive.js-C6rz09rJ.js.map +1 -0
- package/build/server/chunks/chunks/array.js-CBp0H4t3.js +8 -0
- package/build/server/chunks/chunks/array.js-CBp0H4t3.js.map +1 -0
- package/build/server/chunks/chunks/attributes.js-BVWo7wbg.js +25 -0
- package/build/server/chunks/chunks/attributes.js-BVWo7wbg.js.map +1 -0
- package/build/server/chunks/chunks/{env2.js-D7xrfc37.js → aws.js-DkPqAybc.js} +13 -64
- package/build/server/chunks/chunks/aws.js-DkPqAybc.js.map +1 -0
- package/build/server/chunks/chunks/band.js-C7izXphE.js +155 -0
- package/build/server/chunks/chunks/band.js-C7izXphE.js.map +1 -0
- package/build/server/chunks/chunks/centroid.js-BnGN3LH0.js +1356 -0
- package/build/server/chunks/chunks/centroid.js-BnGN3LH0.js.map +1 -0
- package/build/server/chunks/chunks/common.js-BWQB8Die.js +5760 -0
- package/build/server/chunks/chunks/common.js-BWQB8Die.js.map +1 -0
- package/build/server/chunks/chunks/env2.js-CcIskxOZ.js +60 -0
- package/build/server/chunks/chunks/env2.js-CcIskxOZ.js.map +1 -0
- package/build/server/chunks/chunks/{exports.js-CvFPPbaH.js → exports.js-ClPPfcHj.js} +6 -1
- package/build/server/chunks/chunks/exports.js-ClPPfcHj.js.map +1 -0
- package/build/server/chunks/chunks/group.js-DBFzWAox.js +30 -0
- package/build/server/chunks/chunks/group.js-DBFzWAox.js.map +1 -0
- package/build/server/chunks/chunks/index-server.js-DNLOKvM0.js +248 -0
- package/build/server/chunks/chunks/index-server.js-DNLOKvM0.js.map +1 -0
- package/build/server/chunks/chunks/index-server2.js-l0sNRNKZ.js +2 -0
- package/build/server/chunks/chunks/index-server2.js-l0sNRNKZ.js.map +1 -0
- package/build/server/chunks/chunks/{internal2.js-DZ8ZzzjN.js → internal.js-DZ8ZzzjN.js} +1 -1
- package/build/server/chunks/chunks/internal.js-DZ8ZzzjN.js.map +1 -0
- package/build/server/chunks/chunks/internal2.js-DDcktblR.js +188 -0
- package/build/server/chunks/chunks/internal2.js-DDcktblR.js.map +1 -0
- package/build/server/chunks/chunks/key.svelte.js-CWIvXc6L.js +695 -0
- package/build/server/chunks/chunks/key.svelte.js-CWIvXc6L.js.map +1 -0
- package/build/server/chunks/chunks/{internal.js-B-DZpK-L.js → legacy-client.js-DJmDw3Zx.js} +13 -439
- package/build/server/chunks/chunks/legacy-client.js-DJmDw3Zx.js.map +1 -0
- package/build/server/chunks/chunks/level-filter.js-H_AdNbCw.js +297 -0
- package/build/server/chunks/chunks/level-filter.js-H_AdNbCw.js.map +1 -0
- package/build/server/chunks/chunks/lineRadial.js-Dm0GMqo3.js +126 -0
- package/build/server/chunks/chunks/lineRadial.js-Dm0GMqo3.js.map +1 -0
- package/build/server/chunks/chunks/linear.js-CTOKrHn9.js +1046 -0
- package/build/server/chunks/chunks/linear.js-CTOKrHn9.js.map +1 -0
- package/build/server/chunks/chunks/log-buffer.js-CpI7gyom.js +471 -0
- package/build/server/chunks/chunks/log-buffer.js-CpI7gyom.js.map +1 -0
- package/build/server/chunks/chunks/math.js-BqujbaBs.js +50 -0
- package/build/server/chunks/chunks/math.js-BqujbaBs.js.map +1 -0
- package/build/server/chunks/chunks/max.js-CuS0BpRI.js +11 -0
- package/build/server/chunks/chunks/max.js-CuS0BpRI.js.map +1 -0
- package/build/server/chunks/chunks/min.js-DZWt5tPl.js +11 -0
- package/build/server/chunks/chunks/min.js-DZWt5tPl.js.map +1 -0
- package/build/server/chunks/chunks/motion.svelte.js-DCHSFMu-.js +677 -0
- package/build/server/chunks/chunks/motion.svelte.js-DCHSFMu-.js.map +1 -0
- package/build/server/chunks/chunks/occlusion.js-DJ-AdK9u.js +33 -0
- package/build/server/chunks/chunks/occlusion.js-DJ-AdK9u.js.map +1 -0
- package/build/server/chunks/chunks/path.js-BizMrlzb.js +488 -0
- package/build/server/chunks/chunks/path.js-BizMrlzb.js.map +1 -0
- package/build/server/chunks/chunks/path2.js-pquT2Kg6.js +27 -0
- package/build/server/chunks/chunks/path2.js-pquT2Kg6.js.map +1 -0
- package/build/server/chunks/chunks/pointRadial.js-Deeu1LXx.js +7 -0
- package/build/server/chunks/chunks/pointRadial.js-Deeu1LXx.js.map +1 -0
- package/build/server/chunks/chunks/rect.svelte.js-zeS7FWjB.js +187 -0
- package/build/server/chunks/chunks/rect.svelte.js-zeS7FWjB.js.map +1 -0
- package/build/server/chunks/chunks/scales.svelte.js-D_1popPi.js +276 -0
- package/build/server/chunks/chunks/scales.svelte.js-D_1popPi.js.map +1 -0
- package/build/server/chunks/chunks/series-buckets.js-CX_rWCeH.js +123 -0
- package/build/server/chunks/chunks/series-buckets.js-CX_rWCeH.js.map +1 -0
- package/build/server/chunks/chunks/{server.js-ByNB8uti.js → server.js-CAEBuMIH.js} +167 -2
- package/build/server/chunks/chunks/server.js-CAEBuMIH.js.map +1 -0
- package/build/server/chunks/chunks/source.js-DZEVvuho.js +21 -0
- package/build/server/chunks/chunks/source.js-DZEVvuho.js.map +1 -0
- package/build/server/chunks/chunks/{state.js-V2NYGhy_.js → state.js-Dves8H8h.js} +5 -4
- package/build/server/chunks/chunks/state.js-Dves8H8h.js.map +1 -0
- package/build/server/chunks/chunks/types.js-x3GyGPOz.js +11 -0
- package/build/server/chunks/chunks/types.js-x3GyGPOz.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/archive/_server.ts.js-sNd3lS0y.js +39 -0
- package/build/server/chunks/entries/endpoints/api/archive/_server.ts.js-sNd3lS0y.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/health/{_server.ts.js-aiNZfkco.js → _server.ts.js-VctnBhIJ.js} +5 -3
- package/build/server/chunks/entries/endpoints/api/health/_server.ts.js-VctnBhIJ.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/identity/_server.ts.js-Dt540Sio.js +73 -0
- package/build/server/chunks/entries/endpoints/api/identity/_server.ts.js-Dt540Sio.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/log-groups/{_server.ts.js-D143-vRl.js → _server.ts.js-BGyWbj78.js} +57 -7
- package/build/server/chunks/entries/endpoints/api/log-groups/_server.ts.js-BGyWbj78.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/regions/{_server.ts.js-CDNL7msU.js → _server.ts.js-CBqfN6Pg.js} +5 -3
- package/build/server/chunks/entries/endpoints/api/regions/_server.ts.js-CBqfN6Pg.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/series/_server.ts.js-DjGMqXKo.js +194 -0
- package/build/server/chunks/entries/endpoints/api/series/_server.ts.js-DjGMqXKo.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/stream/{_server.ts.js-D_i-xW6_.js → _server.ts.js-C-XDemlx.js} +324 -222
- package/build/server/chunks/entries/endpoints/api/stream/_server.ts.js-C-XDemlx.js.map +1 -0
- package/build/server/chunks/entries/fallbacks/{error.svelte.js-CAasf3PF.js → error.svelte.js-DdUYlUJV.js} +7 -5
- package/build/server/chunks/entries/fallbacks/{error.svelte.js-CAasf3PF.js.map → error.svelte.js-DdUYlUJV.js.map} +1 -1
- package/build/server/chunks/entries/pages/_layout.svelte.js-CBQpEToy.js +22 -0
- package/build/server/chunks/entries/pages/_layout.svelte.js-CBQpEToy.js.map +1 -0
- package/build/server/chunks/entries/pages/{_page.svelte.js-DzAAqWz2.js → _page.svelte.js-B-kVwiNh.js} +563 -349
- package/build/server/chunks/entries/pages/_page.svelte.js-B-kVwiNh.js.map +1 -0
- package/build/server/chunks/{handler-CuWqZDuC.js → handler-BHf-eLnl.js} +3 -3
- package/build/server/chunks/{handler-CuWqZDuC.js.map → handler-BHf-eLnl.js.map} +1 -1
- package/build/server/chunks/{index.js-NHTL74z2.js → index.js-D6Auup1Z.js} +6 -5
- package/build/server/chunks/index.js-D6Auup1Z.js.map +1 -0
- package/build/server/chunks/manifest.js-B3Q7n9I6.js +91 -0
- package/build/server/chunks/manifest.js-B3Q7n9I6.js.map +1 -0
- package/build/server/chunks/nodes/0.js-BMP_Yttt.js +9 -0
- package/build/server/chunks/nodes/{0.js-Bg4_hDjk.js.map → 0.js-BMP_Yttt.js.map} +1 -1
- package/build/server/chunks/nodes/1.js-2HQHUOse.js +9 -0
- package/build/server/chunks/nodes/{1.js-CDSk3TvW.js.map → 1.js-2HQHUOse.js.map} +1 -1
- package/build/server/chunks/nodes/2.js-7RtHpiHN.js +9 -0
- package/build/server/chunks/nodes/2.js-7RtHpiHN.js.map +1 -0
- package/dist/cli/completions.js +6 -0
- package/dist/cli/index.js +149 -30
- package/dist/cli/options.js +28 -2
- package/dist/cli/preflight.js +72 -0
- package/dist/cli/ui.js +7 -3
- package/dist/lib/cli/aws.js +12 -4
- package/dist/lib/log-buffer.js +314 -0
- package/dist/lib/log-format.js +185 -0
- package/dist/lib/types.js +0 -4
- package/package.json +7 -2
- package/build/client/_app/immutable/assets/0.CocWI5K4.css +0 -2
- package/build/client/_app/immutable/assets/0.CocWI5K4.css.br +0 -0
- package/build/client/_app/immutable/assets/0.CocWI5K4.css.gz +0 -0
- package/build/client/_app/immutable/chunks/05WsiB4A.js +0 -1
- package/build/client/_app/immutable/chunks/05WsiB4A.js.br +0 -0
- package/build/client/_app/immutable/chunks/05WsiB4A.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DyIYFD7i.js.br +0 -0
- package/build/client/_app/immutable/chunks/DyIYFD7i.js.gz +0 -0
- package/build/client/_app/immutable/chunks/ZAD2y5xJ.js +0 -3
- package/build/client/_app/immutable/chunks/ZAD2y5xJ.js.br +0 -0
- package/build/client/_app/immutable/chunks/ZAD2y5xJ.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.D0_YFW4X.js +0 -2
- package/build/client/_app/immutable/entry/app.D0_YFW4X.js.br +0 -0
- package/build/client/_app/immutable/entry/app.D0_YFW4X.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.D1rrHrg2.js +0 -1
- package/build/client/_app/immutable/entry/start.D1rrHrg2.js.br +0 -0
- package/build/client/_app/immutable/entry/start.D1rrHrg2.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.DHBUWbOo.js +0 -1
- package/build/client/_app/immutable/nodes/0.DHBUWbOo.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.DHBUWbOo.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.D1vhnOgO.js +0 -1
- package/build/client/_app/immutable/nodes/1.D1vhnOgO.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.D1vhnOgO.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.BPdL6B7F.js +0 -5
- package/build/client/_app/immutable/nodes/2.BPdL6B7F.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.BPdL6B7F.js.gz +0 -0
- package/build/server/chunks/chunks/env2.js-D7xrfc37.js.map +0 -1
- package/build/server/chunks/chunks/exports.js-CvFPPbaH.js.map +0 -1
- package/build/server/chunks/chunks/internal.js-B-DZpK-L.js.map +0 -1
- package/build/server/chunks/chunks/internal2.js-DZ8ZzzjN.js.map +0 -1
- package/build/server/chunks/chunks/server.js-ByNB8uti.js.map +0 -1
- package/build/server/chunks/chunks/state.js-V2NYGhy_.js.map +0 -1
- package/build/server/chunks/entries/endpoints/api/health/_server.ts.js-aiNZfkco.js.map +0 -1
- package/build/server/chunks/entries/endpoints/api/log-groups/_server.ts.js-D143-vRl.js.map +0 -1
- package/build/server/chunks/entries/endpoints/api/regions/_server.ts.js-CDNL7msU.js.map +0 -1
- package/build/server/chunks/entries/endpoints/api/stream/_server.ts.js-D_i-xW6_.js.map +0 -1
- package/build/server/chunks/entries/pages/_layout.svelte.js-Bx7lXkAi.js +0 -22
- package/build/server/chunks/entries/pages/_layout.svelte.js-Bx7lXkAi.js.map +0 -1
- package/build/server/chunks/entries/pages/_page.svelte.js-DzAAqWz2.js.map +0 -1
- package/build/server/chunks/index.js-NHTL74z2.js.map +0 -1
- package/build/server/chunks/manifest.js-8g4gjxwf.js +0 -70
- package/build/server/chunks/manifest.js-8g4gjxwf.js.map +0 -1
- package/build/server/chunks/nodes/0.js-Bg4_hDjk.js +0 -9
- package/build/server/chunks/nodes/1.js-CDSk3TvW.js +0 -9
- package/build/server/chunks/nodes/2.js-BVhBcWrN.js +0 -9
- package/build/server/chunks/nodes/2.js-BVhBcWrN.js.map +0 -1
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import '../../chunks/
|
|
2
|
-
import {
|
|
1
|
+
import { ah as escape_html, ag as attr, ai as attr_style, ac as derived, aj as attr_class, ak as ensure_array_like, al as stringify, am as bind_props, an as clsx } from '../../chunks/server.js-CAEBuMIH.js';
|
|
2
|
+
import { L as LogStore, t as tokenizeJson, f as filterByLevel, b as filterEvents, e as effectiveLevel, a as detectLevel, c as formatLogMessage, g as eventKey, l as levelColorClass } from '../../chunks/log-buffer.js-CpI7gyom.js';
|
|
3
3
|
import { R as REGION_CODES } from '../../chunks/regions.js-IQPMyP5C.js';
|
|
4
4
|
import { P as PRESET_LABELS, a as presetLabel, H as HISTORIC_PRESETS, t as toDateTimeLocal, d as describeWindow } from '../../chunks/time-range.js-BLMT8aU3.js';
|
|
5
|
-
import { p as page, r as replaceState } from '../../chunks/state.js-
|
|
5
|
+
import { p as page, r as replaceState } from '../../chunks/state.js-Dves8H8h.js';
|
|
6
|
+
import { S as SERIES_LEVEL_COLOR, g as groupByLevel, i as isMeaningfulBrush, b as bucketEvents } from '../../chunks/series-buckets.js-CX_rWCeH.js';
|
|
6
7
|
import '../../chunks/uneval.js-DaakSYFQ.js';
|
|
8
|
+
import '../../chunks/index-server.js-DNLOKvM0.js';
|
|
7
9
|
import '../../chunks/shared.js-CgjUIvRK.js';
|
|
8
|
-
import '../../chunks/exports.js-
|
|
10
|
+
import '../../chunks/exports.js-ClPPfcHj.js';
|
|
11
|
+
import '../../chunks/internal2.js-DDcktblR.js';
|
|
12
|
+
import '../../chunks/legacy-client.js-DJmDw3Zx.js';
|
|
9
13
|
import '../../chunks/utils.js-BIlHjgGW.js';
|
|
10
14
|
|
|
11
15
|
//#region src/lib/format.ts
|
|
@@ -138,7 +142,8 @@ var STORAGE_KEYS = {
|
|
|
138
142
|
sidebarWidth: "watch-stream:sidebar-width",
|
|
139
143
|
prefixWidth: "watch-stream:prefix-width",
|
|
140
144
|
wrap: "watch-stream:wrap",
|
|
141
|
-
jsonView: "watch-stream:json-view"
|
|
145
|
+
jsonView: "watch-stream:json-view",
|
|
146
|
+
chartOpen: "watch-stream:chart-open"
|
|
142
147
|
};
|
|
143
148
|
/** Default width of the group-list pane, in rem (matches the original layout). */
|
|
144
149
|
var SIDEBAR_WIDTH = {
|
|
@@ -162,6 +167,85 @@ function remToPx(rem, rootFontSize = 16) {
|
|
|
162
167
|
return rem * rootFontSize;
|
|
163
168
|
}
|
|
164
169
|
//#endregion
|
|
170
|
+
//#region src/lib/components/EventScatterPanel.svelte
|
|
171
|
+
function EventScatterPanel($$renderer, $$props) {
|
|
172
|
+
$$renderer.component(($$renderer) => {
|
|
173
|
+
/**
|
|
174
|
+
* The chart panel above the log view: a header that is always cheap to render
|
|
175
|
+
* (title, totals, per-level legend) and the chart below it, loaded on demand.
|
|
176
|
+
*
|
|
177
|
+
* The charting library is imported dynamically and only once the panel is open,
|
|
178
|
+
* so layerchart never delays the first paint of the log view. The header stays
|
|
179
|
+
* available while the chart chunk is still loading, and the whole panel
|
|
180
|
+
* collapses to that header so a user who does not want the chart can reclaim
|
|
181
|
+
* the space - the choice is remembered.
|
|
182
|
+
*/
|
|
183
|
+
/** Props the lazily loaded chart takes. */
|
|
184
|
+
/** The chart component, as its module exports it. */
|
|
185
|
+
/** Fetches the chart module; injectable so tests need no chunk fetch. */
|
|
186
|
+
/**
|
|
187
|
+
* The real loader: a dynamic import, which is what keeps layerchart out of the
|
|
188
|
+
* initial bundle. It only runs in the browser, and only once the panel is open.
|
|
189
|
+
*/
|
|
190
|
+
const loadChartModule = () => import('../../chunks/EventScatter.js-DqCMPeHG.js');
|
|
191
|
+
/** Bucketed counts for the window. */
|
|
192
|
+
/** Inclusive start of the window, epoch ms. */
|
|
193
|
+
/** Inclusive end of the window, epoch ms. */
|
|
194
|
+
/** Bucket width in ms, shown in the summary. */
|
|
195
|
+
/** Log groups the points cover, for the summary. */
|
|
196
|
+
/** True while the counts are being fetched. */
|
|
197
|
+
/** Called with the brushed range when the drag ends, or `null` on clear. */
|
|
198
|
+
/** Chart height in px. */
|
|
199
|
+
/** Start open, which is also what the first visit uses. */
|
|
200
|
+
/** Chart module loader; defaults to the dynamic import. */
|
|
201
|
+
let { points = [], from = 0, to = 0, bucketMs = 0, groups = [], loading = false, onBrush, height = 200, open = true, loadChart = loadChartModule } = $$props;
|
|
202
|
+
let expanded = derived(() => open);
|
|
203
|
+
/** Totals and labels per level, in severity order. */
|
|
204
|
+
let levelGroups = derived(() => groupByLevel(points));
|
|
205
|
+
/** Total events drawn. */
|
|
206
|
+
let totalEvents = derived(() => levelGroups().reduce((sum, entry) => sum + entry.events, 0));
|
|
207
|
+
/** Buckets drawn. */
|
|
208
|
+
let bucketCount = derived(() => new Set(points.map((point) => point.t)).size);
|
|
209
|
+
/** True when there is something to draw. */
|
|
210
|
+
let hasPoints = derived(() => points.length > 0);
|
|
211
|
+
/** Clears the brush, for the host's Reset zoom control. */
|
|
212
|
+
function reset() {
|
|
213
|
+
onBrush?.(null);
|
|
214
|
+
}
|
|
215
|
+
$$renderer.push(`<section class="flex min-w-0 flex-col gap-1 rounded-lg border border-neutral-800 bg-neutral-950/60 p-2" data-testid="event-scatter" aria-label="Events over time"><div class="flex flex-wrap items-center gap-x-3 gap-y-1 px-1 text-[0.6875rem]"><button type="button"${attr("aria-expanded", expanded())}${attr("title", expanded() ? "Hide the chart" : "Show the chart")} data-testid="scatter-toggle" class="flex items-center gap-1 rounded-md border border-neutral-800 bg-neutral-900 px-2 py-0.5 font-semibold uppercase tracking-wider text-neutral-400 transition-colors hover:border-neutral-700 hover:text-neutral-200"><span aria-hidden="true">${escape_html(expanded() ? "▾" : "▸")}</span> Events over time</button> <span class="text-neutral-400" data-testid="scatter-summary">${escape_html(formatCount(totalEvents()))} events in ${escape_html(formatCount(bucketCount()))} buckets</span> `);
|
|
216
|
+
if (groups.length > 1) $$renderer.push(`<!--[0--><span class="text-neutral-500" data-testid="scatter-groups">across ${escape_html(groups.length)} groups</span>`);
|
|
217
|
+
else $$renderer.push("<!--[-1-->");
|
|
218
|
+
$$renderer.push(`<!--]--> <!--[-->`);
|
|
219
|
+
const each_array = ensure_array_like(levelGroups());
|
|
220
|
+
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
|
|
221
|
+
let entry = each_array[$$index];
|
|
222
|
+
$$renderer.push(`<span class="flex items-center gap-1 text-neutral-400"${attr("data-testid", `scatter-legend-${stringify(entry.level)}`)}><span class="inline-block h-2 w-2 rounded-full"${attr_style(`background-color: ${stringify(SERIES_LEVEL_COLOR[entry.level])}`)}></span> ${escape_html(entry.label)} <span class="text-neutral-500">${escape_html(formatCount(entry.events))}</span></span>`);
|
|
223
|
+
}
|
|
224
|
+
$$renderer.push(`<!--]--> `);
|
|
225
|
+
if (loading && expanded()) $$renderer.push(`<!--[0--><span class="text-amber-300" data-testid="scatter-loading">counting…</span>`);
|
|
226
|
+
else $$renderer.push("<!--[-1-->");
|
|
227
|
+
$$renderer.push(`<!--]--> `);
|
|
228
|
+
if (expanded()) {
|
|
229
|
+
$$renderer.push("<!--[0-->");
|
|
230
|
+
$$renderer.push(`<!--[-1--><span class="ml-auto text-neutral-500" data-testid="scatter-hint">drag to zoom · click to clear</span>`);
|
|
231
|
+
$$renderer.push(`<!--]-->`);
|
|
232
|
+
} else $$renderer.push("<!--[-1-->");
|
|
233
|
+
$$renderer.push(`<!--]--></div> `);
|
|
234
|
+
if (expanded()) {
|
|
235
|
+
$$renderer.push("<!--[0-->");
|
|
236
|
+
if (hasPoints()) {
|
|
237
|
+
$$renderer.push("<!--[1-->");
|
|
238
|
+
$$renderer.push(`<!--[-1--><div class="w-full animate-pulse rounded bg-neutral-900/60"${attr_style(`height: ${stringify(height)}px`)}></div>`);
|
|
239
|
+
$$renderer.push(`<!--]-->`);
|
|
240
|
+
} else if (!loading) $$renderer.push(`<!--[2--><p class="px-1 py-6 text-xs text-neutral-500" data-testid="scatter-empty">No events in this window yet.</p>`);
|
|
241
|
+
else $$renderer.push("<!--[-1-->");
|
|
242
|
+
$$renderer.push(`<!--]-->`);
|
|
243
|
+
} else $$renderer.push("<!--[-1-->");
|
|
244
|
+
$$renderer.push(`<!--]--></section>`);
|
|
245
|
+
bind_props($$props, { reset });
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
//#endregion
|
|
165
249
|
//#region src/lib/components/ColumnResizer.svelte
|
|
166
250
|
function ColumnResizer($$renderer, $$props) {
|
|
167
251
|
$$renderer.component(($$renderer) => {
|
|
@@ -189,11 +273,12 @@ function RangeControls($$renderer, $$props) {
|
|
|
189
273
|
/** Active custom bounds as epoch ms, owned by the page. */
|
|
190
274
|
/** True when no group is selected, so there is nothing to scope. */
|
|
191
275
|
/** True while a historic window is being fetched. */
|
|
276
|
+
/** True when the source cannot follow live events, such as the local archive. */
|
|
192
277
|
/** Called when the user switches mode or applies a range. */
|
|
193
|
-
let { mode = "live", range = "15m", from = null, to = null, disabled = false, loading = false} = $$props;
|
|
278
|
+
let { mode = "live", range = "15m", from = null, to = null, disabled = false, loading = false, liveDisabled = false} = $$props;
|
|
194
279
|
/** Label shown on the custom chip. */
|
|
195
280
|
let customLabel = derived(() => mode === "historic" && range === "" && from !== null && to !== null ? `${toDateTimeLocal(from).replace("T", " ")} → ${toDateTimeLocal(to).replace("T", " ")}` : "Custom…");
|
|
196
|
-
$$renderer.push(`<div class="flex flex-wrap items-center gap-2" data-testid="range-controls"><div class="flex items-center gap-1 rounded-lg border border-neutral-800 bg-neutral-900 p-0.5" role="group" aria-label="Tail mode"><button type="button"${attr("aria-pressed", mode === "live")} data-testid="mode-live"${attr_class(`rounded-md border px-2 py-1 text-xs font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${mode === "live" ? "border-sky-700 bg-sky-950/60 text-sky-300" : "border-transparent text-neutral-400 hover:text-neutral-200"}`)}>Live</button> <button type="button"${attr("aria-pressed", mode === "historic")} data-testid="mode-historic"${attr_class(`rounded-md border px-2 py-1 text-xs font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${mode === "historic" ? "border-amber-700 bg-amber-950/50 text-amber-300" : "border-transparent text-neutral-400 hover:text-neutral-200"}`)}>Historic</button></div> `);
|
|
281
|
+
$$renderer.push(`<div class="flex flex-wrap items-center gap-2" data-testid="range-controls"><div class="flex items-center gap-1 rounded-lg border border-neutral-800 bg-neutral-900 p-0.5" role="group" aria-label="Tail mode"><button type="button"${attr("aria-pressed", mode === "live")}${attr("disabled", liveDisabled, true)}${attr("title", liveDisabled ? "There is nothing to tail: the local archive holds historic windows" : void 0)} data-testid="mode-live"${attr_class(`rounded-md border px-2 py-1 text-xs font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${mode === "live" ? "border-sky-700 bg-sky-950/60 text-sky-300" : "border-transparent text-neutral-400 hover:text-neutral-200"} disabled:opacity-40`)}>Live</button> <button type="button"${attr("aria-pressed", mode === "historic")} data-testid="mode-historic"${attr_class(`rounded-md border px-2 py-1 text-xs font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${mode === "historic" ? "border-amber-700 bg-amber-950/50 text-amber-300" : "border-transparent text-neutral-400 hover:text-neutral-200"}`)}>Historic</button></div> `);
|
|
197
282
|
if (mode === "historic") {
|
|
198
283
|
$$renderer.push(`<!--[0--><div class="flex flex-wrap items-center gap-1" role="group" aria-label="Historic range"><!--[-->`);
|
|
199
284
|
const each_array = ensure_array_like(HISTORIC_PRESETS);
|
|
@@ -216,8 +301,8 @@ function RangeControls($$renderer, $$props) {
|
|
|
216
301
|
//#region src/lib/groups-client.ts
|
|
217
302
|
/**
|
|
218
303
|
* Browser-side fetch helpers for the watch-stream JSON API (`/api/health`, `/api/regions`,
|
|
219
|
-
* `/api/log-groups`). Every helper throws {@link ApiError} on a non-2xx response so
|
|
220
|
-
* the API `error` message instead of an opaque failure.
|
|
304
|
+
* `/api/log-groups`, `/api/archive`). Every helper throws {@link ApiError} on a non-2xx response so
|
|
305
|
+
* the UI can show the API `error` message instead of an opaque failure.
|
|
221
306
|
*/
|
|
222
307
|
/** Region list used by the picker before (or instead of) `/api/regions`. */
|
|
223
308
|
var DEFAULT_REGIONS = REGION_CODES;
|
|
@@ -251,11 +336,30 @@ function apiErrorMessage(value, fallback = "Unexpected API error") {
|
|
|
251
336
|
function buildLogGroupsUrl(query) {
|
|
252
337
|
const search = new URLSearchParams();
|
|
253
338
|
if (query.region) search.set("region", query.region);
|
|
339
|
+
if (query.source) search.set("source", query.source);
|
|
254
340
|
if (query.prefix) search.set("prefix", query.prefix);
|
|
255
341
|
if (typeof query.limit === "number" && Number.isFinite(query.limit)) search.set("limit", String(query.limit));
|
|
256
342
|
const suffix = search.toString();
|
|
257
343
|
return suffix === "" ? "/api/log-groups" : `/api/log-groups?${suffix}`;
|
|
258
344
|
}
|
|
345
|
+
/** Builds the `/api/series` URL for a query. */
|
|
346
|
+
function buildSeriesUrl(query) {
|
|
347
|
+
const search = new URLSearchParams();
|
|
348
|
+
if (query.region) search.set("region", query.region);
|
|
349
|
+
search.set("source", "archive");
|
|
350
|
+
const groups = (query.groups ?? []).filter((name) => name.length > 0);
|
|
351
|
+
if (groups.length > 0) search.set("groups", groups.join(","));
|
|
352
|
+
if (typeof query.from === "number" && Number.isFinite(query.from)) search.set("from", String(Math.round(query.from)));
|
|
353
|
+
if (typeof query.to === "number" && Number.isFinite(query.to)) search.set("to", String(Math.round(query.to)));
|
|
354
|
+
const levels = (query.levels ?? []).filter((level) => level.length > 0);
|
|
355
|
+
if (levels.length > 0) search.set("level", levels.join(","));
|
|
356
|
+
if (typeof query.bucketMs === "number" && Number.isFinite(query.bucketMs) && query.bucketMs > 0) search.set("bucket", String(Math.round(query.bucketMs)));
|
|
357
|
+
return `/api/series?${search.toString()}`;
|
|
358
|
+
}
|
|
359
|
+
/** `GET /api/series` - bucketed event counts for the chart. */
|
|
360
|
+
function fetchSeries(query) {
|
|
361
|
+
return getJson(buildSeriesUrl(query), query);
|
|
362
|
+
}
|
|
259
363
|
/** True for an abort, which is a normal way to cancel a request. */
|
|
260
364
|
function isAbort(error) {
|
|
261
365
|
return error instanceof Error && error.name === "AbortError";
|
|
@@ -287,7 +391,11 @@ async function getJson(url, options) {
|
|
|
287
391
|
});
|
|
288
392
|
return body;
|
|
289
393
|
}
|
|
290
|
-
/**
|
|
394
|
+
/**
|
|
395
|
+
* `GET /api/log-groups` - log groups for a region, from CloudWatch or the local archive.
|
|
396
|
+
*
|
|
397
|
+
* The archive list needs no credentials and never reaches AWS.
|
|
398
|
+
*/
|
|
291
399
|
function fetchLogGroups(query) {
|
|
292
400
|
return getJson(buildLogGroupsUrl(query), query);
|
|
293
401
|
}
|
|
@@ -295,6 +403,18 @@ function fetchLogGroups(query) {
|
|
|
295
403
|
function noGroupsMessage(region) {
|
|
296
404
|
return `No log groups found in ${region}. Start floci and run \`pnpm seed\` to create sample groups.`;
|
|
297
405
|
}
|
|
406
|
+
/** Plain statement of what the archive source shows, used next to the toggle. */
|
|
407
|
+
var ARCHIVE_NOTE = "Locally archived events - read from the local DuckDB file, no CloudWatch credentials, historic windows only.";
|
|
408
|
+
/** Tooltip for the archive indicator: what it shows and which file backs it. */
|
|
409
|
+
function describeArchive(path) {
|
|
410
|
+
const trimmed = typeof path === "string" ? path.trim() : "";
|
|
411
|
+
const head = "Locally archived events instead of live CloudWatch data";
|
|
412
|
+
return trimmed === "" ? head : `${head} (${trimmed})`;
|
|
413
|
+
}
|
|
414
|
+
/** Message shown when the archive holds nothing for a region. */
|
|
415
|
+
function noArchivedGroupsMessage(region) {
|
|
416
|
+
return `Nothing archived for ${region === "" ? "this region" : region} yet - stream a group from CloudWatch once and it will appear here.`;
|
|
417
|
+
}
|
|
298
418
|
/** Drops a trailing period so an appended sentence does not double up. */
|
|
299
419
|
function trimSentence(text) {
|
|
300
420
|
return text.trim().replace(/\.+$/, "");
|
|
@@ -316,25 +436,57 @@ function describeStreamError(region, group, message) {
|
|
|
316
436
|
function LogGroupList($$renderer, $$props) {
|
|
317
437
|
$$renderer.component(($$renderer) => {
|
|
318
438
|
/** Groups returned for the active region. */
|
|
439
|
+
/** Where the list came from; the archive list shows local counts instead of sizes. */
|
|
319
440
|
/** Active region, used for context in the empty state. */
|
|
320
|
-
/**
|
|
441
|
+
/** Names of the groups currently being tailed; empty when none are selected. */
|
|
321
442
|
/** API error message, shown instead of the list. */
|
|
322
443
|
/** Row cap; defaults to {@link MAX_GROUP_ROWS}. */
|
|
323
|
-
/** Called with the group name when a row is clicked. */
|
|
444
|
+
/** Called with the group name when a row is clicked, replacing the selection. */
|
|
445
|
+
/** Called with the group name when its checkbox is toggled in or out of the selection. */
|
|
324
446
|
/** Called when the refresh button is clicked. */
|
|
325
|
-
let { groups = [], region = "", selected =
|
|
447
|
+
let { groups = [], source = "cloudwatch", region = "", selected = [], loading = false, error = null, maxRows = 500} = $$props;
|
|
326
448
|
let search = "";
|
|
449
|
+
/** True when the list holds archived groups rather than live CloudWatch ones. */
|
|
450
|
+
let archived = derived(() => source === "archive");
|
|
451
|
+
/** Badge text for a group: how many events the archive holds for it. */
|
|
452
|
+
function archivedCount(group) {
|
|
453
|
+
return group.archivedEvents === void 0 ? null : `${formatCount(group.archivedEvents)} archived`;
|
|
454
|
+
}
|
|
455
|
+
/** `oldest -> newest` span of a group's archived events, or `null` when unknown. */
|
|
456
|
+
function archivedSpan(group) {
|
|
457
|
+
if (group.archivedOldest === void 0 || group.archivedNewest === void 0) return null;
|
|
458
|
+
return `${formatTimestamp(group.archivedOldest)} \u2192 ${formatTimestamp(group.archivedNewest)}`;
|
|
459
|
+
}
|
|
327
460
|
/** Groups matching the search box, case-insensitively. */
|
|
328
461
|
let filtered = derived(() => groups.filter((group) => group.name.toLowerCase().includes(search.trim().toLowerCase())));
|
|
329
462
|
/** Rows actually rendered, capped at `maxRows`. */
|
|
330
463
|
let visible = derived(() => filtered().slice(0, maxRows));
|
|
331
464
|
let truncated = derived(() => filtered().length > visible().length);
|
|
332
465
|
let query = derived(() => search.trim());
|
|
333
|
-
/**
|
|
466
|
+
/**
|
|
467
|
+
* Selection as a flat list of names. `selected` is typed as a list, but callers that have not
|
|
468
|
+
* migrated yet may still pass a single name (or `null`), so those are normalised here.
|
|
469
|
+
*/
|
|
470
|
+
function selectionList(value) {
|
|
471
|
+
if (value === null || value === void 0) return [];
|
|
472
|
+
return Array.isArray(value) ? value : [value];
|
|
473
|
+
}
|
|
474
|
+
let activeSelection = derived(() => selectionList(selected));
|
|
475
|
+
/** True when `name` is part of the current (possibly multi-group) selection. */
|
|
476
|
+
function isSelected(name) {
|
|
477
|
+
return activeSelection().includes(name);
|
|
478
|
+
}
|
|
479
|
+
/** Row styling, highlighting every group that is currently being tailed. */
|
|
334
480
|
function rowTone(name) {
|
|
335
|
-
return name
|
|
481
|
+
return isSelected(name) ? "border-sky-800 bg-sky-950/50 text-sky-200" : "border-transparent bg-neutral-900/60 text-neutral-300 hover:border-neutral-800 hover:text-neutral-100";
|
|
336
482
|
}
|
|
337
|
-
$$renderer.push(`<section class="flex min-h-0 flex-1 flex-col gap-3"><div class="flex items-center justify-between gap-2"><div class="flex items-baseline gap-2"><h2 class="text-sm font-semibold text-neutral-200"
|
|
483
|
+
$$renderer.push(`<section class="flex min-h-0 flex-1 flex-col gap-3"><div class="flex items-center justify-between gap-2"><div class="flex items-baseline gap-2"><h2 class="text-sm font-semibold text-neutral-200">${escape_html(archived() ? "Archived groups" : "Log groups")}</h2> <span class="text-xs text-neutral-500" data-testid="group-count">${escape_html(formatCount(filtered().length))} of ${escape_html(formatCount(groups.length))}</span> `);
|
|
484
|
+
if (activeSelection().length > 1) $$renderer.push(`<!--[0--><span class="text-xs font-medium text-sky-400" data-testid="selected-count">${escape_html(formatCount(activeSelection().length))} selected</span>`);
|
|
485
|
+
else $$renderer.push("<!--[-1-->");
|
|
486
|
+
$$renderer.push(`<!--]--> `);
|
|
487
|
+
if (archived()) $$renderer.push(`<!--[0--><span class="rounded-full border border-teal-900 bg-teal-950/50 px-1.5 py-0.5 text-[0.625rem] font-medium text-teal-300" data-testid="group-source-badge">local</span>`);
|
|
488
|
+
else $$renderer.push("<!--[-1-->");
|
|
489
|
+
$$renderer.push(`<!--]--></div> <button type="button"${attr("disabled", loading, true)} class="rounded-md border border-neutral-800 bg-neutral-900 px-2 py-1 text-xs font-medium text-neutral-300 transition-colors hover:border-neutral-700 hover:text-neutral-100 disabled:cursor-not-allowed disabled:opacity-50">${escape_html(loading ? "Refreshing…" : "Refresh")}</button></div> <div class="flex flex-col gap-1.5"><label class="sr-only" for="group-search">Search log groups</label> <input id="group-search" type="search" placeholder="Search log groups"${attr("value", search)} class="w-full rounded-md border border-neutral-800 bg-neutral-900 px-2.5 py-1.5 text-sm text-neutral-100 outline-none transition-colors placeholder:text-neutral-600 hover:border-neutral-700 focus:border-sky-600"/></div> `);
|
|
338
490
|
if (error !== null && error !== "") $$renderer.push(`<!--[0--><p role="alert" data-testid="group-error" class="rounded-md border border-red-900 bg-red-950/50 px-3 py-2 text-xs leading-5 text-red-300">${escape_html(error)}</p>`);
|
|
339
491
|
else if (loading && groups.length === 0) {
|
|
340
492
|
$$renderer.push(`<!--[1--><div class="flex flex-col gap-2" data-testid="group-loading" aria-busy="true"><!--[-->`);
|
|
@@ -344,15 +496,23 @@ function LogGroupList($$renderer, $$props) {
|
|
|
344
496
|
$$renderer.push(`<div class="h-5 animate-pulse rounded bg-neutral-800/80"></div>`);
|
|
345
497
|
}
|
|
346
498
|
$$renderer.push(`<!--]--> <p class="text-xs text-neutral-500">Loading log groups…</p></div>`);
|
|
347
|
-
} else if (groups.length === 0) $$renderer.push(`<!--[2--><p data-testid="group-empty" class="rounded-md border border-neutral-800 bg-neutral-900/60 px-3 py-2 text-xs leading-5 text-neutral-400">${escape_html(noGroupsMessage(region))}</p>`);
|
|
499
|
+
} else if (groups.length === 0) $$renderer.push(`<!--[2--><p data-testid="group-empty" class="rounded-md border border-neutral-800 bg-neutral-900/60 px-3 py-2 text-xs leading-5 text-neutral-400">${escape_html(archived() ? noArchivedGroupsMessage(region) : noGroupsMessage(region))}</p>`);
|
|
348
500
|
else if (visible().length === 0) $$renderer.push(`<!--[3--><p data-testid="group-no-match" class="px-1 py-2 text-xs text-neutral-500">No log groups match “${escape_html(query())}”.</p>`);
|
|
349
501
|
else {
|
|
350
502
|
$$renderer.push(`<!--[-1--><ul class="flex min-h-0 flex-1 flex-col gap-1 overflow-y-auto pr-1"><!--[-->`);
|
|
351
503
|
const each_array_1 = ensure_array_like(visible());
|
|
352
504
|
for (let $$index_1 = 0, $$length = each_array_1.length; $$index_1 < $$length; $$index_1++) {
|
|
353
505
|
let group = each_array_1[$$index_1];
|
|
354
|
-
$$renderer.push(`<li><button type="button" data-testid="group-row"${attr("aria-current", group.name
|
|
355
|
-
if (
|
|
506
|
+
$$renderer.push(`<li class="flex items-center gap-1.5"><input type="checkbox"${attr("data-testid", `group-check-${stringify(group.name)}`)}${attr("aria-label", `Include ${group.name} in the selection`)}${attr("checked", isSelected(group.name), true)} class="size-3.5 shrink-0 cursor-pointer accent-sky-500"/> <button type="button" data-testid="group-row"${attr("aria-current", isSelected(group.name) ? "true" : void 0)}${attr_class(`flex min-w-0 flex-1 items-center justify-between gap-2 rounded-md border px-2.5 py-1.5 text-left text-xs transition-colors ${stringify(rowTone(group.name))}`)}><span class="truncate font-mono">${escape_html(group.name)}</span> `);
|
|
507
|
+
if (archived()) {
|
|
508
|
+
$$renderer.push(`<!--[0--><span class="flex shrink-0 items-center gap-1.5">`);
|
|
509
|
+
if (archivedCount(group) !== null) $$renderer.push(`<!--[0--><span data-testid="group-archived-count"${attr("title", archivedSpan(group) ?? "Events held in the local DuckDB archive")} class="rounded-full border border-teal-900 bg-teal-950/50 px-1.5 py-0.5 text-[0.625rem] font-medium text-teal-300">${escape_html(archivedCount(group))}</span>`);
|
|
510
|
+
else $$renderer.push("<!--[-1-->");
|
|
511
|
+
$$renderer.push(`<!--]--> `);
|
|
512
|
+
if (archivedSpan(group) !== null) $$renderer.push(`<!--[0--><span class="hidden text-[0.6875rem] text-neutral-500 xl:inline" data-testid="group-archived-span">${escape_html(archivedSpan(group))}</span>`);
|
|
513
|
+
else $$renderer.push("<!--[-1-->");
|
|
514
|
+
$$renderer.push(`<!--]--></span>`);
|
|
515
|
+
} else if (group.storedBytes !== void 0) $$renderer.push(`<!--[1--><span class="shrink-0 text-[0.6875rem] text-neutral-500">${escape_html(formatBytes(group.storedBytes))}</span>`);
|
|
356
516
|
else $$renderer.push("<!--[-1-->");
|
|
357
517
|
$$renderer.push(`<!--]--></button></li>`);
|
|
358
518
|
}
|
|
@@ -402,297 +562,6 @@ function StatusBadge($$renderer, $$props) {
|
|
|
402
562
|
$$renderer.push(`<span data-testid="status-badge"${attr("data-status", status)}${attr_class(`inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-medium ${stringify(tone())}`)}><span${attr_class(`size-1.5 rounded-full ${stringify(dot())}`)} aria-hidden="true"></span> ${escape_html(text())}</span>`);
|
|
403
563
|
}
|
|
404
564
|
//#endregion
|
|
405
|
-
//#region src/lib/log-buffer.ts
|
|
406
|
-
/** Maximum number of lines kept in the visible client buffer. */
|
|
407
|
-
var DEFAULT_CAPACITY = 5e3;
|
|
408
|
-
var ERROR_PATTERN = /\b(ERROR|FATAL|CRITICAL|CRIT|PANIC|EXCEPTION|TRACEBACK|SEVERE|FAILED|FAILURE)\b/;
|
|
409
|
-
var WARN_PATTERN = /\b(WARN|WARNING)\b/;
|
|
410
|
-
var DEBUG_PATTERN = /\b(DEBUG|TRACE|VERBOSE)\b/;
|
|
411
|
-
/**
|
|
412
|
-
* Guesses the severity of a log line from its text.
|
|
413
|
-
* Plain structured logs such as `{"level":"WARN"}` are detected too, because the level word is
|
|
414
|
-
* matched anywhere in the message. Order of precedence: error, warn, debug, info.
|
|
415
|
-
*/
|
|
416
|
-
function detectLevel(message) {
|
|
417
|
-
const upper = (message ?? "").toUpperCase();
|
|
418
|
-
if (ERROR_PATTERN.test(upper)) return "error";
|
|
419
|
-
if (WARN_PATTERN.test(upper)) return "warn";
|
|
420
|
-
if (DEBUG_PATTERN.test(upper)) return "debug";
|
|
421
|
-
return "info";
|
|
422
|
-
}
|
|
423
|
-
/** Tailwind text colour class for a severity. */
|
|
424
|
-
function levelColorClass(level) {
|
|
425
|
-
switch (level) {
|
|
426
|
-
case "error": return "text-red-400";
|
|
427
|
-
case "warn": return "text-amber-300";
|
|
428
|
-
case "debug": return "text-neutral-500";
|
|
429
|
-
default: return "text-neutral-200";
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
/** Stable key for rendering a log line, since event ids can repeat or be missing. */
|
|
433
|
-
function eventKey(event, index) {
|
|
434
|
-
return `${event.id ?? "no-id"}:${event.timestamp}:${index}`;
|
|
435
|
-
}
|
|
436
|
-
/** Case-insensitive substring match over the message and the stream name. */
|
|
437
|
-
function matchesFilter(event, query) {
|
|
438
|
-
const needle = (query ?? "").trim().toLowerCase();
|
|
439
|
-
if (needle === "") return true;
|
|
440
|
-
return `${event.message ?? ""} ${event.streamName ?? ""}`.toLowerCase().includes(needle);
|
|
441
|
-
}
|
|
442
|
-
/** Returns the events matching `query`, in arrival order. */
|
|
443
|
-
function filterEvents(events, query) {
|
|
444
|
-
if ((query ?? "").trim() === "") return events.slice();
|
|
445
|
-
return events.filter((event) => matchesFilter(event, query));
|
|
446
|
-
}
|
|
447
|
-
/** Creates an array of `length` undefined slots. */
|
|
448
|
-
function emptySlots(length) {
|
|
449
|
-
return Array.from({ length });
|
|
450
|
-
}
|
|
451
|
-
/**
|
|
452
|
-
* Fixed-capacity ring buffer that overwrites the oldest item once it is full.
|
|
453
|
-
*/
|
|
454
|
-
var LogRingBuffer = class {
|
|
455
|
-
/** Maximum number of items kept. */
|
|
456
|
-
capacity;
|
|
457
|
-
#items;
|
|
458
|
-
#start = 0;
|
|
459
|
-
#size = 0;
|
|
460
|
-
#dropped = 0;
|
|
461
|
-
constructor(capacity = DEFAULT_CAPACITY) {
|
|
462
|
-
if (!Number.isInteger(capacity) || capacity <= 0) throw new RangeError(`capacity must be a positive integer, received ${capacity}`);
|
|
463
|
-
this.capacity = capacity;
|
|
464
|
-
this.#items = emptySlots(capacity);
|
|
465
|
-
}
|
|
466
|
-
/** Number of items currently stored. */
|
|
467
|
-
get size() {
|
|
468
|
-
return this.#size;
|
|
469
|
-
}
|
|
470
|
-
/** Number of items discarded because the buffer was full. */
|
|
471
|
-
get dropped() {
|
|
472
|
-
return this.#dropped;
|
|
473
|
-
}
|
|
474
|
-
/** True when the buffer holds `capacity` items. */
|
|
475
|
-
get full() {
|
|
476
|
-
return this.#size === this.capacity;
|
|
477
|
-
}
|
|
478
|
-
/** Appends one item, dropping the oldest item when the buffer is full. */
|
|
479
|
-
push(item) {
|
|
480
|
-
if (this.#size === this.capacity) {
|
|
481
|
-
this.#dropped += 1;
|
|
482
|
-
this.#start = (this.#start + 1) % this.capacity;
|
|
483
|
-
this.#size -= 1;
|
|
484
|
-
}
|
|
485
|
-
const index = (this.#start + this.#size) % this.capacity;
|
|
486
|
-
this.#items[index] = item;
|
|
487
|
-
this.#size += 1;
|
|
488
|
-
}
|
|
489
|
-
/** Appends several items in order. */
|
|
490
|
-
pushMany(items) {
|
|
491
|
-
for (const item of items) this.push(item);
|
|
492
|
-
}
|
|
493
|
-
/** Copies the stored items into an array ordered oldest to newest. */
|
|
494
|
-
toArray() {
|
|
495
|
-
const output = emptySlots(this.#size);
|
|
496
|
-
for (let offset = 0; offset < this.#size; offset += 1) output[offset] = this.#items[(this.#start + offset) % this.capacity];
|
|
497
|
-
return output;
|
|
498
|
-
}
|
|
499
|
-
/** Oldest stored item, or `undefined` when empty. */
|
|
500
|
-
first() {
|
|
501
|
-
return this.#size === 0 ? void 0 : this.#items[this.#start];
|
|
502
|
-
}
|
|
503
|
-
/** Newest stored item, or `undefined` when empty. */
|
|
504
|
-
last() {
|
|
505
|
-
return this.#size === 0 ? void 0 : this.#items[(this.#start + this.#size - 1) % this.capacity];
|
|
506
|
-
}
|
|
507
|
-
/** Removes every item and resets the dropped counter. */
|
|
508
|
-
clear() {
|
|
509
|
-
this.#items = emptySlots(this.capacity);
|
|
510
|
-
this.#start = 0;
|
|
511
|
-
this.#size = 0;
|
|
512
|
-
this.#dropped = 0;
|
|
513
|
-
}
|
|
514
|
-
};
|
|
515
|
-
/**
|
|
516
|
-
* Log store used by the UI: a visible ring buffer plus a pending buffer that fills up while the
|
|
517
|
-
* view is paused. `push()` never drops silent data, it either appends to the visible buffer or to
|
|
518
|
-
* the pending buffer; `resume()` moves the pending events into the visible buffer in order.
|
|
519
|
-
*/
|
|
520
|
-
var LogStore = class {
|
|
521
|
-
buffer;
|
|
522
|
-
#pending;
|
|
523
|
-
#paused = false;
|
|
524
|
-
constructor(capacity = DEFAULT_CAPACITY) {
|
|
525
|
-
this.buffer = new LogRingBuffer(capacity);
|
|
526
|
-
this.#pending = new LogRingBuffer(capacity);
|
|
527
|
-
}
|
|
528
|
-
/** True when incoming events are buffered instead of appended to the visible buffer. */
|
|
529
|
-
get paused() {
|
|
530
|
-
return this.#paused;
|
|
531
|
-
}
|
|
532
|
-
/** Number of visible lines. */
|
|
533
|
-
get size() {
|
|
534
|
-
return this.buffer.size;
|
|
535
|
-
}
|
|
536
|
-
/** Number of lines held back while paused. */
|
|
537
|
-
get pendingCount() {
|
|
538
|
-
return this.#pending.size;
|
|
539
|
-
}
|
|
540
|
-
/** Number of visible lines dropped because the buffer was full. */
|
|
541
|
-
get droppedCount() {
|
|
542
|
-
return this.buffer.dropped;
|
|
543
|
-
}
|
|
544
|
-
/** Appends events to the visible buffer, or holds them back while paused. */
|
|
545
|
-
push(events) {
|
|
546
|
-
if (this.#paused) {
|
|
547
|
-
this.#pending.pushMany(events);
|
|
548
|
-
return;
|
|
549
|
-
}
|
|
550
|
-
this.buffer.pushMany(events);
|
|
551
|
-
}
|
|
552
|
-
/** Pauses or resumes appending. Resuming flushes the pending events immediately. */
|
|
553
|
-
setPaused(paused) {
|
|
554
|
-
if (paused === this.#paused) return [];
|
|
555
|
-
this.#paused = paused;
|
|
556
|
-
return paused ? [] : this.flush();
|
|
557
|
-
}
|
|
558
|
-
/** Pauses appending to the visible buffer. */
|
|
559
|
-
pause() {
|
|
560
|
-
this.#paused = true;
|
|
561
|
-
}
|
|
562
|
-
/** Resumes appending and returns the events flushed from the pending buffer. */
|
|
563
|
-
resume() {
|
|
564
|
-
this.#paused = false;
|
|
565
|
-
return this.flush();
|
|
566
|
-
}
|
|
567
|
-
/** Moves every pending event into the visible buffer, oldest first. */
|
|
568
|
-
flush() {
|
|
569
|
-
const flushed = this.#pending.toArray();
|
|
570
|
-
this.#pending.clear();
|
|
571
|
-
this.buffer.pushMany(flushed);
|
|
572
|
-
return flushed;
|
|
573
|
-
}
|
|
574
|
-
/** Copies the visible lines, oldest first. */
|
|
575
|
-
toArray() {
|
|
576
|
-
return this.buffer.toArray();
|
|
577
|
-
}
|
|
578
|
-
/** Empties the visible buffer and discards pending events. */
|
|
579
|
-
clear() {
|
|
580
|
-
this.buffer.clear();
|
|
581
|
-
this.#pending.clear();
|
|
582
|
-
}
|
|
583
|
-
};
|
|
584
|
-
//#endregion
|
|
585
|
-
//#region src/lib/log-format.ts
|
|
586
|
-
/**
|
|
587
|
-
* Parses a log message that is entirely JSON.
|
|
588
|
-
*
|
|
589
|
-
* Only objects and arrays count: a message that happens to be `42` or `"ok"` is
|
|
590
|
-
* left alone, and anything that does not parse is not JSON. Leading and trailing
|
|
591
|
-
* whitespace is ignored.
|
|
592
|
-
*/
|
|
593
|
-
function detectJson(message) {
|
|
594
|
-
if (typeof message !== "string") return null;
|
|
595
|
-
const trimmed = message.trim();
|
|
596
|
-
if (trimmed.length === 0) return null;
|
|
597
|
-
const first = trimmed[0];
|
|
598
|
-
if (first !== "{" && first !== "[") return null;
|
|
599
|
-
try {
|
|
600
|
-
const parsed = JSON.parse(trimmed);
|
|
601
|
-
if (parsed === null || typeof parsed !== "object") return null;
|
|
602
|
-
return parsed;
|
|
603
|
-
} catch {
|
|
604
|
-
return null;
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
/**
|
|
608
|
-
* Returns the text to render for a log message.
|
|
609
|
-
*
|
|
610
|
-
* `prettyJson` is normally true: JSON messages are re-serialised with two-space
|
|
611
|
-
* indentation. With `prettyJson` false the raw message is returned unchanged,
|
|
612
|
-
* which is what the viewer's JSON toggle switches to.
|
|
613
|
-
*/
|
|
614
|
-
function formatLogMessage(message, options = {}) {
|
|
615
|
-
const { prettyJson = true } = options;
|
|
616
|
-
const value = detectJson(message);
|
|
617
|
-
if (value === null || !prettyJson) return {
|
|
618
|
-
text: message,
|
|
619
|
-
isJson: value !== null,
|
|
620
|
-
value
|
|
621
|
-
};
|
|
622
|
-
return {
|
|
623
|
-
text: JSON.stringify(value, null, 2),
|
|
624
|
-
isJson: true,
|
|
625
|
-
value
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
var NUMBER_PATTERN = /^-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?$/;
|
|
629
|
-
/**
|
|
630
|
-
* Splits pretty-printed JSON into coloured tokens.
|
|
631
|
-
*
|
|
632
|
-
* The concatenation of every token's `text` is always identical to the input, so
|
|
633
|
-
* rendering tokens can never lose or alter a character. This is a display-only
|
|
634
|
-
* scanner: it assumes well-formed JSON produced by `JSON.stringify`.
|
|
635
|
-
*/
|
|
636
|
-
function tokenizeJson(text) {
|
|
637
|
-
const tokens = [];
|
|
638
|
-
let index = 0;
|
|
639
|
-
const push = (type, value) => {
|
|
640
|
-
if (value.length > 0) tokens.push({
|
|
641
|
-
type,
|
|
642
|
-
text: value
|
|
643
|
-
});
|
|
644
|
-
};
|
|
645
|
-
while (index < text.length) {
|
|
646
|
-
const char = text[index];
|
|
647
|
-
if (char === " " || char === "\n" || char === " " || char === "\r") {
|
|
648
|
-
let end = index;
|
|
649
|
-
while (end < text.length && " \n \r".includes(text[end])) end += 1;
|
|
650
|
-
push("whitespace", text.slice(index, end));
|
|
651
|
-
index = end;
|
|
652
|
-
continue;
|
|
653
|
-
}
|
|
654
|
-
if (char === "\"") {
|
|
655
|
-
let end = index + 1;
|
|
656
|
-
while (end < text.length) {
|
|
657
|
-
if (text[end] === "\\") {
|
|
658
|
-
end += 2;
|
|
659
|
-
continue;
|
|
660
|
-
}
|
|
661
|
-
if (text[end] === "\"") break;
|
|
662
|
-
end += 1;
|
|
663
|
-
}
|
|
664
|
-
const raw = text.slice(index, Math.min(end + 1, text.length));
|
|
665
|
-
let lookahead = index + raw.length;
|
|
666
|
-
while (lookahead < text.length && " \n \r".includes(text[lookahead])) lookahead += 1;
|
|
667
|
-
push(text[lookahead] === ":" ? "key" : "string", raw);
|
|
668
|
-
index += raw.length;
|
|
669
|
-
continue;
|
|
670
|
-
}
|
|
671
|
-
if (char === "{" || char === "}" || char === "[" || char === "]" || char === "," || char === ":") {
|
|
672
|
-
push("punctuation", char);
|
|
673
|
-
index += 1;
|
|
674
|
-
continue;
|
|
675
|
-
}
|
|
676
|
-
if (text.startsWith("true", index) || text.startsWith("false", index)) {
|
|
677
|
-
const literal = text.startsWith("true", index) ? "true" : "false";
|
|
678
|
-
push("boolean", literal);
|
|
679
|
-
index += literal.length;
|
|
680
|
-
continue;
|
|
681
|
-
}
|
|
682
|
-
if (text.startsWith("null", index)) {
|
|
683
|
-
push("null", "null");
|
|
684
|
-
index += 4;
|
|
685
|
-
continue;
|
|
686
|
-
}
|
|
687
|
-
let end = index;
|
|
688
|
-
while (end < text.length && !"{}[],:\" \n \r".includes(text[end])) end += 1;
|
|
689
|
-
const raw = text.slice(index, Math.max(end, index + 1));
|
|
690
|
-
push(NUMBER_PATTERN.test(raw) ? "number" : "other", raw);
|
|
691
|
-
index += raw.length;
|
|
692
|
-
}
|
|
693
|
-
return tokens;
|
|
694
|
-
}
|
|
695
|
-
//#endregion
|
|
696
565
|
//#region src/lib/components/LogViewer.svelte
|
|
697
566
|
function LogViewer($$renderer, $$props) {
|
|
698
567
|
$$renderer.component(($$renderer) => {
|
|
@@ -710,7 +579,15 @@ function LogViewer($$renderer, $$props) {
|
|
|
710
579
|
/** Active tail mode, used for the window chip. */
|
|
711
580
|
/** Last `ready` payload, which describes the window the server is scanning. */
|
|
712
581
|
/** Reason the server ended the stream, for example `window-complete`. */
|
|
713
|
-
|
|
582
|
+
/** Database file behind the local archive, shown in the archive tooltip. */
|
|
583
|
+
/** Log groups in the view; more than one adds a group column. */
|
|
584
|
+
/**
|
|
585
|
+
* Active level filter. Leave it undefined to let the viewer own the filter;
|
|
586
|
+
* pass a value (including `null`) to control it from outside, which is how
|
|
587
|
+
* the chart above stays in step with the chips.
|
|
588
|
+
*/
|
|
589
|
+
/** Called when the level filter changes while it is controlled. */
|
|
590
|
+
let { lines = [], status = "idle", error = null, region = "", group = null, filter = "", paused = false, autoScroll = true, pendingCount = 0, droppedCount = 0, receivedCount = 0, mode = "live", ready = null, endReason = null, archivePath = null, groups = [], level = void 0, onLevelChange, onFilterChange, onPauseToggle, onClear, onAutoScrollToggle } = $$props;
|
|
714
591
|
const TIMESTAMP_COLUMN_PX = remToPx(7.5);
|
|
715
592
|
const ROW_PADDING_PX = remToPx(.75);
|
|
716
593
|
const ROW_GAP_PX = remToPx(.5);
|
|
@@ -729,28 +606,88 @@ function LogViewer($$renderer, $$props) {
|
|
|
729
606
|
let jsonView = true;
|
|
730
607
|
let prefixPx = PREFIX_DEFAULT_PX;
|
|
731
608
|
/** One rendered row, pre-formatted so the template stays declarative. */
|
|
732
|
-
/**
|
|
733
|
-
|
|
609
|
+
/**
|
|
610
|
+
* Payload found inside the raw line, used when pretty-printing is off and
|
|
611
|
+
* the row is expanded. `null` when the line carries no JSON.
|
|
612
|
+
*/
|
|
613
|
+
/** Log group the line came from, when the payload carries one. */
|
|
614
|
+
/** Detected level, or `null` when the line carried no signal. */
|
|
615
|
+
/** Level the viewer owns when the parent does not control it. */
|
|
616
|
+
let ownLevelFilter = null;
|
|
617
|
+
/** Active level filter: the parent's when given, otherwise the viewer's. */
|
|
618
|
+
let levelFilter = derived(() => level === void 0 ? ownLevelFilter : level);
|
|
619
|
+
/** The four levels offered as filter chips, in severity order. */
|
|
620
|
+
const LEVEL_CHIPS = [
|
|
621
|
+
{
|
|
622
|
+
level: "error",
|
|
623
|
+
label: "Error",
|
|
624
|
+
activeClass: "border-red-800 bg-red-950/50 text-red-300"
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
level: "warn",
|
|
628
|
+
label: "Warn",
|
|
629
|
+
activeClass: "border-amber-800 bg-amber-950/40 text-amber-300"
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
level: "info",
|
|
633
|
+
label: "Info",
|
|
634
|
+
activeClass: "border-neutral-700 bg-neutral-800 text-neutral-100"
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
level: "debug",
|
|
638
|
+
label: "Debug",
|
|
639
|
+
activeClass: "border-neutral-700 bg-neutral-800 text-neutral-400"
|
|
640
|
+
}
|
|
641
|
+
];
|
|
642
|
+
/** Lines matching the text filter and the level filter. */
|
|
643
|
+
let visible = derived(() => filterByLevel(filterEvents(lines, filter), levelFilter()));
|
|
644
|
+
/** How many lines of each level are loaded, including `unknown`. */
|
|
645
|
+
let levelCounts = derived(() => {
|
|
646
|
+
const counts = {
|
|
647
|
+
error: 0,
|
|
648
|
+
warn: 0,
|
|
649
|
+
info: 0,
|
|
650
|
+
debug: 0,
|
|
651
|
+
unknown: 0
|
|
652
|
+
};
|
|
653
|
+
for (const event of lines) {
|
|
654
|
+
const rowLevel = effectiveLevel(event);
|
|
655
|
+
counts[rowLevel ?? "unknown"] = (counts[rowLevel ?? "unknown"] ?? 0) + 1;
|
|
656
|
+
}
|
|
657
|
+
return counts;
|
|
658
|
+
});
|
|
734
659
|
/** Presentation rows for the visible lines. */
|
|
735
660
|
let rows = derived(() => visible().map((event, index) => {
|
|
736
|
-
const
|
|
661
|
+
const rowLevel = event.level !== void 0 ? event.level : detectLevel(event.message);
|
|
737
662
|
const formatted = formatLogMessage(event.message, { prettyJson: jsonView });
|
|
663
|
+
const levelClass = levelColorClass(rowLevel);
|
|
738
664
|
return {
|
|
739
665
|
key: eventKey(event, index),
|
|
740
666
|
time: formatTime(event.timestamp),
|
|
741
667
|
timestamp: formatTimestamp(event.timestamp),
|
|
742
668
|
streamName: event.streamName,
|
|
669
|
+
group: event.group ?? null,
|
|
743
670
|
message: formatted.text,
|
|
744
671
|
tokens: formatted.isJson ? tokenizeJson(formatted.text) : null,
|
|
745
|
-
|
|
746
|
-
|
|
672
|
+
expandable: null,
|
|
673
|
+
level: rowLevel,
|
|
674
|
+
levelClass
|
|
747
675
|
};
|
|
748
676
|
}));
|
|
749
677
|
/** Keeps the newest line in view while auto-scroll is on (vertical only). */
|
|
750
678
|
/** Error text with the group and region context. */
|
|
751
679
|
let errorText = derived(() => error === null ? null : describeStreamError(region, group, error.message));
|
|
680
|
+
/** True when the ready frame says the events come from the local archive. */
|
|
681
|
+
let archived = derived(() => ready?.source === "archive");
|
|
682
|
+
/**
|
|
683
|
+
* Status the badge shows. The archive has no live tail: it replays a fixed window and ends by
|
|
684
|
+
* itself, so its connecting/live/reconnecting states would all be misleading.
|
|
685
|
+
*/
|
|
686
|
+
let displayStatus = derived(() => archived() && (status === "connecting" || status === "live" || status === "reconnecting") ? "idle" : status);
|
|
687
|
+
/** Tooltip of the archive indicator, naming the database file it reads. */
|
|
688
|
+
let archiveTitle = derived(() => describeArchive(archivePath));
|
|
752
689
|
/** Body text when there is nothing to render. */
|
|
753
|
-
let emptyText = derived(() => group === null || group === "" ? "Select a log group to start tailing." : filter.trim()
|
|
690
|
+
let emptyText = derived(() => group === null || group === "" ? "Select a log group to start tailing." : filter.trim() !== "" ? "No lines match the filter." : archived() ? `No archived events for ${group} in this window.` : `Waiting for events from ${group}\u2026`);
|
|
754
691
|
/** Auto-scroll button colour. */
|
|
755
692
|
let autoScrollTone = derived(() => autoScroll ? "text-sky-300" : "text-neutral-400");
|
|
756
693
|
/** Chip describing the active window. */
|
|
@@ -769,6 +706,12 @@ function LogViewer($$renderer, $$props) {
|
|
|
769
706
|
let handleLeft = derived(() => ROW_PADDING_PX + TIMESTAMP_COLUMN_PX + ROW_GAP_PX + prefixPx);
|
|
770
707
|
/** Prefix column width as a CSS length. */
|
|
771
708
|
let prefixStyle = derived(() => `max-width: ${pxToRem(prefixPx)}`);
|
|
709
|
+
/** Rows the user has opened, keyed by row key. */
|
|
710
|
+
let expandedRows = {};
|
|
711
|
+
/** True when a row is open. */
|
|
712
|
+
function isExpanded(key) {
|
|
713
|
+
return expandedRows[key] === true;
|
|
714
|
+
}
|
|
772
715
|
/** Colour for a JSON token, keeping punctuation and whitespace quiet. */
|
|
773
716
|
function tokenClass(type) {
|
|
774
717
|
switch (type) {
|
|
@@ -786,8 +729,11 @@ function LogViewer($$renderer, $$props) {
|
|
|
786
729
|
writePref(STORAGE_KEYS.prefixWidth, String(Math.round(prefixPx)));
|
|
787
730
|
}
|
|
788
731
|
$$renderer.push(`<section class="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden rounded-lg border border-neutral-800 bg-neutral-950"><div class="flex flex-wrap items-center gap-x-3 gap-y-2 border-b border-neutral-800 bg-neutral-900/40 px-3 py-2">`);
|
|
789
|
-
StatusBadge($$renderer, { status });
|
|
790
|
-
$$renderer.push(`<!---->
|
|
732
|
+
StatusBadge($$renderer, { status: displayStatus() });
|
|
733
|
+
$$renderer.push(`<!----> `);
|
|
734
|
+
if (archived()) $$renderer.push(`<!--[0--><span data-testid="archive-badge"${attr("title", archiveTitle())} class="rounded-full border border-teal-900 bg-teal-950/60 px-2 py-0.5 text-[0.6875rem] font-medium text-teal-300">local archive</span>`);
|
|
735
|
+
else $$renderer.push("<!--[-1-->");
|
|
736
|
+
$$renderer.push(`<!--]--> <span class="text-xs text-neutral-400" data-testid="visible-count">${escape_html(formatCount(visible().length))} shown</span> <span class="text-xs text-neutral-500" data-testid="received-count">${escape_html(formatCount(receivedCount))} received</span> `);
|
|
791
737
|
if (pendingCount > 0) $$renderer.push(`<!--[0--><span class="text-xs text-amber-300" data-testid="pending-count">${escape_html(formatCount(pendingCount))} buffered</span>`);
|
|
792
738
|
else $$renderer.push("<!--[-1-->");
|
|
793
739
|
$$renderer.push(`<!--]--> `);
|
|
@@ -799,7 +745,16 @@ function LogViewer($$renderer, $$props) {
|
|
|
799
745
|
$$renderer.push(`<!--]--> `);
|
|
800
746
|
if (windowComplete()) $$renderer.push(`<!--[0--><span class="text-[0.6875rem] text-emerald-400/80" data-testid="window-complete">window complete</span>`);
|
|
801
747
|
else $$renderer.push("<!--[-1-->");
|
|
802
|
-
$$renderer.push(`<!--]--> <div class="ml-auto flex flex-wrap items-center gap-2"><
|
|
748
|
+
$$renderer.push(`<!--]--> <div class="ml-auto flex flex-wrap items-center gap-2"><div class="flex items-center gap-1" role="group" aria-label="Filter by level"><button type="button"${attr("aria-pressed", levelFilter() === null)} title="Show every level, including lines with no level" data-testid="level-all"${attr_class(`rounded-md border px-2 py-1 text-xs font-medium transition-colors hover:border-neutral-700 ${levelFilter() === null ? "border-sky-700 bg-sky-950/60 text-sky-200" : "border-neutral-800 bg-neutral-900 text-neutral-400 hover:border-neutral-700"}`)}>All</button> <!--[-->`);
|
|
749
|
+
const each_array = ensure_array_like(LEVEL_CHIPS);
|
|
750
|
+
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
|
|
751
|
+
let chip = each_array[$$index];
|
|
752
|
+
$$renderer.push(`<button type="button"${attr("aria-pressed", levelFilter() === chip.level)}${attr("title", `Show only ${stringify(chip.label)} lines (${stringify(levelCounts()[chip.level] ?? 0)} loaded)`)}${attr("data-testid", `level-${stringify(chip.level)}`)}${attr_class(`rounded-md border px-2 py-1 text-xs font-medium transition-colors hover:border-neutral-700 ${stringify(levelFilter() === chip.level ? chip.activeClass : "border-neutral-800 bg-neutral-900 text-neutral-400 hover:border-neutral-700")}`)}>${escape_html(chip.label)} `);
|
|
753
|
+
if ((levelCounts()[chip.level] ?? 0) > 0) $$renderer.push(`<!--[0--><span class="ml-1 opacity-70">${escape_html(formatCount(levelCounts()[chip.level] ?? 0))}</span>`);
|
|
754
|
+
else $$renderer.push("<!--[-1-->");
|
|
755
|
+
$$renderer.push(`<!--]--></button>`);
|
|
756
|
+
}
|
|
757
|
+
$$renderer.push(`<!--]--></div> <input type="search" aria-label="Filter log lines" placeholder="Filter lines"${attr("value", filter)} class="w-40 rounded-md border border-neutral-800 bg-neutral-900 px-2 py-1 text-xs text-neutral-100 outline-none transition-colors placeholder:text-neutral-600 hover:border-neutral-700 focus:border-sky-600"/> <button type="button"${attr("aria-pressed", wrapLines)} title="Wrap long lines instead of scrolling sideways" data-testid="wrap-toggle"${attr_class(`rounded-md border border-neutral-800 bg-neutral-900 px-2 py-1 text-xs font-medium transition-colors hover:border-neutral-700 ${wrapTone()}`)}>Wrap</button> <button type="button"${attr("aria-pressed", jsonView)} title="Pretty-print JSON log entries (off: click a line to open its JSON)" data-testid="json-toggle"${attr_class(`rounded-md border border-neutral-800 bg-neutral-900 px-2 py-1 text-xs font-medium transition-colors hover:border-neutral-700 ${jsonTone()}`)}>JSON</button> <button type="button"${attr("aria-pressed", paused)} class="rounded-md border border-neutral-800 bg-neutral-900 px-2 py-1 text-xs font-medium text-neutral-300 transition-colors hover:border-neutral-700 hover:text-neutral-100">${escape_html(paused ? "Resume" : "Pause")}</button> <button type="button" class="rounded-md border border-neutral-800 bg-neutral-900 px-2 py-1 text-xs font-medium text-neutral-300 transition-colors hover:border-neutral-700 hover:text-neutral-100">Clear</button> <button type="button"${attr("aria-pressed", autoScroll)}${attr_class(`rounded-md border border-neutral-800 bg-neutral-900 px-2 py-1 text-xs font-medium transition-colors hover:border-neutral-700 ${autoScrollTone()}`)}>Auto-scroll</button></div></div> `);
|
|
803
758
|
if (errorText() !== null) $$renderer.push(`<!--[0--><p role="alert" data-testid="stream-error" class="border-b border-red-900 bg-red-950/50 px-3 py-2 text-xs leading-5 text-red-300">${escape_html(errorText())}</p>`);
|
|
804
759
|
else $$renderer.push("<!--[-1-->");
|
|
805
760
|
$$renderer.push(`<!--]--> <div class="min-h-0 min-w-0 flex-1 overflow-auto bg-neutral-950" data-testid="log-scroller">`);
|
|
@@ -807,20 +762,38 @@ function LogViewer($$renderer, $$props) {
|
|
|
807
762
|
else if (rows().length === 0) $$renderer.push(`<!--[1--><p class="px-3 py-6 text-sm text-neutral-500" data-testid="viewer-empty">${escape_html(emptyText())}</p>`);
|
|
808
763
|
else {
|
|
809
764
|
$$renderer.push(`<!--[-1--><div${attr_class(`relative min-h-full ${listClass()}`)} data-testid="log-canvas"><ol class="font-mono text-xs leading-5" data-testid="log-lines"><!--[-->`);
|
|
810
|
-
const
|
|
811
|
-
for (let $$
|
|
812
|
-
let row =
|
|
813
|
-
|
|
765
|
+
const each_array_1 = ensure_array_like(rows());
|
|
766
|
+
for (let $$index_3 = 0, $$length = each_array_1.length; $$index_3 < $$length; $$index_3++) {
|
|
767
|
+
let row = each_array_1[$$index_3];
|
|
768
|
+
const expandable = row.expandable !== null;
|
|
769
|
+
const open = expandable && isExpanded(row.key);
|
|
770
|
+
$$renderer.push(`<li${attr_class(`flex flex-wrap items-baseline gap-2 px-3 py-0.5 hover:bg-neutral-900/60 ${listClass()} ${expandable ? "cursor-pointer" : ""}`)} data-testid="log-line"${attr("data-level", row.level ?? "unknown")}${attr("data-expandable", expandable ? "true" : void 0)}${attr("data-expanded", open ? "true" : void 0)}${attr("role", expandable ? "button" : void 0)}${attr("tabindex", expandable ? 0 : void 0)}${attr("aria-expanded", expandable ? open : void 0)}${attr("title", expandable ? open ? "Hide the JSON in this line" : "Show this line as JSON" : void 0)}><span class="w-[7.5rem] shrink-0 text-neutral-500"${attr("title", row.timestamp)}>${escape_html(row.time)}</span> `);
|
|
771
|
+
if (groups.length > 1) $$renderer.push(`<!--[0--><span class="w-[9rem] shrink-0 truncate text-teal-400/80"${attr("title", row.group ?? "")} data-testid="log-group">${escape_html(row.group ?? "")}</span>`);
|
|
772
|
+
else $$renderer.push("<!--[-1-->");
|
|
773
|
+
$$renderer.push(`<!--]--> <span class="shrink-0 truncate text-sky-400/80"${attr_style(prefixStyle())}${attr("title", row.streamName ?? "")} data-testid="log-stream">${escape_html(row.streamName ?? "")}</span> `);
|
|
774
|
+
if (expandable) $$renderer.push(`<!--[0--><span class="shrink-0 text-neutral-600" aria-hidden="true">${escape_html(open ? "▾" : "▸")}</span>`);
|
|
775
|
+
else $$renderer.push("<!--[-1-->");
|
|
776
|
+
$$renderer.push(`<!--]--> <span${attr_class(`${messageClass()} ${stringify(row.levelClass)}`)} data-testid="log-message">`);
|
|
814
777
|
if (row.tokens !== null) {
|
|
815
778
|
$$renderer.push(`<!--[0--><!--[-->`);
|
|
816
|
-
const
|
|
817
|
-
for (let tokenIndex = 0, $$length =
|
|
818
|
-
let token =
|
|
779
|
+
const each_array_2 = ensure_array_like(row.tokens);
|
|
780
|
+
for (let tokenIndex = 0, $$length = each_array_2.length; tokenIndex < $$length; tokenIndex++) {
|
|
781
|
+
let token = each_array_2[tokenIndex];
|
|
819
782
|
$$renderer.push(`<span${attr_class(clsx(tokenClass(token.type)))}>${escape_html(token.text)}</span>`);
|
|
820
783
|
}
|
|
821
784
|
$$renderer.push(`<!--]-->`);
|
|
822
785
|
} else $$renderer.push(`<!--[-1-->${escape_html(row.message)}`);
|
|
823
|
-
$$renderer.push(`<!--]--></span
|
|
786
|
+
$$renderer.push(`<!--]--></span> `);
|
|
787
|
+
if (open && row.expandable !== null) {
|
|
788
|
+
$$renderer.push(`<!--[0--><span class="w-full whitespace-pre text-xs text-neutral-300" data-testid="log-json-expanded"><!--[-->`);
|
|
789
|
+
const each_array_3 = ensure_array_like(tokenizeJson(row.expandable));
|
|
790
|
+
for (let tokenIndex = 0, $$length = each_array_3.length; tokenIndex < $$length; tokenIndex++) {
|
|
791
|
+
let token = each_array_3[tokenIndex];
|
|
792
|
+
$$renderer.push(`<span${attr_class(clsx(tokenClass(token.type)))}>${escape_html(token.text)}</span>`);
|
|
793
|
+
}
|
|
794
|
+
$$renderer.push(`<!--]--></span>`);
|
|
795
|
+
} else $$renderer.push("<!--[-1-->");
|
|
796
|
+
$$renderer.push(`<!--]--></li>`);
|
|
824
797
|
}
|
|
825
798
|
$$renderer.push(`<!--]--></ol> `);
|
|
826
799
|
ColumnResizer($$renderer, {
|
|
@@ -870,6 +843,29 @@ function RegionSelect($$renderer, $$props) {
|
|
|
870
843
|
$$renderer.push(`</div>`);
|
|
871
844
|
});
|
|
872
845
|
}
|
|
846
|
+
//#endregion
|
|
847
|
+
//#region src/lib/components/SourceControls.svelte
|
|
848
|
+
function SourceControls($$renderer, $$props) {
|
|
849
|
+
$$renderer.component(($$renderer) => {
|
|
850
|
+
/** Active source, owned by the page. */
|
|
851
|
+
/**
|
|
852
|
+
* True only when `GET /api/archive` reported `available: true`.
|
|
853
|
+
* The toggle is hidden otherwise, so the app keeps its CloudWatch-only look.
|
|
854
|
+
*/
|
|
855
|
+
/** Database file behind the archive, shown in the tooltip. */
|
|
856
|
+
/** Called with the newly chosen source. */
|
|
857
|
+
let { source = "cloudwatch", archiveAvailable = false, archivePath = null} = $$props;
|
|
858
|
+
/** Tooltip of the archive button: what it reads and which file backs it. */
|
|
859
|
+
let archiveTitle = derived(() => describeArchive(archivePath));
|
|
860
|
+
if (archiveAvailable) {
|
|
861
|
+
$$renderer.push(`<!--[0--><div class="flex flex-col gap-1.5"><div class="flex items-center gap-1 rounded-lg border border-neutral-800 bg-neutral-900 p-0.5" role="group" aria-label="Log source"><button type="button"${attr("aria-pressed", source === "cloudwatch")} title="Stream from CloudWatch Logs with your AWS credentials" data-testid="source-cloudwatch"${attr_class(`rounded-md border px-2 py-1 text-xs font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${source === "cloudwatch" ? "border-sky-700 bg-sky-950/60 text-sky-300" : "border-transparent text-neutral-400 hover:text-neutral-200"}`)}>CloudWatch</button> <button type="button"${attr("aria-pressed", source === "archive")}${attr("title", archiveTitle())} data-testid="source-archive"${attr_class(`rounded-md border px-2 py-1 text-xs font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${source === "archive" ? "border-teal-700 bg-teal-950/60 text-teal-300" : "border-transparent text-neutral-400 hover:text-neutral-200"}`)}>Local archive</button></div> `);
|
|
862
|
+
if (source === "archive") $$renderer.push(`<!--[0--><p class="text-[0.6875rem] leading-4 text-teal-300/80"${attr("title", archiveTitle())} data-testid="archive-note">${escape_html(ARCHIVE_NOTE)}</p>`);
|
|
863
|
+
else $$renderer.push("<!--[-1-->");
|
|
864
|
+
$$renderer.push(`<!--]--></div>`);
|
|
865
|
+
} else $$renderer.push("<!--[-1-->");
|
|
866
|
+
$$renderer.push(`<!--]-->`);
|
|
867
|
+
});
|
|
868
|
+
}
|
|
873
869
|
function createBrowserEventSource(url) {
|
|
874
870
|
return new EventSource(url);
|
|
875
871
|
}
|
|
@@ -877,7 +873,10 @@ function createBrowserEventSource(url) {
|
|
|
877
873
|
function buildStreamUrl(target, base = "") {
|
|
878
874
|
const search = new URLSearchParams();
|
|
879
875
|
search.set("region", target.region);
|
|
880
|
-
|
|
876
|
+
const groups = (target.groups ?? []).filter((name) => name.length > 0);
|
|
877
|
+
if (groups.length > 1) search.set("groups", groups.join(","));
|
|
878
|
+
else search.set("group", groups[0] ?? target.group);
|
|
879
|
+
if (target.source === "archive") search.set("source", "archive");
|
|
881
880
|
if (target.filterPattern) search.set("filterPattern", target.filterPattern);
|
|
882
881
|
if (target.mode === "historic") {
|
|
883
882
|
search.set("mode", "historic");
|
|
@@ -899,7 +898,7 @@ function parsePayload(data) {
|
|
|
899
898
|
}
|
|
900
899
|
}
|
|
901
900
|
/**
|
|
902
|
-
*
|
|
901
|
+
* View over one log group: the live CloudWatch stream, or the local archive replay.
|
|
903
902
|
*
|
|
904
903
|
* Pause policy: pausing keeps the connection open and keeps receiving events; those events are
|
|
905
904
|
* held in a pending buffer and flushed into the visible buffer on resume.
|
|
@@ -1116,7 +1115,12 @@ function _page($$renderer, $$props) {
|
|
|
1116
1115
|
const stream = new LogStream();
|
|
1117
1116
|
/** Region picker state. Seeded from the URL so a shared link restores the view. */
|
|
1118
1117
|
let region = page.url.searchParams.get("region") ?? "";
|
|
1119
|
-
/**
|
|
1118
|
+
/**
|
|
1119
|
+
* Where the view reads from, seeded from the URL. `archive` is only offered while the local
|
|
1120
|
+
* DuckDB archive reports `available: true`, so the fallback below is applied during bootstrap.
|
|
1121
|
+
*/
|
|
1122
|
+
let source = page.url.searchParams.get("source") === "archive" ? "archive" : "cloudwatch";
|
|
1123
|
+
/** Tail mode and historic window, seeded from the URL; bootstrap forces historic for the archive. */
|
|
1120
1124
|
let mode = page.url.searchParams.get("mode") === "historic" ? "historic" : "live";
|
|
1121
1125
|
let range = page.url.searchParams.get("range") ?? "15m";
|
|
1122
1126
|
let windowFrom = parseUrlEpoch(page.url.searchParams.get("from"));
|
|
@@ -1129,8 +1133,25 @@ function _page($$renderer, $$props) {
|
|
|
1129
1133
|
let endpoint = null;
|
|
1130
1134
|
let filter = "";
|
|
1131
1135
|
let autoScroll = true;
|
|
1132
|
-
/**
|
|
1133
|
-
|
|
1136
|
+
/**
|
|
1137
|
+
* Log groups in the view, seeded from the URL. One entry is the normal case;
|
|
1138
|
+
* more than one tails them together and adds a group column.
|
|
1139
|
+
*/
|
|
1140
|
+
let selectedGroups = parseUrlGroups(page.url.searchParams);
|
|
1141
|
+
/** Primary group: the one the viewer names the view after. */
|
|
1142
|
+
let selectedGroup = derived(() => selectedGroups[0] ?? null);
|
|
1143
|
+
/** Level filter shared by the log view and the chart. */
|
|
1144
|
+
let levelFilter = null;
|
|
1145
|
+
/** Bucketed counts behind the chart. */
|
|
1146
|
+
let seriesPoints = [];
|
|
1147
|
+
/** True while the archive counts are being fetched. */
|
|
1148
|
+
let seriesLoading = false;
|
|
1149
|
+
/** True only when the local archive reports that it can be read. */
|
|
1150
|
+
let archiveAvailable = derived(() => false);
|
|
1151
|
+
/** Database file behind the archive, or `null` when it is unknown. */
|
|
1152
|
+
let archivePath = derived(() => null);
|
|
1153
|
+
/** Tooltip of the archive badge in the header. */
|
|
1154
|
+
let archiveTitle = derived(() => describeArchive(archivePath()));
|
|
1134
1155
|
/** Sidebar width in pixels; the layout default is 22rem. */
|
|
1135
1156
|
let sidebarPx = remToPx(SIDEBAR_WIDTH.defaultRem);
|
|
1136
1157
|
/** Largest sidebar width allowed: 44vw, but never below the minimum. */
|
|
@@ -1144,21 +1165,29 @@ function _page($$renderer, $$props) {
|
|
|
1144
1165
|
localStorage.setItem(STORAGE_KEYS.sidebarWidth, String(Math.round(sidebarPx)));
|
|
1145
1166
|
} catch {}
|
|
1146
1167
|
}
|
|
1147
|
-
/**
|
|
1168
|
+
/**
|
|
1169
|
+
* Loads the log groups of a region from the active source; a newer region or source change wins
|
|
1170
|
+
* the race. The CloudWatch URL is left exactly as it was, so only the archive names its source.
|
|
1171
|
+
*/
|
|
1148
1172
|
async function loadGroups(target) {
|
|
1149
1173
|
groupsLoading = true;
|
|
1150
1174
|
groupsError = null;
|
|
1175
|
+
const requestedSource = source;
|
|
1176
|
+
const query = requestedSource === "archive" ? {
|
|
1177
|
+
region: target,
|
|
1178
|
+
source: requestedSource
|
|
1179
|
+
} : { region: target };
|
|
1151
1180
|
try {
|
|
1152
|
-
const response = await fetchLogGroups(
|
|
1153
|
-
if (target !== region) return;
|
|
1181
|
+
const response = await fetchLogGroups(query);
|
|
1182
|
+
if (target !== region || requestedSource !== source) return;
|
|
1154
1183
|
if (response.endpoint !== null) endpoint = response.endpoint;
|
|
1155
1184
|
groups = response.groups;
|
|
1156
1185
|
} catch (error) {
|
|
1157
|
-
if (target !== region) return;
|
|
1186
|
+
if (target !== region || requestedSource !== source) return;
|
|
1158
1187
|
groups = [];
|
|
1159
1188
|
groupsError = describeGroupsError(target, error);
|
|
1160
1189
|
} finally {
|
|
1161
|
-
if (target === region) groupsLoading = false;
|
|
1190
|
+
if (target === region && requestedSource === source) groupsLoading = false;
|
|
1162
1191
|
}
|
|
1163
1192
|
}
|
|
1164
1193
|
/** Parses an epoch-millisecond URL parameter, or `null`. */
|
|
@@ -1167,21 +1196,83 @@ function _page($$renderer, $$props) {
|
|
|
1167
1196
|
const parsed = Number(value);
|
|
1168
1197
|
return Number.isFinite(parsed) ? parsed : null;
|
|
1169
1198
|
}
|
|
1199
|
+
/** The window the current mode asks for. The archive is always a historic window. */
|
|
1200
|
+
function windowParams() {
|
|
1201
|
+
if (mode === "live" && source !== "archive") return { mode: "live" };
|
|
1202
|
+
if (range === "" && windowFrom !== null && windowTo !== null) return {
|
|
1203
|
+
mode: "historic",
|
|
1204
|
+
from: windowFrom,
|
|
1205
|
+
to: windowTo
|
|
1206
|
+
};
|
|
1207
|
+
return {
|
|
1208
|
+
mode: "historic",
|
|
1209
|
+
range: range === "" ? "15m" : range
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
/** Stream target for the active source, region, groups and window. */
|
|
1213
|
+
function streamTarget() {
|
|
1214
|
+
const window = windowParams();
|
|
1215
|
+
const [primary, ...rest] = selectedGroups;
|
|
1216
|
+
const target = {
|
|
1217
|
+
region,
|
|
1218
|
+
group: primary ?? "",
|
|
1219
|
+
groups: [...selectedGroups],
|
|
1220
|
+
...window
|
|
1221
|
+
};
|
|
1222
|
+
if (source === "archive") target.source = "archive";
|
|
1223
|
+
return target;
|
|
1224
|
+
}
|
|
1225
|
+
/** Applies a mode or range change: restarts the stream and mirrors it into the URL. */
|
|
1226
|
+
function applyRange(payload) {
|
|
1227
|
+
if (source === "archive" && payload.mode === "live") return;
|
|
1228
|
+
mode = payload.mode;
|
|
1229
|
+
range = payload.range;
|
|
1230
|
+
windowFrom = payload.from;
|
|
1231
|
+
windowTo = payload.to;
|
|
1232
|
+
syncUrl(region, selectedGroups);
|
|
1233
|
+
if (selectedGroups.length === 0) return;
|
|
1234
|
+
rangeLoading = payload.mode === "historic";
|
|
1235
|
+
stream.start(streamTarget());
|
|
1236
|
+
loadSeries();
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Applies a brushed range from the chart as the new historic window, which
|
|
1240
|
+
* re-scopes the log view to exactly what was brushed.
|
|
1241
|
+
*/
|
|
1242
|
+
function applyBrush(selection) {
|
|
1243
|
+
if (selection === null) return;
|
|
1244
|
+
const window = chartWindow();
|
|
1245
|
+
if (!isMeaningfulBrush(selection, window, chartBucketMs(window.from, window.to))) return;
|
|
1246
|
+
applyRange({
|
|
1247
|
+
mode: "historic",
|
|
1248
|
+
range: "",
|
|
1249
|
+
from: selection.from,
|
|
1250
|
+
to: selection.to
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1170
1253
|
/** Switches region: stops the stream, clears the list and reloads the groups. */
|
|
1171
1254
|
function changeRegion(next) {
|
|
1172
1255
|
if (next === "" || next === region) return;
|
|
1173
1256
|
region = next;
|
|
1174
1257
|
stream.stop();
|
|
1175
1258
|
groups = [];
|
|
1176
|
-
|
|
1259
|
+
selectedGroups = [];
|
|
1260
|
+
seriesPoints = [];
|
|
1261
|
+
syncUrl(next, []);
|
|
1177
1262
|
loadGroups(next);
|
|
1178
1263
|
}
|
|
1179
1264
|
/** Replaces the view parameters in the address bar. */
|
|
1180
|
-
function syncUrl(nextRegion,
|
|
1265
|
+
function syncUrl(nextRegion, nextGroups) {
|
|
1181
1266
|
const url = new URL(page.url);
|
|
1182
1267
|
if (nextRegion === "") url.searchParams.delete("region");
|
|
1183
1268
|
else url.searchParams.set("region", nextRegion);
|
|
1269
|
+
if (source === "archive") url.searchParams.set("source", "archive");
|
|
1270
|
+
else url.searchParams.delete("source");
|
|
1184
1271
|
url.searchParams.delete("group");
|
|
1272
|
+
url.searchParams.delete("groups");
|
|
1273
|
+
const names = nextGroups.filter((name) => name.length > 0);
|
|
1274
|
+
if (names.length === 1) url.searchParams.set("group", names[0]);
|
|
1275
|
+
else if (names.length > 1) url.searchParams.set("groups", names.join(","));
|
|
1185
1276
|
if (mode === "live") {
|
|
1186
1277
|
url.searchParams.delete("mode");
|
|
1187
1278
|
url.searchParams.delete("range");
|
|
@@ -1199,12 +1290,106 @@ function _page($$renderer, $$props) {
|
|
|
1199
1290
|
}
|
|
1200
1291
|
if (url.href !== page.url.href) replaceState();
|
|
1201
1292
|
}
|
|
1293
|
+
/** Clears the loading hint once the server reports its window. */
|
|
1294
|
+
/**
|
|
1295
|
+
* Keeps the chart in step with the view: a new window, selection, source, level
|
|
1296
|
+
* or a new batch of lines all re-load the counts.
|
|
1297
|
+
*/
|
|
1298
|
+
/**
|
|
1299
|
+
* Reacts to `archive-unavailable` from the stream (the file was locked or removed after boot) by
|
|
1300
|
+
* re-checking the archive, so the toggle hides itself instead of offering a source that fails.
|
|
1301
|
+
*/
|
|
1302
|
+
/** Parses the group selection from the URL, accepting both parameter shapes. */
|
|
1303
|
+
function parseUrlGroups(params) {
|
|
1304
|
+
const names = (params.get("groups") ?? "").split(",").map((name) => name.trim()).filter((name) => name.length > 0);
|
|
1305
|
+
if (names.length > 0) return [...new Set(names)];
|
|
1306
|
+
const single = params.get("group")?.trim() ?? "";
|
|
1307
|
+
return single.length > 0 ? [single] : [];
|
|
1308
|
+
}
|
|
1309
|
+
/** Bucket width for the chart of one window, mirroring the server's choice. */
|
|
1310
|
+
function chartBucketMs(from, to) {
|
|
1311
|
+
const span = Math.max(1, to - from);
|
|
1312
|
+
return [
|
|
1313
|
+
1e3,
|
|
1314
|
+
5e3,
|
|
1315
|
+
1e4,
|
|
1316
|
+
3e4,
|
|
1317
|
+
6e4,
|
|
1318
|
+
3e5,
|
|
1319
|
+
9e5,
|
|
1320
|
+
18e5
|
|
1321
|
+
].find((step) => span / step <= 90) ?? 36e5;
|
|
1322
|
+
}
|
|
1323
|
+
/** Window the chart describes: the active historic window, or a rolling quarter hour. */
|
|
1324
|
+
function chartWindow() {
|
|
1325
|
+
if (mode === "historic") {
|
|
1326
|
+
const to = windowTo ?? stream.ready?.endTime ?? Date.now();
|
|
1327
|
+
const from = windowFrom ?? stream.ready?.startTime ?? to - 9e5;
|
|
1328
|
+
return {
|
|
1329
|
+
from: Math.min(from, to - 1e3),
|
|
1330
|
+
to
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
const to = Date.now();
|
|
1334
|
+
return {
|
|
1335
|
+
from: to - 9e5,
|
|
1336
|
+
to
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Loads the counts behind the chart.
|
|
1341
|
+
*
|
|
1342
|
+
* The archive can count a whole window in one statement, so it is asked for
|
|
1343
|
+
* them; a CloudWatch view has no aggregate endpoint, so the events already
|
|
1344
|
+
* streamed are bucketed here. Both end up as the same points.
|
|
1345
|
+
*/
|
|
1346
|
+
async function loadSeries() {
|
|
1347
|
+
if (selectedGroups.length === 0 || region === "") {
|
|
1348
|
+
seriesPoints = [];
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
const { from, to } = chartWindow();
|
|
1352
|
+
if (source !== "archive") {
|
|
1353
|
+
seriesPoints = bucketEvents(stream.lines, {
|
|
1354
|
+
from,
|
|
1355
|
+
to,
|
|
1356
|
+
bucketMs: chartBucketMs(from, to),
|
|
1357
|
+
level: levelFilter,
|
|
1358
|
+
fallbackGroup: selectedGroup() ?? ""
|
|
1359
|
+
});
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
seriesLoading = true;
|
|
1363
|
+
try {
|
|
1364
|
+
seriesPoints = (await fetchSeries({
|
|
1365
|
+
region,
|
|
1366
|
+
groups: selectedGroups,
|
|
1367
|
+
from,
|
|
1368
|
+
to,
|
|
1369
|
+
levels: levelFilter === null ? [] : [levelFilter]
|
|
1370
|
+
})).points;
|
|
1371
|
+
} catch {
|
|
1372
|
+
seriesPoints = [];
|
|
1373
|
+
} finally {
|
|
1374
|
+
seriesLoading = false;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1202
1377
|
$$renderer.push(`<div class="flex flex-wrap items-center gap-x-3 gap-y-2"><span class="text-[0.6875rem] font-semibold uppercase tracking-wider text-neutral-500">Watching</span> <span class="max-w-[22rem] truncate rounded-md border border-neutral-800 bg-neutral-900 px-2 py-1 font-mono text-xs text-neutral-200">${escape_html(selectedGroup() ?? "no group selected")}</span> <span class="text-xs text-neutral-500">in</span> <span class="font-mono text-xs text-neutral-300">${escape_html(region === "" ? "resolving region…" : region)}</span> `);
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1378
|
+
if (source === "archive") {
|
|
1379
|
+
$$renderer.push("<!--[0-->");
|
|
1380
|
+
EndpointBadge($$renderer, {
|
|
1381
|
+
endpoint: null,
|
|
1382
|
+
credentials: null
|
|
1383
|
+
});
|
|
1384
|
+
$$renderer.push(`<!----> <span data-testid="archive-source-badge"${attr("title", archiveTitle())} class="rounded-full border border-teal-900 bg-teal-950/60 px-2.5 py-1 text-xs font-medium text-teal-300">local archive</span>`);
|
|
1385
|
+
} else {
|
|
1386
|
+
$$renderer.push("<!--[-1-->");
|
|
1387
|
+
EndpointBadge($$renderer, {
|
|
1388
|
+
endpoint,
|
|
1389
|
+
credentials: null
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
$$renderer.push(`<!--]--> `);
|
|
1208
1393
|
$$renderer.push("<!--[-1-->");
|
|
1209
1394
|
$$renderer.push(`<!--]--></div> `);
|
|
1210
1395
|
$$renderer.push("<!--[-1-->");
|
|
@@ -1215,8 +1400,14 @@ function _page($$renderer, $$props) {
|
|
|
1215
1400
|
from: windowFrom,
|
|
1216
1401
|
to: windowTo,
|
|
1217
1402
|
loading: rangeLoading,
|
|
1218
|
-
disabled: selectedGroup() === null
|
|
1403
|
+
disabled: selectedGroup() === null,
|
|
1404
|
+
liveDisabled: source === "archive"});
|
|
1219
1405
|
$$renderer.push(`<!----> <div class="flex min-h-0 min-w-0 flex-1 flex-col gap-3 lg:flex-row"><div class="flex min-h-0 w-full flex-col gap-3 rounded-lg border border-neutral-800 bg-neutral-950/60 p-3 lg:shrink-0"${attr_style(sidebarStyle())} data-testid="sidebar">`);
|
|
1406
|
+
SourceControls($$renderer, {
|
|
1407
|
+
source,
|
|
1408
|
+
archiveAvailable: archiveAvailable(),
|
|
1409
|
+
archivePath: archivePath()});
|
|
1410
|
+
$$renderer.push(`<!----> `);
|
|
1220
1411
|
RegionSelect($$renderer, {
|
|
1221
1412
|
regions,
|
|
1222
1413
|
value: region,
|
|
@@ -1225,8 +1416,9 @@ function _page($$renderer, $$props) {
|
|
|
1225
1416
|
$$renderer.push(`<!----> `);
|
|
1226
1417
|
LogGroupList($$renderer, {
|
|
1227
1418
|
groups,
|
|
1419
|
+
source,
|
|
1228
1420
|
region,
|
|
1229
|
-
selected:
|
|
1421
|
+
selected: selectedGroups,
|
|
1230
1422
|
loading: groupsLoading,
|
|
1231
1423
|
error: groupsError});
|
|
1232
1424
|
$$renderer.push(`<!----></div> `);
|
|
@@ -1241,6 +1433,19 @@ function _page($$renderer, $$props) {
|
|
|
1241
1433
|
class: "hidden lg:block lg:w-2"
|
|
1242
1434
|
});
|
|
1243
1435
|
$$renderer.push(`<!----> <div class="flex min-h-0 min-w-0 flex-1 flex-col">`);
|
|
1436
|
+
EventScatterPanel($$renderer, {
|
|
1437
|
+
points: seriesPoints,
|
|
1438
|
+
from: chartWindow().from,
|
|
1439
|
+
to: chartWindow().to,
|
|
1440
|
+
bucketMs: chartBucketMs(chartWindow().from, chartWindow().to),
|
|
1441
|
+
groups: selectedGroups,
|
|
1442
|
+
loading: seriesLoading,
|
|
1443
|
+
onBrush: applyBrush
|
|
1444
|
+
});
|
|
1445
|
+
$$renderer.push(`<!----> `);
|
|
1446
|
+
if (windowFrom !== null && windowTo !== null && mode === "historic") $$renderer.push(`<!--[0--><div class="flex flex-wrap items-center gap-2 text-[0.6875rem] text-neutral-500"><span data-testid="brush-window">zoomed to ${escape_html(new Date(windowFrom).toLocaleTimeString())} – ${escape_html(new Date(windowTo).toLocaleTimeString())}</span> <button type="button" data-testid="brush-clear" class="rounded-md border border-neutral-800 bg-neutral-900 px-2 py-0.5 font-medium text-neutral-300 transition-colors hover:border-neutral-700 hover:text-neutral-100">Reset zoom</button></div>`);
|
|
1447
|
+
else $$renderer.push("<!--[-1-->");
|
|
1448
|
+
$$renderer.push(`<!--]--> `);
|
|
1244
1449
|
LogViewer($$renderer, {
|
|
1245
1450
|
lines: stream.lines,
|
|
1246
1451
|
status: stream.status,
|
|
@@ -1248,17 +1453,26 @@ function _page($$renderer, $$props) {
|
|
|
1248
1453
|
mode,
|
|
1249
1454
|
ready: stream.ready,
|
|
1250
1455
|
endReason: stream.endReason,
|
|
1456
|
+
archivePath: archivePath(),
|
|
1251
1457
|
region,
|
|
1252
1458
|
group: selectedGroup(),
|
|
1459
|
+
groups: selectedGroups,
|
|
1460
|
+
level: levelFilter,
|
|
1461
|
+
onLevelChange: (next) => levelFilter = next,
|
|
1253
1462
|
filter,
|
|
1254
1463
|
paused: stream.paused,
|
|
1255
1464
|
autoScroll,
|
|
1256
1465
|
pendingCount: stream.pendingCount,
|
|
1257
1466
|
droppedCount: stream.droppedCount,
|
|
1258
|
-
receivedCount: stream.receivedCount
|
|
1467
|
+
receivedCount: stream.receivedCount,
|
|
1468
|
+
onFilterChange: (value) => filter = value,
|
|
1469
|
+
onPauseToggle: () => stream.togglePause(),
|
|
1470
|
+
onClear: () => stream.clear(),
|
|
1471
|
+
onAutoScrollToggle: () => autoScroll = !autoScroll
|
|
1472
|
+
});
|
|
1259
1473
|
$$renderer.push(`<!----></div></div>`);
|
|
1260
1474
|
});
|
|
1261
1475
|
}
|
|
1262
1476
|
|
|
1263
1477
|
export { _page as default };
|
|
1264
|
-
//# sourceMappingURL=_page.svelte.js-
|
|
1478
|
+
//# sourceMappingURL=_page.svelte.js-B-kVwiNh.js.map
|