Compare commits

..

380 Commits

Author SHA1 Message Date
Amir Raminfar 443084964a chore: release v8.14.2 2025-09-25 14:33:39 -07:00
renovate[bot] dc1314ddc3 fix(deps): update all non-major dependencies (#4146)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-25 21:08:04 +00:00
Amir Raminfar c4060b2206 chore: uses trim instead 2025-09-25 14:05:23 -07:00
Amir Raminfar 5675389049 fix: fixes roles for proxy to default to all (#4145) 2025-09-25 14:01:50 -07:00
Amir Raminfar bc3c812c74 chore: release v8.14.1 2025-09-23 15:36:14 -07:00
Amir Raminfar 5aea48f3ca fix: resets session for existing users who do not have roles defined (#4141) 2025-09-23 11:05:32 -07:00
Amir Raminfar ab86a6c59e chore: release v8.14.0 2025-09-23 10:11:10 -07:00
Amir Raminfar f667e5a271 fix: fixes release 2025-09-23 10:10:21 -07:00
Amir Raminfar 58146d1ac5 chore: updates docs 2025-09-23 10:00:34 -07:00
Amir Raminfar a6b65a1341 chore: release v8.14.0 2025-09-23 09:57:05 -07:00
Amir Raminfar a6003e4c1c chore: reverts release 2025-09-23 09:55:25 -07:00
Amir Raminfar 4e59bb2f8f chore: release v8.14.0 2025-09-23 09:45:13 -07:00
renovate[bot] 220dcd9416 fix(deps): update all non-major dependencies (#4140)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-23 16:38:28 +00:00
Amir Raminfar fb359cb9d5 fix: if roles changed after a restart of Dozzle, then it resets user session (#4139) 2025-09-23 16:03:07 +00:00
Amir Raminfar 395d8641ad fix: fixes dates and labels being incorrect size when changed (#4138) 2025-09-22 23:47:42 +00:00
renovate[bot] 84ec399e50 fix(deps): update dependency unplugin-vue-components to ^29.1.0 (#4134)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-22 01:15:56 +00:00
Amir Raminfar 2b0c9124d7 chore: adds all and throws warning if role does not exist 2025-09-21 16:43:19 -07:00
Amir Raminfar e75b4d03bc chore: quick clean up 2025-09-21 14:57:06 -07:00
Dmitry Mazurov 5f24657b41 feat: Add user roles (#4133) 2025-09-21 14:51:16 -07:00
renovate[bot] 54799b420c fix(deps): update all non-major dependencies (#4132)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-20 14:49:34 +00:00
renovate[bot] 23ad32d05c fix(deps): update all non-major dependencies (#4128)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-17 23:45:55 +00:00
严浩 6e4a5238d3 fix: add full width to side menu (#4127)
Co-authored-by: 严浩 <h_mini2024@oo1.dev>
2025-09-17 10:17:29 -07:00
renovate[bot] a1cedd4b16 fix(deps): update all non-major dependencies (#4126)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-17 15:08:02 +00:00
renovate[bot] 84edb6bdab chore(deps): update actions/setup-node action to v5 (#4106)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 13:29:38 -07:00
Amir Raminfar a8c673544f chore: adds pnpm setup (#4125) 2025-09-16 12:54:27 -07:00
renovate[bot] b2c3363659 fix(deps): update all non-major dependencies (#4124)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-16 19:42:00 +00:00
renovate[bot] 3f376a0bce fix(deps): update all non-major dependencies (#4121)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 01:27:19 +00:00
renovate[bot] 16fc75b563 chore(deps): update dependency jsdom to v27 (#4120)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-14 11:00:42 -07:00
renovate[bot] 7ed0ec498d fix(deps): update all non-major dependencies (#4119)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-14 17:50:55 +00:00
Amir Raminfar 11d12d1f6a Revert "feat: enables the archiving of dozzle binaries"
This reverts commit ed5090a9ec.
2025-09-10 16:49:01 -07:00
Amir Raminfar e88c412d62 Revert "more changes"
This reverts commit da7730cffd.
2025-09-10 16:48:59 -07:00
Amir Raminfar 96a02bf213 Revert "chore: release v8.13.13"
This reverts commit 10597521de.
2025-09-10 16:48:58 -07:00
Amir Raminfar 35b2c919cc Revert "chore: fix deploy"
This reverts commit 1168c03fc9.
2025-09-10 16:48:56 -07:00
Amir Raminfar 85396d7631 Revert "chore: release v8.13.14"
This reverts commit a3a6a8ec5c.
2025-09-10 16:48:49 -07:00
Amir Raminfar a3a6a8ec5c chore: release v8.13.14 2025-09-10 14:32:07 -07:00
Amir Raminfar 1168c03fc9 chore: fix deploy 2025-09-10 13:07:33 -07:00
Amir Raminfar 10597521de chore: release v8.13.13 2025-09-10 12:43:13 -07:00
Amir Raminfar da7730cffd more changes 2025-09-10 12:42:26 -07:00
Amir Raminfar ed5090a9ec feat: enables the archiving of dozzle binaries 2025-09-10 12:05:35 -07:00
dependabot[bot] c97a9cc34f chore(deps): bump @duckdb/duckdb-wasm from 1.29.2 to 1.30.0 (#4114) 2025-09-09 07:55:38 -07:00
renovate[bot] 31d7fe5c67 fix(deps): update all non-major dependencies (#4113)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-09 01:40:38 +00:00
renovate[bot] ed8ce856ba fix(deps): update dependency entities to v7 (#4112)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 15:02:09 -07:00
renovate[bot] 940ba30633 fix(deps): update all non-major dependencies (#4111)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 01:19:39 +00:00
bakerboy448 a4b0b05978 docs: Fix typo in container groups (#4109) 2025-09-07 08:50:58 -07:00
renovate[bot] 146ff180e0 fix(deps): update all non-major dependencies (#4107)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-05 19:16:15 +00:00
renovate[bot] db4f11c68e chore(deps): update actions/setup-go action to v6 (#4105) 2025-09-04 10:39:53 -07:00
renovate[bot] e702b62324 fix(deps): update all non-major dependencies (#4104)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-04 17:31:38 +00:00
Amir Raminfar cf97100b71 chore: updates npm modules to latest (#4103) 2025-09-04 10:25:52 -07:00
Amir Raminfar b5cc4bd514 chore: release v8.13.12 2025-09-03 09:42:45 -07:00
Akash R f6e3d487ac fix: panic - waitGroup counter goes negative (#4101) 2025-09-03 09:42:08 -07:00
renovate[bot] a2cc759538 fix(deps): update dependency daisyui to v5.1.6 (#4100)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-03 13:44:54 +00:00
renovate[bot] cfd757e876 fix(deps): update all non-major dependencies (#4097)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-02 15:00:33 +00:00
uponminiature 01c564228b feat: release check mode (#4095) 2025-09-01 14:28:49 -07:00
Amir Raminfar 0b401aacd1 chore: release v8.13.11 2025-08-31 08:41:31 -07:00
Amir Raminfar d7335a8cbe chore: updates d files 2025-08-29 14:33:42 -07:00
uponminiature 6df66dade9 feat: implement ability to disable avatars (#4091)
Co-authored-by: uponminiature <226739065+uponminiature@users.noreply.github.com>
2025-08-29 21:20:03 +00:00
Amir Raminfar ae083e955a chore: release v8.13.10 2025-08-28 11:30:00 -07:00
renovate[bot] 99faac6e8e fix(deps): update all non-major dependencies (#4089)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-28 18:21:32 +00:00
Amir Raminfar 16495a66a0 feat: Changes the fetching of containers from serial to parallel. (#4088) 2025-08-27 20:24:45 -07:00
renovate[bot] 03a2eb2f3b fix(deps): update all non-major dependencies (#4086)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-26 17:29:50 +00:00
renovate[bot] bf18fdd1d8 chore(deps): update actions/upload-pages-artifact action to v4 (#4082)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-26 10:22:42 -07:00
renovate[bot] 912595f781 fix(deps): update module github.com/stretchr/testify to v1.11.0 (#4084)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-25 02:09:15 +00:00
Amir Raminfar 2a5a0b0c55 chore: clean up 2025-08-22 15:56:58 -07:00
renovate[bot] 4e9667e8cf fix(deps): update all non-major dependencies (#4081)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-22 16:20:36 +00:00
renovate[bot] 8130889484 fix(deps): update all non-major dependencies (#4078)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-19 19:36:31 +00:00
Amir Raminfar 013f7c5cde chore: release v8.13.9
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
2025-08-15 10:36:48 -07:00
Amir Raminfar 37c1fd032f chore: fixes env in Dockerfile
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-08-15 08:38:51 -07:00
renovate[bot] cd4f98fd66 fix(deps): update all non-major dependencies (#4075)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-15 15:35:36 +00:00
renovate[bot] b78dab6da1 chore(deps): update amannn/action-semantic-pull-request action to v6 (#4076)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-15 15:34:59 +00:00
Amir Raminfar 7be0515648 chore: uses wg.Go where possible (#4074)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-08-15 15:29:25 +00:00
Amir Raminfar af93918f8c chore: enables json-v2 for go (#4073) 2025-08-15 07:57:29 -07:00
Adedamola 9fa0b5256c docs: multiple namespaces use DOZZLE_NAMESPACE (comma-separated), not DOZZLE_NAMESPACES (#4072)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-08-14 06:17:23 -07:00
renovate[bot] f037b81c92 fix(deps): update all non-major dependencies (#4070)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-13 23:53:33 +00:00
renovate[bot] a97d976c4f chore(deps): update actions/checkout action to v5 (#4071)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
2025-08-12 21:17:09 -07:00
Amir Raminfar 6f87c42d2b fix: fixes context menu to be to the side instead of bottom (#4069)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-08-12 20:29:30 +00:00
renovate[bot] 1064af3175 fix(deps): update all non-major dependencies (#4068)
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-10 15:20:00 +00:00
renovate[bot] 9939b0001e fix(deps): update dependency unplugin-vue-components to v29 (#4064)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-07 10:12:24 -07:00
renovate[bot] a54dd42531 fix(deps): update all non-major dependencies (#4063)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-07 16:21:41 +00:00
Amir Raminfar ba9faa7691 chore: updates generated files
Test / Go Staticcheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
2025-08-05 09:43:25 -07:00
renovate[bot] e044ecd4a1 fix(deps): update dependency unplugin-auto-import to v20 (#4059)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-05 09:32:30 -07:00
renovate[bot] da2d554bee fix(deps): update all non-major dependencies (#4058)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-05 16:23:41 +00:00
renovate[bot] 9f0791a38b fix(deps): update all non-major dependencies (#4057)
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-04 00:58:35 +00:00
renovate[bot] eb20f9e4fa fix(deps): update all non-major dependencies (#4056)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-31 16:04:29 +00:00
renovate[bot] 3ca6495404 fix(deps): update module github.com/docker/docker to v28.3.3+incompatible [security] (#4054)
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-29 20:07:48 +00:00
renovate[bot] 44d30b8451 fix(deps): update all non-major dependencies to ^13.6.0 (#4052)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-28 18:57:55 +00:00
renovate[bot] 1f4410f257 fix(deps): update all non-major dependencies (#4051)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-28 09:44:40 +00:00
renovate[bot] bd3e789d4c fix(deps): update all non-major dependencies (#4050)
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-25 14:33:26 +00:00
Amir Raminfar 5d32e04af8 chore: release v8.13.8
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-07-24 08:58:59 -07:00
Amir Raminfar 26c85a42b5 fix: fixes search z-index (#4048) 2025-07-24 15:58:36 +00:00
renovate[bot] 4aa06778d7 fix(deps): update all non-major dependencies (#4046)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 02:35:52 +00:00
renovate[bot] 5755afcedd fix(deps): update all non-major dependencies (#4044)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-19 15:12:07 +00:00
Amir Raminfar 482c5ae445 chore: release v8.13.7 2025-07-19 07:32:40 -07:00
Zen Voich 8506f6123e feat: group containers by dev.dozzle.group label (#4043) 2025-07-19 06:09:31 -07:00
renovate[bot] 4d5f94c2bb fix(deps): update all non-major dependencies (#4040)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-16 15:05:30 +00:00
Amir Raminfar 90ca59d283 chore: release v8.13.6 2025-07-15 11:22:30 -07:00
Amir Raminfar 84e608eab1 docs: adds dtop to menu 2025-07-15 11:11:09 -07:00
renovate[bot] 1dddbec30a chore(deps): update dependency @types/node to ^24.0.14 (#4039)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-15 17:04:38 +00:00
Cavazza Tommaso 0d9ff396d6 fix: star conflicting with dropdown (#4038) 2025-07-15 09:58:40 -07:00
Amir Raminfar dc9c6b727d docs: add dtop 2025-07-15 08:35:20 -07:00
Amir Raminfar 4bf342da87 chore: release v8.13.5 2025-07-14 09:26:41 -07:00
renovate[bot] 44c02f6676 fix(deps): update all non-major dependencies (#4036)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-13 17:40:48 +00:00
Jérémy Touati f843635060 fix: allows dots inside URLs (not at end) (#4035) 2025-07-13 10:17:13 -07:00
renovate[bot] bcc8506d8c chore(deps): update pnpm to v10.13.1 (#4032)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-09 15:12:40 +00:00
renovate[bot] c106808c1d chore(deps): update all non-major dependencies (#4030)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-09 02:06:52 +00:00
Amir Raminfar f7ff41881a chore: updates deps 2025-07-08 10:46:20 -07:00
renovate[bot] b58648d562 chore(deps): update dependency @apache-arrow/esnext-esm to v21 (#4029) 2025-07-08 07:14:54 -07:00
renovate[bot] 6d829b9cae fix(deps): update all non-major dependencies (#4028)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-07 03:01:17 +00:00
renovate[bot] 9250db2c7e fix(deps): update all non-major dependencies (#4024)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-03 14:51:18 +00:00
Amir Raminfar 0c41da9e65 chore: release v8.13.4
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
2025-06-30 08:39:20 -07:00
Amir Raminfar 0c25f0f82e fix: removes period at the end of URLs (#4016) 2025-06-30 15:25:08 +00:00
Amir Raminfar 500ffa6b5b fix: fixes null values breaking log viewer (#4014) 2025-06-30 08:22:45 -07:00
renovate[bot] 0d61493e29 fix(deps): update all non-major dependencies (#4012)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 02:13:45 +00:00
Amir Raminfar f8db11a4df chore: release v8.13.3
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
2025-06-27 06:49:08 -07:00
Amir Raminfar f596ebc5f3 chore: updates expire 2025-06-27 06:48:26 -07:00
renovate[bot] 46b48b0f1f fix(deps): update all non-major dependencies (#4009)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-27 13:47:25 +00:00
renovate[bot] a80337177f fix(deps): update all non-major dependencies (#4006)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-26 21:56:38 +00:00
renovate[bot] b6cf75020f fix(deps): update all non-major dependencies (#4005)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-25 20:37:47 +00:00
renovate[bot] 135fffabbc chore(deps): update dependency @vitejs/plugin-vue to v6 (#4003)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
2025-06-24 14:45:27 +00:00
renovate[bot] d0fbebea30 fix(deps): update dependency vite to v7 (#4004) 2025-06-24 07:39:37 -07:00
renovate[bot] ae058d3b09 fix(deps): update all non-major dependencies (#4002)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-24 14:28:21 +00:00
renovate[bot] 573afcfd3b chore(deps): update all non-major dependencies (#4001)
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-23 03:12:26 +00:00
Amir Raminfar 5de0e23b9e chore: release v8.13.2
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-06-22 15:41:02 -07:00
Amir Raminfar d5b5b92701 chore: moves log to trace 2025-06-22 08:40:27 -07:00
renovate[bot] def47b560e fix(deps): update all non-major dependencies (#4000)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-22 14:50:08 +00:00
renovate[bot] 3916e3aec8 fix(deps): update all non-major dependencies (#3998)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-20 22:29:41 +00:00
renovate[bot] 743678da33 fix(deps): update module github.com/go-chi/chi/v5 to v5.2.2 [security] (#3997)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-20 17:04:59 +00:00
renovate[bot] 7291448d90 fix(deps): update all non-major dependencies (#3992)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-19 17:15:44 +00:00
renovate[bot] 99d0af05d5 fix(deps): update all non-major dependencies (#3991)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-18 22:08:39 +00:00
Amir Raminfar a87908d957 fix: fixes z-index on some menu and improves scroll progress fading (#3989)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-06-17 16:06:08 -07:00
renovate[bot] 8d38f36745 fix(deps): update all non-major dependencies (#3990)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-17 23:05:26 +00:00
Amir Raminfar 100204022d docs: updates guide
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-06-17 07:24:01 -07:00
Amir Raminfar 35dea10a1c chore: docs
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-06-16 16:23:20 -07:00
Amir Raminfar 176d41aff3 chore: moves log to debug
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-06-16 10:07:31 -07:00
Amir Raminfar cd9a771cab chore: release v8.13.1 2025-06-16 09:18:00 -07:00
Amir Raminfar 633d794539 feat: supports wrn level. see #3984 (#3985) 2025-06-16 16:12:07 +00:00
Amir Raminfar c1955a3301 feat: supports nested arrays and objects (#3979) 2025-06-16 16:07:36 +00:00
renovate[bot] 3a4f0a6e79 chore(deps): update all non-major dependencies (#3982)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-15 21:01:36 +00:00
Amir Raminfar 2c50d870d2 fixes tests
Test / Typecheck (push) Has been cancelled
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
2025-06-14 08:23:00 -07:00
renovate[bot] 229758c1db chore(deps): update all non-major dependencies 2025-06-14 09:13:41 +00:00
blaze6x6 6ee4a8594c chore: adds locale for menu items (#3969) (#3977)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-06-13 07:06:49 -07:00
Amir Raminfar e2b35b3c8c chore: release v8.13.0
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-06-12 13:47:04 -07:00
Amir Raminfar 2d39629de1 fix: fixes incorrect escaping of array strings. fixes #3962 (#3973) 2025-06-12 11:47:34 -07:00
Amir Raminfar 61af442264 chore: updates types (#3974)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-06-12 16:13:09 +00:00
Amir Raminfar 8dde2fe9ac feat: changes log actions to hover just a little and take up less space (#3972) 2025-06-12 09:06:36 -07:00
Amir Raminfar da83f8af11 chore: small drop shadow 2025-06-12 06:51:46 -07:00
Amir Raminfar eadf6c98fe chore: reverts playwright 2025-06-12 06:49:18 -07:00
renovate[bot] 931c85afb6 fix(deps): update all non-major dependencies (#3968)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-12 06:39:28 -07:00
Amir Raminfar a47be8730a chore: adds locale for menu items (#3969)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-06-12 02:59:59 +00:00
Amir Raminfar bcb859509b chore: fixes scroll down 2025-06-11 19:29:32 -07:00
Amir Raminfar d98000b35a feat: adds the ability to show a specific log from the past with a permanent link (#3958)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-06-11 23:23:48 +00:00
Nebulea a8dced5c2b docs: remove superfluous run in docker command (#3967) 2025-06-11 14:09:34 -07:00
Amir Raminfar 3c2a2119d8 fix: adds scoll to menu (#3966)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-06-11 08:33:58 -07:00
renovate[bot] a5ed9b65d5 chore(deps): update all non-major dependencies (#3965)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-10 15:27:08 +00:00
Amir Raminfar e01baf7119 feat: makes shadow on dark mode light (#3961)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-06-09 14:31:22 -07:00
Amir Raminfar 064d5c21f2 feat: adds icon to log out link (#3960) 2025-06-09 12:02:07 -07:00
Amir Raminfar fae248b066 chore: clean up 2025-06-09 10:12:11 -07:00
renovate[bot] f22c51690b fix(deps): update all non-major dependencies (#3959)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-09 02:45:09 +00:00
renovate[bot] 373f9ae025 fix(deps): update all non-major dependencies (#3957)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-08 14:29:34 +00:00
Jungeon d4dab0ad1a feat(i18n): add Korean translation (#3956)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-06-07 21:06:36 -07:00
renovate[bot] 098f032d03 fix(deps): update all non-major dependencies (#3952)
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-06 16:33:45 +00:00
Amir Raminfar 9db559c9ce chore: refactors code by moving loader to a log entry (#3951) 2025-06-06 16:28:57 +00:00
Amir Raminfar 7257e35f1b chore: release v8.12.21
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-06-05 12:19:05 -07:00
GJS 1c20aba5d7 fix: Enhance accuracy of CPU usage metrics in the UI (#3944) 2025-06-05 12:17:24 -07:00
Amir Raminfar 252d865460 fix: fixes really big logs being split in Docker that exceeded 16KB (#3946) 2025-06-04 13:54:13 -07:00
renovate[bot] 02c28fd001 fix(deps): update all non-major dependencies (#3949)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-04 16:02:58 +00:00
renovate[bot] b39f2ae6a8 chore(deps): update all non-major dependencies (#3947)
Test / JavaScript Tests (push) Has been cancelled
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-03 21:14:52 +00:00
GJS 7e2dd1c69e fix: Adjusted <meter> thresholds for CPU and Memory usage display (#3940)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-06-02 05:46:05 -07:00
renovate[bot] 7e5b413c48 chore(deps): update dependency @types/node to ^22.15.29 (#3941)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-02 00:51:36 +00:00
Amir Raminfar ec513adde2 chore: release v8.12.20 2025-06-01 07:46:42 -07:00
GJS 186b4504fc fix: Improve Dutch translation for help support message in nl.yml (#3938)
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
2025-05-30 21:20:51 -07:00
Amir Raminfar 9dc286643e chore: locale for collapse (#3937) 2025-05-30 13:22:24 -07:00
Amir Raminfar 553cfd558d chore: fixes settings page broken detailed page 2025-05-30 12:06:08 -07:00
Amir Raminfar 31174a5214 feat: adds a option to collapse all in side menu (#3930) 2025-05-30 10:20:58 -07:00
renovate[bot] 515849e72a fix(deps): update all non-major dependencies (#3936)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-30 16:23:46 +00:00
Amir Raminfar f442635f55 chore: release v8.12.19 2025-05-30 08:46:22 -07:00
Amir Raminfar a8f2dc17d6 fix: fixes raw message not being there from agents (#3935) 2025-05-30 08:37:32 -07:00
Amir Raminfar 1faa84abc5 chore: fixes settings page and moves donation to top (#3929) 2025-05-29 17:01:13 -07:00
renovate[bot] bdcb1673e1 fix(deps): update all non-major dependencies (#3928)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-29 15:32:04 +00:00
Amir Raminfar 4de23926eb chore: fix github link 2025-05-28 12:52:53 -07:00
Amir Raminfar d4defe680e chore: release v8.12.18 2025-05-28 10:58:12 -07:00
Amir Raminfar 4c2b459fa8 feat: adds support for settings page and adds links to github (#3922) 2025-05-28 16:01:37 +00:00
renovate[bot] aeadae0d30 fix(deps): update all non-major dependencies (#3921)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-28 15:51:59 +00:00
Amir Raminfar f6f1b5207e feat: adds a button to fetch logs when skipping happens (#3920) 2025-05-28 08:47:01 -07:00
renovate[bot] cfe7285bf7 fix(deps): update all non-major dependencies (#3919)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-27 16:29:23 +00:00
renovate[bot] defaba29c2 fix(deps): update dependency vue-i18n to ^11.1.4 (#3914)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-26 01:49:23 +00:00
Amir Raminfar 2aad18d729 fix: fixes logfmt in details (#3912) 2025-05-24 13:47:13 -07:00
Amir Raminfar 9c52d604bc chore: release v8.12.17 2025-05-23 09:01:39 -07:00
renovate[bot] 7f936a9a38 fix(deps): update all non-major dependencies (#3910)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-23 16:00:23 +00:00
Amir Raminfar 0c9065a210 feat: uses colored meters instead of progress bar for homepage (#3908) 2025-05-23 08:54:44 -07:00
Amir Raminfar b70c2da290 fix: fixes search and urls in detail panel (#3907) 2025-05-21 19:59:00 -07:00
Amir Raminfar 804199aa9a fix: fixes url pattern matcher with quotes (#3902) 2025-05-21 08:52:26 -07:00
Amir Raminfar 50c3608a5e chore: release v8.12.16 2025-05-19 06:18:36 -07:00
renovate[bot] 65b68ad907 chore(deps): update dependency @types/node to ^22.15.19 (#3900)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-19 01:18:47 +00:00
Amir Raminfar fd663b9c0e feat: adds option to instantly redirect to new container (#3897) 2025-05-18 18:14:06 -07:00
Amir Raminfar 7a0296fc30 fix: fixes broken relative time in scroller (#3899) 2025-05-18 11:15:03 -07:00
renovate[bot] b8a3325ea7 fix(deps): update all non-major dependencies (#3896)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-18 14:24:36 +00:00
Maciej Wakuła 0ade181ab2 docs: remove leading tcp:// (#3893)
Co-authored-by: Maciej Wakula <adderek.pl@gmail.com>
2025-05-16 13:25:12 -07:00
renovate[bot] 7d60d951ff fix(deps): update all non-major dependencies (#3892)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-16 16:30:50 +00:00
Amir Raminfar 66b0dfe280 chore: adds shell to analytics (#3891) 2025-05-15 15:21:53 +00:00
renovate[bot] c273cb5320 fix(deps): update all non-major dependencies (#3890)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-15 15:18:42 +00:00
renovate[bot] 570ef771dd fix(deps): update all non-major dependencies (#3888)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-14 18:14:33 +00:00
renovate[bot] 07014470e9 chore(deps): update dependency @apache-arrow/esnext-esm to v20 (#3889)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-14 11:09:29 -07:00
Amir Raminfar bd3c2f5c24 chore: typo 2025-05-11 18:39:22 -07:00
Amir Raminfar 6f45faf27a docs: updates guide 2025-05-11 18:36:57 -07:00
StevenMcElligott e94a245440 docs(Agents): Add more explanations about Socket Proxies and agents (#3883) 2025-05-11 11:24:06 -07:00
Fadilah Riczky daa02dd899 feat(i18n): add Indonesian translation (#3884) 2025-05-11 11:23:28 -07:00
renovate[bot] 6a27f9f367 chore(deps): update dependency lint-staged to v16 (#3882)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-10 12:13:04 -07:00
renovate[bot] 9d7b8f26a6 fix(deps): update all non-major dependencies (#3881)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-10 19:09:13 +00:00
Amir Raminfar 82576ae452 chore: release v8.12.15 2025-05-08 07:20:49 -07:00
Amir Raminfar 2ca71a49ba chore: updates esbuild 2025-05-08 07:15:41 -07:00
Amir Raminfar 3abeeec814 chore: updates esbuild 2025-05-08 07:13:40 -07:00
renovate[bot] 5619201886 fix(deps): update all non-major dependencies (#3877)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-07 15:48:15 +00:00
Amir Raminfar 0c06d25ae3 chore: clean up 2025-05-07 08:41:36 -07:00
Amir Raminfar 7a070b90ed fix: improves translations (#3876) 2025-05-07 15:40:24 +00:00
Amir Raminfar 9c70416aec feat: localizes distance time (#3874) 2025-05-07 08:37:17 -07:00
GJS 68ca6d3040 feat: Add Dutch Translation (I18N) Support (#3872) 2025-05-05 12:17:49 -07:00
Amir Raminfar 711745eb1f fix: i18n for no-logs (#3873) 2025-05-05 18:49:42 +00:00
Amir Raminfar c172f6f73f chore: updates docs
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Push container / Push branches and PRs (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
2025-05-05 08:49:13 -07:00
renovate[bot] 7941bd7b6f fix(deps): update module github.com/puzpuzpuz/xsync/v4 to v4.1.0 (#3870)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-05 03:18:53 +00:00
Amir Raminfar 595e39a139 chore: release v8.12.14
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
2025-05-01 16:50:29 -07:00
renovate[bot] 8e074df145 fix(deps): update dependency daisyui to v5.0.35 (#3869)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-01 15:21:07 +00:00
Amir Raminfar 54c6e1ff93 chore: fixes tests
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-05-01 05:52:46 -07:00
Amir Raminfar 10e819fc09 chore: removes width and height 2025-05-01 05:47:37 -07:00
iamdabe c16cfa2c71 updated logo and favicon for docs 2025-05-01 12:23:05 +02:00
iamdabe 56eebf784a Updated SVG logo 2025-05-01 12:13:51 +02:00
Amir Raminfar 5c206b6de3 chore: fix css bug (#3865)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-05-01 03:56:55 +00:00
renovate[bot] 2add15bf42 fix(deps): update all non-major dependencies (#3864)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-01 03:40:02 +00:00
Amir Raminfar 7ff1ccdffb chore: cleans up css (#3862) 2025-05-01 03:29:19 +00:00
Amir Raminfar b961b11499 feat: updates dozzle logos and favicons (#3858) 2025-04-30 15:50:27 -07:00
Amir Raminfar 514c690c7a chore: updates deps
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-30 11:17:05 -07:00
Amir Raminfar e8b3e72265 chore: fixes dates on mobile 2025-04-30 08:41:23 -07:00
Amir Raminfar e8d8b72406 feat: makes container name narrower on mobile (#3853)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-29 06:19:56 -07:00
Amir Raminfar 0c962eec43 chore: centers host title
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-27 14:33:59 -07:00
Amir Raminfar c7f318cc5c chore: release v8.12.13
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
2025-04-27 07:15:44 -07:00
Amir Raminfar 0e8452c146 feat: supports multiple namespaces for k8s (#3848) 2025-04-27 14:02:03 +00:00
renovate[bot] 2d3f6e2b1b fix(deps): update all non-major dependencies (#3850)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-27 13:58:21 +00:00
Amir Raminfar 92665a76de chore: cleans up css styles (#3849)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-26 20:35:14 -07:00
Amir Raminfar 78ace3178b chore: typo fix 2025-04-26 13:58:47 -07:00
renovate[bot] a13b7b0e9d fix(deps): update all non-major dependencies (#3847)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-26 15:10:32 +00:00
Amir Raminfar 4da0486295 chore: little improvements to the login page (#3846)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-25 09:37:38 -07:00
Amir Raminfar 11bc1c7fc2 fix: fixes total limit memory with multiple containers on the same host (#3842) 2025-04-25 06:23:26 -07:00
Amir Raminfar f001b48fa3 chore: release v8.12.12
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-24 15:15:45 -07:00
Amir Raminfar baf9f74fd0 fix: adds html content type to index.html (#3845) 2025-04-24 15:14:37 -07:00
Amir Raminfar f817452fb5 chore: remove @oddbird/css-anchor-positioning 2025-04-24 14:37:50 -07:00
Amir Raminfar 95b27f60cb fix: fixes width login page (#3843) 2025-04-24 20:45:33 +00:00
Amir Raminfar 9daa3459ad chore: release v8.12.11 2025-04-24 12:23:37 -07:00
Amir Raminfar 01343b74bf feat: improves dropdowns for other containers (#3840) 2025-04-24 19:12:53 +00:00
Amir Raminfar 5433939690 fix: fixes searching escaped html and by unescaping before searching (#3838)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-24 11:27:53 -07:00
renovate[bot] effeb5aa52 fix(deps): update all non-major dependencies (#3841)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-24 16:35:20 +00:00
Amir Raminfar 7c59060e1e chore: minor clean up
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-23 14:57:28 -07:00
Amir Raminfar 85c42eed11 chore: makes animation better 2025-04-23 14:38:15 -07:00
Amir Raminfar 433c0eb59e feat: makes homepage more responsive and adds some borders for better visibility (#3837) 2025-04-23 13:52:32 -07:00
Amir Raminfar 044ef7a2b2 chore: updates docs
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-23 08:44:36 -07:00
Amir Raminfar 847c4ee115 chore: clean up
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-22 15:20:38 -07:00
Amir Raminfar 4f912b6fdc fix: fixes icons on mobile for close (#3833)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-22 10:13:07 -07:00
Amir Raminfar 5616c07bad fix: fixes drawer being broken on pwa mode (#3832) 2025-04-22 15:11:04 +00:00
Amir Raminfar 31c51f1939 fix: fixes top padding for drawer in PWA mode (#3829)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-21 22:15:03 +00:00
Amir Raminfar 10705f05a6 fix: fixes potential nil pointer when no container exists (#3828) 2025-04-21 12:55:52 -07:00
Amir Raminfar 3645100402 fix: fixes mobile menu having the wrong margin in pwa (#3826) 2025-04-21 12:13:28 -07:00
Amir Raminfar 757c1385f7 chore: fixes static checker 2025-04-21 11:19:58 -07:00
Amir Raminfar ccfab708d4 feat: uses brotli on the fly for static files and gzip for streaming (#3825) 2025-04-21 18:14:57 +00:00
renovate[bot] 55168fd81a fix(deps): update all non-major dependencies (#3827)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-21 18:04:29 +00:00
renovate[bot] bea1876c7c fix(deps): update dependency @intlify/unplugin-vue-i18n to ^6.0.7 (#3822)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-21 02:34:50 +00:00
Amir Raminfar ae7eee9bca chore: release v8.12.10
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-20 08:02:41 -07:00
Amir Raminfar d98b84e24a fix: fixes sql analytics when error occurs (#3820)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-19 16:44:40 +00:00
Amir Raminfar 18c87256cc fix: fixes alerts on mobile (#3819) 2025-04-19 09:41:57 -07:00
Amir Raminfar e96631930e chore: adds some shadows and makes colors better (#3818)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-19 08:04:59 -07:00
Amir Raminfar 76b1229341 chore: release v8.12.9
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-18 14:24:57 -07:00
renovate[bot] 440f9394d6 fix(deps): update all non-major dependencies (#3817)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-18 20:58:05 +00:00
Amir Raminfar ec792318e6 feat: adds better disabled colors and loader while fetching big logs (#3816) 2025-04-18 20:52:53 +00:00
Amir Raminfar 16db90b660 docs: updates docs to Ed25519
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-17 17:21:33 -07:00
Amir Raminfar eb22752b9a chore: updates to Ed25519 2025-04-17 17:20:19 -07:00
Amir Raminfar 642ab45ef7 chore: fixes tests
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-17 14:24:34 -07:00
renovate[bot] 12fadb9348 fix(deps): update all non-major dependencies (#3813)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-17 14:09:33 -07:00
Amir Raminfar 1cf178e37d feat: more improvements to analytic and gzips logs for better performance (#3814) 2025-04-17 14:09:06 -07:00
Amir Raminfar 061cd0c445 feat: creates betters messaging for analytics mode (#3812) 2025-04-17 17:32:20 +00:00
renovate[bot] e8f796a7d6 fix(deps): update all non-major dependencies (#3811)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-17 15:42:02 +00:00
Amir Raminfar 78f94f1f16 chore: updates d.ts
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
2025-04-15 15:16:35 -07:00
renovate[bot] cc980361fd fix(deps): update module github.com/yuin/goldmark to v1.7.10 (#3809)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 18:55:20 +00:00
Amir Raminfar 736ad8ed5b chore: updates modules (#3808) 2025-04-15 11:28:30 -07:00
renovate[bot] d0dbd2051e fix(deps): update all non-major dependencies (#3807)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-15 15:54:30 +00:00
Amir Raminfar 40a17b4088 chore: release v8.12.8
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-14 08:33:33 -07:00
renovate[bot] b3da3d70a9 chore(deps): update dependency jsdom to ^26.1.0 (#3806)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-14 03:44:40 +00:00
Amir Raminfar 28588fdfd2 chore: fixes center css (#3804) 2025-04-13 15:31:42 -07:00
Amir Raminfar a33f7bf3b2 fix: reverts confirm password 2025-04-13 15:31:28 -07:00
Amir Raminfar febdbff0de fix(docs): updates typos in docs 2025-04-13 15:27:36 -07:00
Amir Raminfar 0fee11e036 fix: fixes broken link on container title (#3803)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-13 12:54:20 -07:00
Amir Raminfar b768cbfc89 docs: updates generate command 2025-04-13 12:33:10 -07:00
Amir Raminfar d43de49b6d fix: fixes collapse color visually hard to see on light theme (#3801) 2025-04-13 09:42:49 -07:00
Amir Raminfar facc137a93 chore: updates duckdb (#3799)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-12 21:05:28 +00:00
renovate[bot] a052f55dc6 fix(deps): update all non-major dependencies (#3798)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-12 21:00:54 +00:00
Amir Raminfar 481f2bd592 chore: format 2025-04-12 13:44:40 -07:00
Amir Raminfar b33ae4fa4e fix: Use /tmp instead of ./ to ensure that user permissions can be mounted correctly. (#3797) 2025-04-12 13:44:21 -07:00
Amir Raminfar f35c472088 chore: release v8.12.7
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
2025-04-11 09:42:13 -07:00
Amir Raminfar 7455a4c53c feat: improves generate with confirm to catch typos (#3794) 2025-04-11 09:16:04 -07:00
renovate[bot] 84101620f8 fix(deps): update dependency vite to v6.2.6 [security] (#3792)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-11 15:53:32 +00:00
renovate[bot] 51695b486e fix(deps): update dependency daisyui to v5.0.18 (#3793)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-11 15:53:16 +00:00
Piyush Dixit 2f84190faa feat: Add support for both .yml and .yaml files and prioritize .yml for users file (#3789)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-10 21:46:03 -07:00
Amir Raminfar ebcedb2a67 chore: cleans up proto tranformation (#3791)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-10 16:31:23 -07:00
Amir Raminfar a514168e45 chore: adds fully loaded to agent proto (#3787)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-09 17:33:44 -07:00
Amir Raminfar 6bb3777a64 chore: release v8.12.6 2025-04-09 15:18:37 -07:00
Amir Raminfar d22e6f9014 chore: removes unused imports 2025-04-09 15:17:42 -07:00
renovate[bot] b779998476 fix(deps): update all non-major dependencies (#3786)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-09 22:07:32 +00:00
Amir Raminfar 70c23b0b1e fix: fixes broken cpu and memory limits on swarm mode. correctly implements limits with agents (#3785) 2025-04-09 15:02:38 -07:00
Amir Raminfar ddff265023 fix: fixes cpu limit being miscalculated (#3782)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-09 08:58:21 -07:00
Amir Raminfar 52edcce8d8 chore: updates documentation
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-08 11:00:34 -07:00
Amir Raminfar 0f75010b35 chore: release v8.12.5 2025-04-08 10:51:06 -07:00
Amir Raminfar 5e228756f3 chore: fixes alpha on background 2025-04-08 10:50:43 -07:00
Amir Raminfar 3775170457 fix: fixes backdrop on fuzzy search (#3776) 2025-04-08 10:49:12 -07:00
renovate[bot] 42edc4c623 fix(deps): update all non-major dependencies (#3773)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-08 16:04:46 +00:00
Amir Raminfar f363b6f366 feat: adds limit to docker containers (#3766) 2025-04-08 15:57:33 +00:00
Amir Raminfar df7e461876 chore: improves logging for duplicate hosts (#3772) 2025-04-08 08:50:02 -07:00
Andrea Titolo f691a22d05 fix: update and improve it locale (#3771) 2025-04-08 07:43:32 -07:00
Amir Raminfar 9c2e03727b fix: fixes mobile menu having the wrong padding (#3765)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-04-07 23:57:50 +00:00
renovate[bot] 2177320259 fix(deps): update all non-major dependencies (#3763)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-07 03:37:35 +00:00
Agneev Mukherjee 2a2f6bc9c6 fix: use light mode bg color as theme color (#3757)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
2025-04-05 09:29:17 -07:00
renovate[bot] e4fab590b9 fix(deps): update all non-major dependencies (#3759)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-05 14:42:59 +00:00
Amir Raminfar cf750b6e39 chore: release v8.12.4 2025-04-03 13:43:49 -07:00
renovate[bot] 8e2ae35ad2 fix(deps): update all non-major dependencies (#3755)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-03 20:42:37 +00:00
Amir Raminfar 8b4f5ea32d fix: trims spaces around remote agents and hosts (#3754) 2025-04-03 10:16:13 -07:00
Amir Raminfar 2fc848254d chore: updates sync package to v4 (#3751) 2025-04-02 20:25:19 +00:00
renovate[bot] fa2dc863ea fix(deps): update module github.com/puzpuzpuz/xsync/v3 to v4 (#3749)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-02 09:52:22 -07:00
renovate[bot] 11a78629ef fix(deps): update all non-major dependencies (#3748)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-02 16:46:45 +00:00
Amir Raminfar 8b058cae07 feat: adds shell support for k8s mode (#3747) 2025-04-02 09:27:57 -07:00
liutianping 8c4fcc6a7a fix(docs): update nginx websocket config example (#3745) 2025-04-01 21:25:03 -07:00
Amir Raminfar 9b5aaad109 fix(docs): updates docs home page 2025-04-01 10:20:14 -07:00
Amir Raminfar aad9287b97 chore: clean up mods 2025-04-01 08:50:32 -07:00
renovate[bot] 1e6b7da245 fix(deps): update all non-major dependencies (#3741)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-01 15:49:35 +00:00
Amir Raminfar b8bc681f7a fix(docs): updates docs for shell 2025-03-31 16:50:22 -07:00
Amir Raminfar c646281e46 chore: release v8.12.3 2025-03-31 16:35:24 -07:00
Amir Raminfar 27478eb652 feat: adds shell support for agents and swarm mode (#3739) 2025-03-31 15:42:26 -07:00
Amir Raminfar 492c5bfd32 chore: release v8.12.2 2025-03-31 13:49:52 -07:00
Amir Raminfar 397f42a223 chore: fixes wording 2025-03-31 13:48:50 -07:00
Amir Raminfar ddcc5ccbe1 fix(docs): updates docs for shell 2025-03-31 13:41:33 -07:00
Amir Raminfar 9ae7c4cbd8 fix: fixes cursor color on shell (#3738) 2025-03-31 20:36:04 +00:00
Amir Raminfar 076f5d0fc9 feat: adds shell behind a flag or env var (#3737) 2025-03-31 13:31:42 -07:00
Peter Dave Hello b8a8781a9b fix: Update and improve zh-tw Traditional Chinese locale (#3736) 2025-03-31 16:49:13 +00:00
renovate[bot] d5abf1868a fix(deps): update all non-major dependencies (#3735)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-31 16:31:09 +00:00
Amir Raminfar d5a2841e02 chore: release v8.12.1 2025-03-31 08:54:29 -07:00
Amir Raminfar 410f5f305c fix: only shows attach and shell for docker (#3731)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
2025-03-30 10:19:35 -07:00
Amir Raminfar 48d9958e0c chore: release v8.12.0 2025-03-30 08:34:01 -07:00
Amir Raminfar ea2132efc9 feat: adds terminal mode to attach or exec shell on a container (#3726) 2025-03-30 08:32:59 -07:00
Amir Raminfar 7a6914c6cf fix: fixes rotate degree on close button for drawer (#3729)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
2025-03-29 18:25:39 -07:00
renovate[bot] da66540952 fix(deps): update all non-major dependencies (#3725)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-29 20:15:55 +00:00
renovate[bot] f863fc1bd3 fix(deps): update all non-major dependencies (#3722)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-26 16:12:57 +00:00
renovate[bot] 91b9c26c3a fix(deps): update dependency vite to v6.2.3 [security] (#3721)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-25 20:16:37 +00:00
renovate[bot] ae483639bb fix(deps): update module github.com/rs/zerolog to v1.34.0 (#3718)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-22 16:18:23 +00:00
renovate[bot] 38383e8924 fix(deps): update all non-major dependencies (#3717)
Push container / Push branches and PRs (push) Waiting to run
Deploy VitePress site to Pages / build (push) Waiting to run
Deploy VitePress site to Pages / Deploy (push) Blocked by required conditions
Test / Typecheck (push) Waiting to run
Test / JavaScript Tests (push) Waiting to run
Test / Go Tests (push) Waiting to run
Test / Go Staticcheck (push) Waiting to run
Test / Integration Tests (push) Waiting to run
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-21 16:22:04 +00:00
renovate[bot] c66bbc520c fix(deps): update all non-major dependencies (#3715)
Push container / Push branches and PRs (push) Waiting to run
Deploy VitePress site to Pages / build (push) Waiting to run
Deploy VitePress site to Pages / Deploy (push) Blocked by required conditions
Test / Typecheck (push) Waiting to run
Test / JavaScript Tests (push) Waiting to run
Test / Go Tests (push) Waiting to run
Test / Go Staticcheck (push) Waiting to run
Test / Integration Tests (push) Waiting to run
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-20 22:35:25 +00:00
renovate[bot] 7c2b7a5dcb fix(deps): update all non-major dependencies (#3714)
Push container / Push branches and PRs (push) Waiting to run
Deploy VitePress site to Pages / build (push) Waiting to run
Deploy VitePress site to Pages / Deploy (push) Blocked by required conditions
Test / Typecheck (push) Waiting to run
Test / JavaScript Tests (push) Waiting to run
Test / Go Tests (push) Waiting to run
Test / Go Staticcheck (push) Waiting to run
Test / Integration Tests (push) Waiting to run
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-20 14:44:50 +00:00
Amir Raminfar 0c96821f15 chore: moves sortables to async import (#3710)
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
2025-03-17 12:22:56 -07:00
renovate[bot] d1a225adc1 chore(deps): update pnpm to v10.6.4 (#3707)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-17 16:35:43 +00:00
Amir Raminfar 3a7efe9ada chore: updates vue plugin (#3709) 2025-03-17 09:30:58 -07:00
Amir Raminfar 3e3bb02b5f chore: fixes code suggestions (#3708) 2025-03-17 09:20:04 -07:00
Amir Raminfar c3ee2da33c fix: fixes theme color in dark and light mode (#3706)
Push container / Push branches and PRs (push) Waiting to run
Deploy VitePress site to Pages / build (push) Waiting to run
Deploy VitePress site to Pages / Deploy (push) Blocked by required conditions
Test / Typecheck (push) Waiting to run
Test / JavaScript Tests (push) Waiting to run
Test / Go Tests (push) Waiting to run
Test / Go Staticcheck (push) Waiting to run
Test / Integration Tests (push) Waiting to run
2025-03-16 23:38:43 +00:00
Amir Raminfar a488e2e7ee chore: updates vue-tsc to alpha (#3705) 2025-03-16 15:57:17 -07:00
renovate[bot] 46fdb88b81 fix(deps): update dependency splitpanes to ^4.0.3 (#3704)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-16 22:36:54 +00:00
renovate[bot] c88429c854 fix(deps): update all non-major dependencies (#3702)
Test / Go Staticcheck (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-15 16:42:00 +00:00
renovate[bot] 9e2d053ec8 fix(deps): update all non-major dependencies (#3700)
Push container / Push branches and PRs (push) Waiting to run
Deploy VitePress site to Pages / build (push) Waiting to run
Deploy VitePress site to Pages / Deploy (push) Blocked by required conditions
Test / JavaScript Tests (push) Waiting to run
Test / Typecheck (push) Waiting to run
Test / Go Tests (push) Waiting to run
Test / Go Staticcheck (push) Waiting to run
Test / Integration Tests (push) Waiting to run
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-13 21:21:01 +00:00
renovate[bot] 97ece4c5ac fix(deps): update all non-major dependencies (#3699)
Test / JavaScript Tests (push) Has been cancelled
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-12 15:54:18 +00:00
Amir Raminfar 4da50bbbd9 chore: release v8.11.9
Push container / Push branches and PRs (push) Has been cancelled
Deploy VitePress site to Pages / build (push) Has been cancelled
Test / Typecheck (push) Has been cancelled
Test / JavaScript Tests (push) Has been cancelled
Test / Go Tests (push) Has been cancelled
Test / Go Staticcheck (push) Has been cancelled
Test / Integration Tests (push) Has been cancelled
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
2025-03-10 09:21:20 -07:00
renovate[bot] ababa789c3 fix(deps): update vueuse monorepo to v13 (major) (#3697)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-10 09:20:45 -07:00
renovate[bot] 60100dfdbb chore(deps): update pnpm to v10.6.2 (#3696)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-10 16:16:18 +00:00
Amir Raminfar 0370759f69 chore: fixes deprecated mobile-web-app 2025-03-10 09:15:51 -07:00
Amir Raminfar 1b01710aa8 chore: adds a new test for everything complex (#3695)
Push container / Push branches and PRs (push) Waiting to run
Deploy VitePress site to Pages / build (push) Waiting to run
Deploy VitePress site to Pages / Deploy (push) Blocked by required conditions
Test / Go Staticcheck (push) Waiting to run
Test / Typecheck (push) Waiting to run
Test / JavaScript Tests (push) Waiting to run
Test / Go Tests (push) Waiting to run
Test / Integration Tests (push) Waiting to run
2025-03-09 19:59:02 +00:00
Amir Raminfar 0ffdd53568 feat: adds inset for left and right (#3694)
Push container / Push branches and PRs (push) Waiting to run
Deploy VitePress site to Pages / build (push) Waiting to run
Deploy VitePress site to Pages / Deploy (push) Blocked by required conditions
Test / JavaScript Tests (push) Waiting to run
Test / Go Tests (push) Waiting to run
Test / Go Staticcheck (push) Waiting to run
Test / Typecheck (push) Waiting to run
Test / Integration Tests (push) Waiting to run
2025-03-09 17:51:19 +00:00
Amir Raminfar d9c9d20fff chore: fixes test cases 2025-03-09 08:35:25 -07:00
Stas Darevskiy e0797d7675 feat: safe area styling for ios pwa (#3693) 2025-03-09 08:32:59 -07:00
Amir Raminfar 25d8ba413a chore: release v8.11.8 2025-03-08 16:42:10 -08:00
Amir Raminfar 80bbb317aa feat: adds pwa in production mode (#3691) 2025-03-08 16:41:21 -08:00
Amir Raminfar c1c61c0ce6 fix: fixes broken mobile splitpanes (#3692) 2025-03-08 23:43:31 +00:00
Amir Raminfar 792a9f1187 fix: fixes analytics tab when mixed logs are found (#3690) 2025-03-08 14:19:51 -08:00
Amir Raminfar a3a6c03f11 chore: unignores splitplanes 2025-03-08 13:54:13 -08:00
Amir Raminfar 46b4fc0994 chore: updates splitpanes (#3689) 2025-03-08 13:52:57 -08:00
Amir Raminfar 4878144592 Revert "chore: npmrc clean up"
This reverts commit 99f203a23e.
2025-03-07 11:11:01 -08:00
Amir Raminfar 99f203a23e chore: npmrc clean up 2025-03-07 11:03:24 -08:00
renovate[bot] 211a442147 fix(deps): update all non-major dependencies (#3682)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-07 19:01:25 +00:00
Amir Raminfar 02aafac2a9 chore: updates mods (#3684) 2025-03-07 18:56:14 +00:00
Amir Raminfar 3b260bef27 chore: ignores splitpanes 2025-03-07 10:55:14 -08:00
renovate[bot] fe0902e62a fix(deps): update dependency vue-i18n to v11.1.2 [security] (#3685)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-07 16:46:48 +00:00
208 changed files with 7956 additions and 4941 deletions
+2 -2
View File
@@ -5,7 +5,7 @@ tmp_dir = "tmp"
[build]
args_bin = []
bin = "./tmp/dozzle"
cmd = "go build -race -o ./tmp/dozzle ."
cmd = "GOEXPERIMENT=jsonv2 go build -race -o ./tmp/dozzle ."
delay = 1000
exclude_dir = [
"assets",
@@ -35,7 +35,7 @@ pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = true
stop_on_error = false
stop_on_error = true
[color]
app = ""
+8 -7
View File
@@ -48,17 +48,18 @@ body:
id: environment
attributes:
label: 💻 Environment
description: Run `docker system info` and paste the output in the field below..
description: Run `docker system info` and paste the output in the field below.
validations:
required: true
- type: textarea
attributes:
label: 📜 Dozzle logs are required for debugging purposes. You may need to enable debug mode. See https://dozzle.dev/guide/debugging.
description: Drag log files or paste the logs into the text input below
validations:
required: true
- type: textarea
attributes:
label: 📸 If applicable, add screenshots to help explain your bug
description: Drag issues into the text input below
validations:
required: false
- type: textarea
attributes:
label: 📜 If applicable, attach your Dozzle logs. You many need to enable debug mode. See https://dozzle.dev/guide/debugging.
description: Drag screenshots into the text input below
validations:
required: false
+22 -16
View File
@@ -8,14 +8,16 @@ jobs:
name: JavaScript Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
name: Install pnpm
- uses: actions/setup-node@v5
name: Install Node
with:
node-version: latest
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v4
- uses: actions/setup-node@v5
with:
node-version: latest
cache: "pnpm"
@@ -29,12 +31,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: 1.22.x
go-version: 1.25.1
check-latest: true
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Install Protoc
uses: arduino/setup-protoc@v3
- name: Install gRPC and Go
@@ -48,14 +50,16 @@ jobs:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
name: Install pnpm
- uses: actions/setup-node@v5
name: Install Node
with:
node-version: latest
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v4
- uses: actions/setup-node@v5
with:
node-version: latest
cache: "pnpm"
@@ -90,20 +94,20 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Login to DockerHub
uses: docker/login-action@v3.3.0
uses: docker/login-action@v3.5.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the Container registry
uses: docker/login-action@v3.3.0
uses: docker/login-action@v3.5.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
@@ -121,7 +125,7 @@ jobs:
echo "${{ secrets.TTL_KEY }}" > shared_key.pem
echo "${{ secrets.TTL_CERT }}" > shared_cert.pem
- name: Build and push
uses: docker/build-push-action@v6.15.0
uses: docker/build-push-action@v6.18.0
with:
push: true
context: .
@@ -137,11 +141,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: pnpm/action-setup@v4
name: Install pnpm
- name: Install Node
uses: actions/setup-node@v4
uses: actions/setup-node@v5
- name: Release to Github
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+5 -5
View File
@@ -13,20 +13,20 @@ jobs:
if: ${{ !github.event.repository.fork && !github.event.pull_request.head.repo.fork && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == 'amir20/dozzle') }}
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Login to DockerHub
uses: docker/login-action@v3.3.0
uses: docker/login-action@v3.5.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the Container registry
uses: docker/login-action@v3.3.0
uses: docker/login-action@v3.5.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
@@ -39,7 +39,7 @@ jobs:
echo "${{ secrets.TTL_KEY }}" > shared_key.pem
echo "${{ secrets.TTL_CERT }}" > shared_cert.pem
- name: Build and push
uses: docker/build-push-action@v6.15.0
uses: docker/build-push-action@v6.18.0
with:
context: .
push: true
+3 -3
View File
@@ -24,12 +24,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0 # Not needed if lastUpdated is not enabled
- uses: pnpm/action-setup@v2
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v5
with:
node-version: latest
cache: pnpm # or pnpm / yarn
@@ -42,7 +42,7 @@ jobs:
pnpm docs:build
touch docs/.vitepress/dist/.nojekyll
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
uses: actions/upload-pages-artifact@v4
with:
path: docs/.vitepress/dist
+1 -1
View File
@@ -15,6 +15,6 @@ jobs:
name: Validate PR title
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v5
- uses: amannn/action-semantic-pull-request@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+22 -16
View File
@@ -11,14 +11,16 @@ jobs:
name: Typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
name: Install pnpm
- uses: actions/setup-node@v5
name: Install Node
with:
node-version: latest
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v4
- uses: actions/setup-node@v5
with:
node-version: latest
cache: "pnpm"
@@ -31,14 +33,16 @@ jobs:
name: JavaScript Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
name: Install pnpm
- uses: actions/setup-node@v5
name: Install Node
with:
node-version: latest
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v4
- uses: actions/setup-node@v5
with:
node-version: latest
cache: "pnpm"
@@ -52,12 +56,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: "1.23"
go-version: "1.25.1"
check-latest: true
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Install Protoc
uses: arduino/setup-protoc@v3
with:
@@ -73,16 +77,16 @@ jobs:
name: Go Staticcheck
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: "1.24"
go-version: "1.25.1"
check-latest: true
- name: Generate dependencies
run: make fake_assets shared_key.pem shared_cert.pem
- name: Stactic checker
uses: dominikh/staticcheck-action@v1.3.1
uses: dominikh/staticcheck-action@v1.4.0
with:
install-go: false
int-test:
@@ -90,14 +94,16 @@ jobs:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
name: Install pnpm
- uses: actions/setup-node@v5
name: Install Node
with:
node-version: latest
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v4
- uses: actions/setup-node@v5
with:
node-version: latest
cache: "pnpm"
+4 -3
View File
@@ -1,5 +1,5 @@
# Build assets
FROM --platform=$BUILDPLATFORM node:23.9.0-alpine AS node
FROM --platform=$BUILDPLATFORM node:23.11.1-alpine AS node
RUN corepack enable
@@ -22,7 +22,7 @@ COPY public ./public
# Build assets
RUN pnpm build
FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder
FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder
# install gRPC dependencies
RUN apk add --no-cache ca-certificates protoc protobuf-dev\
@@ -54,13 +54,14 @@ ARG TARGETOS TARGETARCH
RUN go generate
# Build binary
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -ldflags "-s -w -X github.com/amir20/dozzle/internal/support/cli.Version=$TAG" -o dozzle
RUN GOEXPERIMENT=jsonv2 GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -ldflags "-s -w -X github.com/amir20/dozzle/internal/support/cli.Version=$TAG" -o dozzle
RUN mkdir /data
FROM scratch
COPY --from=builder /data /data
COPY --from=builder /tmp /tmp
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=builder /dozzle/dozzle /dozzle
+2 -2
View File
@@ -43,11 +43,11 @@ int:
docker compose up --build --force-recreate --exit-code-from playwright
shared_key.pem:
@openssl genpkey -algorithm RSA -out shared_key.pem -pkeyopt rsa_keygen_bits:2048
@openssl genpkey -algorithm Ed25519 -out shared_key.pem
shared_cert.pem: shared_key.pem
@openssl req -new -key shared_key.pem -out shared_request.csr -subj "/C=US/ST=California/L=San Francisco/O=Dozzle"
@openssl x509 -req -in shared_request.csr -signkey shared_key.pem -out shared_cert.pem -days 365
@openssl x509 -req -in shared_request.csr -signkey shared_key.pem -out shared_cert.pem -days 1825
@rm shared_request.csr
$(GEN_DIR)/%.pb.go: $(PROTO_DIR)/%.proto
+4 -20
View File
@@ -9,6 +9,9 @@ https://github.com/user-attachments/assets/66a7b4b2-d6c9-4fca-ab04-aef6cd7c0c31
[![Docker Version](https://img.shields.io/docker/v/amir20/dozzle?sort=semver)](https://hub.docker.com/r/amir20/dozzle/)
![Test](https://github.com/amir20/dozzle/workflows/Test/badge.svg)
> [!NOTE]
> If you like Dozzle, check out [`dtop`](https://github.com/amir20/dtop) which is a top like application for monitoring Docker containers. It integrates with Dozzle to allow for linking directly to container logs.
## Features
- Intelligent fuzzy search for container names 🤖
@@ -115,26 +118,7 @@ If you do not want to be tracked at all, see the `--no-analytics` flag below.
## Environment variables and configuration
Dozzle follows the [12-factor](https://12factor.net/) model. Configurations can use the CLI flags or environment variables. The table below outlines all supported options and their respective env vars.
| Flag | Env Variable | Default |
| --------------------- | -------------------------- | -------------- |
| `--addr` | `DOZZLE_ADDR` | `:8080` |
| `--base` | `DOZZLE_BASE` | `/` |
| `--hostname` | `DOZZLE_HOSTNAME` | `""` |
| `--level` | `DOZZLE_LEVEL` | `info` |
| `--auth-provider` | `DOZZLE_AUTH_PROVIDER` | `none` |
| `--auth-header-user` | `DOZZLE_AUTH_HEADER_USER` | `Remote-User` |
| `--auth-header-email` | `DOZZLE_AUTH_HEADER_EMAIL` | `Remote-Email` |
| `--auth-header-name` | `DOZZLE_AUTH_HEADER_NAME` | `Remote-Name` |
| `--enable-actions` | `DOZZLE_ENABLE_ACTIONS` | `false` |
| `--filter` | `DOZZLE_FILTER` | `""` |
| `--no-analytics` | `DOZZLE_NO_ANALYTICS` | `false` |
| `--mode` | `DOZZLE_MODE` | `server` |
| `--remote-host` | `DOZZLE_REMOTE_HOST` | |
| `--remote-agent` | `DOZZLE_REMOTE_AGENT` | |
| `--timeout` | `DOZZLE_TIMEOUT` | `10s` |
| `--namespace` | `DOZZLE_NAMESPACE` | `""` |
Dozzle follows the [12-factor](https://12factor.net/) model. Configurations can use the CLI flags or environment variables. See documentation at [https://dozzle.dev/guide/supported-env-vars](https://dozzle.dev/guide/supported-env-vars) for more details.
## Support
+1
View File
@@ -15,6 +15,7 @@ watchEffect(() => {
if (theme === "auto") {
theme = mode.value;
}
document.querySelector('meta[name="theme-color"]')?.setAttribute("content", theme == "dark" ? "#121212" : "#F5F5F5");
document.documentElement.setAttribute("data-theme", theme);
});
</script>
+15 -1
View File
@@ -31,6 +31,7 @@ declare global {
const createInjectionState: typeof import('@vueuse/core')['createInjectionState']
const createPinia: typeof import('pinia')['createPinia']
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
const createRef: typeof import('@vueuse/core')['createRef']
const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate']
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise']
@@ -52,6 +53,7 @@ declare global {
const getActivePinia: typeof import('pinia')['getActivePinia']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const getCurrentWatcher: typeof import('vue')['getCurrentWatcher']
const getDeep: typeof import('./utils/index')['getDeep']
const globalShowPopup: typeof import('./composable/popup')['globalShowPopup']
const h: typeof import('vue')['h']
@@ -67,7 +69,9 @@ declare global {
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const isShallow: typeof import('vue')['isShallow']
const lightTheme: typeof import('./stores/settings')['lightTheme']
const loadBetween: typeof import('./composable/eventStreams')['loadBetween']
const locale: typeof import('./stores/settings')['locale']
const loggingContextKey: typeof import('./composable/logContext')['loggingContextKey']
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
@@ -150,6 +154,7 @@ declare global {
const toReactive: typeof import('@vueuse/core')['toReactive']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toRelativeTime: typeof import('./utils/index')['toRelativeTime']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
@@ -237,6 +242,7 @@ declare global {
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
const useGroupedStream: typeof import('./composable/eventStreams')['useGroupedStream']
const useHead: typeof import('@vueuse/head')['useHead']
const useHistoricalContainerLog: typeof import('./composable/historicalLogs')['useHistoricalContainerLog']
const useHostStream: typeof import('./composable/eventStreams')['useHostStream']
const useHosts: typeof import('./stores/hosts')['useHosts']
const useI18n: typeof import('vue-i18n')['useI18n']
@@ -326,6 +332,7 @@ declare global {
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
const useTimeAgoIntl: typeof import('@vueuse/core')['useTimeAgoIntl']
const useTimeout: typeof import('@vueuse/core')['useTimeout']
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
@@ -372,7 +379,7 @@ declare global {
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
// @ts-ignore
export type { DrawerWidth } from './composable/drawer'
@@ -421,6 +428,7 @@ declare module 'vue' {
readonly createInjectionState: UnwrapRef<typeof import('@vueuse/core')['createInjectionState']>
readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
readonly createReactiveFn: UnwrapRef<typeof import('@vueuse/core')['createReactiveFn']>
readonly createRef: UnwrapRef<typeof import('@vueuse/core')['createRef']>
readonly createReusableTemplate: UnwrapRef<typeof import('@vueuse/core')['createReusableTemplate']>
readonly createSharedComposable: UnwrapRef<typeof import('@vueuse/core')['createSharedComposable']>
readonly createTemplatePromise: UnwrapRef<typeof import('@vueuse/core')['createTemplatePromise']>
@@ -442,6 +450,7 @@ declare module 'vue' {
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
readonly getCurrentWatcher: UnwrapRef<typeof import('vue')['getCurrentWatcher']>
readonly getDeep: UnwrapRef<typeof import('./utils/index')['getDeep']>
readonly globalShowPopup: UnwrapRef<typeof import('./composable/popup')['globalShowPopup']>
readonly h: UnwrapRef<typeof import('vue')['h']>
@@ -457,7 +466,9 @@ declare module 'vue' {
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
readonly isShallow: UnwrapRef<typeof import('vue')['isShallow']>
readonly lightTheme: UnwrapRef<typeof import('./stores/settings')['lightTheme']>
readonly loadBetween: UnwrapRef<typeof import('./composable/eventStreams')['loadBetween']>
readonly locale: UnwrapRef<typeof import('./stores/settings')['locale']>
readonly loggingContextKey: UnwrapRef<typeof import('./composable/logContext')['loggingContextKey']>
readonly makeDestructurable: UnwrapRef<typeof import('@vueuse/core')['makeDestructurable']>
@@ -540,6 +551,7 @@ declare module 'vue' {
readonly toReactive: UnwrapRef<typeof import('@vueuse/core')['toReactive']>
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
readonly toRelativeTime: UnwrapRef<typeof import('./utils/index')['toRelativeTime']>
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
readonly tryOnBeforeMount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeMount']>
@@ -627,6 +639,7 @@ declare module 'vue' {
readonly useGeolocation: UnwrapRef<typeof import('@vueuse/core')['useGeolocation']>
readonly useGroupedStream: UnwrapRef<typeof import('./composable/eventStreams')['useGroupedStream']>
readonly useHead: UnwrapRef<typeof import('@vueuse/head')['useHead']>
readonly useHistoricalContainerLog: UnwrapRef<typeof import('./composable/historicalLogs')['useHistoricalContainerLog']>
readonly useHostStream: UnwrapRef<typeof import('./composable/eventStreams')['useHostStream']>
readonly useHosts: UnwrapRef<typeof import('./stores/hosts')['useHosts']>
readonly useI18n: UnwrapRef<typeof import('vue-i18n')['useI18n']>
@@ -716,6 +729,7 @@ declare module 'vue' {
readonly useThrottleFn: UnwrapRef<typeof import('@vueuse/core')['useThrottleFn']>
readonly useThrottledRefHistory: UnwrapRef<typeof import('@vueuse/core')['useThrottledRefHistory']>
readonly useTimeAgo: UnwrapRef<typeof import('@vueuse/core')['useTimeAgo']>
readonly useTimeAgoIntl: UnwrapRef<typeof import('@vueuse/core')['useTimeAgoIntl']>
readonly useTimeout: UnwrapRef<typeof import('@vueuse/core')['useTimeout']>
readonly useTimeoutFn: UnwrapRef<typeof import('@vueuse/core')['useTimeoutFn']>
readonly useTimeoutPoll: UnwrapRef<typeof import('@vueuse/core')['useTimeoutPoll']>
+19 -5
View File
@@ -11,7 +11,6 @@ declare module 'vue' {
Announcements: typeof import('./components/Announcements.vue')['default']
'Carbon:caretDown': typeof import('~icons/carbon/caret-down')['default']
'Carbon:circleSolid': typeof import('~icons/carbon/circle-solid')['default']
'Carbon:copyFile': typeof import('~icons/carbon/copy-file')['default']
'Carbon:information': typeof import('~icons/carbon/information')['default']
'Carbon:logoKubernetes': typeof import('~icons/carbon/logo-kubernetes')['default']
'Carbon:macShift': typeof import('~icons/carbon/mac-shift')['default']
@@ -29,6 +28,7 @@ declare module 'vue' {
'Cil:xCircle': typeof import('~icons/cil/x-circle')['default']
ComplexLogItem: typeof import('./components/LogViewer/ComplexLogItem.vue')['default']
ContainerActionsToolbar: typeof import('./components/ContainerViewer/ContainerActionsToolbar.vue')['default']
ContainerDropdown: typeof import('./components/ContainerDropdown.vue')['default']
ContainerEventLogItem: typeof import('./components/LogViewer/ContainerEventLogItem.vue')['default']
ContainerHealth: typeof import('./components/ContainerViewer/ContainerHealth.vue')['default']
ContainerLog: typeof import('./components/ContainerViewer/ContainerLog.vue')['default']
@@ -36,48 +36,59 @@ declare module 'vue' {
ContainerTable: typeof import('./components/ContainerTable.vue')['default']
ContainerTitle: typeof import('./components/ContainerViewer/ContainerTitle.vue')['default']
DateTime: typeof import('./components/common/DateTime.vue')['default']
DistanceTime: typeof import('./components/common/DistanceTime.vue')['default']
Dropdown: typeof import('./components/common/Dropdown.vue')['default']
DropdownMenu: typeof import('./components/common/DropdownMenu.vue')['default']
EventSource: typeof import('./components/LogViewer/EventSource.vue')['default']
FuzzySearchModal: typeof import('./components/FuzzySearchModal.vue')['default']
GroupedLog: typeof import('./components/GroupedViewer/GroupedLog.vue')['default']
GroupMenu: typeof import('./components/GroupMenu.vue')['default']
HistoricalContainerLog: typeof import('./components/ContainerViewer/HistoricalContainerLog.vue')['default']
HostIcon: typeof import('./components/common/HostIcon.vue')['default']
HostList: typeof import('./components/HostList.vue')['default']
HostLog: typeof import('./components/HostViewer/HostLog.vue')['default']
HostMenu: typeof import('./components/HostMenu.vue')['default']
'Ic:sharpKeyboardReturn': typeof import('~icons/ic/sharp-keyboard-return')['default']
IndeterminateBar: typeof import('./components/common/IndeterminateBar.vue')['default']
InfiniteLoader: typeof import('./components/InfiniteLoader.vue')['default']
'Ion:ellipsisVertical': typeof import('~icons/ion/ellipsis-vertical')['default']
KeyShortcut: typeof import('./components/common/KeyShortcut.vue')['default']
LabeledInput: typeof import('./components/common/LabeledInput.vue')['default']
Links: typeof import('./components/Links.vue')['default']
LoadMoreLogItem: typeof import('./components/LogViewer/LoadMoreLogItem.vue')['default']
LogActions: typeof import('./components/LogViewer/LogActions.vue')['default']
LogAnalytics: typeof import('./components/LogViewer/LogAnalytics.vue')['default']
LogDate: typeof import('./components/LogViewer/LogDate.vue')['default']
LogDetails: typeof import('./components/LogViewer/LogDetails.vue')['default']
LogItem: typeof import('./components/LogViewer/LogItem.vue')['default']
LogLevel: typeof import('./components/LogViewer/LogLevel.vue')['default']
LogList: typeof import('./components/LogViewer/LogList.vue')['default']
LogMessageActions: typeof import('./components/LogViewer/LogMessageActions.vue')['default']
LogStd: typeof import('./components/LogViewer/LogStd.vue')['default']
LogViewer: typeof import('./components/LogViewer/LogViewer.vue')['default']
'MaterialSymbols:codeBlocksRounded': typeof import('~icons/material-symbols/code-blocks-rounded')['default']
'MaterialSymbols:contentCopy': typeof import('~icons/material-symbols/content-copy')['default']
'MaterialSymbols:eyeTracking': typeof import('~icons/material-symbols/eye-tracking')['default']
'MaterialSymbols:link': typeof import('~icons/material-symbols/link')['default']
'MaterialSymbols:logout': typeof import('~icons/material-symbols/logout')['default']
'MaterialSymbols:person': typeof import('~icons/material-symbols/person')['default']
'MaterialSymbols:terminal': typeof import('~icons/material-symbols/terminal')['default']
'MaterialSymbolsLight:collapseAll': typeof import('~icons/material-symbols-light/collapse-all')['default']
'Mdi:account': typeof import('~icons/mdi/account')['default']
'Mdi:announcement': typeof import('~icons/mdi/announcement')['default']
'Mdi:arrowUp': typeof import('~icons/mdi/arrow-up')['default']
'Mdi:beer': typeof import('~icons/mdi/beer')['default']
'Mdi:check': typeof import('~icons/mdi/check')['default']
'Mdi:chevronDoubleDown': typeof import('~icons/mdi/chevron-double-down')['default']
'Mdi:chevronLeft': typeof import('~icons/mdi/chevron-left')['default']
'Mdi:chevronRight': typeof import('~icons/mdi/chevron-right')['default']
'Mdi:close': typeof import('~icons/mdi/close')['default']
'Mdi:cog': typeof import('~icons/mdi/cog')['default']
'Mdi:contentCopy': typeof import('~icons/mdi/content-copy')['default']
'Mdi:docker': typeof import('~icons/mdi/docker')['default']
'Mdi:gauge': typeof import('~icons/mdi/gauge')['default']
'Mdi:github': typeof import('~icons/mdi/github')['default']
'Mdi:hamburgerMenu': typeof import('~icons/mdi/hamburger-menu')['default']
'Mdi:hexagonMultiple': typeof import('~icons/mdi/hexagon-multiple')['default']
'Mdi:key': typeof import('~icons/mdi/key')['default']
'Mdi:keyboardEsc': typeof import('~icons/mdi/keyboard-esc')['default']
'Mdi:lightningBolt': typeof import('~icons/mdi/lightning-bolt')['default']
'Mdi:magnify': typeof import('~icons/mdi/magnify')['default']
'Mdi:satelliteVariant': typeof import('~icons/mdi/satellite-variant')['default']
MobileMenu: typeof import('./components/common/MobileMenu.vue')['default']
@@ -103,6 +114,8 @@ declare module 'vue' {
'Ph:stackSimple': typeof import('~icons/ph/stack-simple')['default']
Popup: typeof import('./components/Popup.vue')['default']
RandomColorTag: typeof import('./components/LogViewer/RandomColorTag.vue')['default']
RelativeTime: typeof import('./components/common/RelativeTime.vue')['default']
'Ri:terminalWindowFill': typeof import('~icons/ri/terminal-window-fill')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
ScrollableView: typeof import('./components/ScrollableView.vue')['default']
@@ -121,6 +134,7 @@ declare module 'vue' {
StatSparkline: typeof import('./components/LogViewer/StatSparkline.vue')['default']
SwarmMenu: typeof import('./components/SwarmMenu.vue')['default']
Tag: typeof import('./components/common/Tag.vue')['default']
Terminal: typeof import('./components/Terminal.vue')['default']
TimedButton: typeof import('./components/common/TimedButton.vue')['default']
ToastModal: typeof import('./components/common/ToastModal.vue')['default']
Toggle: typeof import('./components/common/Toggle.vue')['default']
+8 -4
View File
@@ -1,5 +1,9 @@
<template>
<Dropdown class="dropdown-end" @closed="releaseSeen = mostRecent?.tag ?? config.version">
<Dropdown
class="dropdown-end"
@click="config.releaseCheckMode === 'manual' && fetchReleases()"
@closed="releaseSeen = mostRecent?.tag ?? config.version"
>
<template #trigger>
<mdi:announcement class="size-6 -rotate-12" />
<template v-if="announcements.length > 0 && releaseSeen != mostRecent?.tag">
@@ -22,7 +26,7 @@
>
{{ release.name }}
</a>
<span class="ml-1 text-xs"><distance-time :date="release.createdAt" /></span>
<span class="ml-1 text-xs"><RelativeTime :date="release.createdAt" /></span>
</div>
<div class="text-base-content/80 text-sm">
{{ release.body }}
@@ -39,7 +43,7 @@
>
{{ release.name }}
</a>
<span class="ml-1 text-xs"><distance-time :date="release.createdAt" /></span>
<span class="ml-1 text-xs"><RelativeTime :date="release.createdAt" /></span>
<Tag class="bg-red ml-auto px-1 py-1 text-xs" v-if="release.latest">
{{ $t("releases.latest") }}
</Tag>
@@ -63,7 +67,7 @@
<script setup lang="ts">
import { useAnnouncements } from "@/stores/announcements";
const { announcements, mostRecent } = useAnnouncements();
const { announcements, mostRecent, fetchReleases } = useAnnouncements();
const { t } = useI18n();
const releaseSeen = useProfileStorage("releaseSeen", config.version);
+24
View File
@@ -0,0 +1,24 @@
<template>
<div class="dropdown">
<button tabindex="0" role="button" class="btn btn-xs md:btn-sm"><slot /> <carbon:caret-down /></button>
<ul tabindex="0" class="dropdown-content menu rounded-box bg-base-100 border-base-content/20 border shadow-sm">
<li v-for="other in containers">
<router-link :to="{ name: '/container/[id]', params: { id: other.id } }" class="text-nowrap">
<div
class="status data-[state=exited]:status-error data-[state=running]:status-success"
:data-state="other.state"
></div>
{{ other.name }}
<div v-if="other.state === 'running'">running</div>
<RelativeTime :date="other.finishedAt" class="text-base-content/70 text-xs" v-else />
</router-link>
</li>
</ul>
</div>
</template>
<script lang="ts" setup>
import { type Container } from "@/models/Container";
const { containers } = defineProps<{
containers: Container[];
}>();
</script>
+28 -24
View File
@@ -1,28 +1,32 @@
<template>
<div>
<span class="font-light capitalize"> STATE </span>
<span class="font-semibold uppercase"> {{ container.state }} </span>
</div>
<div v-if="container.startedAt.getFullYear() > 0">
<span class="font-light capitalize"> STARTED </span>
<span class="font-semibold">
<DistanceTime :date="container.startedAt" strict />
</span>
</div>
<div v-if="container.state != 'running' && container.finishedAt.getFullYear() > 0">
<span class="font-light capitalize"> FINISHED </span>
<span class="font-semibold">
<DistanceTime :date="container.finishedAt" strict />
</span>
</div>
<div v-if="container.state == 'running'">
<span class="font-light capitalize"> Load </span>
<span class="font-semibold"> {{ container.stat.cpu.toFixed(2) }}% </span>
</div>
<div v-if="container.state == 'running'">
<span class="font-light capitalize"> MEM </span>
<span class="font-semibold"> {{ formatBytes(container.stat.memoryUsage) }} </span>
</div>
<table class="w-full border-separate border-spacing-x-1">
<tbody>
<tr>
<th class="text-right font-light capitalize">STATE</th>
<td class="font-semibold uppercase">{{ container.state }}</td>
</tr>
<tr v-if="container.startedAt.getFullYear() > 0">
<th class="text-right font-light capitalize">STARTED</th>
<td class="font-semibold">
<RelativeTime :date="container.startedAt" />
</td>
</tr>
<tr v-if="container.state != 'running' && container.finishedAt.getFullYear() > 0">
<th class="text-right font-light capitalize">FINISHED</th>
<td class="font-semibold">
<RelativeTime :date="container.finishedAt" />
</td>
</tr>
<tr v-if="container.state == 'running'">
<th class="text-right font-light capitalize">Load</th>
<td class="font-semibold">{{ container.stat.cpu.toFixed(2) }}%</td>
</tr>
<tr v-if="container.state == 'running'">
<th class="text-right font-light capitalize">MEM</th>
<td class="font-semibold">{{ formatBytes(container.stat.memoryUsage) }}</td>
</tr>
</tbody>
</table>
</template>
<script lang="ts" setup>
+133 -104
View File
@@ -1,112 +1,120 @@
!
<template>
<div class="flex flex-row">
<div v-if="Object.keys(hosts).length > 1" class="flex-1">
<div role="tablist" class="tabs-boxed tabs block" v-if="Object.keys(hosts).length < 4">
<input
type="radio"
name="host"
role="tab"
class="tab rounded-sm!"
aria-label="Show All"
v-model="selectedHost"
:value="null"
/>
<input
type="radio"
name="host"
role="tab"
class="tab rounded-sm!"
:aria-label="host.name"
v-for="host in hosts"
:value="host.id"
:key="host.id"
<div class="flex flex-col gap-4">
<div class="flex flex-row">
<div v-if="Object.keys(hosts).length > 1" class="flex-1">
<div role="tablist" class="tabs-boxed tabs block" v-if="Object.keys(hosts).length < 4">
<input
type="radio"
name="host"
role="tab"
class="tab rounded-sm!"
aria-label="Show All"
v-model="selectedHost"
:value="null"
/>
<input
type="radio"
name="host"
role="tab"
class="tab rounded-sm!"
:aria-label="host.name"
v-for="host in hosts"
:value="host.id"
:key="host.id"
v-model="selectedHost"
/>
</div>
<DropdownMenu
class="btn-sm"
v-model="selectedHost"
:options="[
{ label: 'Show All', value: null },
...Object.values(hosts).map((host) => ({ label: host.name, value: host.id })),
]"
v-else
/>
</div>
<div class="flex-1 text-right" v-show="containers.length > pageSizes[0]">
{{ $t("label.per-page") }}
<DropdownMenu
class="btn-sm"
v-model="selectedHost"
:options="[
{ label: 'Show All', value: null },
...Object.values(hosts).map((host) => ({ label: host.name, value: host.id })),
]"
v-else
/>
<DropdownMenu
class="dropdown-left btn-xs md:btn-sm"
v-model="perPage"
:options="pageSizes.map((i) => ({ label: i.toLocaleString(), value: i }))"
/>
</div>
</div>
<div class="flex-1 text-right" v-show="containers.length > pageSizes[0]">
{{ $t("label.per-page") }}
<DropdownMenu
class="dropdown-left btn-xs md:btn-sm"
v-model="perPage"
:options="pageSizes.map((i) => ({ label: i.toLocaleString(), value: i }))"
/>
<div class="rounded-box border-base-content/10 overflow-x-auto border">
<table class="table-md md:table-lg table-zebra table">
<thead>
<tr :data-direction="direction > 0 ? 'asc' : 'desc'">
<th
v-for="(value, key) in fields"
:key="key"
@click.prevent="sort(key)"
:class="{ 'selected-sort': key === sortField }"
v-show="isVisible(key)"
>
<a class="inline-flex cursor-pointer gap-2 text-sm uppercase">
<span>{{ $t(value.label) }}</span>
<span class="h-4" data-icon>
<mdi:arrow-up />
</span>
</a>
</th>
</tr>
</thead>
<tbody class="bg-base-300/30">
<tr v-for="container in paginated" :key="container.id" class="hover:bg-base-100/80!">
<td v-if="isVisible('name')">
<router-link :to="{ name: '/container/[id]', params: { id: container.id } }" :title="container.name">
{{ container.name }}
</router-link>
</td>
<td v-if="isVisible('host')">{{ container.hostLabel }}</td>
<td v-if="isVisible('state')">{{ container.state }}</td>
<td v-if="isVisible('created')">
<RelativeTime :date="container.created" />
</td>
<td v-if="isVisible('cpu')">
<div class="flex flex-row items-center gap-1">
<progress
class="progress h-3 w-full rounded-3xl"
:class="getProgressColorClass(containerAverageCpu(container))"
:value="containerAverageCpu(container)"
max="100"
></progress>
<span class="w-8 text-right text-sm"> {{ containerAverageCpu(container).toFixed(0) }}% </span>
</div>
</td>
<td v-if="isVisible('mem')">
<div class="flex flex-row items-center gap-1">
<progress
class="progress h-3 w-full rounded-3xl"
:class="getProgressColorClass(container.movingAverage.memory)"
:value="container.movingAverage.memory"
max="100"
></progress>
<span class="w-8 text-right text-sm"> {{ container.movingAverage.memory.toFixed(0) }}% </span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="p-4 text-center">
<nav class="join" v-if="isPaginated">
<input
class="btn btn-square join-item"
type="radio"
v-model="currentPage"
:aria-label="`${i}`"
:value="i"
v-for="i in totalPages"
/>
</nav>
</div>
</div>
<table class="table-lg bg-base-200 table">
<thead>
<tr :data-direction="direction > 0 ? 'asc' : 'desc'">
<th
v-for="(value, key) in fields"
:key="key"
@click.prevent="sort(key)"
:class="{ 'selected-sort': key === sortField }"
v-show="isVisible(key)"
>
<a class="inline-flex cursor-pointer gap-2 text-sm uppercase">
<span>{{ $t(value.label) }}</span>
<span class="h-4" data-icon>
<mdi:arrow-up />
</span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr v-for="container in paginated" :key="container.id">
<td v-if="isVisible('name')">
<router-link :to="{ name: '/container/[id]', params: { id: container.id } }" :title="container.name">
{{ container.name }}
</router-link>
</td>
<td v-if="isVisible('host')">{{ container.hostLabel }}</td>
<td v-if="isVisible('state')">{{ container.state }}</td>
<td v-if="isVisible('created')">
<distance-time :date="container.created" strict :suffix="false"></distance-time>
</td>
<td v-if="isVisible('cpu')">
<div class="flex flex-row items-center gap-1">
<progress
class="progress progress-primary"
:value="Math.min(container.movingAverage.cpu, 100)"
:max="100"
></progress>
<span class="text-sm">{{ container.movingAverage.cpu.toFixed(0) }}%</span>
</div>
</td>
<td v-if="isVisible('mem')">
<div class="flex flex-row items-center gap-1">
<progress class="progress progress-primary" :value="container.movingAverage.memory" max="100"></progress>
<span class="text-sm">{{ container.movingAverage.memory.toFixed(0) }}%</span>
</div>
</td>
</tr>
</tbody>
</table>
<div class="p-4 text-center">
<nav class="join" v-if="isPaginated">
<input
class="btn btn-square join-item"
type="radio"
v-model="currentPage"
:aria-label="`${i}`"
:value="i"
v-for="i in totalPages"
/>
</nav>
</div>
</template>
@@ -194,10 +202,31 @@ function sort(field: keys) {
function isVisible(field: keys) {
return fields[field].mobileVisible || !isMobile.value;
}
function getContainerCores(container: Container): number {
if (container.cpuLimit && container.cpuLimit > 0) {
return container.cpuLimit;
}
const hostInfo = hosts.value[container.host];
return hostInfo?.nCPU ?? 1;
}
function containerAverageCpu(container: Container): number {
const cores = getContainerCores(container);
const scaledCpu = container.movingAverage.cpu / cores;
return Math.min(scaledCpu, 100);
}
function getProgressColorClass(value: number): string {
if (value <= 70) return "progress-success";
if (value <= 80) return "progress-secondary";
if (value <= 90) return "progress-warning";
return "progress-error";
}
</script>
<style scoped>
@import "@/main.css" reference;
@reference "@/main.css";
[data-icon] {
display: none;
@@ -208,7 +237,7 @@ function isVisible(field: keys) {
}
th {
@apply border-base-100 border-b-2;
@apply border-base-200 border-b-2;
&.selected-sort {
font-weight: bold;
@apply border-primary;
@@ -1,27 +1,31 @@
<template>
<div class="dropdown dropdown-end dropdown-hover">
<div class="dropdown dropdown-end dropdown-hover z-20">
<label tabindex="0" class="btn btn-ghost btn-sm w-10 gap-0.5 px-2">
<carbon:circle-solid class="text-red w-2.5" v-if="streamConfig.stderr" />
<carbon:circle-solid class="text-blue w-2.5" v-if="streamConfig.stdout" />
</label>
<ul tabindex="0" class="menu dropdown-content rounded-box bg-base-200 z-50 w-52 p-1 shadow-sm">
<li>
<a @click.prevent="clear()">
<ul
tabindex="0"
class="menu dropdown-content rounded-box bg-base-200 border-base-content/20 z-50 w-52 border p-1 shadow-sm"
@click="hideMenu"
>
<li v-if="!historical">
<a @click="clear()">
<octicon:trash-24 /> {{ $t("toolbar.clear") }}
<KeyShortcut char="k" :modifiers="['shift', 'meta']" />
</a>
</li>
<li>
<li v-if="enableDownload">
<a :href="downloadUrl" download> <octicon:download-24 /> {{ $t("toolbar.download") }} </a>
</li>
<li>
<a @click.prevent="showSearch = true">
<li v-if="!historical">
<a @click="showSearch = true">
<mdi:magnify /> {{ $t("toolbar.search") }}
<KeyShortcut char="f" />
</a>
</li>
<li v-if="hasComplexLogs">
<a @click.prevent="showDrawer(LogAnalytics, { container }, 'lg')">
<a @click="showDrawer(LogAnalytics, { container }, 'lg')">
<ph:file-sql /> SQL Analytics
<KeyShortcut char="f" :modifiers="['shift', 'meta']" />
</a>
@@ -103,7 +107,7 @@
</li>
<!-- Container Actions (Enabled via config) -->
<template v-if="enableActions">
<template v-if="enableActions && !historical">
<li class="line"></li>
<li>
<button
@@ -134,6 +138,24 @@
</button>
</li>
</template>
<template v-if="enableShell && !historical">
<li class="line"></li>
<li>
<a @click="showDrawer(Terminal, { container, action: 'attach' }, 'lg')">
<ri:terminal-window-fill />
{{ $t("toolbar.attach") }}
<KeyShortcut char="a" :modifiers="['shift', 'meta']" />
</a>
</li>
<li>
<a @click="showDrawer(Terminal, { container, action: 'exec' }, 'lg')">
<material-symbols:terminal />
{{ $t("toolbar.shell") }}
<KeyShortcut char="e" :modifiers="['shift', 'meta']" />
</a>
</li>
</template>
</ul>
</div>
</template>
@@ -142,13 +164,14 @@
import { Container } from "@/models/Container";
import { allLevels } from "@/composable/logContext";
import LogAnalytics from "../LogViewer/LogAnalytics.vue";
import Terminal from "@/components/Terminal.vue";
const { showSearch } = useSearchFilter();
const { enableActions } = config;
const { enableActions, enableShell, enableDownload } = config;
const { streamConfig, hasComplexLogs, levels } = useLoggingContext();
const showDrawer = useDrawer();
const { container } = defineProps<{ container: Container }>();
const { container, historical = false } = defineProps<{ container: Container; historical?: boolean }>();
const clear = defineEmit();
const { actionStates, start, stop, restart } = useContainerActions(toRef(() => container));
@@ -160,6 +183,21 @@ onKeyStroke("f", (e) => {
}
}
});
if (enableShell) {
onKeyStroke("a", (e) => {
if ((e.ctrlKey || e.metaKey) && e.shiftKey) {
showDrawer(Terminal, { container, action: "attach" }, "lg");
e.preventDefault();
}
});
onKeyStroke("e", (e) => {
if ((e.ctrlKey || e.metaKey) && e.shiftKey) {
showDrawer(Terminal, { container, action: "exec" }, "lg");
e.preventDefault();
}
});
}
const downloadParams = computed(() =>
Object.entries(toValue(streamConfig))
@@ -185,10 +223,20 @@ const toggleAllLevels = computed({
}
},
});
const hideMenu = (e: MouseEvent) => {
if (e.target instanceof HTMLAnchorElement) {
setTimeout(() => {
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
}, 50);
}
};
</script>
<style scoped>
@import "@/main.css" reference;
@reference "@/main.css";
li.line {
@apply bg-base-content/20 h-px;
@@ -15,7 +15,7 @@ defineProps<{
</script>
<style scoped>
@import "@/main.css" reference;
@reference "@/main.css";
[health="unhealthy"] {
@apply text-red;
}
@@ -1,44 +1,48 @@
<template>
<div class="@container flex flex-1 items-center gap-1.5 truncate md:gap-2">
<div class="@container flex flex-1 items-center gap-1.5 md:gap-2">
<label class="swap swap-rotate size-4">
<input type="checkbox" v-model="pinned" />
<carbon:star-filled class="swap-on text-secondary" />
<carbon:star class="swap-off" />
</label>
<div class="inline-flex items-center text-sm">
<div class="breadcrumbs p-0">
<div class="breadcrumbs overflow-x-visible p-0 font-mono">
<ul>
<li v-if="config.hosts.length > 1" class="font-thin max-md:hidden">
{{ container.hostLabel }}
</li>
<li>
<div v-if="otherContainers.length === 0" class="font-mono">{{ container.name }}</div>
<div class="wrapper" ref="wrapper" v-else>
<button popovertarget="popover-container-list" class="btn btn-xs md:btn-sm anchor font-mono">
{{ container.name }} <carbon:caret-down />
</button>
<ul popover id="popover-container-list" class="dropdown menu rounded-box bg-base-100 tethered shadow-sm">
<li v-for="other in otherContainers">
<router-link :to="{ name: '/container/[id]', params: { id: other.id } }">
<div
class="status data-[state=exited]:status-error data-[state=running]:status-success"
:data-state="other.state"
></div>
<div class="font-mono" v-if="other.isSwarm">{{ other.swarmId }}</div>
<div class="font-mono" v-else>{{ other.name }}</div>
<div v-if="other.state === 'running'">running</div>
<DistanceTime :date="other.created" strict class="text-base-content/70 text-xs" v-else />
</router-link>
</li>
</ul>
<template v-if="otherContainers.length === 0">{{ container.name }}</template>
<div v-else>
<div class="dropdown">
<button tabindex="0" role="button" class="btn btn-xs md:btn-sm">
{{ container.name }} <carbon:caret-down />
</button>
<ul
tabindex="0"
class="dropdown-content menu rounded-box bg-base-100 border-base-content/20 border shadow-sm"
>
<li v-for="other in otherContainers">
<router-link :to="{ name: '/container/[id]', params: { id: other.id } }">
<div
class="status data-[state=exited]:status-error data-[state=running]:status-success"
:data-state="other.state"
></div>
<div v-if="other.isSwarm">{{ other.swarmId }}</div>
<div v-else>{{ other.name }}</div>
<div v-if="other.state === 'running'">running</div>
<RelativeTime :date="other.finishedAt" class="text-base-content/70 text-xs" v-else />
</router-link>
</li>
</ul>
</div>
</div>
</li>
</ul>
</div>
</div>
<ContainerHealth :health="container.health" v-if="container.health" />
<Tag class="hidden font-mono max-md:hidden @3xl:block" size="small">
<Tag class="hidden! font-mono @xl:block!" size="small">
{{ container.image.replace(/@sha.*/, "") }}
</Tag>
</div>
@@ -62,38 +66,10 @@ const store = useContainerStore();
const { containers: allContainers } = storeToRefs(store);
const otherContainers = computed(() =>
[...allContainers.value.filter((c) => c.name === container.name && c.id !== container.id)].sort(
(a, b) => +b.created - +a.created,
),
allContainers.value
.filter((c) => c.name === container.name && c.id !== container.id)
.sort((a, b) => +b.created - +a.created),
);
const wrapper = useTemplateRef("wrapper");
onMounted(async () => {
if (!("anchorName" in document.documentElement.style)) {
// @ts-ignore
const module = await import("@oddbird/css-anchor-positioning/fn");
// @ts-ignore
await module.default([wrapper.value]);
}
});
</script>
<style scoped>
/* https://github.com/oddbird/css-anchor-positioning/issues/282 */
.wrapper {
anchor-scope: --anchor;
}
.anchor {
anchor-name: --anchor;
}
.tethered {
margin: 0;
padding: 0;
position-anchor: --anchor;
position: absolute;
top: anchor(bottom);
left: anchor(left);
}
</style>
<style scoped></style>
@@ -0,0 +1,63 @@
<template>
<ScrollableView :scrollable="scrollable" v-if="container">
<template #header v-if="showTitle">
<div class="@container mx-2 flex items-center gap-2 md:ml-4">
<ContainerTitle :container="container" />
<router-link
:to="{ name: '/container/[id]', params: { id: container.id } }"
class="btn btn-secondary btn-sm"
v-if="container.state === 'running'"
>
<mdi:lightning-bolt />
Live Logs
</router-link>
<ContainerActionsToolbar class="max-md:hidden" :container="container" historical />
<a class="btn btn-circle btn-xs" @click="close()" v-if="closable">
<mdi:close />
</a>
</div>
</template>
<template #default>
<ViewerWithSource
ref="viewer"
:stream-source="useHistoricalContainerLog"
:entity="historicalContainer"
:visible-keys="visibleKeys"
/>
</template>
</ScrollableView>
</template>
<script lang="ts" setup>
import ViewerWithSource from "@/components/LogViewer/ViewerWithSource.vue";
import { HistoricalContainer } from "@/models/Container";
import { ComponentExposed } from "vue-component-type-helpers";
const {
id,
showTitle = false,
scrollable = false,
closable = false,
date,
} = defineProps<{
id: string;
showTitle?: boolean;
scrollable?: boolean;
closable?: boolean;
date: Date;
}>();
const close = defineEmit();
const store = useContainerStore();
const container = store.currentContainer(toRef(() => id));
const historicalContainer = toRef(() => new HistoricalContainer(container.value, date));
const visibleKeys = persistentVisibleKeysForContainer(container);
const viewer = useTemplateRef<ComponentExposed<typeof ViewerWithSource>>("viewer");
provideLoggingContext(
toRef(() => [container.value]),
{ showContainerName: false, showHostname: false, historical: true },
);
</script>
@@ -42,6 +42,8 @@ function createFuzzySearchModal() {
"host",
{},
"running",
0,
0,
[],
),
new Container(
@@ -55,6 +57,8 @@ function createFuzzySearchModal() {
"host",
{},
"running",
0,
0,
[],
),
new Container(
@@ -68,6 +72,8 @@ function createFuzzySearchModal() {
"host",
{},
"running",
0,
0,
[],
),
],
+9 -6
View File
@@ -1,5 +1,5 @@
<template>
<div class="dropdown dropdown-open w-full">
<div class="dropdown dropdown-open w-full shadow-md">
<div class="input input-xl input-primary flex w-full items-center">
<mdi:magnify class="flex size-8" />
<input
@@ -13,8 +13,11 @@
v-model="query"
:placeholder="$t('placeholder.search-containers')"
/>
<form method="dialog">
<button class="swap hover:swap-active">
<form method="dialog" class="flex">
<button v-if="isMobile">
<mdi:close />
</button>
<button v-else class="swap hover:swap-active outline-hidden">
<mdi:keyboard-esc class="swap-off" />
<mdi:close class="swap-on" />
</button>
@@ -30,7 +33,7 @@
<a
class="grid auto-cols-max grid-cols-[min-content_auto] gap-2 py-4"
@click.prevent="selected(result.item)"
:class="index === selectedIndex ? 'menu-focus' : ''"
:class="{ 'menu-focus': index === selectedIndex }"
>
<div :class="{ 'text-primary': result.item.state === 'running' }">
<template v-if="result.item.type === 'container'">
@@ -50,7 +53,7 @@
<span data-name v-html="matchedName(result)"></span>
</div>
<DistanceTime :date="result.item.created" class="text-xs font-light" />
<RelativeTime :date="result.item.created" class="text-xs font-light" />
<span
@click.stop.prevent="addColumn(result.item)"
:title="$t('tooltip.pin-column')"
@@ -214,7 +217,7 @@ function matchedName({ item, matches = [] }: FuseResult<Item>) {
</script>
<style scoped>
@import "@/main.css" reference;
@reference "@/main.css";
:deep(mark) {
@apply bg-transparent text-inherit underline underline-offset-2;
}
@@ -2,11 +2,9 @@
<ScrollableView :scrollable="scrollable" v-if="group.containers.length && ready">
<template #header>
<div class="mx-2 flex items-center gap-2 md:ml-4">
<div class="@container flex flex-1 gap-1.5 truncate md:gap-2">
<div class="inline-flex font-mono text-sm">
<div class="font-semibold">{{ $t("label.container", group.containers.length) }}</div>
</div>
</div>
<ContainerDropdown :containers="group.containers">
{{ $t("label.container", group.containers.length) }}
</ContainerDropdown>
<MultiContainerStat class="ml-auto" :containers="group.containers" />
<MultiContainerActionToolbar class="max-md:hidden" @clear="viewer?.clear()" />
</div>
+1 -1
View File
@@ -51,7 +51,7 @@
:style="`--value: ${Math.floor((weightedStats[host.id].weighted.totalMem / host.memTotal) * 100)};`"
role="progressbar"
>
{{ formatBytes(weightedStats[host.id].weighted.totalMem, 1) }}
{{ formatBytes(weightedStats[host.id].weighted.totalMem, { decimals: 1, short: true }) }}
</div>
</div>
</div>
+22 -4
View File
@@ -24,15 +24,21 @@
<div class="flex-none">
<div class="dropdown dropdown-end dropdown-hover">
<label tabindex="0" class="btn btn-square btn-ghost btn-sm">
<ph:dots-three-vertical-bold />
<ion:ellipsis-vertical />
</label>
<ul tabindex="0" class="menu dropdown-content rounded-box bg-base-200 z-50 w-52 p-1 shadow-sm">
<ul
tabindex="0"
class="menu dropdown-content rounded-box bg-base-200 border-base-content/20 z-50 w-52 border p-1 shadow-sm"
>
<li>
<a class="text-sm capitalize" @click="toggleShowAllContainers()">
<mdi:check class="w-4" v-if="showAllContainers" />
<div v-else class="w-4"></div>
Show all containers
{{ $t("label.show-all-containers") }}
</a>
<a class="text-sm capitalize" @click="collapseAll()">
<material-symbols-light:collapse-all class="w-4" />
{{ $t("label.collapse-all") }}
</a>
</li>
</ul>
@@ -141,6 +147,18 @@ const updateCollapsedGroups = (event: Event, label: string) => {
} else {
collapsedGroups.value.add(label);
}
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
};
const collapseAll = () => {
menuItems.value.forEach(({ label }) => {
collapsedGroups.value.add(label);
});
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
};
const debouncedPinnedContainers = debouncedRef(pinnedContainers, 200);
+1 -1
View File
@@ -2,7 +2,7 @@
<ScrollableView :scrollable="scrollable" v-if="host">
<template #header>
<div class="mx-2 flex items-center gap-2 md:ml-4">
<div class="flex flex-1 gap-1.5 truncate md:gap-2">
<div class="flex flex-1 items-center gap-1.5 truncate md:gap-2">
<ph:computer-tower />
<div class="inline-flex font-mono text-sm">
<div class="font-semibold">{{ host.name }}</div>
-31
View File
@@ -1,31 +0,0 @@
<template>
<div ref="root" class="flex min-h-[1px] justify-center">
<span class="loading loading-bars loading-md text-primary mt-4" v-show="isLoading"></span>
</div>
</template>
<script lang="ts" setup>
const { onLoadMore = () => Promise.resolve(), enabled } = defineProps<{
onLoadMore: () => Promise<void>;
enabled: boolean;
}>();
const isLoading = ref(false);
const root = ref<HTMLElement>();
const observer = new IntersectionObserver(async (entries) => {
if (entries[0].intersectionRatio <= 0) return;
if (onLoadMore && enabled) {
const scrollingParent = root.value?.closest("[data-scrolling]") || document.documentElement;
const previousHeight = scrollingParent.scrollHeight;
isLoading.value = true;
await onLoadMore();
isLoading.value = false;
await nextTick();
scrollingParent.scrollTop += scrollingParent.scrollHeight - previousHeight;
}
});
onMounted(() => observer.observe(root.value!));
onUnmounted(() => observer.disconnect());
</script>
+16 -8
View File
@@ -14,23 +14,31 @@
<dropdown class="dropdown-end" v-if="config.user">
<template #trigger>
<img
class="ring-base-content/60 size-6 max-w-none rounded-full p-px ring-1"
:src="withBase('/api/profile/avatar')"
/>
<template v-if="config.disableAvatars || !config.user.email">
<material-symbols:person class="size-6" />
</template>
<template v-else>
<img
class="ring-base-content/60 size-6 max-w-none rounded-full p-px ring-1"
:src="withBase('/api/profile/avatar')"
/>
</template>
</template>
<template #content>
<div class="p-2">
<div class="font-bold">
{{ config.user.name }}
</div>
<div class="text-sm font-light">
<div v-if="config.user.email" class="text-sm font-light">
{{ config.user.email }}
</div>
</div>
<ul class="menu mt-4 p-0">
<li v-if="config.authProvider === 'simple'">
<button @click.prevent="logout()" class="text-primary p-2">{{ $t("button.logout") }}</button>
<ul v-if="config.authProvider === 'simple'" class="menu mt-4 p-0">
<li>
<button @click.prevent="logout()" class="text-primary p-2">
<material-symbols:logout />
{{ $t("button.logout") }}
</button>
</li>
</ul>
</template>
+49 -12
View File
@@ -1,15 +1,31 @@
<template>
<LogItem :logEntry @click="showDrawer(LogDetails, { entry: logEntry })" class="clickable">
<ul class="space-x-4">
<li v-for="(value, name) in validValues" :key="name" class="inline-flex">
<span class="text-light">{{ name }}=</span><span class="font-bold" v-if="value === null">&lt;null&gt;</span>
<template v-else-if="Array.isArray(value)">
<span class="font-bold" v-html="JSON.stringify(value)"> </span>
</template>
<span class="font-bold" v-html="stripAnsi(value.toString())" v-else></span>
<DefineTemplate v-slot="{ data }">
<ul class="inline-flex flex-wrap space-x-4" @click="preventDefaultOnLinks">
<li v-for="(value, name) in data" :key="name" v-if="isObject(data)">
<span class="key">{{ name }}=</span>
<span class="value" v-if="value === null">&lt;null&gt;</span>
<ReuseTemplate :data="value" v-else-if="isObject(value) || Array.isArray(value)" />
<span v-else class="value" :class="typeof value" v-html="stripAnsi(value.toString())"></span>
</li>
<li class="text-light" v-if="Object.keys(validValues).length === 0">all values are hidden</li>
<li v-else-if="Array.isArray(data)">
<ul class="array inline-flex flex-wrap space-x-1">
<li
v-for="(item, index) in data"
:key="index"
class="after:text-base-content/70 not-last:after:content-[',']"
>
<ReuseTemplate :data="item" v-if="isObject(item) || Array.isArray(item)" />
<span v-else class="value" :class="typeof item" v-html="stripAnsi(item.toString())"></span>
</li>
</ul>
</li>
<li class="key" v-if="Object.keys(validValues).length === 0">all values are hidden</li>
</ul>
</DefineTemplate>
<LogItem :logEntry>
<div @click="containers.length > 0 && showDrawer(LogDetails, { entry: logEntry })" class="cursor-pointer">
<ReuseTemplate :data="validValues" />
</div>
</LogItem>
</template>
<script lang="ts" setup>
@@ -22,16 +38,37 @@ const { logEntry } = defineProps<{
showContainerName?: boolean;
}>();
const { containers } = useLoggingContext();
const [DefineTemplate, ReuseTemplate] = createReusableTemplate();
const validValues = computed(() => {
return Object.fromEntries(Object.entries(logEntry.message).filter(([_, value]) => value !== undefined));
});
const showDrawer = useDrawer();
function preventDefaultOnLinks(event: MouseEvent) {
if (event.target instanceof HTMLAnchorElement && event.target.rel?.includes("external")) {
event.stopImmediatePropagation();
}
}
</script>
<style scoped>
@import "@/main.css" reference;
.text-light {
@apply text-base-content/70;
@reference "@/main.css";
.key {
@apply text-base-content/70 font-light;
}
.value {
@apply text-base-content font-bold;
}
.array {
@apply before:text-base-content/80 after:text-base-content/80 before:content-['['] after:content-[']'];
}
.string {
@apply before:content-['"'] after:content-['"'];
}
</style>
@@ -13,7 +13,7 @@ const { logEntry } = defineProps<{
</script>
<style scoped>
@import "@/main.css" reference;
@reference "@/main.css";
[data-event="container-stopped"] {
@apply text-red;
}
@@ -65,12 +65,29 @@ describe("<ContainerEventSource />", () => {
template: "Test from createLogEventSource",
},
},
{
name: "/container/[id].time.[datetime]",
path: "/container/:id/time/:datetime",
component: {
template: "Test from createLogEventSource",
},
},
],
});
return mount(Component, {
global: {
plugins: [router, createTestingPinia({ createSpy: vi.fn }), createI18n({})],
plugins: [
router,
createTestingPinia({
createSpy: vi.fn,
stubActions: false,
initialState: {
container: { containers: [{ id: "abc", image: "test:v123", host: "localhost" }] },
},
}),
createI18n({}),
],
components: {
LogViewer,
},
@@ -84,6 +101,7 @@ describe("<ContainerEventSource />", () => {
streamConfig: reactive({ stdout: true, stderr: true }),
hasComplexLogs: ref(false),
levels: new Set<Level>(["info"]),
historical: ref(false),
},
},
},
@@ -105,6 +123,8 @@ describe("<ContainerEventSource />", () => {
"localhost",
{},
"created",
0,
0,
[],
),
},
@@ -136,7 +156,7 @@ describe("<ContainerEventSource />", () => {
const wrapper = createLogEventSource();
sources[sourceUrl].emitOpen();
sources[sourceUrl].emitMessage({
data: `{"ts":1560336942459, "m":"This is a message.", "id":1}`,
data: `{"ts":1560336942459, "m":"This is a message.", "id":1, "rm": "This is a message.", "c": "abc"}`,
});
vi.runAllTimers();
@@ -152,39 +172,39 @@ describe("<ContainerEventSource />", () => {
const wrapper = createLogEventSource();
sources[sourceUrl].emitOpen();
sources[sourceUrl].emitMessage({
data: `{"ts":1560336942459, "m":"This is a message.", "id":1}`,
data: `{"ts":1560336942459, "m":"This is a message.", "id":1, "rm": "This is a message.", "c": "abc"}`,
});
vi.runAllTimers();
await nextTick();
expect(wrapper.find("ul.events").html()).toMatchSnapshot();
expect(wrapper.find("ul[data-logs]").html()).toMatchSnapshot();
});
test("should render dates with 12 hour style", async () => {
const wrapper = createLogEventSource({ hourStyle: "12" });
sources[sourceUrl].emitOpen();
sources[sourceUrl].emitMessage({
data: `{"ts":1560336942459, "m":"foo bar", "id":1}`,
data: `{"ts":1560336942459, "m":"foo bar", "id":1, "rm": "foo bar", "c": "abc"}`,
});
vi.runAllTimers();
await nextTick();
expect(wrapper.find("ul.events").html()).toMatchSnapshot();
expect(wrapper.find("ul[data-logs]").html()).toMatchSnapshot();
});
test("should render dates with 24 hour style", async () => {
const wrapper = createLogEventSource({ hourStyle: "24" });
sources[sourceUrl].emitOpen();
sources[sourceUrl].emitMessage({
data: `{"ts":1560336942459, "m":"foo bar", "id":1}`,
data: `{"ts":1560336942459, "m":"foo bar", "id":1, "c": "abc"}`,
});
vi.runAllTimers();
await nextTick();
expect(wrapper.find("ul.events").html()).toMatchSnapshot();
expect(wrapper.find("ul[data-logs]").html()).toMatchSnapshot();
});
});
});
+39 -18
View File
@@ -1,5 +1,4 @@
<template>
<InfiniteLoader :onLoadMore="fetchMore" :enabled="!loadingMore && messages.length > 10" />
<ul class="flex animate-pulse flex-col gap-4 p-4" v-if="loading || (noLogs && waitingForMoreLog)">
<div class="flex flex-row gap-2" v-for="size in sizes">
<div class="bg-base-content/50 h-3 w-40 shrink-0 rounded-full opacity-50"></div>
@@ -7,21 +6,26 @@
</div>
<span class="sr-only">Loading...</span>
</ul>
<div v-else-if="noLogs && !waitingForMoreLog" class="p-4">Container has no logs yet</div>
<div v-else-if="noLogs && !waitingForMoreLog" class="p-4">
{{ $t("label.no-logs") }}
</div>
<slot :messages="messages" v-else></slot>
<IndeterminateBar :color />
<IndeterminateBar :color v-if="!historical" />
</template>
<script lang="ts" setup generic="T">
import { LogStreamSource } from "@/composable/eventStreams";
const route = useRoute();
const { entity, streamSource } = $defineProps<{
streamSource: (t: Ref<T>) => LogStreamSource;
entity: T;
}>();
const { messages, loadOlderLogs, isLoadingMore, opened, loading, error, eventSourceURL } = streamSource($$(entity));
const { loadingMore } = useLoggingContext();
const { historical } = useLoggingContext();
const { messages, opened, loading, error } = streamSource(toRef(() => entity));
const color = computed(() => {
if (error.value) return "error";
if (loading.value) return "secondary";
@@ -37,19 +41,36 @@ defineExpose({
clear: () => (messages.value = []),
});
const fetchMore = async () => {
if (!isLoadingMore.value) {
loadingMore.value = true;
await loadOlderLogs();
loadingMore.value = false;
}
};
if (historical.value && route.query.logId) {
watchOnce(messages, async () => {
await nextTick();
document.getElementById(route.query.logId as string)?.scrollIntoView({ behavior: "instant", block: "center" });
});
}
const shuffle = (items: any[]) => {
return items.sort(() => Math.random() - 0.5);
};
const sizes = computedWithControl(eventSourceURL, () =>
shuffle(["w-3/5", "w-2/3", "w-9/12", "w-1/2", "w-1/3", "w-3/4"]),
const sizes = ref<string[]>([]);
watch(
opened,
(value) => {
if (value) return;
const sizeOptions = [
"w-2/12",
"w-3/12",
"w-4/12",
"w-5/12",
"w-6/12",
"w-7/12",
"w-8/12",
"w-9/12",
"w-10/12",
"w-11/12",
"w-full",
];
sizes.value = Array.from({ length: 18 }, () => sizeOptions[Math.floor(Math.random() * sizeOptions.length)]);
},
{
flush: "sync",
immediate: true,
},
);
</script>
@@ -0,0 +1,31 @@
<template>
<div ref="root" class="flex min-h-[1px] flex-1 content-center justify-center">
<span class="loading loading-bars loading-md text-primary m-2" v-show="isLoading"></span>
</div>
</template>
<script lang="ts" setup>
import { LoadMoreLogEntry } from "@/models/LogEntry";
const { logEntry } = defineProps<{
logEntry: LoadMoreLogEntry;
}>();
const isLoading = ref(false);
const root = ref<HTMLElement>();
useIntersectionObserver(root, async (entries) => {
if (entries[0].intersectionRatio <= 0) return;
if (isLoading.value) return;
const scrollingParent = root.value?.closest("[data-scrolling]") || document.documentElement;
const previousHeight = scrollingParent.scrollHeight;
isLoading.value = true;
await logEntry.loadMore();
isLoading.value = false;
await nextTick();
if (logEntry.rememberScrollPosition) {
scrollingParent.scrollTop += scrollingParent.scrollHeight - previousHeight;
}
});
</script>
<style scoped></style>
+133
View File
@@ -0,0 +1,133 @@
<template>
<div class="dropdown dropdown-right dropdown-hover absolute -left-2 z-10 font-sans" v-show="container">
<router-link
v-if="isSearching"
@click="resetSearch()"
tabindex="0"
class="btn btn-square btn-xs border-base-content/20 bg-base-100 pointer-events-auto! opacity-0 shadow-sm group-hover/entry:opacity-90"
:to="{
name: '/container/[id].time.[datetime]',
params: { id: container.id, datetime: logEntry.date.toISOString() },
query: { logId: logEntry.id },
}"
>
<material-symbols:eye-tracking />
</router-link>
<button
tabindex="0"
class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"
v-else
>
<ion:ellipsis-vertical />
</button>
<ul
tabindex="0"
class="menu dropdown-content rounded-box bg-base-200 border-base-content/20 z-50 w-52 border p-1 text-sm shadow-sm"
@click="hideMenu"
>
<li v-if="isSearching">
<router-link
@click="resetSearch()"
:to="{
name: '/container/[id].time.[datetime]',
params: { id: container.id, datetime: logEntry.date.toISOString() },
query: { logId: logEntry.id },
}"
>
<material-symbols:eye-tracking />
{{ $t("action.see-in-context") }}
</router-link>
</li>
<li v-if="isSupported">
<a @click="copyLogMessage()">
<material-symbols:content-copy />
{{ $t("action.copy-log") }}
</a>
</li>
<li v-if="isSupported">
<a @click="copyPermalink()">
<material-symbols:link />
{{ $t("action.copy-link") }}
</a>
</li>
<li v-if="logEntry instanceof ComplexLogEntry">
<a @click="showDrawer(LogDetails, { entry: logEntry })">
<material-symbols:code-blocks-rounded />
{{ $t("action.show-details") }}
</a>
</li>
</ul>
</div>
</template>
<script lang="ts" setup>
import stripAnsi from "strip-ansi";
import { Container } from "@/models/Container";
import { LogEntry, SimpleLogEntry, ComplexLogEntry, JSONObject } from "@/models/LogEntry";
import LogDetails from "./LogDetails.vue";
const { logEntry, container } = defineProps<{
logEntry: LogEntry<string | JSONObject>;
container: Container;
}>();
const { showToast } = useToast();
const showDrawer = useDrawer();
const router = useRouter();
const { isSearching, resetSearch } = useSearchFilter();
const { copy, isSupported, copied } = useClipboard();
const { t } = useI18n();
async function copyLogMessage() {
if (logEntry instanceof ComplexLogEntry) {
await copy(stripAnsi(logEntry.rawMessage));
} else if (logEntry instanceof SimpleLogEntry) {
await copy(stripAnsi(logEntry.rawMessage));
}
if (copied.value) {
showToast(
{
title: t("toasts.copied.title"),
message: t("toasts.copied.message"),
type: "info",
},
{ expire: 2000 },
);
}
}
async function copyPermalink() {
const url = router.resolve({
name: "/container/[id].time.[datetime]",
params: { id: container.id, datetime: logEntry.date.toISOString() },
query: { logId: logEntry.id },
}).href;
const resolved = new URL(url, window.location.origin);
await copy(resolved.href);
if (copied.value) {
showToast(
{
title: t("toasts.copied.title"),
message: t("toasts.copied.message"),
type: "info",
},
{ expire: 2000 },
);
}
}
function hideMenu(e: MouseEvent) {
if (e.target instanceof HTMLAnchorElement) {
setTimeout(() => {
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
}, 50);
}
}
</script>
+60 -24
View File
@@ -2,7 +2,7 @@
<aside>
<header class="flex items-center gap-4">
<h1 class="text-2xl max-md:hidden">{{ container.name }}</h1>
<h2 class="text-sm"><DistanceTime :date="container.created" /></h2>
<h2 class="text-sm"><RelativeTime :date="container.created" /></h2>
</header>
<div class="mt-8 flex flex-col gap-2">
@@ -11,18 +11,26 @@
<textarea
v-model="query"
class="textarea textarea-primary w-full font-mono text-lg"
:class="{ 'textarea-error': error }"
:class="{ 'textarea-error!': error }"
:disabled="state === 'downloading'"
></textarea>
<div class="label max-h-48 overflow-y-auto pr-2">
<span class="label-text-alt text-error" v-if="error">{{ error }}</span>
<span class="label-text-alt" v-else>
Total {{ results.numRows }} records
<template v-if="results.numRows > pageLimit"> . Showing first {{ page.numRows }}. </template>
<div class="mt-2">
<span class="text-error" v-if="error">{{ error }}</span>
<span v-else-if="state === 'initializing'">{{ $t("analytics.creating_table") }}</span>
<span v-else-if="state === 'downloading'">{{
$t("analytics.downloading", { size: formatBytes(bytes, { decimals: 1 }) })
}}</span>
<span v-else-if="evaluating">{{ $t("analytics.evaluating_query") }}</span>
<span v-else>
{{ $t("analytics.total_records", { count: results.numRows.toLocaleString() }) }}
<template v-if="results.numRows > pageLimit">{{
$t("analytics.showing_first", { count: page.numRows.toLocaleString() })
}}</template>
</span>
</div>
</label>
</section>
<SQLTable :table="page" :loading="evaluating || !isReady" />
<SQLTable :table="page" :loading="evaluating || state !== 'ready'" />
</div>
</aside>
</template>
@@ -30,12 +38,15 @@
<script setup lang="ts">
import { Container } from "@/models/Container";
import { type Table } from "@apache-arrow/esnext-esm";
const { container } = defineProps<{ container: Container }>();
const query = ref("SELECT * FROM logs LIMIT 100");
const error = ref<string | null>(null);
const debouncedQuery = debouncedRef(query, 500);
const evaluating = ref(false);
const pageLimit = 1000;
const state = ref<"downloading" | "ready" | "initializing">("downloading");
const bytes = ref(0);
const url = withBase(
`/api/hosts/${container.host}/containers/${container.id}/logs?stdout=1&stderr=1&everything&jsonOnly`,
@@ -51,27 +62,49 @@ if (!response.ok) {
const { db, conn } = await useDuckDB();
const empty = await conn.query<Record<string, any>>(`SELECT 1 LIMIT 0`);
const { isReady } = useAsyncState(
async () => {
await db.registerFileBuffer("logs.json", new Uint8Array(await response.arrayBuffer()));
onMounted(async () => {
try {
state.value = "downloading";
const reader = response.body?.getReader();
if (!reader) throw new Error("No reader available from stream");
const chunks: Uint8Array[] = [];
bytes.value = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
bytes.value += value.length;
}
const arrayBuffer = new Uint8Array(bytes.value);
let position = 0;
for (const chunk of chunks) {
arrayBuffer.set(chunk, position);
position += chunk.length;
}
await db.registerFileBuffer("logs.json", arrayBuffer);
state.value = "initializing";
await conn.query(
`CREATE TABLE logs AS SELECT unnest(m) FROM read_json('logs.json', ignore_errors = true, format = 'newline_delimited')`,
);
},
undefined,
{
onError: (e) => {
console.error(e);
if (e instanceof Error) {
error.value = e.message;
}
},
},
);
state.value = "ready";
} catch (e) {
console.error(e);
if (e instanceof Error) {
error.value = e.message;
}
}
});
const results = computedAsync(
async () => {
if (isReady.value) {
if (state.value === "ready") {
return await conn.query<Record<string, any>>(debouncedQuery.value);
} else {
return empty;
@@ -89,7 +122,10 @@ const results = computedAsync(
},
);
whenever(evaluating, () => (error.value = null));
whenever(evaluating, () => {
error.value = null;
state.value = "ready";
});
const page = computed(() =>
results.value.numRows > pageLimit ? results.value.slice(0, pageLimit) : results.value,
) as unknown as ComputedRef<Table<Record<string, any>>>;
+1 -1
View File
@@ -1,5 +1,5 @@
<template>
<Tag size="small" class="items-start!">
<Tag class="items-start!">
<DateTime :date="date" class="text-blue whitespace-nowrap" />
</Tag>
</template>
+16 -14
View File
@@ -1,10 +1,10 @@
<template>
<header class="flex items-center gap-4">
<Tag :data-level="entry.level" class="text-white uppercase" v-if="entry.level">{{ entry.level }}</Tag>
<Tag :data-level="entry.level" class="show-unknown text-white uppercase" v-if="entry.level">{{ entry.level }}</Tag>
<h1 class="text-lg max-md:hidden">
<DateTime :date="entry.date" />
</h1>
<h2 class="text-sm"><DistanceTime :date="entry.date" /> on {{ entry.std }}</h2>
<h2 class="text-sm"><RelativeTime :date="entry.date" /> on {{ entry.std }}</h2>
</header>
<div class="mt-8 flex flex-col gap-10">
@@ -29,15 +29,15 @@
<div class="flex gap-2">
Raw JSON
<UseClipboard v-slot="{ copy, copied }" :source="JSON.stringify(entry.unfilteredMessage)">
<UseClipboard v-slot="{ copy, copied }" :source="entry.rawMessage">
<button class="swap outline-hidden" @click="copy()" :class="{ 'hover:swap-active': copied }">
<mdi:check class="swap-on" />
<mdi:content-copy class="swap-off" />
<material-symbols:content-copy class="swap-off" />
</button>
</UseClipboard>
</div>
<div class="bg-base-200 max-h-48 overflow-scroll rounded-sm border border-white/20 p-2">
<pre v-html="syntaxHighlight(entry.unfilteredMessage)"></pre>
<pre v-html="syntaxHighlight(entry.rawMessage)"></pre>
</div>
</section>
<table class="table-pin-rows table table-fixed" v-if="entry instanceof ComplexLogEntry">
@@ -59,7 +59,7 @@
{{ key.join(".") }}
</td>
<td class="truncate max-md:hidden">
<code v-html="JSON.stringify(value)"></code>
<code>{{ JSON.stringify(value) }}</code>
</td>
<td>
<input type="checkbox" class="toggle toggle-primary" :checked="enabled" @change="toggleField(key)" />
@@ -72,7 +72,6 @@
<script setup lang="ts">
import { ComplexLogEntry } from "@/models/LogEntry";
import { useSortable } from "@vueuse/integrations/useSortable";
import { UseClipboard } from "@vueuse/components";
const { entry } = defineProps<{ entry: ComplexLogEntry }>();
@@ -82,6 +81,8 @@ const container = currentContainer(toRef(() => entry.containerID));
const visibleKeys = persistentVisibleKeysForContainer(container);
const { hosts } = useHosts();
const { useSortable } = await import("@vueuse/integrations/useSortable");
function toggleField(key: string[]) {
if (visibleKeys.value.size === 0) {
visibleKeys.value = new Map<string[], boolean>(fields.value.map(({ key }) => [key, true]));
@@ -95,14 +96,15 @@ function toggleField(key: string[]) {
const fields = computed({
get() {
const fieldsWithValue: { key: string[]; value: any; enabled: boolean }[] = [];
const allFields = flattenJSONToMap(entry.unfilteredMessage);
const rawFields = JSON.parse(entry.rawMessage);
const allFields = flattenJSONToMap(rawFields);
if (visibleKeys.value.size === 0) {
for (const [key, value] of allFields) {
fieldsWithValue.push({ key, value, enabled: true });
}
} else {
for (const [key, enabled] of visibleKeys.value) {
const value = getDeep(entry.unfilteredMessage, key);
const value = getDeep(rawFields, key);
fieldsWithValue.push({ key, value, enabled });
}
@@ -140,14 +142,14 @@ const toggleAllFields = computed({
},
});
function syntaxHighlight(json: any) {
json = JSON.stringify(json, null, 2);
function syntaxHighlight(json: string) {
json = JSON.stringify(JSON.parse(json.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")), null, 2);
return json.replace(
/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|\b\d+\b)/g,
function (match: string) {
var cls = "json-number";
if (/^"/.test(match)) {
if (/:$/.test(match)) {
if (match.startsWith('"')) {
if (match.endsWith(":")) {
cls = "json-key";
} else {
cls = "json-string";
@@ -165,7 +167,7 @@ function syntaxHighlight(json: any) {
useSortable(list, fields);
</script>
<style scoped>
@import "@/main.css" reference;
@reference "@/main.css";
.font-mono {
font-family:
ui-monospace,
+20 -3
View File
@@ -1,9 +1,24 @@
<template>
<div class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
<LogActions :logEntry :container />
<LogStd :std="logEntry.std" class="shrink-0 select-none" v-if="showStd" />
<RandomColorTag class="shrink-0 select-none" :value="host.name" v-if="showHostname" />
<RandomColorTag class="shrink-0 select-none" :value="container.name" v-if="showContainerName" truncateRight />
<LogDate :date="logEntry.date" v-if="showTimestamp" class="shrink-0 select-none" />
<div class="flex gap-x-2 gap-y-1 group-[.compact]:gap-y-0 has-[>_*:nth-of-type(2)]:flex-col-reverse md:flex-row!">
<RandomColorTag class="w-30 shrink-0 select-none md:w-40" :value="host.name" v-if="showHostname" />
<RandomColorTag
v-if="showContainerName"
class="w-30 shrink-0 select-none group-[.compact]:flex-1 md:w-40"
:value="container.name"
truncateRight
/>
<LogDate
v-if="showTimestamp"
:date="logEntry.date"
class="shrink-0 select-none"
:class="{ 'bg-secondary': route.query.logId === logEntry.id.toString() }"
/>
</div>
<LogLevel
class="flex select-none"
:level="logEntry.level"
@@ -25,4 +40,6 @@ const { hosts } = useHosts();
const container = currentContainer(toRef(() => logEntry.containerID));
const host = computed(() => hosts.value[container.value.host]);
const route = useRoute();
</script>
+20 -6
View File
@@ -1,17 +1,27 @@
<template>
<div :data-level="level" :data-position="position" class="mt-1.5 size-2.5 flex-none rounded-lg"></div>
<div
:data-level="level"
:data-position="position"
class="mt-1.5 size-2.5 flex-none rounded-lg"
:class="{ showUnknown }"
></div>
</template>
<script lang="ts" setup>
import { Position } from "@/models/LogEntry";
import { Position, Level } from "@/models/LogEntry";
defineProps<{
level?: string;
const {
level,
position,
showUnknown = false,
} = defineProps<{
level?: Level;
position?: Position;
showUnknown?: boolean;
}>();
</script>
<style scoped>
@import "@/main.css" reference;
@reference "@/main.css";
[data-position="start"] {
border-radius: 0.5em 0.5em 0 0;
height: 70%;
@@ -35,7 +45,7 @@ defineProps<{
}
</style>
<style>
@import "@/main.css" reference;
@reference "@/main.css";
[data-level="debug"],
[data-level="trace"] {
@apply !bg-purple;
@@ -53,4 +63,8 @@ defineProps<{
[data-level="warn"] {
@apply !bg-orange;
}
[data-level="unknown"].show-unknown {
@apply !bg-base-300;
}
</style>
+15 -11
View File
@@ -1,10 +1,10 @@
<template>
<ul class="events group pt-4" :class="{ 'disable-wrap': !softWrap, [size]: true, compact }">
<ul class="group pt-4" :class="{ 'disable-wrap': !softWrap, [size]: true, compact }" data-logs>
<li
v-for="item in messages"
ref="list"
:key="item.id"
:data-key="item.id"
:id="item.id.toString()"
:data-time="item.date.getTime()"
class="group/entry"
>
@@ -16,20 +16,17 @@
<script lang="ts" setup>
import { type JSONObject, LogEntry } from "@/models/LogEntry";
const { loading, progress, currentDate } = useScrollContext();
const { progress, currentDate } = useScrollContext();
const { messages } = defineProps<{
messages: LogEntry<string | JSONObject>[];
}>();
watchEffect(() => {
loading.value = messages.length === 0;
});
const { containers } = useLoggingContext();
const list = ref<HTMLElement[]>([]);
let previousDate = new Date();
useIntersectionObserver(
list,
(entries) => {
@@ -40,9 +37,12 @@ useIntersectionObserver(
const time = entry.target.getAttribute("data-time");
if (time) {
const date = new Date(parseInt(time));
if (+date === +previousDate) break;
previousDate = date;
const diff = new Date().getTime() - container.created.getTime();
progress.value = (date.getTime() - container.created.getTime()) / diff;
currentDate.value = date;
break;
}
}
}
@@ -54,8 +54,8 @@ useIntersectionObserver(
);
</script>
<style scoped>
@import "@/main.css" reference;
.events {
@reference "@/main.css";
ul {
font-family:
ui-monospace,
SFMono-Regular,
@@ -67,7 +67,7 @@ useIntersectionObserver(
monospace;
> li {
@apply has-[.clickable]:hover:bg-primary/10 flex px-2 py-1 break-words last:snap-end odd:bg-gray-400/[0.07] has-[.clickable]:cursor-pointer md:px-4;
@apply flex px-2 py-1 break-words last:snap-end odd:bg-gray-400/[0.07] md:px-4;
&:last-child {
scroll-margin-block-end: 5rem;
}
@@ -82,7 +82,7 @@ useIntersectionObserver(
}
&.large {
@apply text-lg;
@apply text-[1em];
}
&.compact {
@@ -99,6 +99,10 @@ useIntersectionObserver(
@apply bg-secondary inline-block rounded-xs;
animation: pops 200ms ease-out;
}
:deep(a[rel~="external"]) {
@apply text-primary underline-offset-4 hover:underline;
}
}
@keyframes pops {
@@ -1,44 +0,0 @@
<template>
<div class="flex gap-2">
<div
class="flex min-w-[0.98rem] items-start justify-end align-bottom hover:cursor-pointer"
v-if="isSupported"
:title="t('log_actions.copy_log')"
>
<span
class="text-primary rounded-sm bg-slate-800/60 px-1.5 py-1 hover:bg-slate-700"
@click.prevent="copyLogMessageToClipBoard()"
>
<carbon:copy-file />
</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { LogEntry, JSONObject } from "@/models/LogEntry";
const { message } = defineProps<{
message: () => string;
logEntry: LogEntry<string | JSONObject>;
}>();
const { showToast } = useToast();
const { copy, isSupported, copied } = useClipboard();
const { t } = useI18n();
async function copyLogMessageToClipBoard() {
await copy(message());
if (copied.value) {
showToast(
{
title: t("toasts.copied.title"),
message: t("toasts.copied.message"),
type: "info",
},
{ expire: 2000 },
);
}
}
</script>
+2 -2
View File
@@ -1,5 +1,5 @@
<template>
<Tag size="small" :std="std" class="items-start!">
<Tag :std="std" class="items-start!">
{{ std }}
</Tag>
</template>
@@ -13,7 +13,7 @@ defineProps<{
</script>
<style scoped>
@import "@/main.css" reference;
@reference "@/main.css";
[std="stdout"] {
@apply text-blue;
}
-24
View File
@@ -13,30 +13,6 @@ const props = defineProps<{
const { messages, visibleKeys } = toRefs(props);
const { filteredPayload } = useVisibleFilter(visibleKeys);
const { debouncedSearchFilter } = useSearchFilter();
const { streamConfig } = useLoggingContext();
const visibleMessages = filteredPayload(messages);
const router = useRouter();
watchEffect(() => {
const query = {} as Record<string, string>;
if (debouncedSearchFilter.value !== "") {
query.search = debouncedSearchFilter.value;
}
if (!streamConfig.value.stderr) {
query.stderr = streamConfig.value.stderr.toString();
}
if (!streamConfig.value.stdout) {
query.stdout = streamConfig.value.stdout.toString();
}
router.push({
query,
replace: true,
});
});
</script>
<style scoped></style>
@@ -1,21 +1,25 @@
<template>
<div class="dropdown dropdown-end dropdown-hover">
<div class="dropdown dropdown-end dropdown-hover z-20">
<label tabindex="0" class="btn btn-ghost btn-sm gap-0.5 px-2">
<carbon:circle-solid class="text-red w-2.5" v-if="streamConfig.stderr" />
<carbon:circle-solid class="text-blue w-2.5" v-if="streamConfig.stdout" />
</label>
<ul tabindex="0" class="menu dropdown-content rounded-box bg-base-200 z-50 w-52 p-1 shadow-sm">
<ul
tabindex="0"
class="menu dropdown-content rounded-box bg-base-200 border-base-content/20 z-50 w-52 border p-1 shadow-sm"
@click="hideMenu"
>
<li>
<a @click.prevent="clear()">
<a @click="clear()">
<octicon:trash-24 /> {{ $t("toolbar.clear") }}
<KeyShortcut char="k" :modifiers="['shift', 'meta']" />
</a>
</li>
<li>
<li v-if="enableDownload">
<a :href="downloadUrl" download> <octicon:download-24 /> {{ $t("toolbar.download") }} </a>
</li>
<li>
<a @click.prevent="showSearch = true">
<a @click="showSearch = true">
<mdi:magnify /> {{ $t("toolbar.search") }}
<KeyShortcut char="f" />
</a>
@@ -84,7 +88,7 @@
<script lang="ts" setup>
const { showSearch } = useSearchFilter();
const { enableDownload } = config;
const clear = defineEmit();
const { streamConfig, showHostname, showContainerName, containers } = useLoggingContext();
@@ -100,10 +104,20 @@ const downloadUrl = computed(() =>
`/api/containers/${containers.value.map((c) => c.host + "~" + c.id).join(",")}/download?${new URLSearchParams(downloadParams.value).toString()}`,
),
);
const hideMenu = (e: MouseEvent) => {
if (e.target instanceof HTMLAnchorElement) {
setTimeout(() => {
if (document.activeElement instanceof HTMLElement) {
document.activeElement.blur();
}
}, 50);
}
};
</script>
<style scoped>
@import "@/main.css" reference;
@reference "@/main.css";
li.line {
@apply bg-base-content/20 h-px;
}
@@ -1,7 +1,17 @@
<template>
<div class="flex gap-4">
<StatMonitor :data="memoryData" label="mem" :stat-value="formatBytes(totalStat.memoryUsage)" />
<StatMonitor :data="cpuData" label="load" :stat-value="Math.max(0, totalStat.cpu).toFixed(2) + '%'" />
<StatMonitor
:data="memoryData"
label="mem"
:stat-value="formatBytes(totalStat.memoryUsage)"
:limit="formatBytes(limits.memory, { short: true, decimals: 1 })"
/>
<StatMonitor
:data="cpuData"
label="load"
:stat-value="Math.max(0, totalStat.cpu).toFixed(2) + '%'"
:limit="roundCPU(limits.cpu) + ' CPU'"
/>
</div>
</template>
@@ -15,6 +25,9 @@ const { containers } = defineProps<{
const totalStat = ref<Stat>({ cpu: 0, memory: 0, memoryUsage: 0 });
const { history, reset } = useSimpleRefHistory(totalStat, { capacity: 300 });
const { hosts } = useHosts();
const roundCPU = (num: number) => (Number.isInteger(num) ? num.toFixed(0) : num.toFixed(1));
watch(
() => containers,
@@ -42,6 +55,43 @@ watch(
{ immediate: true },
);
const limits = computed(() => {
const hostLimits = new Map<string, { cpu: number; memory: number }>();
for (const container of containers) {
if (!hostLimits.has(container.host)) {
hostLimits.set(container.host, {
cpu: 0,
memory: 0,
});
}
if (hostLimits.get(container.host)!.cpu < hosts.value[container.host].nCPU) {
if (container.cpuLimit == 0) {
hostLimits.get(container.host)!.cpu = hosts.value[container.host].nCPU;
} else {
hostLimits.get(container.host)!.cpu = hostLimits.get(container.host)!.cpu + container.cpuLimit;
}
}
if (hostLimits.get(container.host)!.memory < hosts.value[container.host].memTotal) {
if (container.memoryLimit == 0) {
hostLimits.get(container.host)!.memory = hosts.value[container.host].memTotal;
} else {
hostLimits.get(container.host)!.memory = hostLimits.get(container.host)!.memory + container.memoryLimit;
}
}
}
return hostLimits.values().reduce(
(acc, { cpu, memory }) => {
return {
cpu: acc.cpu + cpu,
memory: acc.memory + memory,
};
},
{ cpu: 0, memory: 0 },
);
});
useIntervalFn(() => {
totalStat.value = containers.reduce(
(acc, { stat }) => {
@@ -1,5 +1,5 @@
<template>
<div class="tag grid w-40 overflow-hidden rounded-sm text-center text-sm text-white">
<div class="tag grid overflow-hidden rounded-sm text-center text-sm text-white">
<div class="random-color col-start-1 row-start-1 brightness-75"></div>
<div class="col-start-1 row-start-1 truncate px-2 brightness-100" :class="truncateRight ? '[direction:rtl]' : ''">
{{ value }}
+2 -20
View File
@@ -1,21 +1,14 @@
<template>
<LogItem :logEntry>
<div
class="log-wrapper [word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap"
v-html="linkify(colorize(logEntry.message))"
class="[word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap"
v-html="colorize(logEntry.message)"
></div>
<LogMessageActions
class="absolute -right-1 opacity-0 transition-opacity delay-150 duration-250 group-hover/entry:opacity-100"
:message="() => decodeXML(stripAnsi(logEntry.message))"
:log-entry="logEntry"
/>
</LogItem>
</template>
<script lang="ts" setup>
import { SimpleLogEntry } from "@/models/LogEntry";
import { decodeXML } from "entities";
import AnsiConvertor from "ansi-to-html";
import stripAnsi from "strip-ansi";
const ansiConvertor = new AnsiConvertor({
escapeXML: false,
@@ -28,15 +21,4 @@ defineProps<{
}>();
const colorize = (value: string) => ansiConvertor.toHtml(value);
const urlPattern =
/https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b[-a-zA-Z0-9()@:%_+.~#?&\/=]*/g;
const linkify = (text: string) =>
text.replace(urlPattern, (url) => `<a href="${url}" target="_blank" rel="noopener noreferrer">${url}</a>`);
</script>
<style scoped>
@import "@/main.css" reference;
.log-wrapper :deep(a) {
@apply text-primary underline-offset-4 hover:underline;
}
</style>
@@ -2,16 +2,16 @@
<div class="my-4 flex-1 text-center">
<div class="relative">
<ZigZag class="absolute inset-0 mt-2" />
<span class="bg-base-200 relative px-4 py-2 font-bold whitespace-pre-wrap">
<button class="btn btn-primary btn-xs relative whitespace-pre-wrap" @click="logEntry.loadSkippedEntries()">
{{ $t("error.logs-skipped", { total: logEntry.totalSkipped }) }}
</span>
</button>
</div>
</div>
</template>
<script lang="ts" setup>
import { SkippedLogsEntry } from "@/models/LogEntry";
defineProps<{
const { logEntry } = defineProps<{
logEntry: SkippedLogsEntry;
}>();
</script>
+16 -10
View File
@@ -1,25 +1,31 @@
<template>
<div class="hover:text-secondary relative" @mouseenter="mouseOver = true" @mouseleave="mouseOver = false">
<div class="border-primary hidden overflow-hidden rounded-xs border px-px pt-1 pb-px md:flex">
<div class="border-primary overflow-hidden rounded-xs border px-px pt-1 pb-px max-md:hidden">
<StatSparkline :data="data" @selected-point="onSelectedPoint" />
</div>
<div class="bg-base-200 inline-flex gap-1 rounded-sm p-px text-xs md:absolute md:-top-2 md:-left-0.5">
<div class="font-light uppercase">{{ label }}</div>
<div class="font-bold select-none">
{{ mouseOver ? (selectedPoint?.value ?? selectedPoint?.y ?? statValue) : statValue }}
<span v-if="limit !== -1 && !mouseOver" class="max-md:hidden"> / {{ limit }} </span>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
const { data, label, statValue } = defineProps<{ data: Point<unknown>[]; label: string; statValue: string | number }>();
let selectedPoint: Point<unknown> | undefined = $ref();
function onSelectedPoint(point: Point<unknown>) {
selectedPoint = point;
}
let mouseOver = $ref(false);
const {
data,
label,
statValue,
limit = -1,
} = defineProps<{
data: Point<unknown>[];
label: string;
statValue: string | number;
limit?: string | number;
}>();
const selectedPoint = ref<Point<unknown> | undefined>();
const onSelectedPoint = (point: Point<unknown>) => (selectedPoint.value = point);
const mouseOver = ref(false);
</script>
@@ -11,7 +11,7 @@ import { scaleLinear } from "d3-scale";
import { area, curveStep } from "d3-shape";
const d3 = { extent, scaleLinear, area, curveStep };
const { data, width = 150, height = 30 } = defineProps<{ data: Point<unknown>[]; width?: number; height?: number }>();
const { data, width = 175, height = 30 } = defineProps<{ data: Point<unknown>[]; width?: number; height?: number }>();
const x = d3.scaleLinear().range([0, width]);
const y = d3.scaleLinear().range([height, 0]);
@@ -1,73 +1,117 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`<ContainerEventSource /> > render html correctly > should render dates with 12 hour style 1`] = `
"<ul data-v-cf9ff940="" class="events group pt-4 medium" show-container-name="false">
<li data-v-cf9ff940="" data-key="1" data-time="1560336942459" class="group/entry">
<div data-v-a49e52d4="" data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
"<ul data-v-cf9ff940="" class="group pt-4 medium" data-logs="" show-container-name="false">
<li data-v-cf9ff940="" id="1560336942709" data-time="1560336942709" class="group/entry">
<div data-v-cf9ff940="" class="flex min-h-[1px] flex-1 content-center justify-center"><span class="loading loading-bars loading-md text-primary m-2" style="display: none;"></span></div>
</li>
<li data-v-cf9ff940="" id="1" data-time="1560336942459" class="group/entry">
<div data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
<div class="dropdown dropdown-right dropdown-hover absolute -left-2 z-10 font-sans"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
<circle cx="256" cy="256" r="48" fill="currentColor"></circle>
<circle cx="256" cy="416" r="48" fill="currentColor"></circle>
<circle cx="256" cy="96" r="48" fill="currentColor"></circle>
</svg></button>
<ul tabindex="0" class="menu dropdown-content rounded-box bg-base-200 border-base-content/20 z-50 w-52 border p-1 text-sm shadow-sm">
<!--v-if-->
<!--v-if-->
<!--v-if-->
<!--v-if-->
</ul>
</div>
<!--v-if-->
<!--v-if-->
<!--v-if-->
<div class="tag bg-base-100 inline-flex items-center justify-center rounded-sm px-2 py-[0.2em] [[size='small']]:text-[0.8rem] items-start! shrink-0 select-none" size="small">
<div class="inline-flex gap-2 text-blue whitespace-nowrap"><time datetime="2019-06-12T10:55:42.459Z" class="max-md:hidden">06/12/2019</time><time datetime="2019-06-12T10:55:42.459Z">10:55:42 AM</time></div>
<div class="flex gap-x-2 gap-y-1 group-[.compact]:gap-y-0 has-[>_*:nth-of-type(2)]:flex-col-reverse md:flex-row!">
<!--v-if-->
<!--v-if-->
<div class="tag bg-base-100 inline-flex items-center justify-center rounded-sm px-2 py-[0.2em] [[size='small']]:text-[0.8rem] items-start! shrink-0 select-none">
<div class="inline-flex gap-2 text-blue whitespace-nowrap"><time datetime="2019-06-12T10:55:42.459Z" class="max-md:hidden">06/12/2019</time><time datetime="2019-06-12T10:55:42.459Z">10:55:42 AM</time></div>
</div>
</div>
<div data-v-e625cddd="" class="mt-1.5 size-2.5 flex-none rounded-lg flex select-none"></div>
<div data-v-a49e52d4="" class="log-wrapper [word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap">foo bar</div>
<div data-v-a49e52d4="" class="flex gap-2 absolute -right-1 opacity-0 transition-opacity delay-150 duration-250 group-hover/entry:opacity-100">
<!--v-if-->
</div>
<div class="[word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap">foo bar</div>
</div>
</li>
</ul>"
`;
exports[`<ContainerEventSource /> > render html correctly > should render dates with 24 hour style 1`] = `
"<ul data-v-cf9ff940="" class="events group pt-4 medium" show-container-name="false">
<li data-v-cf9ff940="" data-key="1" data-time="1560336942459" class="group/entry">
<div data-v-a49e52d4="" data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
"<ul data-v-cf9ff940="" class="group pt-4 medium" data-logs="" show-container-name="false">
<li data-v-cf9ff940="" id="1560336942709" data-time="1560336942709" class="group/entry">
<div data-v-cf9ff940="" class="flex min-h-[1px] flex-1 content-center justify-center"><span class="loading loading-bars loading-md text-primary m-2" style="display: none;"></span></div>
</li>
<li data-v-cf9ff940="" id="1" data-time="1560336942459" class="group/entry">
<div data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
<div class="dropdown dropdown-right dropdown-hover absolute -left-2 z-10 font-sans"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
<circle cx="256" cy="256" r="48" fill="currentColor"></circle>
<circle cx="256" cy="416" r="48" fill="currentColor"></circle>
<circle cx="256" cy="96" r="48" fill="currentColor"></circle>
</svg></button>
<ul tabindex="0" class="menu dropdown-content rounded-box bg-base-200 border-base-content/20 z-50 w-52 border p-1 text-sm shadow-sm">
<!--v-if-->
<!--v-if-->
<!--v-if-->
<!--v-if-->
</ul>
</div>
<!--v-if-->
<!--v-if-->
<!--v-if-->
<div class="tag bg-base-100 inline-flex items-center justify-center rounded-sm px-2 py-[0.2em] [[size='small']]:text-[0.8rem] items-start! shrink-0 select-none" size="small">
<div class="inline-flex gap-2 text-blue whitespace-nowrap"><time datetime="2019-06-12T10:55:42.459Z" class="max-md:hidden">06/12/2019</time><time datetime="2019-06-12T10:55:42.459Z">10:55:42</time></div>
<div class="flex gap-x-2 gap-y-1 group-[.compact]:gap-y-0 has-[>_*:nth-of-type(2)]:flex-col-reverse md:flex-row!">
<!--v-if-->
<!--v-if-->
<div class="tag bg-base-100 inline-flex items-center justify-center rounded-sm px-2 py-[0.2em] [[size='small']]:text-[0.8rem] items-start! shrink-0 select-none">
<div class="inline-flex gap-2 text-blue whitespace-nowrap"><time datetime="2019-06-12T10:55:42.459Z" class="max-md:hidden">06/12/2019</time><time datetime="2019-06-12T10:55:42.459Z">10:55:42</time></div>
</div>
</div>
<div data-v-e625cddd="" class="mt-1.5 size-2.5 flex-none rounded-lg flex select-none"></div>
<div data-v-a49e52d4="" class="log-wrapper [word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap">foo bar</div>
<div data-v-a49e52d4="" class="flex gap-2 absolute -right-1 opacity-0 transition-opacity delay-150 duration-250 group-hover/entry:opacity-100">
<!--v-if-->
</div>
<div class="[word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap">foo bar</div>
</div>
</li>
</ul>"
`;
exports[`<ContainerEventSource /> > render html correctly > should render messages 1`] = `
"<ul data-v-cf9ff940="" class="events group pt-4 medium" show-container-name="false">
<li data-v-cf9ff940="" data-key="1" data-time="1560336942459" class="group/entry">
<div data-v-a49e52d4="" data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
"<ul data-v-cf9ff940="" class="group pt-4 medium" data-logs="" show-container-name="false">
<li data-v-cf9ff940="" id="1560336942709" data-time="1560336942709" class="group/entry">
<div data-v-cf9ff940="" class="flex min-h-[1px] flex-1 content-center justify-center"><span class="loading loading-bars loading-md text-primary m-2" style="display: none;"></span></div>
</li>
<li data-v-cf9ff940="" id="1" data-time="1560336942459" class="group/entry">
<div data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
<div class="dropdown dropdown-right dropdown-hover absolute -left-2 z-10 font-sans"><button tabindex="0" class="btn btn-square btn-xs border-base-content/20 bg-base-100 border opacity-0 shadow-sm group-hover/entry:opacity-90"><svg viewBox="0 0 512 512" width="1.2em" height="1.2em">
<circle cx="256" cy="256" r="48" fill="currentColor"></circle>
<circle cx="256" cy="416" r="48" fill="currentColor"></circle>
<circle cx="256" cy="96" r="48" fill="currentColor"></circle>
</svg></button>
<ul tabindex="0" class="menu dropdown-content rounded-box bg-base-200 border-base-content/20 z-50 w-52 border p-1 text-sm shadow-sm">
<!--v-if-->
<!--v-if-->
<!--v-if-->
<!--v-if-->
</ul>
</div>
<!--v-if-->
<!--v-if-->
<!--v-if-->
<div class="tag bg-base-100 inline-flex items-center justify-center rounded-sm px-2 py-[0.2em] [[size='small']]:text-[0.8rem] items-start! shrink-0 select-none" size="small">
<div class="inline-flex gap-2 text-blue whitespace-nowrap"><time datetime="2019-06-12T10:55:42.459Z" class="max-md:hidden">06/12/2019</time><time datetime="2019-06-12T10:55:42.459Z">10:55:42 AM</time></div>
<div class="flex gap-x-2 gap-y-1 group-[.compact]:gap-y-0 has-[>_*:nth-of-type(2)]:flex-col-reverse md:flex-row!">
<!--v-if-->
<!--v-if-->
<div class="tag bg-base-100 inline-flex items-center justify-center rounded-sm px-2 py-[0.2em] [[size='small']]:text-[0.8rem] items-start! shrink-0 select-none">
<div class="inline-flex gap-2 text-blue whitespace-nowrap"><time datetime="2019-06-12T10:55:42.459Z" class="max-md:hidden">06/12/2019</time><time datetime="2019-06-12T10:55:42.459Z">10:55:42 AM</time></div>
</div>
</div>
<div data-v-e625cddd="" class="mt-1.5 size-2.5 flex-none rounded-lg flex select-none"></div>
<div data-v-a49e52d4="" class="log-wrapper [word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap">This is a message.</div>
<div data-v-a49e52d4="" class="flex gap-2 absolute -right-1 opacity-0 transition-opacity delay-150 duration-250 group-hover/entry:opacity-100">
<!--v-if-->
</div>
<div class="[word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap">This is a message.</div>
</div>
</li>
</ul>"
`;
exports[`<ContainerEventSource /> > should parse messages 1`] = `
SimpleLogEntry {
"_message": "This is a message.",
"containerID": undefined,
"date": 2019-06-12T10:55:42.459Z,
"id": 1,
LoadMoreLogEntry {
"_message": "",
"containerID": "",
"date": 2019-06-12T10:55:42.709Z,
"id": 1560336942709,
"level": undefined,
"position": undefined,
"loader": [Function],
"rawMessage": "info",
"rememberScrollPosition": true,
"std": "stderr",
}
`;
@@ -2,12 +2,8 @@
<ScrollableView :scrollable="scrollable" v-if="containers.length && ready">
<template #header>
<div class="mx-2 flex items-center gap-2 md:ml-4">
<div class="@container flex flex-1 gap-1.5 truncate md:gap-2">
<octicon:container-24 />
<div class="inline-flex font-mono text-sm">
<div class="font-semibold">{{ containers.length }} containers</div>
</div>
</div>
<octicon:container-24 />
<ContainerDropdown :containers="containers">{{ $t("label.container", containers.length) }}</ContainerDropdown>
<MultiContainerStat class="ml-auto" :containers="containers" />
<MultiContainerActionToolbar class="max-md:hidden" @clear="viewer?.clear()" />
</div>
+1 -1
View File
@@ -1,5 +1,5 @@
<template>
<div class="flex flex-col gap-8 px-4 py-4 md:px-8">
<div class="flex flex-col gap-5 px-4 py-4 md:px-8">
<section>
<Links>
<template #more-items>
+6 -6
View File
@@ -3,8 +3,8 @@
<teleport to="body">
<transition name="fade">
<div
v-show="show && (delayedShow || glopbalShow)"
class="border-base-content/20 bg-base-100 fixed z-50 rounded-sm border p-4 shadow-sm"
v-show="show && (delayedShow || globalShow)"
class="ring-base-content/20 bg-base-100 fixed z-50 rounded-sm p-3 shadow-sm ring"
ref="content"
>
<slot name="content"></slot>
@@ -16,14 +16,14 @@
<script lang="ts" setup>
import { globalShowPopup } from "@/composable/popup";
const glopbalShow = globalShowPopup();
const show = ref(glopbalShow.value);
const globalShow = globalShowPopup();
const show = ref(globalShow.value);
const delayedShow = refDebounced(show, 1000);
const content = ref<HTMLElement>();
const onMouseEnter = (e: Event) => {
show.value = true;
glopbalShow.value = true;
globalShow.value = true;
if (content.value && e.target instanceof HTMLElement) {
const { left, top, width } = e.target.getBoundingClientRect();
@@ -37,7 +37,7 @@ const onMouseEnter = (e: Event) => {
const onMouseLeave = () => {
show.value = false;
glopbalShow.value = false;
globalShow.value = false;
};
const el: Ref<HTMLElement> = useCurrentElement();
+2 -2
View File
@@ -12,7 +12,7 @@
<span> % </span>
</div>
</div>
<DistanceTime :date="date" class="text-sm whitespace-nowrap" />
<RelativeTime :date="date" class="text-sm whitespace-nowrap" />
</div>
</transition>
</template>
@@ -93,7 +93,7 @@ svg {
}
.fadeout-leave-active {
@apply transition-opacity;
@apply transition-opacity duration-400;
}
.fadeout-leave-to {
+24 -23
View File
@@ -2,7 +2,7 @@
<section :class="{ 'h-screen min-h-0': scrollable }" class="flex flex-col">
<header
v-if="$slots.header"
class="border-base-content/10 bg-base-200 sticky top-[55px] z-2 border-b py-2 shadow-[1px_1px_2px_0_rgb(0,0,0,0.05)] md:top-0"
class="border-base-content/10 bg-base-200 sticky top-[calc(55px+env(safe-area-inset-top))] z-20 border-b py-2 shadow-[1px_1px_2px_0_rgb(0,0,0,0.05)] md:top-0"
>
<slot name="header"></slot>
</header>
@@ -25,10 +25,10 @@
<div ref="scrollObserver" class="h-px"></div>
</main>
<div class="mr-16 text-right">
<div class="mr-16 text-right" v-if="!historical">
<transition name="fade">
<button
class="transition-colorsblur-xs dark btn btn-primary text-primary-content fixed bottom-8 rounded-sm p-3 shadow-sm"
class="btn btn-primary text-primary-content fixed bottom-8 rounded-sm p-3 shadow-sm transition-colors"
:class="hasMore ? 'btn-secondary animate-bounce-fast text-secondary-content' : ''"
@click="scrollToBottom()"
v-show="scrollContext.paused"
@@ -49,28 +49,29 @@ const scrollableContent = ref<HTMLElement>();
const scrollContext = provideScrollContext();
const { loadingMore } = useLoggingContext();
const { loadingMore, historical } = useLoggingContext();
if (!historical.value) {
useIntersectionObserver(scrollObserver, ([entry]) => (scrollContext.paused = entry.intersectionRatio == 0), {
threshold: [0, 1],
rootMargin: "40px 0px",
});
useIntersectionObserver(scrollObserver, ([entry]) => (scrollContext.paused = entry.intersectionRatio == 0), {
threshold: [0, 1],
rootMargin: "40px 0px",
});
useMutationObserver(
scrollableContent,
(records) => {
if (!scrollContext.paused) {
scrollToBottom();
} else {
const record = records[records.length - 1];
const children = (record.target as HTMLElement).children;
if (children[children.length - 1] == record.addedNodes[record.addedNodes.length - 1]) {
hasMore.value = true;
useMutationObserver(
scrollableContent,
(records) => {
if (!scrollContext.paused) {
scrollToBottom();
} else {
const record = records[records.length - 1];
const children = (record.target as HTMLElement).children;
if (children[children.length - 1] == record.addedNodes[record.addedNodes.length - 1]) {
hasMore.value = true;
}
}
}
},
{ childList: true, subtree: true },
);
},
{ childList: true, subtree: true },
);
}
function scrollToBottom(behavior: "auto" | "smooth" = "auto") {
scrollObserver.value?.scrollIntoView({ behavior });
+1 -1
View File
@@ -1,7 +1,7 @@
<template>
<transition name="slide">
<div
class="fixed z-10 flex w-full justify-end p-2"
class="fixed z-50 flex w-full justify-end p-2"
v-show="showSearch"
v-if="search"
ref="container"
@@ -2,15 +2,8 @@
<ScrollableView :scrollable="scrollable" v-if="service.name">
<template #header>
<div class="mx-2 flex items-center gap-2 md:ml-4">
<div class="@container flex flex-1 gap-1.5 truncate md:gap-2">
<ph:stack-simple />
<div class="inline-flex font-mono text-sm">
<div class="font-semibold">{{ service.name }}</div>
</div>
<Tag class="hidden font-mono max-md:hidden @3xl:block" size="small">
{{ $t("label.container", service.containers.length) }}
</Tag>
</div>
<ph:stack-simple />
<ContainerDropdown :containers="service.containers">{{ service.name }}</ContainerDropdown>
<MultiContainerStat class="ml-auto" :containers="service.containers" />
<MultiContainerActionToolbar class="max-md:hidden" @clear="viewer?.clear()" />
</div>
+1 -1
View File
@@ -1,5 +1,5 @@
<template>
<div v-if="ready" data-testid="side-menu" class="flex min-h-0 flex-col">
<div v-if="ready" data-testid="side-menu" class="flex min-h-0 flex-col w-full">
<Carousel v-model="selectedCard" class="flex-1">
<CarouselItem :title="$t('label.host-menu')" id="host">
<HostMenu />
+7 -8
View File
@@ -1,17 +1,16 @@
<template>
<aside class="fixed flex h-screen w-[inherit] flex-col gap-4 p-3" data-testid="navigation">
<h1>
<router-link :to="{ name: '/' }">
<LogoWithText class="logo [&_.secondary-fill]:fill-secondary [&_.content-fill]:fill-base-content h-16 w-40" />
<router-link :to="{ name: '/' }" class="flex w-full items-center gap-4 overflow-hidden text-4xl font-thin">
<Logo class="h-14 w-14 shrink-0" />
Dozzle
</router-link>
<small class="mb-4 block text-xs font-light" v-if="hostname">
{{ hostname }}
</small>
<small class="mt-4 block text-sm font-light" v-if="hostname">{{ hostname }}</small>
</h1>
<button
class="input input-sm hover:border-primary inline-flex w-auto cursor-pointer items-center gap-2 self-start font-light"
class="input input-sm hover:border-primary mt-2 inline-flex w-auto cursor-pointer items-center gap-2 self-start font-light"
@click="$emit('search')"
:title="$t('tooltip.search')"
data-testid="search"
@@ -21,12 +20,12 @@
<key-shortcut char="k" class="text-base-content/70"></key-shortcut>
</button>
<SideMenu class="mt-2 flex-1" />
<SideMenu class="flex-1" />
</aside>
</template>
<script lang="ts" setup>
import LogoWithText from "@/logo-text.svg";
import Logo from "@/logo.svg";
const { hostname } = config;
</script>
+4 -9
View File
@@ -2,17 +2,12 @@
<ScrollableView :scrollable="scrollable" v-if="stack.name">
<template #header>
<div class="mx-2 flex items-center gap-2 md:ml-4">
<div class="@container flex flex-1 gap-1.5 truncate md:gap-2">
<div class="@container flex flex-1 items-center gap-1.5 md:gap-2">
<ph:stack />
<div class="inline-flex font-mono text-sm">
<div class="font-semibold">{{ stack.name }}</div>
</div>
<Tag class="hidden font-mono max-md:hidden @3xl:block" size="small">
<div class="font-mono text-sm font-semibold">{{ stack.name }}</div>
<ContainerDropdown :containers="stack.containers">
{{ $t("label.container", stack.containers.length) }}
</Tag>
<Tag class="hidden font-mono max-md:hidden @3xl:block" size="small">
{{ $t("label.service", stack.services.length) }}
</Tag>
</ContainerDropdown>
</div>
<MultiContainerStat class="ml-auto" :containers="stack.containers" />
<MultiContainerActionToolbar class="max-md:hidden" @clear="viewer?.clear()" />
+31 -1
View File
@@ -1,5 +1,28 @@
<template>
<ul class="menu w-full p-0 text-[0.95rem]">
<div class="mb-2 flex items-center">
<div class="flex-1">
{{ $t("label.service", services.length) }}
</div>
<div class="flex-none">
<div class="dropdown dropdown-end dropdown-hover">
<label tabindex="0" class="btn btn-square btn-ghost btn-sm">
<ph:dots-three-vertical-bold />
</label>
<ul
tabindex="0"
class="menu dropdown-content rounded-box bg-base-200 border-base-content/20 z-50 w-52 border p-1 shadow-sm"
>
<li>
<a class="text-sm capitalize" @click="collapseAll()">
<material-symbols-light:collapse-all class="w-4" />
{{ $t("label.collapse-all") }}
</a>
</li>
</ul>
</div>
</div>
</div>
<ul class="menu w-full p-0 text-[0.95rem]" ref="menu">
<li v-for="{ name, services } in stacks" :key="name">
<details open>
<summary class="text-base-content/80 font-light">
@@ -55,4 +78,11 @@ const store = useSwarmStore();
const { stacks, services } = storeToRefs(store);
const servicesWithoutStacks = computed(() => services.value.filter((service) => !service.stack));
const menu = useTemplateRef("menu");
const collapseAll = () => {
const details = menu.value?.querySelectorAll("details");
details?.forEach((detail) => (detail.open = false));
};
</script>
+95
View File
@@ -0,0 +1,95 @@
<template>
<aside>
<header class="flex items-center gap-4">
<material-symbols:terminal class="size-8" />
<h1 class="text-2xl max-md:hidden">{{ container.name }}</h1>
<h2 class="text-sm">Started <RelativeTime :date="container.created" /></h2>
</header>
<div class="mt-8 flex flex-col gap-2">
<section>
<div ref="host" class="shell"></div>
</section>
</div>
</aside>
</template>
<script setup lang="ts">
import { Container } from "@/models/Container";
import "@xterm/xterm/css/xterm.css";
const { container, action } = defineProps<{ container: Container; action: "attach" | "exec" }>();
const { Terminal } = await import("@xterm/xterm");
const { WebLinksAddon } = await import("@xterm/addon-web-links");
const host = useTemplateRef<HTMLDivElement>("host");
const terminal = new Terminal({
cursorBlink: true,
cursorStyle: "block",
});
terminal.loadAddon(new WebLinksAddon());
let ws: WebSocket | null = null;
onMounted(() => {
terminal.open(host.value!);
terminal.resize(100, 40);
ws = new WebSocket(withBase(`/api/hosts/${container.host}/containers/${container.id}/${action}`));
ws.onopen = () => {
terminal.writeln(`Attaching to ${container.name} 🚀`);
if (action === "attach") {
ws?.send("\r");
}
terminal.onData((data) => {
ws?.send(data);
});
terminal.focus();
};
ws.onmessage = (event) => terminal.write(event.data);
ws.addEventListener("close", () => {
terminal.writeln("⚠️ Connection closed");
});
});
onUnmounted(() => {
console.log("Closing WebSocket");
terminal.dispose();
ws?.close();
});
</script>
<style scoped>
@reference "@/main.css";
.shell {
& :deep(.terminal) {
@apply overflow-hidden rounded border-1 p-2;
&:is(.focus) {
@apply border-primary;
}
}
& :deep(.xterm-viewport) {
@apply bg-base-200!;
}
& :deep(.xterm-rows) {
@apply text-base-content;
}
& :deep(.xterm-cursor-block.xterm-cursor-blink) {
animation-name: blink !important;
}
}
@keyframes blink {
0% {
background-color: var(--color-base-content);
color: #000000;
}
50% {
background-color: inherit;
color: var(--color-base-content);
}
}
</style>
-30
View File
@@ -1,30 +0,0 @@
<template>
<time :datetime="date.toISOString()" data-ci-skip>{{ text }}</time>
</template>
<script lang="ts" setup>
import { formatDistanceToNow } from "date-fns/formatDistanceToNow";
import { formatDistanceToNowStrict } from "date-fns/formatDistanceToNowStrict";
const {
date,
strict = false,
suffix = true,
} = defineProps<{
date: Date;
strict?: boolean;
suffix?: boolean;
}>();
const text = ref<string>();
watch($$(date), updateFromNow, { immediate: true });
function updateFromNow() {
const fn = strict ? formatDistanceToNowStrict : formatDistanceToNow;
text.value = fn(date, {
addSuffix: suffix,
});
}
useIntervalFn(updateFromNow, 30_000, { immediateCallback: true });
</script>
+2 -2
View File
@@ -1,10 +1,10 @@
<template>
<details class="dropdown" ref="details" v-on-click-outside="close">
<details class="dropdown dropdown-end" ref="details" v-on-click-outside="close">
<summary class="btn btn-primary flex-nowrap" v-bind="$attrs">
<slot name="trigger"> {{ label }} <carbon:caret-down /></slot>
</summary>
<ul
class="menu dropdown-content rounded-box border-base-content/20 bg-base-200 z-50 mt-1 w-52 border p-2 shadow-sm"
class="menu dropdown-content rounded-box border-base-content/20 bg-base-200 z-50 mt-1 max-h-72 w-48 flex-nowrap overflow-auto border p-2 shadow-sm"
>
<slot>
<li v-for="item in options">
+1 -1
View File
@@ -1,6 +1,6 @@
<template>
<label class="label text-base-content cursor-pointer gap-4">
<div class="flex-1"><slot name="label" /></div>
<div class="flex-1 whitespace-normal"><slot name="label" /></div>
<slot name="input" />
</label>
</template>
+23 -21
View File
@@ -1,27 +1,29 @@
<template>
<nav class="border-base-content/20 bg-base-200 fixed top-0 z-10 w-full border-b p-2" data-testid="navigation">
<div class="flex items-center">
<router-link :to="{ name: '/' }">
<Logo class="logo [&_.secondary-fill]:fill-secondary h-8" />
</router-link>
<nav class="border-base-content/20 bg-base-200 pt-safe fixed top-0 z-10 w-full border-b" data-testid="navigation">
<div class="p-2">
<div class="flex items-center">
<router-link :to="{ name: '/' }">
<Logo class="h-10" />
</router-link>
<div class="ml-auto flex items-center gap-2">
<a class="btn btn-circle flex" @click="$emit('search')" :title="$t('tooltip.search')">
<mdi:magnify class="size-5" />
</a>
<label class="btn btn-circle swap swap-rotate" data-testid="hamburger">
<input type="checkbox" v-model="show" />
<mdi:close class="swap-on" />
<mdi:hamburger-menu class="swap-off" />
</label>
<div class="ml-auto flex items-center gap-2">
<a class="btn btn-circle flex" @click="$emit('search')" :title="$t('tooltip.search')">
<mdi:magnify class="size-5" />
</a>
<label class="btn btn-circle swap swap-rotate" data-testid="hamburger">
<input type="checkbox" v-model="show" />
<mdi:close class="swap-on" />
<mdi:hamburger-menu class="swap-off" />
</label>
</div>
</div>
<transition name="fade">
<div v-show="show" class="flex h-[calc(100svh-55px)]">
<SideMenu class="flex-1" />
</div>
</transition>
</div>
<transition name="fade">
<div v-show="show" class="flex h-[calc(100svh-55px)]">
<SideMenu class="flex-1" />
</div>
</transition>
</nav>
</template>
@@ -35,7 +37,7 @@ watch(route, () => {
});
</script>
<style scoped>
@import "@/main.css" reference;
@reference "@/main.css";
.fade-enter-active,
.fade-leave-active {
@apply transition-opacity;
+17
View File
@@ -0,0 +1,17 @@
<template>
<time :datetime="date.toISOString()">{{ text }}</time>
</template>
<script lang="ts" setup>
const { date } = defineProps<{
date: Date;
}>();
const text = ref<string>();
const updateFromNow = () => {
text.value = toRelativeTime(date, locale.value === "" ? undefined : locale.value);
};
watch(() => date, updateFromNow, { immediate: true });
useIntervalFn(updateFromNow, 30_000);
</script>
+13 -8
View File
@@ -1,13 +1,18 @@
<template>
<dialog ref="panel" class="modal-right modal items-start outline-hidden backdrop:bg-none">
<div class="modal-box" :width="width">
<form method="dialog">
<button class="swap swap-rotate hover:swap-active absolute top-4 right-4 outline-hidden">
<mdi:keyboard-esc class="swap-off" />
<mdi:close class="swap-on" />
</button>
</form>
<slot v-if="open"></slot>
<div class="pt-safe relative">
<form method="dialog" class="absolute right-0">
<button v-if="isMobile">
<mdi:close />
</button>
<button v-else class="swap hover:swap-active outline-hidden">
<mdi:keyboard-esc class="swap-off" />
<mdi:close class="swap-on" />
</button>
</form>
<slot v-if="open"></slot>
</div>
</div>
<form method="dialog" class="modal-backdrop">
<button>close</button>
@@ -33,7 +38,7 @@ defineExpose({
useEventListener(panel, "close", () => (open.value = false));
</script>
<style scoped>
@import "@/main.css" reference;
@reference "@/main.css";
.modal-right :where(.modal-box) {
@apply bg-base-100 fixed right-0 h-lvh max-h-screen translate-x-24 scale-100 rounded-none shadow-none;
+2 -2
View File
@@ -1,7 +1,7 @@
<template>
<div class="toast toast-end whitespace-normal max-md:m-0 max-md:w-full">
<div class="toast toast-end whitespace-normal max-md:end-auto max-md:m-0 max-md:max-w-full">
<div
class="alert max-w-xl max-md:rounded-none"
class="alert max-w-xl shadow-sm max-md:rounded-none"
v-for="{ toast, options: { timed } } in toasts"
:key="toast.id"
:class="{
+89 -44
View File
@@ -9,6 +9,7 @@ import {
ContainerEventLogEntry,
ComplexLogEntry,
SkippedLogsEntry,
LoadMoreLogEntry,
} from "@/models/LogEntry";
import { Service, Stack } from "@/models/Stack";
import { Container, GroupedContainers } from "@/models/Container";
@@ -22,8 +23,7 @@ function parseMessage(data: string): LogEntry<string | JSONObject> {
export function useContainerStream(container: Ref<Container>): LogStreamSource {
const url = computed(() => `/api/hosts/${container.value.host}/containers/${container.value.id}/logs/stream`);
const loadMoreUrl = computed(() => `/api/hosts/${container.value.host}/containers/${container.value.id}/logs`);
return useLogStream(url, loadMoreUrl);
return useLogStream(url, container);
}
export function useHostStream(host: Ref<Host>): LogStreamSource {
@@ -53,13 +53,15 @@ export function useServiceStream(service: Ref<Service>): LogStreamSource {
export type LogStreamSource = ReturnType<typeof useLogStream>;
function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
function useLogStream(url: Ref<string>, container?: Ref<Container>) {
const messages: ShallowRef<LogEntry<string | JSONObject>[]> = shallowRef([]);
const buffer: ShallowRef<LogEntry<string | JSONObject>[]> = shallowRef([]);
const opened = ref(false);
const loading = ref(true);
const error = ref(false);
const { paused: scrollingPaused } = useScrollContext();
const { streamConfig, hasComplexLogs, levels, loadingMore } = useLoggingContext();
let initial = true;
function flushNow() {
if (messages.value.length + buffer.value.length > config.maxLogs) {
@@ -71,10 +73,9 @@ function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
} else {
const firstItem = buffer.value.at(0) as LogEntry<string | JSONObject>;
const lastItem = buffer.value.at(-1) as LogEntry<string | JSONObject>;
messages.value = [
...messages.value,
new SkippedLogsEntry(new Date(), buffer.value.length, firstItem, lastItem),
new SkippedLogsEntry(new Date(), buffer.value.length, firstItem, lastItem, loadSkippedLogs),
];
}
buffer.value = [];
@@ -87,9 +88,15 @@ function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
buffer.value = [];
}
} else {
if (messages.value.length == 0) {
if (initial) {
// sort the buffer the very first time because of multiple logs in parallel
buffer.value.sort((a, b) => a.date.getTime() - b.date.getTime());
if (container) {
const loadMoreItem = new LoadMoreLogEntry(new Date(), loadOlderLogs);
messages.value = [loadMoreItem];
}
initial = false;
}
messages.value = [...messages.value, ...buffer.value];
buffer.value = [];
@@ -111,8 +118,6 @@ function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
buffer.value = [];
}
const { streamConfig, hasComplexLogs, levels } = useLoggingContext();
const params = computed(() => {
const params = new URLSearchParams();
if (streamConfig.value.stdout) params.append("stdout", "1");
@@ -132,6 +137,7 @@ function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
opened.value = false;
loading.value = true;
error.value = false;
initial = true;
es = new EventSource(urlWithParams.value);
es.addEventListener("container-event", (e) => {
const event = JSON.parse((e as MessageEvent).data) as {
@@ -175,46 +181,48 @@ function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
watch(urlWithParams, () => connect(), { immediate: true });
const isLoadingMore = ref(false);
async function loadOlderLogs(entry: LoadMoreLogEntry) {
if (!(messages.value[0] instanceof LoadMoreLogEntry)) throw new Error("No loadMoreLogEntry on first item");
if (!container) throw new Error("No container");
async function loadOlderLogs() {
if (!loadMoreUrl) return;
if (isLoadingMore.value) return;
const to = messages.value[0].date;
const lastSeenId = messages.value[0].id;
const [loader, ...existingLogs] = messages.value;
const to = existingLogs[0].date;
const lastSeenId = existingLogs[0].id;
const last = messages.value[Math.min(messages.value.length - 1, 300)].date;
const delta = to.getTime() - last.getTime();
const from = new Date(to.getTime() + delta);
const abortController = new AbortController();
const signal = abortController.signal;
isLoadingMore.value = true;
try {
const urlWithMoreParams = computed(() => {
const loadMoreParams = new URLSearchParams(params.value);
loadMoreParams.append("from", from.toISOString());
loadMoreParams.append("to", to.toISOString());
loadMoreParams.append("minimum", "100");
loadMoreParams.append("lastSeenId", String(lastSeenId));
return withBase(`${loadMoreUrl.value}?${loadMoreParams.toString()}`);
loadingMore.value = true;
const { logs: newLogs, signal } = await loadBetween(container, params, from, to, {
min: 100,
lastSeenId,
});
const stopWatcher = watchOnce(urlWithMoreParams, () => abortController.abort("stream changed"));
const logs = await (await fetch(urlWithMoreParams.value, { signal })).text();
stopWatcher();
if (logs && signal.aborted === false) {
const newMessages = logs
.trim()
.split("\n")
.map((line) => parseMessage(line));
messages.value = [...newMessages, ...messages.value];
if (newLogs && signal.aborted === false) {
messages.value = [loader, ...newLogs, ...existingLogs];
}
} catch (e) {
console.error("Error loading older logs", e);
} catch (error) {
console.error(error);
} finally {
isLoadingMore.value = false;
loadingMore.value = false;
}
}
async function loadSkippedLogs(entry: SkippedLogsEntry) {
if (!container) throw new Error("No container");
const from = entry.firstSkipped.date;
const to = entry.lastSkippedLog.date;
const lastSeenId = entry.lastSkippedLog.id;
try {
loadingMore.value = true;
const { logs, signal } = await loadBetween(container, params, from, to, { lastSeenId });
if (logs && signal.aborted === false) {
messages.value = messages.value.slice(logs.length).flatMap((log) => (log === entry ? logs : [log]));
}
} catch (error) {
console.error(error);
} finally {
loadingMore.value = false;
}
}
@@ -228,12 +236,49 @@ function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
return {
messages,
loadOlderLogs,
isLoadingMore,
hasComplexLogs,
opened,
error,
loading,
eventSourceURL: urlWithParams,
};
}
export async function loadBetween(
container: Ref<Container>,
params: Ref<URLSearchParams>,
from: Date,
to: Date,
{ lastSeenId, min, maxStart }: { lastSeenId?: number; min?: number; maxStart?: number } = {},
) {
const url = computed(() => `/api/hosts/${container.value.host}/containers/${container.value.id}/logs`);
const abortController = new AbortController();
const signal = abortController.signal;
const urlWithMoreParams = computed(() => {
const loadMoreParams = new URLSearchParams(params.value);
loadMoreParams.append("from", from.toISOString());
loadMoreParams.append("to", to.toISOString());
if (min) {
loadMoreParams.append("min", String(min));
}
if (maxStart) {
loadMoreParams.append("maxStart", String(maxStart));
}
if (lastSeenId) {
loadMoreParams.append("lastSeenId", String(lastSeenId));
}
return withBase(`${url.value}?${loadMoreParams.toString()}`);
});
const stopWatcher = watchOnce(urlWithMoreParams, () => abortController.abort("stream changed"));
const logs = await (await fetch(urlWithMoreParams.value, { signal })).text();
stopWatcher();
if (!logs) return { logs: [], signal };
return {
logs: logs
.trim()
.split("\n")
.map((line) => parseMessage(line)),
signal,
};
}
+125
View File
@@ -0,0 +1,125 @@
import { HistoricalContainer } from "@/models/Container";
import { JSONObject, LoadMoreLogEntry, LogEntry } from "@/models/LogEntry";
import { ShallowRef } from "vue";
import { loadBetween } from "@/composable/eventStreams";
export function useHistoricalContainerLog(historicalContainer: Ref<HistoricalContainer>): LogStreamSource {
const messages: ShallowRef<LogEntry<string | JSONObject>[]> = shallowRef([]);
const opened = ref(false);
const loading = ref(true);
const error = ref(false);
const container = toRef(() => historicalContainer.value.container);
const { streamConfig, levels, loadingMore } = useLoggingContext();
const { isSearching, debouncedSearchFilter } = useSearchFilter();
const params = computed(() => {
const params = new URLSearchParams();
if (streamConfig.value.stdout) params.append("stdout", "1");
if (streamConfig.value.stderr) params.append("stderr", "1");
if (isSearching.value) params.append("filter", debouncedSearchFilter.value);
for (const level of levels.value) {
params.append("levels", level);
}
return params;
});
const route = useRoute();
async function loadLogs() {
loadingMore.value = true;
try {
const lastSeenId = route.query.logId ? +route.query.logId : undefined;
const [{ logs: before }, { logs: after }] = await Promise.all([
loadBetween(
container,
params,
new Date(historicalContainer.value.date.getTime() - 1000 * 60 * 5),
new Date(historicalContainer.value.date.getTime() + 1000),
{
min: 50,
lastSeenId,
},
),
loadBetween(container, params, historicalContainer.value.date, new Date(), {
maxStart: 50,
}),
]);
const loaderOlder = new LoadMoreLogEntry(new Date(), loadOlderLogs);
const loadNewer = new LoadMoreLogEntry(new Date(), loadNewerLogs, false);
messages.value = [loaderOlder, ...before, ...after, loadNewer];
loading.value = false;
opened.value = true;
} catch (error) {
console.error(error);
} finally {
loadingMore.value = false;
}
}
watchArray([params, container], loadLogs, { immediate: true });
async function loadOlderLogs(entry: LoadMoreLogEntry) {
loadingMore.value = true;
try {
const item = messages.value[1];
const { logs, signal } = await loadBetween(
container,
params,
new Date(item.date.getTime() - 1000 * 60 * 5),
item.date,
{
min: 200,
lastSeenId: item.id,
},
);
if (signal.aborted) {
return;
}
if (!logs.length) {
return;
}
const [loader, ...rest] = messages.value;
messages.value = [loader, ...logs, ...rest];
} catch (error) {
console.error(error);
} finally {
loadingMore.value = false;
}
}
async function loadNewerLogs(entry: LoadMoreLogEntry) {
loadingMore.value = true;
try {
const item = messages.value.at(-2)!;
const { logs, signal } = await loadBetween(container, params, item.date, new Date(), {
maxStart: 100,
});
if (signal.aborted) {
return;
}
if (!logs.length) {
return;
}
const loader = messages.value.at(-1)!;
const rest = messages.value.slice(0, -1);
messages.value = [...rest, ...logs, loader];
} catch (error) {
console.error(error);
} finally {
loadingMore.value = false;
}
}
return {
messages,
opened,
error,
loading,
};
}
+4 -1
View File
@@ -9,6 +9,7 @@ type LogContext = {
levels: Set<Level>;
showContainerName: boolean;
showHostname: boolean;
historical: boolean;
};
export const allLevels: Level[] = ["info", "debug", "warn", "error", "fatal", "trace", "unknown"];
@@ -21,7 +22,7 @@ const stderr = searchParams.has("stderr") ? searchParams.get("stderr") === "true
export const provideLoggingContext = (
containers: Ref<Container[]>,
{ showContainerName = false, showHostname = false } = {},
{ showContainerName = false, showHostname = false, historical = false } = {},
) => {
provide(
loggingContextKey,
@@ -33,6 +34,7 @@ export const provideLoggingContext = (
levels: new Set<Level>(allLevels),
showContainerName,
showHostname,
historical,
}),
);
};
@@ -48,6 +50,7 @@ export const useLoggingContext = () => {
levels: new Set<Level>(allLevels),
showContainerName: false,
showHostname: false,
historical: false,
}),
);
+1 -1
View File
@@ -29,7 +29,7 @@ export function useProfileStorage<K extends keyof Profile>(
if (transformer) {
storage.value = transformer.from(config.profile[key]);
} else if (storage.value instanceof Set && config.profile[key] instanceof Array) {
storage.value = new Set([...(config.profile[key] as Iterable<any>)]) as unknown as NonNullable<Profile[K]>;
storage.value = new Set(config.profile[key] as Iterable<any>) as unknown as NonNullable<Profile[K]>;
} else if (config.profile[key] instanceof Array) {
storage.value = config.profile[key] as NonNullable<Profile[K]>;
} else if (config.profile[key] instanceof Object) {
+3 -6
View File
@@ -1,5 +1,4 @@
type ScrollContext = {
loading: boolean;
paused: boolean;
progress: number;
currentDate: Date;
@@ -9,20 +8,18 @@ type ScrollContext = {
export const scrollContextKey = Symbol("scrollContext") as InjectionKey<ScrollContext>;
export const provideScrollContext = () => {
const context = defauleValue();
const context = defaultValue();
provide(scrollContextKey, context);
return context;
};
export const useScrollContext = () => {
const defaultValue = defauleValue();
const context = inject(scrollContextKey, defaultValue);
const context = inject(scrollContextKey, defaultValue());
return toRefs(context);
};
function defauleValue() {
function defaultValue() {
return reactive({
loading: false,
paused: false,
progress: 1,
currentDate: new Date(),
+9 -13
View File
@@ -1,11 +1,11 @@
<template>
<div>
<mobile-menu v-if="isMobile && !forceMenuHidden" @search="showFuzzySearch"></mobile-menu>
<MobileMenu v-if="isMobile && !forceMenuHidden" @search="showFuzzySearch"></MobileMenu>
<Splitpanes @resized="onResized($event)">
<Pane min-size="10" :size="menuWidth" v-if="!isMobile && !collapseNav && !forceMenuHidden">
<SidePanel @search="showFuzzySearch" />
</Pane>
<Pane min-size="10">
<Pane min-size="10" :size="100 - menuWidth">
<Splitpanes>
<Pane class="router-view min-h-screen">
<router-view></router-view>
@@ -25,7 +25,7 @@
</Pane>
</Splitpanes>
<label
class="btn btn-circle swap bg-base-100 swap-rotate fixed bottom-4 -left-12 w-16 transition-all hover:-left-4"
class="btn btn-circle swap bg-base-content/10 swap-rotate border-base-content/20 hover:border-primary fixed bottom-4 -left-12 w-16 shadow-sm transition-all hover:-left-4"
:class="{ '-left-6!': collapseNav }"
v-if="!isMobile && !forceMenuHidden"
>
@@ -34,11 +34,7 @@
<mdi:chevron-left class="swap-off" />
</label>
</div>
<dialog
ref="modal"
class="modal items-start bg-white/20 transition-none backdrop:backdrop-blur-xs"
@close="open = false"
>
<dialog ref="modal" class="modal bg-base-300/50! items-start backdrop-blur-md transition-none!" @close="open = false">
<div class="modal-box max-w-2xl bg-transparent pt-20 shadow-none">
<FuzzySearchModal @close="open = false" v-if="open" />
</div>
@@ -91,18 +87,18 @@ function showFuzzySearch() {
open.value = true;
}
function onResized(e: any) {
if (e.length == 2) {
menuWidth.value = e[0].size;
function onResized({ panes }: { panes: { size: number }[] }) {
if (panes.length == 2) {
menuWidth.value = Math.min(panes[0].size, 50);
}
}
</script>
<style scoped>
@import "@/main.css" reference;
@reference "@/main.css";
:deep(.splitpanes--vertical > .splitpanes__splitter) {
@apply bg-base-100 hover:bg-secondary min-w-[3px];
@apply bg-base-100 hover:bg-secondary min-w-[5px];
}
@media screen and (max-width: 768px) {
+1 -1
View File
@@ -1,5 +1,5 @@
<template>
<div class="hero min-h-dvh">
<div class="hero max-h-500:pt-4 min-h-dvh content-start pt-[20dvh]">
<div class="hero-content">
<router-view></router-view>
</div>
-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 396 114"><path class="secondary-fill" d="M89.408 46.477a6.49 6.49 0 0 0-6.488 6.49 6.488 6.488 0 1 0 12.978 0 6.49 6.49 0 0 0-6.49-6.49m-42.836 0a6.49 6.49 0 1 0 0 12.98 6.49 6.49 0 0 0 0-12.98M28.5 38.295a6.5 6.5 0 0 1-1.897-4.593 6.474 6.474 0 0 1 6.49-6.49c1.663 0 3.327.631 4.592 1.896l-1.517 1.12a51.67 51.67 0 0 1 31.822-10.91c11.999 0 23.028 4.078 31.823 10.91L98.3 29.113a6.47 6.47 0 0 1 4.592-1.897c1.659 0 3.324.63 4.588 1.897a6.52 6.52 0 0 1 1.892 4.593 6.47 6.47 0 0 1-1.892 4.594l1.514 1.113c21.944 21.942-15.406 31.822-15.406 31.822H42.392c-1.077 0-38.88-8.35-15.406-31.822zM102.214 94.411a7.894 7.894 0 0 1-7.894-7.895 7.893 7.893 0 1 1 15.787 0 7.89 7.89 0 0 1-7.893 7.895M37.355 95.13a7.894 7.894 0 0 1-7.895-7.896 7.894 7.894 0 0 1 7.895-7.893 7.894 7.894 0 0 1 7.896 7.893 7.894 7.894 0 0 1-7.896 7.895M44.372 72.866h47.402s-22.981 7.899-47.402 0"/><path class="content-fill" d="M142 79.928V33.446h13.836c3.571 0 6.886.574 9.947 1.786 3.06 1.147 5.675 2.805 7.779 4.846 4.272 4.144 6.822 10.01 6.822 16.514 0 3.379-.637 6.567-1.913 9.5-1.275 2.933-3.06 5.356-5.228 7.396-4.4 4.017-10.648 6.44-17.407 6.44zm4.91-41.89v37.3h8.735c3.889 0 7.332-.83 10.265-2.423 2.997-1.595 5.356-3.762 6.95-6.568 1.658-2.805 2.487-6.057 2.487-9.628 0-5.292-1.977-10.01-5.356-13.39-3.315-3.315-8.48-5.292-14.346-5.292zm86.587 18.554c0 4.4-1.02 8.416-2.997 12.05q-3.06 5.452-8.416 8.608c-3.634 2.104-7.588 3.188-12.051 3.188-3.252 0-6.312-.637-9.245-1.849-2.869-1.211-5.356-2.869-7.397-5.037-2.04-2.04-3.634-4.527-4.845-7.46-1.148-2.933-1.786-5.993-1.786-9.245q0-6.6 3.061-12.051c1.976-3.635 4.782-6.568 8.416-8.672C201.808 34.02 205.825 33 210.224 33c3.252 0 6.313.574 9.246 1.785 2.869 1.276 5.356 2.933 7.396 4.974 2.04 2.168 3.698 4.654 4.846 7.523 1.211 2.934 1.785 5.994 1.785 9.31m-5.037.255c0-5.229-1.913-10.075-5.101-13.581s-7.97-5.739-13.326-5.739c-3.443 0-6.567.893-9.373 2.55-2.805 1.786-4.973 4.081-6.503 6.95q-2.296 4.4-2.296 9.565c0 5.164 1.913 10.074 5.101 13.517 3.188 3.507 7.97 5.739 13.262 5.739 3.444 0 6.568-.893 9.373-2.615 2.87-1.657 5.037-4.017 6.568-6.886 1.53-2.87 2.295-6.057 2.295-9.5m43.932 18.618v4.463h-33.539v-4.463l27.672-37.555H239.81v-4.464h32.454v4.464l-27.8 37.555zm38.192 0v4.463h-33.538v-4.463l27.672-37.555h-26.715v-4.464h32.454v4.464l-27.8 37.555zm8.927 4.463V33.446h4.91v41.891h22.061v4.591zm34.622 0V33.446h29.075v4.527h-24.165v16.068h22.125v4.527h-22.125v16.833h24.42v4.527z"/></svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

+1 -1
View File
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 934 B

After

Width:  |  Height:  |  Size: 8.6 KiB

+22 -1
View File
@@ -4,13 +4,23 @@
@plugin "@tailwindcss/typography";
@theme {
--color-green: oklch(69% 0.119722 188.479048);
--color-green: oklch(0.62 0.119722 158.82);
--color-red: oklch(64% 0.218 28.85);
--color-purple: oklch(51.49% 0.215 321.03);
--color-blue: oklch(65% 0.171 249.5);
--color-orange: oklch(70% 0.186 48.13);
}
@utility pt-safe {
padding-top: env(safe-area-inset-top);
}
@custom-variant max-h-500 {
@media (max-height: 500px) {
@slot;
}
}
@plugin "daisyui/theme" {
name: "dark";
default: false;
@@ -126,3 +136,14 @@
}
}
}
body {
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
}
[class*="shadow-"] {
@apply shadow-base-content/8;
}
+14 -1
View File
@@ -21,6 +21,13 @@ export class GroupedContainers {
) {}
}
export class HistoricalContainer {
constructor(
public readonly container: Container,
public readonly date: Date,
) {}
}
export class Container {
private _stat: Ref<Stat>;
private _name: string;
@@ -38,6 +45,8 @@ export class Container {
public readonly host: string,
public readonly labels = {} as Record<string, string>,
public state: ContainerState,
public readonly cpuLimit: number,
public readonly memoryLimit: number,
stats: Stat[],
public readonly group?: string,
public health?: ContainerHealth,
@@ -71,7 +80,11 @@ export class Container {
}
get namespace() {
return this.labels["com.docker.stack.namespace"] || this.labels["com.docker.compose.project"];
return (
this.labels["dev.dozzle.group"] ||
this.labels["com.docker.stack.namespace"] ||
this.labels["com.docker.compose.project"]
);
}
get customGroup() {
+41 -10
View File
@@ -4,6 +4,7 @@ import ComplexLogItem from "@/components/LogViewer/ComplexLogItem.vue";
import SimpleLogItem from "@/components/LogViewer/SimpleLogItem.vue";
import ContainerEventLogItem from "@/components/LogViewer/ContainerEventLogItem.vue";
import SkippedEntriesLogItem from "@/components/LogViewer/SkippedEntriesLogItem.vue";
import LoadMoreLogItem from "@/components/LogViewer/LoadMoreLogItem.vue";
export type JSONValue = string | number | boolean | JSONObject | Array<JSONValue>;
export type JSONObject = { [x: string]: JSONValue };
@@ -28,6 +29,7 @@ export interface LogEvent {
readonly p: Position;
readonly s: "stdout" | "stderr" | "unknown";
readonly c: string;
readonly rm: string;
}
export abstract class LogEntry<T extends string | JSONObject> {
@@ -38,6 +40,7 @@ export abstract class LogEntry<T extends string | JSONObject> {
public readonly id: number,
public readonly date: Date,
public readonly std: Std,
public readonly rawMessage: string,
public readonly level?: Level,
) {
this._message = message;
@@ -59,8 +62,9 @@ export class SimpleLogEntry extends LogEntry<string> {
public readonly level: Level,
public readonly position: Position,
public readonly std: Std,
public readonly rawMessage: string,
) {
super(message, containerID, id, date, std, level);
super(message, containerID, id, date, std, rawMessage, level);
}
getComponent(): Component {
return SimpleLogItem;
@@ -77,9 +81,10 @@ export class ComplexLogEntry extends LogEntry<JSONObject> {
date: Date,
public readonly level: Level,
public readonly std: Std,
public readonly rawMessage: string,
visibleKeys?: Ref<Map<string[], boolean>>,
) {
super(message, containerID, id, date, std, level);
super(message, containerID, id, date, std, rawMessage, level);
if (visibleKeys) {
this.filteredMessage = computed(() => {
if (visibleKeys.value.size === 0) {
@@ -123,6 +128,7 @@ export class ComplexLogEntry extends LogEntry<JSONObject> {
event.date,
event.level,
event.std,
event.rawMessage,
visibleKeys,
);
}
@@ -143,7 +149,7 @@ export class ContainerEventLogEntry extends LogEntry<string> {
}
export class SkippedLogsEntry extends LogEntry<string> {
private _totalSkipped = 0;
private _totalSkipped = ref(0);
private lastSkipped: LogEntry<string | JSONObject>;
constructor(
@@ -151,9 +157,10 @@ export class SkippedLogsEntry extends LogEntry<string> {
totalSkipped: number,
public readonly firstSkipped: LogEntry<string | JSONObject>,
lastSkipped: LogEntry<string | JSONObject>,
private readonly loader: (i: SkippedLogsEntry) => Promise<void>,
) {
super("", "", date.getTime(), date, "stderr", "info");
this._totalSkipped = totalSkipped;
this._totalSkipped.value = totalSkipped;
this.lastSkipped = lastSkipped;
}
getComponent(): Component {
@@ -161,20 +168,42 @@ export class SkippedLogsEntry extends LogEntry<string> {
}
public get message(): string {
return `Skipped ${this.totalSkipped} entries`;
return `Skipped ${this._totalSkipped.value} entries`;
}
public addSkippedEntries(totalSkipped: number, lastItem: LogEntry<string | JSONObject>) {
this._totalSkipped += totalSkipped;
this._totalSkipped.value += totalSkipped;
this.lastSkipped = lastItem;
}
public get totalSkipped(): number {
return this._totalSkipped;
public get lastSkippedLog(): LogEntry<string | JSONObject> {
return this.lastSkipped;
}
public get lastSkippedItem(): LogEntry<string | JSONObject> {
return this.lastSkipped;
public async loadSkippedEntries(): Promise<void> {
await this.loader(this);
}
public get totalSkipped(): number {
return unref(this._totalSkipped);
}
}
export class LoadMoreLogEntry extends LogEntry<string> {
constructor(
date: Date,
private readonly loader: (i: LoadMoreLogEntry) => Promise<void>,
public readonly rememberScrollPosition: boolean = true,
) {
super("", "", date.getTime(), date, "stderr", "info");
}
getComponent(): Component {
return LoadMoreLogItem;
}
async loadMore(): Promise<void> {
await this.loader(this);
}
}
@@ -187,6 +216,7 @@ export function asLogEntry(event: LogEvent): LogEntry<string | JSONObject> {
new Date(event.ts),
event.l,
event.s === "unknown" ? "stderr" : (event.s ?? "stderr"),
event.rm,
);
} else {
return new SimpleLogEntry(
@@ -197,6 +227,7 @@ export function asLogEntry(event: LogEvent): LogEntry<string | JSONObject> {
event.l,
event.p,
event.s === "unknown" ? "stderr" : (event.s ?? "stderr"),
event.rm,
);
}
}
@@ -0,0 +1,35 @@
<template>
<HistoricalContainerLog :id :date show-title :scrollable="pinnedLogs.length > 0" v-if="currentContainer" />
<div v-else-if="ready" class="hero bg-base-200 min-h-screen">
<div class="hero-content text-center">
<div class="max-w-md">
<p class="py-6 text-2xl font-bold">{{ $t("error.container-not-found") }}</p>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
const route = useRoute("/container/[id].time.[datetime]");
const id = toRef(() => route.params.id);
const date = toRef(() => new Date(route.params.datetime));
const containerStore = useContainerStore();
const currentContainer = containerStore.currentContainer(id);
const { ready } = storeToRefs(containerStore);
const pinnedLogsStore = usePinnedLogsStore();
const { pinnedLogs } = storeToRefs(pinnedLogsStore);
watchEffect(() => {
if (ready.value) {
if (currentContainer.value) {
setTitle(currentContainer.value.name);
} else {
setTitle("Not Found");
}
}
});
</script>
<route lang="yaml">
meta:
menu: host
</route>
+13 -2
View File
@@ -1,6 +1,6 @@
<template>
<Search />
<ContainerLog :id="id" :show-title="true" :scrollable="pinnedLogs.length > 0" v-if="currentContainer" />
<ContainerLog :id show-title :scrollable="pinnedLogs.length > 0" v-if="currentContainer" />
<div v-else-if="ready" class="hero bg-base-200 min-h-screen">
<div class="hero-content text-center">
<div class="max-w-md">
@@ -39,6 +39,7 @@ watch(currentContainer, () => (redirectTrigger.value = false));
watchEffect(() => {
if (redirectTrigger.value) return;
if (automaticRedirect.value === "none") return;
if (!currentContainer.value) return;
if (currentContainer.value.state === "running") return;
if (Date.now() - +currentContainer.value.finishedAt > 5 * 60 * 1000) return;
@@ -49,7 +50,7 @@ watchEffect(() => {
if (!nextContainer) return;
if (automaticRedirect.value) {
if (automaticRedirect.value === "delayed") {
redirectTrigger.value = true;
showToast(
{
@@ -73,6 +74,16 @@ watchEffect(() => {
},
{ timed: 4000 },
);
} else {
router.push({ name: "/container/[id]", params: { id: nextContainer.id } });
showToast(
{
title: t("alert.redirected.title"),
message: t("alert.redirected.message", { containerId: nextContainer.id }),
type: "info",
},
{ expire: 3000 },
);
}
});
</script>
+3 -3
View File
@@ -4,7 +4,7 @@
<form action="" method="post" @submit.prevent="onLogin" ref="form" class="flex flex-col gap-8">
<label class="form-control w-full">
<label
class="input floating-label input-bordered has-[:focus]:input-primary flex items-center gap-2 border-2"
class="input input-lg floating-label input-bordered has-[:focus]:input-primary flex w-full items-center gap-2 border-2"
:class="{ 'input-error': error }"
>
<span class="ml-5">{{ $t("label.username") }}</span>
@@ -28,7 +28,7 @@
</label>
<label class="form-control w-full">
<label
class="input floating-label input-bordered has-[:focus]:input-primary flex items-center gap-2 border-2"
class="input input-lg floating-label input-bordered has-[:focus]:input-primary flex w-full items-center gap-2 border-2"
>
<span class="ml-5">{{ $t("label.password") }}</span>
<mdi:key class="has-[+:focus]:text-primary" />
@@ -44,7 +44,7 @@
</label>
</label>
<button class="btn btn-primary uppercase" type="submit" :disabled="loading">
<button class="btn btn-primary mt-2 uppercase" type="submit" :disabled="loading">
<span class="loading loading-spinner" v-if="loading"></span>
{{ $t("button.login") }}
</button>
+54 -13
View File
@@ -5,12 +5,35 @@
<h2>{{ $t("settings.about") }}</h2>
</div>
<div>
<div class="flex flex-row gap-2">
<span v-html="$t('settings.using-version', { version: config.version })"></span>
<div
<span
v-if="hasRelease"
v-html="$t('settings.update-available', { nextVersion: latestRelease?.name, href: latestRelease?.htmlUrl })"
></div>
></span>
</div>
<div class="mt-4">
{{ $t("settings.help-support") }}
<ul class="mt-6 flex gap-2">
<li>
<a href="https://github.com/amir20/dozzle" target="_blank" rel="noopener noreferrer" class="btn">
<mdi:github /> amir20/dozzle
</a>
</li>
<li>
<a
href="https://buymeacoffee.com/amirraminfar"
target="_blank"
rel="noopener noreferrer"
class="btn btn-secondary"
>
<mdi:beer />
Buy me a beer
</a>
</li>
</ul>
</div>
</section>
@@ -119,13 +142,28 @@
<div class="has-underline">
<h2>{{ $t("settings.options") }}</h2>
</div>
<LabeledInput>
<template #label>
{{ $t("settings.automatic-redirect") }}
</template>
<template #input>
<DropdownMenu
v-model="automaticRedirect"
:options="[
{ label: 'Instant', value: 'instant' },
{ label: 'Delayed', value: 'delayed' },
{ label: 'None', value: 'none' },
]"
/>
</template>
</LabeledInput>
<Toggle v-model="search">
{{ $t("settings.search") }} <key-shortcut char="f" class="align-top"></key-shortcut>
</Toggle>
<Toggle v-model="showAllContainers">{{ $t("settings.show-stopped-containers") }}</Toggle>
<Toggle v-model="automaticRedirect">{{ $t("settings.automatic-redirect") }}</Toggle>
</section>
</PageWithLinks>
</template>
@@ -166,8 +204,8 @@ const hoursAgo = (hours: number) => {
const fakeMessages = computedWithControl(
() => i18n.global.locale.value,
() => [
new SimpleLogEntry(t("settings.log.preview"), "123", 1, hoursAgo(16), "info", undefined, "stdout"),
new SimpleLogEntry(t("settings.log.warning"), "123", 2, hoursAgo(12), "warn", undefined, "stdout"),
new SimpleLogEntry(t("settings.log.preview"), "123", 1, hoursAgo(16), "info", undefined, "stdout", ""),
new SimpleLogEntry(t("settings.log.warning"), "123", 2, hoursAgo(12), "warn", undefined, "stdout", ""),
new SimpleLogEntry(
t("settings.log.multi-line-error.start-line"),
"123",
@@ -176,6 +214,7 @@ const fakeMessages = computedWithControl(
"error",
"start",
"stderr",
"",
),
new SimpleLogEntry(
t("settings.log.multi-line-error.middle-line"),
@@ -185,8 +224,9 @@ const fakeMessages = computedWithControl(
"error",
"middle",
"stderr",
"",
),
new SimpleLogEntry(t("settings.log.multi-line-error.end-line"), "123", 5, new Date(), "error", "end", "stderr"),
new SimpleLogEntry(t("settings.log.multi-line-error.end-line"), "123", 5, new Date(), "error", "end", "stderr", ""),
new ComplexLogEntry(
{
message: t("settings.log.complex"),
@@ -200,23 +240,24 @@ const fakeMessages = computedWithControl(
new Date(),
"info",
"stdout",
"",
),
new SimpleLogEntry(t("settings.log.simple"), "123", 7, new Date(), "debug", undefined, "stderr"),
new SimpleLogEntry(t("settings.log.simple"), "123", 7, new Date(), "debug", undefined, "stderr", ""),
],
);
</script>
<style scoped>
@import "@/main.css" reference;
@reference "@/main.css";
.has-underline {
@apply border-base-content/50 mb-4 border-b py-4;
@apply border-base-content/50 mb-4 border-b py-2;
h2 {
@apply text-2xl;
@apply text-3xl;
}
}
:deep(a:not(.menu a)) {
:deep(a:not(.menu a):not(.btn)) {
@apply text-primary underline-offset-4 hover:underline;
}
</style>
+20 -1
View File
@@ -12,7 +12,25 @@ type Announcement = {
breaking: number;
};
const { data: releases } = useFetch(withBase("/api/releases")).get().json<Announcement[]>();
const releases = ref<Announcement[]>([]);
let fetched = false;
async function fetchReleases() {
if (fetched) return;
fetched = true;
try {
const { data } = await useFetch(withBase("/api/releases")).get().json<Announcement[]>();
releases.value = data.value || [];
} catch (error) {
console.error("Error while fetching releases:\n", error);
fetched = false;
}
}
if (config.releaseCheckMode === "automatic") {
fetchReleases();
}
const otherAnnouncements = [] as Announcement[];
@@ -33,5 +51,6 @@ export function useAnnouncements() {
announcements,
latestRelease,
hasRelease,
fetchReleases,
};
}
+4
View File
@@ -11,6 +11,10 @@ export interface Config {
hosts: Host[];
authProvider: "simple" | "none" | "forward-proxy";
enableActions: boolean;
enableShell: boolean;
enableDownload: boolean;
disableAvatars: boolean;
releaseCheckMode: "automatic" | "manual";
user?: {
username: string;
email: string;
+2
View File
@@ -154,6 +154,8 @@ export const useContainerStore = defineStore("container", () => {
c.host,
c.labels,
c.state,
c.cpuLimit,
c.memoryLimit,
c.stats,
c.group,
c.health,
+10 -2
View File
@@ -14,7 +14,7 @@ export type Settings = {
dateLocale: "auto" | "en-US" | "en-GB" | "de-DE" | "en-CA";
softWrap: boolean;
collapseNav: boolean;
automaticRedirect: boolean;
automaticRedirect: "instant" | "delayed" | "none";
locale: string;
};
export const DEFAULT_SETTINGS: Settings = {
@@ -31,12 +31,20 @@ export const DEFAULT_SETTINGS: Settings = {
dateLocale: "auto",
softWrap: true,
collapseNav: false,
automaticRedirect: true,
automaticRedirect: "delayed",
locale: "",
};
export const settings = useProfileStorage("settings", DEFAULT_SETTINGS);
// @ts-ignore: automaticRedirect is now a string enum, but might be a boolean in older data
if (settings.value.automaticRedirect === true) {
settings.value.automaticRedirect = "delayed";
// @ts-ignore: automaticRedirect is now a string enum, but might be a boolean in older data
} else if (settings.value.automaticRedirect === false) {
settings.value.automaticRedirect = "none";
}
export const {
collapseNav,
compact,
+73
View File
@@ -21,6 +21,7 @@ declare module 'vue-router/auto-routes' {
'/': RouteRecordInfo<'/', '/', Record<never, never>, Record<never, never>>,
'/[...all]': RouteRecordInfo<'/[...all]', '/:all(.*)', { all: ParamValue<true> }, { all: ParamValue<false> }>,
'/container/[id]': RouteRecordInfo<'/container/[id]', '/container/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
'/container/[id].time.[datetime]': RouteRecordInfo<'/container/[id].time.[datetime]', '/container/:id/time/:datetime', { id: ParamValue<true>, datetime: ParamValue<true> }, { id: ParamValue<false>, datetime: ParamValue<false> }>,
'/group/[name]': RouteRecordInfo<'/group/[name]', '/group/:name', { name: ParamValue<true> }, { name: ParamValue<false> }>,
'/host/[id]': RouteRecordInfo<'/host/[id]', '/host/:id', { id: ParamValue<true> }, { id: ParamValue<false> }>,
'/login': RouteRecordInfo<'/login', '/login', Record<never, never>, Record<never, never>>,
@@ -30,4 +31,76 @@ declare module 'vue-router/auto-routes' {
'/show': RouteRecordInfo<'/show', '/show', Record<never, never>, Record<never, never>>,
'/stack/[name]': RouteRecordInfo<'/stack/[name]', '/stack/:name', { name: ParamValue<true> }, { name: ParamValue<false> }>,
}
/**
* Route file to route info map by unplugin-vue-router.
* Used by the volar plugin to automatically type useRoute()
*
* Each key is a file path relative to the project root with 2 properties:
* - routes: union of route names of the possible routes when in this page (passed to useRoute<...>())
* - views: names of nested views (can be passed to <RouterView name="...">)
*
* @internal
*/
export interface _RouteFileInfoMap {
'assets/pages/index.vue': {
routes: '/'
views: never
}
'assets/pages/[...all].vue': {
routes: '/[...all]'
views: never
}
'assets/pages/container/[id].vue': {
routes: '/container/[id]'
views: never
}
'assets/pages/container/[id].time.[datetime].vue': {
routes: '/container/[id].time.[datetime]'
views: never
}
'assets/pages/group/[name].vue': {
routes: '/group/[name]'
views: never
}
'assets/pages/host/[id].vue': {
routes: '/host/[id]'
views: never
}
'assets/pages/login.vue': {
routes: '/login'
views: never
}
'assets/pages/merged/[ids].vue': {
routes: '/merged/[ids]'
views: never
}
'assets/pages/service/[name].vue': {
routes: '/service/[name]'
views: never
}
'assets/pages/settings.vue': {
routes: '/settings'
views: never
}
'assets/pages/show.vue': {
routes: '/show'
views: never
}
'assets/pages/stack/[name].vue': {
routes: '/stack/[name]'
views: never
}
}
/**
* Get a union of possible route names in a certain route component file.
* Used by the volar plugin to automatically type useRoute()
*
* @internal
*/
export type _RouteNamesForFilePath<FilePath extends string> =
_RouteFileInfoMap extends Record<FilePath, infer Info>
? Info['routes']
: keyof RouteNamedMap
}
+2
View File
@@ -16,6 +16,8 @@ export type ContainerJson = {
readonly status: string;
readonly state: ContainerState;
readonly host: string;
readonly cpuLimit: number;
readonly memoryLimit: number;
readonly labels: Record<string, string>;
readonly stats: ContainerStat[];
readonly health?: ContainerHealth;
+35 -2
View File
@@ -1,10 +1,19 @@
export function formatBytes(bytes: number, decimals = 2) {
export function formatBytes(
bytes: number,
{ decimals = 2, short = false }: { decimals?: number; short?: boolean } = { decimals: 2, short: false },
) {
if (bytes === 0) return "0 Bytes";
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
const value = parseFloat((bytes / Math.pow(k, i)).toFixed(dm));
if (short) {
return value + sizes[i].charAt(0);
} else {
return value + " " + sizes[i];
}
}
export function getDeep(obj: Record<string, any>, path: string[]) {
@@ -100,3 +109,27 @@ export function hashCode(str: string) {
}
return hash;
}
const units: [Intl.RelativeTimeFormatUnit, number][] = [
["year", 31536000],
["month", 2592000],
["week", 604800],
["day", 86400],
["hour", 3600],
["minute", 60],
["second", 1],
];
export function toRelativeTime(date: Date, locale: string | undefined): string {
const diffInSeconds = (date.getTime() - new Date().getTime()) / 1000;
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
for (const [unit, seconds] of units) {
const value = Math.round(diffInSeconds / seconds);
if (Math.abs(value) >= 1) {
return rtf.format(value, unit);
}
}
return rtf.format(0, "second");
}
+1 -1
View File
@@ -95,7 +95,7 @@ services:
playwright:
container_name: playwright
image: mcr.microsoft.com/playwright:v1.51.0-jammy
image: mcr.microsoft.com/playwright:v1.55.1-jammy
working_dir: /app
volumes:
- .:/app
+2
View File
@@ -57,6 +57,7 @@ export default defineConfig({
items: [
{ text: "What is Dozzle?", link: "/guide/what-is-dozzle" },
{ text: "Getting Started", link: "/guide/getting-started" },
{ text: "Introducing dtop 🚀", link: "/guide/dtop" },
],
},
{
@@ -72,6 +73,7 @@ export default defineConfig({
items: [
{ text: "Authentication", link: "/guide/authentication" },
{ text: "Actions", link: "/guide/actions" },
{ text: "Shell Access", link: "/guide/shell" },
{ text: "Agent Mode", link: "/guide/agent" },
{ text: "Changing Base", link: "/guide/changing-base" },
{ text: "Container Names", link: "/guide/container-names" },
+1 -1
View File
@@ -4,7 +4,7 @@ title: Container Actions
# Using Container Actions
Dozzle supports Container Actions, which allows you to `start`, `stop` and `restart` containers from within the UI in the dropdown menu. This feature is **disabled** by default and can be enabled by setting the environment variable `DOZZLE_ENABLE_ACTIONS` to `true`.
Dozzle supports container actions, which allows you to `start`, `stop` and `restart` containers from the dropdown menu on the right next to the container stats. This feature is **disabled** by default and can be enabled by setting the environment variable `DOZZLE_ENABLE_ACTIONS` to `true`.
::: code-group
+22 -7
View File
@@ -32,6 +32,10 @@ services:
:::
> [!NOTE] Docker Socket Proxy users
> If you are using a remote agent you **CANNOT** add a socket proxy on top of the agent. Dozzle agents **REPLACE** using a proxy, see [Remote Hosts](/guide/remote-hosts.md) for more info and how to use a socket proxy instead of an agent.
The agent will start and listen on port `7007`. You can connect to the agent using the Dozzle UI by providing the agent's IP address and port. The agent will only show the containers that are available on the host where the agent is running.
> [!TIP]
@@ -64,10 +68,21 @@ Note that when connecting remotely, you don't need to mount local Docker socket.
> [!TIP]
> You can connect to multiple agents by providing multiple `DOZZLE_REMOTE_AGENT` environment variables. For example, `DOZZLE_REMOTE_AGENT=agent1:7007,agent2:7007`.
> [!WARNING]
> Dozzle uses the Docker API to gather information about hosts. Each agent needs a unique host ID. They use Docker's system ID or node ID to identify the host. If you are using swarm, then the node ID is used. If you don't see all hosts, then you may have duplicate hosts configured that have the same host ID. To fix this, remove `/var/lib/docker/engine-id` file. See [FAQ](/guide/faq#i-am-seeing-duplicate-hosts-error-in-the-logs-how-do-i-fix-it) for more information.
## Common Issues
## Setting Up Healthcheck
### Agent Not Showing Up
If you are seeing `An agent with an existing ID was found. Removing the duplicate host.` then you have two hosts that use the same Server ID.
Dozzle utilizes the Docker API to collect information about hosts. Each agent requires a unique host ID that remains consistent across restarts to ensure proper identification. Currently, agents identify the host using either Docker's system ID or node ID.
If you are operating in a Swarm environment, the node ID will be employed for this purpose. However, if you notice that not all hosts are visible, it may be due to the presence of duplicate hosts configured with the same host ID.
To resolve this issue, you should remove `/var/lib/docker/engine-id` from your system and restart. This action will help eliminate any conflicts caused by duplicate host IDs. For additional information and troubleshooting tips, please refer to the [FAQ](/guide/faq#i-am-seeing-duplicate-hosts-error-in-the-logs-how-do-i-fix-it).
## Advanced Options
### Setting Up Healthcheck
You can set a healthcheck for the agent, similar to the healthcheck for the main Dozzle instance. When running in agent mode, healthcheck checks agent connection to Docker. If Docker is not reachable, the agent will be marked as unhealthy and will not be shown in the UI.
@@ -90,7 +105,7 @@ services:
- 7007:7007
```
## Changing Agent's Name
### Changing Agent's Name
Similar to Dozzle instance, you can change the agent's name by providing the `DOZZLE_HOSTNAME` environment variable. Here is an example:
@@ -117,7 +132,7 @@ services:
This will change the agent's name to `my-special-name` and will be reflected on the UI when connecting to the agent.
## Setting Up Filters
### Setting Up Filters
You can set up filters for the agent to limit the containers it can access. These filters are passed directly to Docker, restricting what Dozzle can view.
@@ -134,7 +149,7 @@ services:
This will restrict the agent to displaying only containers with the label `color`. Keep in mind that these filters are combined with the UI filters to narrow down the containers. To learn more about the different types of filters, read the [filters documentation](/guide/filters#ui-agents-and-user-filters).
## Custom Certificates
### Custom Certificates
By default, Dozzle uses self-signed certificates for communication between agents. This is a private certificate which is only valid to other Dozzle instances. This is secure and recommended for most use cases. However, if Dozzle is exposed externally and an attacker knows exactly which port the agent is running on, then they can set up their own Dozzle instance and connect to the agent. To prevent this, you can provide your own certificates.
@@ -169,7 +184,7 @@ This will mount the `cert.pem` and `key.pem` files to the agent. The agent will
To generate certificates, you can use the following command:
```sh
$ openssl genpkey -algorithm RSA -out key.pem -pkeyopt rsa_keygen_bits:2048
$ openssl genpkey -algorithm Ed25519 -out key.pem
$ openssl req -new -key key.pem -out request.csr -subj "/C=US/ST=California/L=San Francisco/O=My Company"
$ openssl x509 -req -in request.csr -signkey key.pem -out cert.pem -days 365
```
+41 -5
View File
@@ -10,7 +10,12 @@ If you do not have an authentication solution, then Dozzle has a simple file-bas
## File-Based User Management
Dozzle supports multi-user authentication by setting `--auth-provider` to `simple`. In this mode, Dozzle will try to read `/data/users.yml`.
Dozzle supports multi-user authentication by setting `--auth-provider` to `simple`. In this mode, Dozzle will attempt to read the users file from `/data/`, prioritizing `users.yml` over `users.yaml` if both files are present. If only one of the files exists, it will be used. The log will indicate which file is being read (e.g., `Reading users.yml file`).
### Example file paths:
- `/data/users.yml`
- `/data/users.yaml`
The content of the file looks like:
@@ -20,15 +25,16 @@ users:
admin:
email: me@email.net
name: Admin
# Generate with docker run amir20/dozzle generate --name Admin --email me@email.net --password secret admin
# Generate with docker run -it --rm amir20/dozzle generate --name Admin --email me@email.net --password secret admin
password: $2a$11$9ho4vY2LdJ/WBopFcsAS0uORC0x2vuFHQgT/yBqZyzclhHsoaIkzK
filter:
roles:
```
Dozzle uses `email` to generate avatars using [Gravatar](https://gravatar.com/). It is optional. The password is hashed using `bcrypt` which can be generated using `docker run amir20/dozzle generate`.
> [!WARNING]
> In previous versions of Dozzle, SHA-256 was used to hash passwords. Bcrypt is now more secure and is recommended for future use. Dozzle will revert to SHA-256 if it does not find a bcrypt hash. It is advisable to update the password hash to bcrypt using `docker run amir20/dozzle generate`. For more details, see [this issue](https://github.com/amir20/dozzle/security/advisories/GHSA-w7qr-q9fh-fj35).
> In previous versions of Dozzle, SHA-256 was used to hash passwords. Bcrypt is now more secure and is recommended for future use. Dozzle will revert to SHA-256 if it does not find a bcrypt hash. It is advisable to update the password hash to bcrypt using `generate`. For more details, see [this issue](https://github.com/amir20/dozzle/security/advisories/GHSA-w7qr-q9fh-fj35).
You will need to mount this file for Dozzle to find it. Here is an example:
@@ -134,15 +140,44 @@ In this example, the `admin` user has no filter, so they can see all containers.
> [!NOTE]
> Filters can also be set [globally](/guide/filters) with the `--filter` flag. This flag is applied to all users. If a user has a filter set, it will override the global filter.
### Setting specific roles for users
Dozzle allows assigning roles to users. Roles define what actions a user can perform on containers. Roles are configured in the users.yml file.
```yaml
users:
admin:
email:
name: Admin
password: $2a$11$9ho4vY2LdJ/WBopFcsAS0uORC0x2vuFHQgT/yBqZyzclhHsoaIkzK
roles:
guest:
email:
name: Guest
password: $2a$11$9ho4vY2LdJ/WBopFcsAS0uORC0x2vuFHQgT/yBqZyzclhHsoaIkzK
roles: shell
```
In this example, the `admin` user has no roles specified, so they have full access to all container actions. The `guest` user has the shell role, meaning they can only open a shell in the containers. Roles make it easy to control and restrict what users can do in Dozzle.
Dozzle supports the following roles:
- **shell** - allows attach and exec in the container
- **actions** - allows performing container actions (start, stop, restart)
- **download** - allows downloading container logs
- **none** - denies all actions
- **all** - allows all actions (default)
## Generating users.yml
Dozzle has a built-in `generate` command to generate `users.yml`. Here is an example:
```sh
docker run amir20/dozzle generate admin --password password --email test@email.net --name "John Doe" --user-filter name=foo > users.yml
docker run -it --rm amir20/dozzle generate admin --password password --email test@email.net --name "John Doe" --user-filter name=foo --user-roles shell > users.yml
```
In this example, `admin` is the username. Email and name are optional but recommended to display accurate avatars. `docker run amir20/dozzle generate --help` displays all options. The `--user-filter` flag is a comma-separated list of filters.
In this example, `admin` is the username. Email and name are optional but recommended to display accurate avatars. `docker run -it --rm amir20/dozzle generate --help` displays all options. The `--user-filter` flag is a comma-separated list of filters. The `--user-roles` flag is a comma-separated list of roles.
## Forward Proxy
@@ -174,6 +209,7 @@ In this mode, Dozzle expects the following headers:
- `Remote-Email` to map to the user's email address. This email is also used to find the right [Gravatar](https://gravatar.com/) for the user.
- `Remote-Name` to be a display name like `John Doe`
- `Remote-Filter` to be a comma-separated list of filters allowed for user.
- `Remote-Roles` to be a comma-separated list of roles allowed for user.
### Setting up Dozzle with Authelia
+3
View File
@@ -42,5 +42,8 @@ location ^~ /foobar/ {
chunked_transfer_encoding off;
proxy_buffering off;
proxy_cache off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
```

Some files were not shown because too many files have changed in this diff Show More