Compare commits

..

345 Commits

Author SHA1 Message Date
Amir Raminfar f8fbd796b7 chore: updates mods 2025-03-07 08:41:50 -08:00
renovate[bot] e4a42125c5 fix(deps): update all non-major dependencies (#3681)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-05 16:04:17 +00:00
renovate[bot] db2b7b12a3 fix(deps): update all non-major dependencies (#3680)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 16:41:58 +00:00
renovate[bot] d6b262aead chore(deps): update dependency prettier to ^3.5.3 (#3679)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-03 02:49:02 +00:00
renovate[bot] 917489838d fix(deps): update all non-major dependencies (#3676)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-02 13:17:57 +00:00
Amir Raminfar cefee41857 chore: updates mods (#3677) 2025-03-01 08:23:26 -08:00
Amir Raminfar 8455e2743d chore: adds readme to examples 2025-03-01 08:22:33 -08:00
Amir Raminfar a5cc1f7ca0 chore: adds readme to examples 2025-03-01 08:22:26 -08:00
Amir Raminfar 1ffc338720 chore: adds readme to examples 2025-03-01 08:21:46 -08:00
renovate[bot] 19bb865913 fix(deps): update all non-major dependencies (#3672)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-27 16:45:35 +00:00
renovate[bot] bb4dca2386 fix(deps): update all non-major dependencies (#3671)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-26 19:09:12 +00:00
Amir Raminfar 808439365f chore: release v8.11.7 2025-02-24 15:28:24 -08:00
Amir Raminfar c1038c0a9e chore: adds warning log if containers are empty using user filter (#3669) 2025-02-24 20:42:30 +00:00
Amir Raminfar 15e410a874 chore: updates readme 2025-02-24 08:29:32 -08:00
renovate[bot] 5049a53a95 chore(deps): update all non-major dependencies to ^2.2.4 (#3668)
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-02-24 01:42:45 +00:00
Amir Raminfar c86d108997 chore: adds app version to logger (#3666)
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-02-23 08:51:30 -08:00
Amir Raminfar 604c4b9b16 chore: updates docs
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-02-22 16:59:54 -08:00
Amir Raminfar 64b03d7b92 chore: updates docs 2025-02-22 16:58:28 -08:00
Amir Raminfar a533533d4c chore: updates docs 2025-02-22 11:37:27 -08:00
Amir Raminfar 2d50820932 fix: uses request context instead of timeout for actions (#3663) 2025-02-22 19:23:59 +00:00
Amir Raminfar 3190868268 chore: release v8.11.6
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-02-22 08:45:12 -08:00
renovate[bot] 2c031da510 fix(deps): update all non-major dependencies (#3661)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-22 16:36:30 +00:00
Amir Raminfar 58ececc42a chore: fixes static
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-02-21 15:05:29 -08:00
Amir Raminfar 7be55cbc08 chore: refactors sub commands 2025-02-21 15:03:14 -08:00
garikkh 90b90ceea4 feat: Supports log levels with common separators (#3656) 2025-02-21 11:56:02 -08:00
renovate[bot] bc842389f9 fix(deps): update all non-major dependencies to v4.0.8 (#3658)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-21 16:25:29 +00:00
Amir Raminfar cf9bf4dcd5 chore: release v8.11.5
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-02-20 10:57:33 -08:00
Amir Raminfar 3eec803e45 chore: updates docker to v28 (#3655) 2025-02-20 18:54:25 +00:00
renovate[bot] e332b4225b fix(deps): update all non-major dependencies (#3653)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-20 18:41:29 +00:00
Amir Raminfar 538d16a5b7 chore: updates docs 2025-02-20 10:28:35 -08:00
Amir Raminfar 561081ebf0 chore: removes crowdin 2025-02-20 08:52:11 -08:00
Amir Raminfar c3d2e7ac09 chore: fix pt lang 2025-02-20 08:47:18 -08:00
Amir Raminfar 90808aff68 chore: Lang updates from crowdin (#3651)
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-02-20 01:52:50 +00:00
Amir Raminfar 77bfb43b6d fix: Removes streams from k8s. See #3646 (#3647)
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-02-19 09:28:34 -08:00
Amir Raminfar 49881c2dfe chore: updates git issues template 2025-02-19 08:40:27 -08:00
Amir Raminfar 3f901769cb chore: updates docs 2025-02-19 08:35:33 -08:00
Amir Raminfar 26f7704edb chore: updates components 2025-02-19 07:49:13 -08:00
renovate[bot] 3686f5c54a fix(deps): update all non-major dependencies (#3645)
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-02-19 15:01:58 +00:00
Amir Raminfar aabd9dd994 chore: removes replica
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-02-18 12:22:45 -08:00
Amir Raminfar 52e0be0ac6 chore: release v8.11.4 2025-02-18 12:20:47 -08:00
Amir Raminfar bb0ffbfa04 chore: updates docs 2025-02-18 12:20:38 -08:00
Amir Raminfar 4022b510e2 feat: adds labels support to k8s (#3642) 2025-02-18 12:15:19 -08:00
renovate[bot] 21fb03c9d0 fix(deps): update all non-major dependencies (#3641)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-18 18:23:05 +00:00
Amir Raminfar 392f480c76 feat: adds streams to k8s
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-02-17 15:47:50 -08:00
renovate[bot] c9418d4b32 chore(deps): update pnpm to v10.4.1 (#3639)
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-02-17 03:40:39 +00:00
Amir Raminfar 14b14e6210 chore: adds funding
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-02-16 08:53:46 -08:00
Amir Raminfar 3d5b279a1b Update Crowdin configuration file 2025-02-16 08:18:48 -08:00
Jérémy Touati aca3e3a219 fix: fixes ansi-to-html fallback colors (#3635) 2025-02-16 06:11:31 -08:00
Amir Raminfar 241d349d39 chore: release v8.11.3
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-02-15 10:08:27 -08:00
Amir Raminfar b098dcaef0 fix: fixes missing client 2025-02-15 10:07:20 -08:00
Amir Raminfar 7cf67eb99a chore: release v8.11.2 2025-02-15 09:49:01 -08:00
renovate[bot] 869d5e5002 fix(deps): update all non-major dependencies (#3633)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-15 17:48:48 +00:00
Amir Raminfar 392dfafc3d chore: updates docs 2025-02-15 09:37:57 -08:00
Amir Raminfar 21fe5b84f2 feat: supports namespaces properly in k8s mode (#3631) 2025-02-15 09:35:32 -08:00
Amir Raminfar 65fa04c877 chore: adds metrics to docs
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-02-14 07:46:58 -08:00
renovate[bot] 3a352a7320 fix(deps): update all non-major dependencies (#3629)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-14 15:01:24 +00:00
Amir Raminfar 3bdc9c04f8 chore: release v8.11.1
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-02-13 16:41:06 -08:00
Amir Raminfar 6e3c811974 fix: fixes broken nil pointer. fixes #3626 (#3627)
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-02-13 13:31:25 -08:00
Amir Raminfar ddff9e96a2 chore: updates static go version 2025-02-13 08:35:11 -08:00
Amir Raminfar 1daf813e1b chore: release v8.11.0 2025-02-13 08:21:04 -08:00
renovate[bot] 09a169851b fix(deps): update all non-major dependencies (#3624)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-13 16:19:57 +00:00
Amir Raminfar 1d5ddee1ae docs: adds k8s in docs (#3623) 2025-02-13 08:19:37 -08:00
Amir Raminfar b7e9871382 chore: adds tests cases back
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-02-12 17:02:24 -08:00
Amir Raminfar 9e028b9916 chore: improves tests 2025-02-12 15:55:49 -08:00
renovate[bot] 78c0e5fd7f fix(deps): update dependency pinia to ^3.0.1 (#3619)
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-02-12 19:59:28 +00:00
Amir Raminfar 4861503488 chore: updates to go v1.24 (#3622) 2025-02-12 11:53:52 -08:00
Amir Raminfar 115b0e475d docs: adds k8s in docs (#3620) 2025-02-12 15:53:56 +00:00
Amir Raminfar 6d928815df chore: updates pinia (#3618)
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-02-11 23:00:43 +00:00
Amir Raminfar 2a70d0b458 chore: add metrics to config 2025-02-11 14:54:37 -08:00
Amir Raminfar 8c9c9a83af fix: fixes deadlocks in store (#3617)
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-02-11 12:32:56 -08:00
Amir Raminfar 571de4084e chore: removes survey 2025-02-11 09:57:36 -08:00
Amir Raminfar 5abf7e4a9d docs: remove banner for survey 2025-02-11 09:22:27 -08:00
renovate[bot] d580e9447b chore(deps): update pnpm to v10.3.0 (#3616)
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-02-11 03:29:31 +00:00
Amir Raminfar 877faaa740 chore: fixes broken interface 2025-02-10 14:30:37 -08:00
Amir Raminfar 1d4698d77a chore: adds error for agent 2025-02-10 14:19:05 -08:00
Amir Raminfar 13da2a4222 feat: supports k8s cluster (#3599)
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-02-10 09:29:39 -08:00
renovate[bot] 209ce312d4 fix(deps): update all non-major dependencies to v4.0.6 (#3615)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-10 17:21:40 +00:00
Amir Raminfar 6da36c4c0d chore: release v8.10.7
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-02-09 14:28:09 -08:00
renovate[bot] d9d93a8147 fix(deps): update all non-major dependencies (#3612)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-09 22:19:39 +00:00
Amir Raminfar eba87fa49e fix: updates default timeout and adds user agent. see #3610 (#3611) 2025-02-09 14:13:31 -08:00
Amir Raminfar 7ae409981c chore: removes flaky test
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-02-09 07:15:17 -08:00
renovate[bot] 20fb7984d2 fix(deps): update all non-major dependencies (#3607)
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-02-07 05:42:10 +00:00
Amir Raminfar 17c15b65a2 Merge branch 'patch-v8.10.6'
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-02-05 10:44:00 -08:00
Amir Raminfar 9ce736a448 chore: release v8.10.6 2025-02-05 10:36:10 -08:00
Amir Raminfar 10c95124e4 fixes build 2025-02-05 10:34:16 -08:00
Amir Raminfar 5e02142f77 Revert "chore: release v8.10.6"
This reverts commit f9585cc656.
2025-02-05 10:29:40 -08:00
Amir Raminfar f9585cc656 chore: release v8.10.6 2025-02-05 10:26:26 -08:00
Amir Raminfar 6da7f6dffd fix: fixes broken download link when host name has : 2025-02-05 10:23:34 -08:00
renovate[bot] 2de739141d fix(deps): update all non-major dependencies (#3603)
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-02-05 17:45:33 +00:00
renovate[bot] 585e1edfee fix(deps): update all non-major dependencies (#3596)
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 Tests (push) Waiting to run
Test / Go Staticcheck (push) Waiting to run
Test / Typecheck (push) Waiting to run
Test / JavaScript Tests (push) Waiting to run
Test / Integration Tests (push) Waiting to run
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-04 16:31:19 +00:00
Amir Raminfar bf81852003 chore: tries to update pnpm (#3598) 2025-02-04 08:26:12 -08:00
Amir Raminfar 5f73b41c57 chore: refactors to be more generic (#3594)
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-02-03 12:42:09 -08:00
renovate[bot] 9f7b17f4ec fix(deps): update dependency fuse.js to ^7.1.0 (#3593)
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-02-03 01:13:30 +00:00
renovate[bot] 6c292be943 fix(deps): update all non-major dependencies (#3592)
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
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-02 18:33:04 +00:00
renovate[bot] 21ae6eda8b fix(deps): update all non-major dependencies to v4.0.3 (#3591)
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
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-01 18:38:58 +00:00
renovate[bot] b732844e7b fix(deps): update all non-major dependencies (#3588)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-31 23:46:07 +00:00
Amir Raminfar 1b38f37ac2 chore: remove data
Test / JavaScript Tests (push) Has been cancelled
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 / 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-01-30 08:26:10 -08:00
renovate[bot] c20ce29f5c fix(deps): update all non-major dependencies (#3584)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-30 16:10:09 +00:00
Amir Raminfar 47e64bb57a fix: fixes --menu-active-bg 2025-01-30 08:05:18 -08:00
renovate[bot] b9a5262257 fix(deps): update all non-major dependencies (#3583)
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-01-29 23:50:02 +00:00
Amir Raminfar 47204b33c4 chore: updates npm modules
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-01-28 10:38:02 -08:00
renovate[bot] 162f9ea580 fix(deps): update module github.com/samber/lo to v1.49.0 (#3579)
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-01-27 00:42:58 +00:00
renovate[bot] 2381a8b8a3 fix(deps): update all non-major dependencies (#3578)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-26 17:50:29 +00:00
Amir Raminfar 8fbbfaf043 feat: adds new menu for custom groups (#3573)
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-01-25 10:06:58 -08:00
renovate[bot] 3d077b9503 chore(deps): update dependency bumpp to v10 (#3577)
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-01-25 15:57:47 +00:00
renovate[bot] 68ea86c55b fix(deps): update all non-major dependencies (#3576)
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-01-24 21:48:43 +00:00
Amir Raminfar ad5c1c4452 fix(docs): fixes label
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-01-23 11:23:34 -08:00
Amir Raminfar 0efd98e999 chore: fixes snapshots 2025-01-23 09:28:37 -08:00
renovate[bot] ff72431a72 fix(deps): update all non-major dependencies (#3572)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-23 09:26:24 -08:00
Amir Raminfar 6d2ed53629 chore: release v8.10.5 2025-01-22 10:01:03 -08:00
Amir Raminfar 992e1a27ae chore: generate 2025-01-22 10:00:53 -08:00
Amir Raminfar 2378a1f115 chore: adds survey as link (#3571) 2025-01-22 17:58:06 +00:00
Amir Raminfar 3b122564d9 chore: adds ref 2025-01-22 09:29:20 -08:00
renovate[bot] a783f7060c fix(deps): update all non-major dependencies (#3570)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-22 16:54:37 +00:00
renovate[bot] 76e5c63436 fix(deps): update all non-major dependencies to v4.0.0 (#3569)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-21 22:16:23 +00:00
Amir Raminfar 5a17a07b8f chore: docs fixes x again 2025-01-21 11:27:04 -08:00
Amir Raminfar d1fc9b750b chore: docs fixes x again 2025-01-21 10:27:12 -08:00
Amir Raminfar 87a59939dd chore: fixes close button 2025-01-21 09:59:35 -08:00
Amir Raminfar 3a159cb89b fix: only truncate right the container name and not hostname (#3567) 2025-01-21 08:40:03 -08:00
Amir Raminfar 84ec46c400 docs: adds banner for survey (#3566) 2025-01-21 08:19:00 -08:00
renovate[bot] 0c4ca6d605 fix(deps): update all non-major dependencies (#3565)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-21 15:55:12 +00:00
renovate[bot] db5d36c89b fix(deps): update all non-major dependencies (#3564)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-20 17:46:49 +00:00
renovate[bot] 8cdacf608a fix(deps): update all non-major dependencies (#3562)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-19 15:23:47 +00:00
renovate[bot] 78487a553c chore(deps): update all non-major dependencies (#3561)
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-01-17 19:32:03 +00:00
renovate[bot] 8da67482d8 chore(deps): update dependency vitest to v3 (#3560)
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-01-16 17:10:57 +00:00
renovate[bot] 60914f3208 chore(deps): update dependency @apache-arrow/esnext-esm to v19 (#3559)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-16 08:36:07 -08:00
renovate[bot] 0dc2a391df chore(deps): update all non-major dependencies (#3558)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-16 16:35:16 +00:00
renovate[bot] 4cfd3d0cfa fix(deps): update all non-major dependencies (#3556)
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-01-15 17:29:46 +00:00
renovate[bot] 9752504097 fix(deps): update all non-major dependencies (#3551)
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-01-14 16:01:45 +00:00
Amir Raminfar f08f2455de chore: release v8.10.4
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-01-13 19:08:55 -08:00
Amir Raminfar b2ea1c592a fix: fixes prefix 2025-01-13 19:08:08 -08:00
Amir Raminfar 6f89a1e70b chore: release v8.10.3 2025-01-13 18:35:59 -08:00
Amir Raminfar 93ce48ebf3 chore: fixes release 2025-01-13 18:35:48 -08:00
Amir Raminfar 0412581b6f Revert "chore: release v8.10.3"
This reverts commit 285d0ef0f4.
2025-01-13 18:31:52 -08:00
Amir Raminfar 285d0ef0f4 chore: release v8.10.3 2025-01-13 18:26:07 -08:00
Webysther Sperandio be313a68a3 feat: add more tags to same release (#3550) 2025-01-13 18:24:00 -08:00
Amir Raminfar 14ddd1c4e8 fix: adds more translations (#3548) 2025-01-13 16:10:19 -08:00
Amir Raminfar 368646a7a3 feat: improves toasts for mobile (#3547)
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-01-13 19:16:17 +00:00
renovate[bot] 691a89660e fix(deps): update dependency unplugin-vue-macros to ^2.13.8 (#3546)
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-01-13 01:24:48 +00:00
Amir Raminfar d03844b488 feat: improves toasts for mobile (#3545) 2025-01-12 14:04:57 -08:00
Amir Raminfar 3f860805f5 fix: moves to the right card depending on route (#3544) 2025-01-12 13:53:53 -08:00
renovate[bot] e8eef82b1c fix(deps): update dependency splitpanes to ^3.1.8 (#3541)
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-01-12 01:01:52 +00:00
Amir Raminfar e7e52cbe80 chore: release v8.10.2
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-01-11 08:21:06 -08:00
Amir Raminfar cd9409f7e8 chore: cleans up cases in default.vue (#3542) 2025-01-11 16:17:56 +00:00
Amir Raminfar d455c3eed8 fix: fixes unclickable items in search (#3540) 2025-01-11 15:56:25 +00:00
Amir Raminfar 35fd47d157 fix(docs): fixes volume on authentication page
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-01-10 13:09:21 -08:00
Amir Raminfar 58d6848084 fix: fixes tests 2025-01-10 12:52:54 -08:00
Amir Raminfar 11e1285158 fix: fixes broken host page on initial page load 2025-01-10 12:47:01 -08:00
Amir Raminfar 43e3f4cd9d chore: adds correct pointer cursor to carasoul dots
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-01-10 08:56:37 -08:00
Amir Raminfar 5b822bdcfb fix: fixes hostname feature for swarm mode (#3535) 2025-01-10 14:52:04 +00:00
renovate[bot] 0f1a916ff5 fix(deps): update all non-major dependencies to ^12.4.0 (#3534)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-10 14:51:31 +00:00
renovate[bot] 5766410892 fix(deps): update all non-major dependencies (#3532)
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-01-09 18:20:20 +00:00
Amir Raminfar 7c061a5ef1 chore: tries to fix bake build (#3531) 2025-01-09 10:15:23 -08:00
renovate[bot] 05f55538b4 chore(deps): update dependency jsdom to v26 (#3530)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-09 09:36:40 -08:00
renovate[bot] e8752c2e25 chore(deps): update docker/bake-action action to v6 (#3526)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-09 09:36:01 -08:00
Amir Raminfar 3cb7418ede fix(lang): adds more locales 2025-01-09 08:30:38 -08:00
renovate[bot] 37eccc5724 fix(deps): update dependency unplugin-vue-components to v28 (#3529)
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-01-09 02:57:31 +00:00
renovate[bot] b29e77e05d fix(deps): update dependency unplugin-auto-import to v19 (#3527)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-09 02:51:52 +00:00
renovate[bot] a389c31b68 fix(deps): update dependency unplugin-icons to v22 (#3528)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-08 18:42:07 -08:00
renovate[bot] 45b15ccc1b chore(deps): update all non-major dependencies (#3525)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-09 02:39:56 +00:00
Amir Raminfar 45ac8740f0 chore: updates docs to have secrets
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-01-08 08:29:59 -08:00
Amir Raminfar 2f0dd4f36f chore: removes mobile-hidden (#3522) 2025-01-08 08:14:15 -08:00
Amir Raminfar 19ebbccc29 chore: release v8.10.1
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-01-07 11:38:18 -08:00
renovate[bot] 90459ff3d5 fix(deps): update all non-major dependencies (#3521)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-07 19:37:53 +00:00
Amir Raminfar 5d57439d3d fix: fixes grouping not working with unknown levels (#3520) 2025-01-07 19:33:19 +00:00
James Shaw 5c33f4437e feat: handle log levels with double quotes (#3519)
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-01-07 08:55:38 -08:00
Amir Raminfar cbbe5f7571 chore: types 2025-01-07 06:26:25 -08:00
Amir Raminfar d79c517425 fix: fixes case for FINISHED
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-01-06 15:24:14 -08:00
Amir Raminfar 130defa367 chore: release v8.10.0 2025-01-06 14:58:57 -08:00
Amir Raminfar f8e7dafd8a fix: fixes broken menu on iphone 2025-01-06 14:51:44 -08:00
Amir Raminfar 81f24d1c26 chore: fix color 2025-01-06 14:49:34 -08:00
Amir Raminfar fdc3f06402 chore: removes transition on fuzzy search 2025-01-06 14:42:52 -08:00
Amir Raminfar a1a9031cda fix: removes empty containers (#3511) 2025-01-06 13:40:29 -08:00
Amir Raminfar 151a3efb16 chore: fixes tests 2025-01-06 13:25:00 -08:00
Amir Raminfar 42cc254eb7 chore: fixes CSP 2025-01-06 13:19:24 -08:00
Amir Raminfar 917c837e90 feat: adds a new drop down to jump to same name containers (#3510) 2025-01-06 12:57:20 -08:00
Amir Raminfar 756b212826 fix: fixes timestamps for finished containers in agent (#3509)
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-01-06 07:12:02 -08:00
Amir Raminfar dae2877bdb fix(docs): fixes documentation wrong port 2025-01-06 07:02:37 -08:00
Amir Raminfar 66cc3ddec8 fix: fixes spacing on homepage 2025-01-06 07:02:37 -08:00
renovate[bot] cd4e233d87 chore(deps): update all non-major dependencies (#3508)
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-01-06 02:07:21 +00:00
renovate[bot] 1b82929bc6 fix(deps): update all non-major dependencies (#3507)
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-01-05 22:25:24 +00:00
Amir Raminfar e99107da90 fix: fixes redirect for very old containers and removes the notification if cancelled (#3506) 2025-01-05 14:19:12 -08:00
Amir Raminfar efa95078ac chore: upgrades to tailwind v4 and daisyUI v5 🎨 (#3505) 2025-01-05 21:38:36 +00:00
Amir Raminfar 40015f7385 chore: fixes finishedAt correctly when container stops
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-01-04 14:20:16 -08:00
Amir Raminfar 46fb09e2f7 feat: handles container exits better and uses the real time when container has exited (#3504)
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-01-03 13:09:36 -08:00
Amir Raminfar cc620ad429 chore: clean up (#3503) 2025-01-03 17:51:05 +00:00
Amir Raminfar 8c6c6882cd fix: updates da and es 2025-01-02 13:22:41 -08:00
Amir Raminfar 64c23bc1db chore: fixes minor height (#3501) 2025-01-02 20:56:16 +00:00
Amir Raminfar 7f12f01267 fix: fixes mobile menu animation and flex (#3500) 2025-01-02 19:04:12 +00:00
renovate[bot] 0f9071944f fix(deps): update all non-major dependencies (#3499)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-02 17:21:35 +00:00
Amir Raminfar 291d4f7f37 feat: improves navigation by implementing horizontal scrolling cards (#3498) 2025-01-02 09:07:27 -08:00
Jason N. White 2bf811d632 Update LICENSE, fix copyright license year (#3497) 2025-01-01 06:41:02 -08:00
Amir Raminfar 2a3f77be48 chore: updates icons (#3496) 2024-12-31 17:22:32 +00:00
Amir Raminfar 9518114816 fix: fixes levels not matching the new set (#3495) 2024-12-31 17:12:16 +00:00
Amir Raminfar 65ef1d3c68 fix: shows container and host name correctly for events. (#3494) 2024-12-31 17:07:52 +00:00
Amir Raminfar f4f61a6a75 chore: clean up 2024-12-31 08:57:15 -08:00
Amir Raminfar cc76c00933 feat: enable merging logs for all containers in a host (#3492) 2024-12-31 08:55:48 -08:00
Amir Raminfar 08e5deba7e feat: adds toggle on menu to show and hide all containers (#3493) 2024-12-31 02:42:51 +00:00
Amir Raminfar 05e01852ce chore: release v8.9.1 2024-12-30 10:09:39 -08:00
Amir Raminfar 9cff809a8e fix: fixes log levels not being parsed correctly and supports short levels like dbg (#3491) 2024-12-30 17:59:01 +00:00
Amir Raminfar d93efedc11 feat: supports downloading a group of containers in a zip file (#3490) 2024-12-30 17:24:55 +00:00
Amir Raminfar 984452c181 feat: deploys to ghcr (#3487) 2024-12-30 07:44:02 -08:00
renovate[bot] 471cc19132 fix(deps): update dependency @intlify/unplugin-vue-i18n to ^6.0.3 (#3488)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-30 00:57:36 +00:00
renovate[bot] 7ec053e893 fix(deps): update dependency @fontsource-variable/playfair-display to ^5.1.1 (#3486)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-30 00:11:43 +00:00
renovate[bot] af0a8f0e2d fix(deps): update all non-major dependencies (#3484)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-28 22:54:43 +00:00
renovate[bot] 52de74e595 fix(deps): update dependency vue-i18n to v11 (#3482) 2024-12-26 16:49:57 +00:00
renovate[bot] 4b19d13d02 fix(deps): update all non-major dependencies (#3481)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-26 16:38:13 +00:00
Amir Raminfar 5a532b50e3 Chore: Update renovate.json 2024-12-26 08:33:07 -08:00
renovate[bot] 91d63882e0 fix(deps): update all non-major dependencies (#3480)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-24 17:03:48 +00:00
renovate[bot] a98b1059d9 fix(deps): update dependency entities to v6 (#3476) 2024-12-22 18:07:11 -08:00
renovate[bot] 0d51523d80 fix(deps): update all non-major dependencies (#3475)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-23 01:46:27 +00:00
Amir Raminfar 1a69694c16 fix: fixes stats hidden on mobile (#3474) 2024-12-22 15:58:42 +00:00
Amir Raminfar e5c1f884a9 fix: uses correct i18n locale for grouped containers (#3469) 2024-12-19 09:02:38 -08:00
Amir Raminfar d34cf32cce fix: handles swarm mode menu on page load (#3470) 2024-12-19 09:02:26 -08:00
renovate[bot] fc2a2766f7 fix(deps): update all non-major dependencies (#3468)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-19 16:39:55 +00:00
Amir Raminfar 4fa04d0176 fix: fixes spacing on mobile (#3466) 2024-12-17 16:24:10 -08:00
Amir Raminfar 86522ccf77 chore: uses air instead of reflex (#3465) 2024-12-17 14:57:40 +00:00
Amir Raminfar 23ba1f6603 chore: improves security by updating all find containers with user scope (#3464) 2024-12-16 21:49:12 +00:00
renovate[bot] 3adf56018a fix(deps): update all non-major dependencies (#3463)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-16 19:06:45 +00:00
Amir Raminfar d02fbae4a9 chore: exposes types (#3461) 2024-12-16 11:01:29 -08:00
renovate[bot] 6479bbba10 fix(deps): update module github.com/go-chi/chi/v5 to v5.2.0 (#3462)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-16 00:30:34 +00:00
Amir Raminfar bd0a81f332 fix: fixes missed cases to validate user scope when downloading, streaming and actions (#3460) 2024-12-14 13:12:43 -08:00
Amir Raminfar f2e56f79b7 chore: release v8.9.0 2024-12-14 10:29:52 -08:00
Amir Raminfar d312871d89 feat: allows filters to be set at user level (#3456) 2024-12-14 18:25:15 +00:00
Amir Raminfar 5a5a0d329b feat: adds small loader while logging in (#3457) 2024-12-14 07:58:15 -08:00
Amir Raminfar 2f382ba34e makes button disabled 2024-12-13 18:31:02 -08:00
Amir Raminfar 92c60b29bd makes error better 2024-12-13 18:27:30 -08:00
Amir Raminfar 85a304b2e8 feat: adds small loader while logging in 2024-12-13 18:27:29 -08:00
Amir Raminfar d3e573d03b feat: enables container filter to be configured at multiple places (#3455) 2024-12-13 18:25:15 -08:00
Amir Raminfar 9f2d172277 fixes missed filter 2024-12-13 15:25:23 -08:00
Amir Raminfar 55baf9e12c fixes static checker 2024-12-13 14:36:06 -08:00
Amir Raminfar d2e1b26fb4 fixes all tests 2024-12-13 14:30:46 -08:00
Amir Raminfar 864af3c24d fixes nil pointer 2024-12-13 14:14:51 -08:00
Amir Raminfar c0b40da585 adds logging 2024-12-13 13:20:15 -08:00
Amir Raminfar 242c3e74dc fixes some tests 2024-12-13 13:15:17 -08:00
Amir Raminfar 9a73d18da6 removes unused code 2024-12-13 12:07:43 -08:00
Amir Raminfar 74b5adad00 feat: enables container filter to be configured at multiple places 2024-12-13 11:59:39 -08:00
renovate[bot] a62cef7e25 fix(deps): update all non-major dependencies (#3454)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-13 15:56:13 +00:00
Amir Raminfar 668a0d9b19 fix: fixes redirect when container was created before it was stopped (#3446) 2024-12-12 15:43:53 -08:00
renovate[bot] 39be1ddf69 fix(deps): update all non-major dependencies (#3453)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-12 20:00:39 +00:00
Amir Raminfar 672a5f8c73 Merge branch 'master' into redirect-fix 2024-12-12 11:48:25 -08:00
Amir Raminfar 289fc661f0 fix: hides stats when columns are too narrow. (#3452) 2024-12-12 11:47:20 -08:00
Amir Raminfar 9b0e731f90 fix: hides stats when columns are too narrow. 2024-12-12 11:43:42 -08:00
Amir Raminfar 7ce5b9514d Merge branch 'master' into redirect-fix 2024-12-12 08:10:40 -08:00
Amir Raminfar f50d45dcb2 removes running 2024-12-12 08:07:04 -08:00
renovate[bot] 2081335867 fix(deps): update module golang.org/x/crypto to v0.31.0 [security] (#3450)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-12 01:20:49 +00:00
Amir Raminfar 69d380e5bd fix: fixes redirect when container was created before it was stopped 2024-12-11 11:04:15 -08:00
Amir Raminfar 8400319a85 fix: fixes flushing race bug where container events were not show on page (#3445) 2024-12-11 10:13:28 -08:00
Amir Raminfar b7bd763b54 fix: fixes flushing race bug where container events were not show on page 2024-12-10 12:25:19 -08:00
renovate[bot] eefa3532c1 fix(deps): update dependency daisyui to ^4.12.20 (#3443)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-09 03:44:21 +00:00
renovate[bot] 441d2d1c7b chore(deps): update all non-major dependencies (#3442)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-09 00:58:34 +00:00
Amir Raminfar 41188947a9 chore: release v8.8.3 2024-12-08 10:54:29 -08:00
renovate[bot] 877f53462c fix(deps): update all non-major dependencies (#3441)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-08 18:50:28 +00:00
Jérémy Touati c2e3879c1f fix: fix ansi link color + regex (#3440) 2024-12-08 18:44:43 +00:00
Amir Raminfar 327ae70428 chore: updates npm modules 2024-12-06 12:15:14 -08:00
renovate[bot] be197ea19d chore(deps): update all non-major dependencies (#3419)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-04 17:08:22 +00:00
Amir Raminfar 749b19f2c1 chore: ignores typescript 2024-12-04 09:03:03 -08:00
Amir Raminfar ec5ab3a0fe chore: updates npm modules (#3436) 2024-12-04 08:57:43 -08:00
Amir Raminfar 0ab5ad2c5d fix: fixes language bug where locales were upper case (#3435) 2024-12-04 08:46:25 -08:00
Amir Raminfar 1448371c23 chore: updates vite (#3433) 2024-12-02 16:11:14 +00:00
Amir Raminfar daee5b7dc4 chore: updates go modules (#3430) 2024-11-30 00:10:46 +00:00
Amir Raminfar 08a7d39e43 chore: updates node modules (#3429) 2024-11-29 15:50:06 +00:00
Amir Raminfar fd13320152 chore: updates node modules (#3427) 2024-11-28 07:34:28 -08:00
Ben 823959f8bb docs: just fix podman run in readme (#3424) 2024-11-26 20:01:43 -08:00
Amir Raminfar a99d711b78 chore: updates node modules (#3423) 2024-11-26 15:49:56 +00:00
Amir Raminfar 75f089b770 chore: removes unused component 2024-11-25 19:05:40 -08:00
Amir Raminfar 508408c0af chore: release v8.8.2 2024-11-25 08:21:59 -08:00
Amir Raminfar 5b4ca4d766 feat: improves healthcheck for swarm nodes (#3422) 2024-11-25 08:10:51 -08:00
Amir Raminfar 036bee5621 chore: enables debug (#3418) 2024-11-25 08:10:34 -08:00
Amir Raminfar c36cf72506 feat: improves colors for containers 2024-11-24 09:07:04 -08:00
Amir Raminfar 129f80e30e chore: cleans up swarm files 2024-11-24 09:05:45 -08:00
Amir Raminfar 3f3346d003 feat: improves loader random order of boxes (#3416) 2024-11-21 16:07:16 +00:00
renovate[bot] ef6e0569b7 fix(deps): update all non-major dependencies to ^11.3.0 (#3415)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-21 15:15:33 +00:00
Amir Raminfar 4c1f5b4062 docs: fixes grammar and spelling errors (#3413) 2024-11-20 09:41:58 -08:00
renovate[bot] 9d244cf26b chore(deps): update all non-major dependencies (#3412)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-20 15:11:51 +00:00
xmnlz 002dbea904 fix(#3347): Improve label styling in scrollable container resolve #3347 (#3411) 2024-11-20 06:59:26 -08:00
Amir Raminfar d41f3f192a chore: release v8.8.1 2024-11-19 15:03:16 -08:00
renovate[bot] 75fa2d1a22 chore(deps): update all non-major dependencies (#3410)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-19 22:36:23 +00:00
Amir Raminfar c3f6ff9c56 feat: adds a short delay while waiting for logs to show (#3409) 2024-11-19 20:01:03 +00:00
Rhea Alleen 95de5a8504 docs: Fixes for previous commit of Podman host not found errors documentation (#3408) 2024-11-19 10:31:27 -08:00
Rhea Alleen 50855a8700 docs: Add podman.md in docs, adjust readme.md/faq.md with host not found error documentation (#3407) 2024-11-19 09:31:56 -08:00
Amir Raminfar bd7f6bc886 feat: adds show hostname and container name to dropdown in mutli log mode (#3406) 2024-11-19 16:46:20 +00:00
Amir Raminfar 7ee260311e feat: improves the loader for logs (#3404) 2024-11-18 12:27:29 -08:00
Amir Raminfar 011294d198 chore: makes swarm fish executable 2024-11-18 11:17:09 -08:00
Amir Raminfar 7ccc312190 fix: improves condense view by expanding the tags (#3402) 2024-11-18 08:04:21 -08:00
renovate[bot] 484260d492 fix(deps): update dependency @iconify-json/material-symbols to ^1.2.7 (#3403)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-18 00:30:47 +00:00
Amir Raminfar cbdd0cc961 chore: adds swarm setup 2024-11-17 13:15:40 -08:00
Amir Raminfar cb808df400 chore: release v8.8.0 2024-11-16 15:40:37 -08:00
Amir Raminfar d4fa7ace90 fix: fixes log levels that have ansi color by stripping (#3400) 2024-11-16 23:38:46 +00:00
Amir Raminfar 1144bdfbd2 feat: enables swarm cluster to connect to external agents (#3394)
Co-authored-by: Mitch Brown <mitch@mitchbrown.ca>
2024-11-16 15:17:29 -08:00
renovate[bot] 9a296ca504 fix(deps): update dependency @intlify/unplugin-vue-i18n to v6 (#3399) 2024-11-16 08:56:23 -08:00
Vladimir Urushev 80d502eb29 fix: fixes log parser (#3398) 2024-11-16 08:24:40 -08:00
Amir Raminfar 56a2ab11a9 chore: release v8.7.4 2024-11-15 09:45:52 -08:00
renovate[bot] 675a4ab334 chore(deps): update all non-major dependencies (#3393)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-15 17:35:50 +00:00
Amir Raminfar 197ab6d4da feat: show loader while opening event source (#3388) 2024-11-15 07:41:44 -08:00
Amir Raminfar 9296b1f7cf chore: clean up mods 2024-11-14 16:04:57 -08:00
renovate[bot] dcf261f418 fix(deps): update all non-major dependencies (#3391)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-14 22:01:53 +00:00
Amir Raminfar c032f788fb feat: supports container rename (#3390) 2024-11-14 21:54:56 +00:00
renovate[bot] b3177e95d9 chore(deps): update all non-major dependencies (#3387)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-13 18:11:20 +00:00
Amir Raminfar 48e509e19f fix: fixes broken minimum logs. see #3383 (#3384) 2024-11-13 05:34:46 -08:00
Amir Raminfar d7662c32d0 chore: release v8.7.3 2024-11-11 13:44:17 -08:00
renovate[bot] 3487d6cb38 fix(deps): update all non-major dependencies (#3382)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-11 21:43:18 +00:00
Amir Raminfar feee4a07b4 chore: attempts to install go for static checker (#3381) 2024-11-11 21:39:22 +00:00
renovate[bot] ea006a0d54 chore(deps): update dependency @vitejs/plugin-vue to v5.1.5 (#3380)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-11 03:48:30 +00:00
Amir Raminfar d99964bbe7 fix: fixes pagination when logs are very long packed together. see #3373 (#3377) 2024-11-10 19:44:19 -08:00
renovate[bot] f3cfe4f84d fix(deps): update all non-major dependencies (#3379)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-10 22:44:44 +00:00
renovate[bot] 8408f887b6 fix(deps): update all non-major dependencies (#3376)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-08 15:25:56 +00:00
renovate[bot] 16d0dc6671 chore(deps): update all non-major dependencies to v1.23.3 (#3375)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-07 16:14:14 +00:00
renovate[bot] 596861412d chore(deps): update all non-major dependencies (#3374)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-06 16:49:49 +00:00
renovate[bot] 06136e716a chore(deps): update all non-major dependencies (#3372)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-04 17:48:35 +00:00
renovate[bot] 5db3448207 chore(deps): update all non-major dependencies (#3371)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-03 23:40:25 +00:00
Amir Raminfar c57d6ed4f0 docs: fix healthcheck page 2024-11-03 14:43:25 -08:00
Amir Raminfar b85eeeddd4 chore: changes healthcheck for debug 2024-11-03 14:21:17 -08:00
Amir Raminfar 964429a95e chore: fixes docs 2024-11-02 10:52:01 -07:00
Amir Raminfar e22d54cf74 chore: release v8.7.2 2024-11-02 10:47:03 -07:00
renovate[bot] 7bf331cf6e fix(deps): update all non-major dependencies (#3369)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-02 17:46:01 +00:00
Amir Raminfar d529ed7855 feat: checks level with equal sign (#3368) 2024-11-02 10:40:49 -07:00
Amir Raminfar 46d124b11a chore: updates version 2024-11-01 16:39:22 -07:00
Amir Raminfar d09aa5d481 chore: updates log 2024-11-01 14:17:24 -07:00
Amir Raminfar cc77c16f92 feat: skips healthcheck for agents (#3366) 2024-11-01 08:28:10 -07:00
renovate[bot] 63be3bf2d8 fix(deps): update all non-major dependencies (#3363)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-30 21:53:50 +00:00
Amir Raminfar ec29c78052 chore: updates to node v23 (#3362) 2024-10-30 08:57:06 -07:00
renovate[bot] df3f04dd50 chore(deps): update dependency @apache-arrow/esnext-esm to v18 (#3361)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-30 07:55:41 -07:00
renovate[bot] 7af7956b7f chore(deps): update all non-major dependencies (#3360)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-30 14:53:33 +00:00
renovate[bot] d21473fe0b chore(deps): update all non-major dependencies (#3359)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-29 17:00:09 +00:00
Jérémy Touati 4093512497 fix: fix link color when using colorized logs (#3357) 2024-10-28 12:36:45 -07:00
Amir Raminfar 0ef6252158 chore: release v8.7.1 2024-10-28 09:51:24 -07:00
Amir Raminfar 5947338966 chore: fixes vue-ignore 2024-10-28 09:37:48 -07:00
renovate[bot] db5e760ae3 chore(deps): update all non-major dependencies (#3351)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-28 15:32:26 +00:00
Amir Raminfar 1ab4464f9a chore: cleans up types (#3356) 2024-10-28 15:27:31 +00:00
renovate[bot] 45ea4ac095 chore(config): migrate renovate config (#3354)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-28 14:40:33 +00:00
Amir Raminfar c6339effd1 fix: fix base ANSI convertor colors (#3353)
Co-authored-by: Jérémy Touati <sielver@gmail.com>
2024-10-27 20:37:02 +00:00
Amir Raminfar 4e217f4e3a chore: release v8.7.0 2024-10-27 08:18:51 -07:00
Amir Raminfar c58f3b4254 chore: reverts changes to proto 2024-10-27 08:15:22 -07:00
Amir Raminfar a7680940ab chore: removes drop shadow for icons (#3350) 2024-10-27 14:21:53 +00:00
Amir Raminfar cce2e8a8b5 fix: fixes toggle all when fields have not been set for JSON (#3349) 2024-10-27 14:16:20 +00:00
Amir Raminfar 8513824fb4 chore: updates protos 2024-10-27 07:04:18 -07:00
renovate[bot] 00da15c2fc fix(deps): update all non-major dependencies (#3348)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-26 15:20:11 +00:00
Amir Raminfar c1d31313b2 fix: unfound fields are shown by default instead of hidden (#3342) 2024-10-26 15:15:29 +00:00
Amir Raminfar 6f7c1d8ba6 chore: updates snapshots 2024-10-25 16:06:50 -07:00
Amir Raminfar f1982c094d chore: fixes logo on mobile 2024-10-25 15:54:08 -07:00
yubiuser cd63b5ded8 fix: limit default SQL log entries to 100 (#3346)
Signed-off-by: yubiuser <github@yubiuser.dev>
2024-10-25 21:08:12 +00:00
Amir Raminfar 96fbee9ece feat: updates logo (#3340) 2024-10-25 17:55:23 +00:00
Amir Raminfar 36a5d7e6b9 fix: fixes timeout flag (#3345) 2024-10-25 17:48:54 +00:00
renovate[bot] 520d82246f chore(deps): update all non-major dependencies (#3343)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-25 16:21:34 +00:00
255 changed files with 9859 additions and 6065 deletions
+57
View File
@@ -0,0 +1,57 @@
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
bin = "./tmp/dozzle"
cmd = "go build -race -o ./tmp/dozzle ."
delay = 1000
exclude_dir = [
"assets",
"tmp",
"data",
"node_modules",
"docs",
"test-results",
"dist",
"e2e",
"examples",
]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
kill_delay = "2s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = true
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
silent = false
time = false
[misc]
clean_on_exit = false
[screen]
clear_on_rebuild = false
keep_scroll = true
+2 -1
View File
@@ -23,12 +23,13 @@ body:
- Agents
- Remote Hosts
- Swarm Mode
- Kubernetes
validations:
required: true
- type: input
attributes:
label: 📦 Dozzle version
description: Can be found in logs or settings page.
description: Run `docker run amir20/dozzle --version` or `curl http://<host>/api/version`.
validations:
required: true
- type: textarea
+1
View File
@@ -0,0 +1 @@
buy_me_a_coffee: amirraminfar
+3 -2
View File
@@ -1,5 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base", "schedule:weekly", "group:allNonMajor", ":automergeMinor"],
"rangeStrategy": "bump"
"extends": ["config:recommended", "schedule:weekly", "group:allNonMajor", ":automergeMinor"],
"rangeStrategy": "bump",
"ignoreDeps": []
}
+18 -4
View File
@@ -69,8 +69,9 @@ jobs:
echo "${{ secrets.TTL_KEY }}" > shared_key.pem
echo "${{ secrets.TTL_CERT }}" > shared_cert.pem
- name: Build
uses: docker/bake-action@v5
uses: docker/bake-action@v6
with:
source: .
load: true
set: |
*.cache-from=type=gha
@@ -89,25 +90,38 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Login to DockerHub
uses: docker/login-action@v3.3.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the Container registry
uses: docker/login-action@v3.3.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout
uses: actions/checkout@v4
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: amir20/dozzle
images: |
amir20/dozzle
ghcr.io/amir20/dozzle
tags: |
type=raw,value=latest
type=semver,pattern={{version}},prefix=v
type=semver,pattern={{major}}.{{minor}},prefix=v
type=semver,pattern={{major}},prefix=v
- name: Writing certs to file
run: |
echo "${{ secrets.TTL_KEY }}" > shared_key.pem
echo "${{ secrets.TTL_CERT }}" > shared_cert.pem
- name: Build and push
uses: docker/build-push-action@v6.9.0
uses: docker/build-push-action@v6.15.0
with:
push: true
context: .
+11 -3
View File
@@ -13,25 +13,33 @@ 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@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Login to DockerHub
uses: docker/login-action@v3.3.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to the Container registry
uses: docker/login-action@v3.3.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout
uses: actions/checkout@v4
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: amir20/dozzle
images: |
amir20/dozzle
ghcr.io/amir20/dozzle
- name: Writing certs to file
run: |
echo "${{ secrets.TTL_KEY }}" > shared_key.pem
echo "${{ secrets.TTL_CERT }}" > shared_cert.pem
- name: Build and push
uses: docker/build-push-action@v6.9.0
uses: docker/build-push-action@v6.15.0
with:
context: .
push: true
+8 -2
View File
@@ -74,12 +74,17 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: "1.24"
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
with:
version: "latest"
install-go: false
int-test:
name: Integration Tests
timeout-minutes: 60
@@ -104,8 +109,9 @@ jobs:
- name: Generate certs
run: make shared_key.pem shared_cert.pem
- name: Build
uses: docker/bake-action@v5
uses: docker/bake-action@v6
with:
source: .
load: true
set: |
*.cache-from=type=gha
+1
View File
@@ -14,3 +14,4 @@ coverage.out
/playwright/.cache/
*.pem
*.csr
tmp
-1
View File
@@ -1 +0,0 @@
-r '\.(go)$' -R 'node_modules' -G '\*\_test.go' -s -- go run -race main.go --level debug agent
-1
View File
@@ -1 +0,0 @@
-r '\.(go)$' -R 'node_modules' -G '\*\_test.go' -s -- go run -race main.go --level debug
-16
View File
@@ -1,16 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Debug test",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/main.go",
"args": ["test"]
}
]
}
-7
View File
@@ -1,7 +0,0 @@
{
"i18n-ally.localesPaths": ["locales"],
"i18n-ally.keystyle": "nested",
"cSpell.words": ["healthcheck", "orderedmap", "Retriable", "stdcopy", "Warnf"],
"editor.formatOnSave": true,
"i18n-ally.extract.autoDetect": true
}
+6 -5
View File
@@ -1,5 +1,5 @@
# Build assets
FROM --platform=$BUILDPLATFORM node:22.10.0-alpine AS node
FROM --platform=$BUILDPLATFORM node:23.9.0-alpine AS node
RUN corepack enable
@@ -22,13 +22,13 @@ COPY public ./public
# Build assets
RUN pnpm build
FROM --platform=$BUILDPLATFORM golang:1.23.2-alpine AS builder
FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder
# install gRPC dependencies
RUN apk add --no-cache ca-certificates protoc protobuf-dev\
&& mkdir /dozzle \
&& go install google.golang.org/protobuf/cmd/protoc-gen-go@latest \
&& go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
&& mkdir /dozzle \
&& go install google.golang.org/protobuf/cmd/protoc-gen-go@latest \
&& go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
WORKDIR /dozzle
@@ -38,6 +38,7 @@ RUN go mod download
# Copy all other files
COPY internal ./internal
COPY types ./types
COPY main.go ./
COPY protos ./protos
COPY shared_key.pem shared_cert.pem ./
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2018 Amir Raminfar
Copyright (c) 2025 Amir Raminfar
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+1 -1
View File
@@ -61,7 +61,7 @@ push: docker
tools:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
go install github.com/cespare/reflex@latest
go install github.com/air-verse/air@latest
run: docker
docker run -it --rm -p 8080:8080 -v /var/run/docker.sock:/var/run/docker.sock amir20/dozzle:latest
+12 -4
View File
@@ -65,7 +65,7 @@ Dozzle can be used to monitor multiple Docker hosts. You can run Dozzle in agent
$ docker run -v /var/run/docker.sock:/var/run/docker.sock -p 7007:7007 amir20/dozzle:latest agent
See the [Agent Mode](https://dozzle.dev/guide/agent-mode) documentation for more details.
See the [Agent Mode](https://dozzle.dev/guide/agent) documentation for more details.
## Technical Details
@@ -94,9 +94,15 @@ If it's not enabled please follow [this tutorial](https://github.com/containers/
Once you have the podman remote socket you can run Dozzle on podman.
```
podman run --volume=/run/user/1000/podman/podman.sock:/var/run/docker.sock -d -p 8080:8080 amir20/dozzle:latest
podman run --volume=/run/user/1000/podman/podman.sock:/var/run/docker.sock -d -p 8080:8080 docker.io/amir20/dozzle:latest
```
Additionally you have to create a fake engine-id to prevent `host not found` errors. Podman doesn't generate an engine-id like Docker by itself due to its daemonless architecture.
Under `/var/lib/docker` create a file named `engine-id`. On a system with Podman you will have to create the folder path as well. Inside the file place the UUID, for instance using `uuidgen > engine-id`. After that the file should have an identifier that looks like this: `b9f1d7fc-b459-4b6e-9f7a-e3d1cd2e14a9`.
For more details check [Podman Infos](docs/guide/podman.md) or the [FAQ](docs/guide/faq.md#i-am-seeing-host-not-found-error-in-the-logs-how-do-i-fix-it)
## Security
Dozzle supports file based authentication and forward proxy like [Authelia](https://www.authelia.com/). These are documented at https://dozzle.dev/guide/authentication.
@@ -121,12 +127,14 @@ Dozzle follows the [12-factor](https://12factor.net/) model. Configurations can
| `--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 |
| `--enable-actions` | `DOZZLE_ENABLE_ACTIONS` | `false` |
| `--filter` | `DOZZLE_FILTER` | `""` |
| `--no-analytics` | `DOZZLE_NO_ANALYTICS` | false |
| `--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` | `""` |
## Support
+1 -1
View File
@@ -18,7 +18,7 @@ watchEffect(() => {
document.documentElement.setAttribute("data-theme", theme);
});
</script>
<style lang="postcss">
<style>
html.has-custom-scrollbars {
::-webkit-scrollbar {
width: 8px;
+30 -4
View File
@@ -87,6 +87,7 @@ declare global {
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
const onDeactivated: typeof import('vue')['onDeactivated']
const onElementRemoval: typeof import('@vueuse/core')['onElementRemoval']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
const onLongPress: typeof import('@vueuse/core')['onLongPress']
@@ -161,6 +162,7 @@ declare global {
const until: typeof import('@vueuse/core')['until']
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
const useAnimate: typeof import('@vueuse/core')['useAnimate']
const useAnnouncements: typeof import('./stores/announcements')['useAnnouncements']
const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery']
const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter']
@@ -191,6 +193,7 @@ declare global {
const useContainerActions: typeof import('./composable/containerActions')['useContainerActions']
const useContainerStore: typeof import('./stores/container')['useContainerStore']
const useContainerStream: typeof import('./composable/eventStreams')['useContainerStream']
const useCountdown: typeof import('@vueuse/core')['useCountdown']
const useCounter: typeof import('@vueuse/core')['useCounter']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVar: typeof import('@vueuse/core')['useCssVar']
@@ -234,6 +237,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 useHostStream: typeof import('./composable/eventStreams')['useHostStream']
const useHosts: typeof import('./stores/hosts')['useHosts']
const useI18n: typeof import('vue-i18n')['useI18n']
const useId: typeof import('vue')['useId']
@@ -280,14 +284,15 @@ declare global {
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
const usePreferredReducedTransparency: typeof import('@vueuse/core')['usePreferredReducedTransparency']
const usePrevious: typeof import('@vueuse/core')['usePrevious']
const useProfileStorage: typeof import('./composable/profileStorage')['useProfileStorage']
const useRafFn: typeof import('@vueuse/core')['useRafFn']
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
const useReleases: typeof import('./stores/releases')['useReleases']
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
const useRoute: typeof import('vue-router')['useRoute']
const useRouter: typeof import('vue-router')['useRouter']
const useSSRWidth: typeof import('@vueuse/core')['useSSRWidth']
const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation']
const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea']
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
@@ -367,9 +372,25 @@ declare global {
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
// @ts-ignore
export type { DrawerWidth } from './composable/drawer'
import('./composable/drawer')
// @ts-ignore
export type { LogStreamSource } from './composable/eventStreams'
import('./composable/eventStreams')
// @ts-ignore
export type { Config, Profile } from './stores/config'
import('./stores/config')
// @ts-ignore
export type { Host } from './stores/hosts'
import('./stores/hosts')
// @ts-ignore
export type { Settings } from './stores/settings'
import('./stores/settings')
}
// for vue template auto import
import { UnwrapRef } from 'vue'
declare module 'vue' {
@@ -456,6 +477,7 @@ declare module 'vue' {
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
readonly onClickOutside: UnwrapRef<typeof import('@vueuse/core')['onClickOutside']>
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
readonly onElementRemoval: UnwrapRef<typeof import('@vueuse/core')['onElementRemoval']>
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
readonly onKeyStroke: UnwrapRef<typeof import('@vueuse/core')['onKeyStroke']>
readonly onLongPress: UnwrapRef<typeof import('@vueuse/core')['onLongPress']>
@@ -530,6 +552,7 @@ declare module 'vue' {
readonly until: UnwrapRef<typeof import('@vueuse/core')['until']>
readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
readonly useAnimate: UnwrapRef<typeof import('@vueuse/core')['useAnimate']>
readonly useAnnouncements: UnwrapRef<typeof import('./stores/announcements')['useAnnouncements']>
readonly useArrayDifference: UnwrapRef<typeof import('@vueuse/core')['useArrayDifference']>
readonly useArrayEvery: UnwrapRef<typeof import('@vueuse/core')['useArrayEvery']>
readonly useArrayFilter: UnwrapRef<typeof import('@vueuse/core')['useArrayFilter']>
@@ -560,6 +583,7 @@ declare module 'vue' {
readonly useContainerActions: UnwrapRef<typeof import('./composable/containerActions')['useContainerActions']>
readonly useContainerStore: UnwrapRef<typeof import('./stores/container')['useContainerStore']>
readonly useContainerStream: UnwrapRef<typeof import('./composable/eventStreams')['useContainerStream']>
readonly useCountdown: UnwrapRef<typeof import('@vueuse/core')['useCountdown']>
readonly useCounter: UnwrapRef<typeof import('@vueuse/core')['useCounter']>
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
readonly useCssVar: UnwrapRef<typeof import('@vueuse/core')['useCssVar']>
@@ -603,6 +627,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 useHostStream: UnwrapRef<typeof import('./composable/eventStreams')['useHostStream']>
readonly useHosts: UnwrapRef<typeof import('./stores/hosts')['useHosts']>
readonly useI18n: UnwrapRef<typeof import('vue-i18n')['useI18n']>
readonly useId: UnwrapRef<typeof import('vue')['useId']>
@@ -649,14 +674,15 @@ declare module 'vue' {
readonly usePreferredDark: UnwrapRef<typeof import('@vueuse/core')['usePreferredDark']>
readonly usePreferredLanguages: UnwrapRef<typeof import('@vueuse/core')['usePreferredLanguages']>
readonly usePreferredReducedMotion: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedMotion']>
readonly usePreferredReducedTransparency: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedTransparency']>
readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
readonly useProfileStorage: UnwrapRef<typeof import('./composable/profileStorage')['useProfileStorage']>
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
readonly useReleases: UnwrapRef<typeof import('./stores/releases')['useReleases']>
readonly useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
readonly useSSRWidth: UnwrapRef<typeof import('@vueuse/core')['useSSRWidth']>
readonly useScreenOrientation: UnwrapRef<typeof import('@vueuse/core')['useScreenOrientation']>
readonly useScreenSafeArea: UnwrapRef<typeof import('@vueuse/core')['useScreenSafeArea']>
readonly useScriptTag: UnwrapRef<typeof import('@vueuse/core')['useScriptTag']>
@@ -733,4 +759,4 @@ declare module 'vue' {
readonly whenever: UnwrapRef<typeof import('@vueuse/core')['whenever']>
readonly withBase: UnwrapRef<typeof import('./stores/config')['withBase']>
}
}
}
+12 -2
View File
@@ -2,15 +2,18 @@
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
// biome-ignore lint: disable
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
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']
'Carbon:play': typeof import('~icons/carbon/play')['default']
'Carbon:restart': typeof import('~icons/carbon/restart')['default']
@@ -18,6 +21,8 @@ declare module 'vue' {
'Carbon:starFilled': typeof import('~icons/carbon/star-filled')['default']
'Carbon:stopFilledAlt': typeof import('~icons/carbon/stop-filled-alt')['default']
'Carbon:warning': typeof import('~icons/carbon/warning')['default']
Carousel: typeof import('./components/common/Carousel.vue')['default']
CarouselItem: typeof import('./components/common/CarouselItem.vue')['default']
'Cil:checkCircle': typeof import('~icons/cil/check-circle')['default']
'Cil:circle': typeof import('~icons/cil/circle')['default']
'Cil:columns': typeof import('~icons/cil/columns')['default']
@@ -27,7 +32,6 @@ declare module 'vue' {
ContainerEventLogItem: typeof import('./components/LogViewer/ContainerEventLogItem.vue')['default']
ContainerHealth: typeof import('./components/ContainerViewer/ContainerHealth.vue')['default']
ContainerLog: typeof import('./components/ContainerViewer/ContainerLog.vue')['default']
ContainerName: typeof import('./components/LogViewer/ContainerName.vue')['default']
ContainerPopup: typeof import('./components/ContainerPopup.vue')['default']
ContainerTable: typeof import('./components/ContainerTable.vue')['default']
ContainerTitle: typeof import('./components/ContainerViewer/ContainerTitle.vue')['default']
@@ -38,10 +42,13 @@ declare module 'vue' {
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']
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']
KeyShortcut: typeof import('./components/common/KeyShortcut.vue')['default']
LabeledInput: typeof import('./components/common/LabeledInput.vue')['default']
@@ -49,6 +56,7 @@ declare module 'vue' {
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']
@@ -87,13 +95,14 @@ declare module 'vue' {
'Ph:computerTower': typeof import('~icons/ph/computer-tower')['default']
'Ph:controlBold': typeof import('~icons/ph/control-bold')['default']
'Ph:cpu': typeof import('~icons/ph/cpu')['default']
'Ph:dotsThreeVerticalBold': typeof import('~icons/ph/dots-three-vertical-bold')['default']
'Ph:fileSql': typeof import('~icons/ph/file-sql')['default']
'Ph:globeSimple': typeof import('~icons/ph/globe-simple')['default']
'Ph:memory': typeof import('~icons/ph/memory')['default']
'Ph:stack': typeof import('~icons/ph/stack')['default']
'Ph:stackSimple': typeof import('~icons/ph/stack-simple')['default']
Popup: typeof import('./components/Popup.vue')['default']
Releases: typeof import('./components/Releases.vue')['default']
RandomColorTag: typeof import('./components/LogViewer/RandomColorTag.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
ScrollableView: typeof import('./components/ScrollableView.vue')['default']
@@ -113,6 +122,7 @@ declare module 'vue' {
SwarmMenu: typeof import('./components/SwarmMenu.vue')['default']
Tag: typeof import('./components/common/Tag.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']
ViewerWithSource: typeof import('./components/LogViewer/ViewerWithSource.vue')['default']
ZigZag: typeof import('./components/LogViewer/ZigZag.vue')['default']
+115
View File
@@ -0,0 +1,115 @@
<template>
<Dropdown class="dropdown-end" @closed="releaseSeen = mostRecent?.tag ?? config.version">
<template #trigger>
<mdi:announcement class="size-6 -rotate-12" />
<template v-if="announcements.length > 0 && releaseSeen != mostRecent?.tag">
<span class="bg-red absolute top-0 right-px size-2 animate-ping rounded-full opacity-75"></span>
<span class="bg-red absolute top-0 right-px size-2 rounded-full"></span>
</template>
</template>
<template #content>
<div class="w-72">
<ul class="space-y-4 p-2">
<li v-for="release in announcements" v-if="announcements.length > 0">
<template v-if="release.announcement">
<div class="flex items-baseline gap-1">
<carbon:information class="text-info self-center" />
<a
:href="release.htmlUrl"
class="link-primary text-lg font-bold"
target="_blank"
rel="noreferrer noopener"
>
{{ release.name }}
</a>
<span class="ml-1 text-xs"><distance-time :date="release.createdAt" /></span>
</div>
<div class="text-base-content/80 text-sm">
{{ release.body }}
</div>
</template>
<template v-else>
<div class="flex items-baseline gap-1">
<carbon:warning class="stroke-orange self-center" v-if="release.breaking > 0" />
<a
:href="release.htmlUrl"
class="link-primary text-lg font-bold"
target="_blank"
rel="noreferrer noopener"
>
{{ release.name }}
</a>
<span class="ml-1 text-xs"><distance-time :date="release.createdAt" /></span>
<Tag class="bg-red ml-auto px-1 py-1 text-xs" v-if="release.latest">
{{ $t("releases.latest") }}
</Tag>
</div>
<div class="text-base-content/80 text-sm">
{{ summary(release) }}
</div>
</template>
</li>
<li v-else>
<div class="text-base-content/80 text-sm">
{{ $t("releases.no_releases") }}
</div>
</li>
</ul>
</div>
</template>
</Dropdown>
</template>
<script setup lang="ts">
import { useAnnouncements } from "@/stores/announcements";
const { announcements, mostRecent } = useAnnouncements();
const { t } = useI18n();
const releaseSeen = useProfileStorage("releaseSeen", config.version);
function summary(release: { features: number; bugFixes: number; breaking: number }) {
if (release.features > 0 && release.bugFixes > 0 && release.breaking > 0) {
return t("releases.three_parts", {
first: t("releases.breaking", { count: release.breaking }),
second: t("releases.features", { count: release.features }),
third: t("releases.bugFixes", { count: release.bugFixes }),
});
}
if (release.features > 0 && release.bugFixes > 0) {
return t("releases.two_parts", {
first: t("releases.features", { count: release.features }),
second: t("releases.bugFixes", { count: release.bugFixes }),
});
}
if (release.features > 0 && release.breaking > 0) {
return t("releases.two_parts", {
first: t("releases.features", { count: release.features }),
second: t("releases.breaking", { count: release.breaking }),
});
}
if (release.bugFixes > 0 && release.breaking > 0) {
return t("releases.two_parts", {
first: t("releases.bugFixes", { count: release.bugFixes }),
second: t("releases.breaking", { count: release.breaking }),
});
}
if (release.features > 0) {
return t("releases.features", { count: release.features });
}
if (release.bugFixes > 0) {
return t("releases.bugFixes", { count: release.bugFixes });
}
if (release.breaking > 0) {
return t("releases.breaking", { count: release.breaking });
}
}
</script>
<style scoped></style>
+14 -4
View File
@@ -1,15 +1,25 @@
<template>
<div>
<span class="font-light capitalize"> RUNNING </span>
<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.created" strict :suffix="false" />
<DistanceTime :date="container.startedAt" strict />
</span>
</div>
<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>
<div v-if="container.state == 'running'">
<span class="font-light capitalize"> MEM </span>
<span class="font-semibold"> {{ formatBytes(container.stat.memoryUsage) }} </span>
</div>
+11 -9
View File
@@ -7,7 +7,7 @@
type="radio"
name="host"
role="tab"
class="tab !rounded"
class="tab rounded-sm!"
aria-label="Show All"
v-model="selectedHost"
:value="null"
@@ -16,7 +16,7 @@
type="radio"
name="host"
role="tab"
class="tab !rounded"
class="tab rounded-sm!"
:aria-label="host.name"
v-for="host in hosts"
:value="host.id"
@@ -24,8 +24,8 @@
v-model="selectedHost"
/>
</div>
<!-- @vue-ignore -->
<dropdown-menu
<DropdownMenu
class="btn-sm"
v-model="selectedHost"
:options="[
@@ -37,8 +37,8 @@
</div>
<div class="flex-1 text-right" v-show="containers.length > pageSizes[0]">
{{ $t("label.per-page") }}
<!-- @vue-ignore -->
<dropdown-menu
<DropdownMenu
class="dropdown-left btn-xs md:btn-sm"
v-model="perPage"
:options="pageSizes.map((i) => ({ label: i.toLocaleString(), value: i }))"
@@ -46,7 +46,7 @@
</div>
</div>
<table class="table table-lg bg-base">
<table class="table-lg bg-base-200 table">
<thead>
<tr :data-direction="direction > 0 ? 'asc' : 'desc'">
<th
@@ -196,7 +196,9 @@ function isVisible(field: keys) {
}
</script>
<style lang="postcss" scoped>
<style scoped>
@import "@/main.css" reference;
[data-icon] {
display: none;
transition: transform 0.2s ease-in-out;
@@ -206,7 +208,7 @@ function isVisible(field: keys) {
}
th {
@apply border-b-2 border-base-lighter;
@apply border-base-100 border-b-2;
&.selected-sort {
font-weight: bold;
@apply border-primary;
@@ -1,10 +1,10 @@
<template>
<div class="dropdown dropdown-end dropdown-hover">
<label tabindex="0" class="btn btn-ghost btn-sm w-10 gap-0.5 px-2">
<carbon:circle-solid class="w-2.5 text-red" v-if="streamConfig.stderr" />
<carbon:circle-solid class="w-2.5 text-blue" v-if="streamConfig.stdout" />
<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 z-50 w-52 rounded-box bg-base p-1 shadow">
<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()">
<octicon:trash-24 /> {{ $t("toolbar.clear") }}
@@ -31,8 +31,8 @@
<details>
<summary>
<div class="flex w-4">
<carbon:circle-solid class="w-2.5 text-red" v-if="streamConfig.stderr" />
<carbon:circle-solid class="w-2.5 text-blue" v-if="streamConfig.stdout" />
<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" />
</div>
Streams
</summary>
@@ -169,7 +169,7 @@ const downloadParams = computed(() =>
const downloadUrl = computed(() =>
withBase(
`/api/hosts/${container.host}/containers/${container.id}/logs/download?${new URLSearchParams(downloadParams.value).toString()}`,
`/api/containers/${container.host}~${container.id}/download?${new URLSearchParams(downloadParams.value).toString()}`,
),
);
@@ -187,9 +187,11 @@ const toggleAllLevels = computed({
});
</script>
<style scoped lang="postcss">
<style scoped>
@import "@/main.css" reference;
li.line {
@apply h-px bg-base-content/20;
@apply bg-base-content/20 h-px;
}
a {
@@ -14,7 +14,8 @@ defineProps<{
}>();
</script>
<style lang="postcss" scoped>
<style scoped>
@import "@/main.css" reference;
[health="unhealthy"] {
@apply text-red;
}
@@ -1,11 +1,15 @@
<template>
<ScrollableView :scrollable="scrollable" v-if="container">
<template #header v-if="showTitle">
<div class="mx-2 flex items-center gap-2 md:ml-4">
<div class="@container mx-2 flex items-center gap-2 md:ml-4">
<ContainerTitle :container="container" />
<MultiContainerStat class="ml-auto" :containers="[container]" />
<MultiContainerStat
class="ml-auto lg:hidden lg:@3xl:flex"
:containers="[container]"
v-if="container.state === 'running'"
/>
<ContainerActionsToolbar @clear="viewer?.clear()" class="mobile-hidden" :container="container" />
<ContainerActionsToolbar @clear="viewer?.clear()" class="max-md:hidden" :container="container" />
<a class="btn btn-circle btn-xs" @click="close()" v-if="closable">
<mdi:close />
</a>
@@ -17,7 +21,6 @@
:stream-source="useContainerStream"
:entity="container"
:visible-keys="visibleKeys"
:show-container-name="false"
/>
</template>
</ScrollableView>
@@ -46,5 +49,8 @@ const container = store.currentContainer(toRef(() => id));
const visibleKeys = persistentVisibleKeysForContainer(container);
const viewer = useTemplateRef<ComponentExposed<typeof ViewerWithSource>>("viewer");
provideLoggingContext(toRef(() => [container.value]));
provideLoggingContext(
toRef(() => [container.value]),
{ showContainerName: false, showHostname: false },
);
</script>
@@ -1,24 +1,44 @@
<template>
<div class="flex flex-1 gap-1.5 truncate @container md:gap-2">
<div class="@container flex flex-1 items-center gap-1.5 truncate 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 font-mono text-sm">
<div v-if="config.hosts.length > 1" class="mobile-hidden font-thin">
{{ container.hostLabel }}<span class="mx-2">/</span>
</div>
<div class="font-semibold">{{ container.name }}</div>
<div
class="mobile-hidden max-w-[1.5em] truncate transition-[max-width] hover:max-w-[400px]"
v-if="container.isSwarm"
>
.{{ container.swarmId }}
<div class="inline-flex items-center text-sm">
<div class="breadcrumbs p-0">
<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>
</div>
</li>
</ul>
</div>
</div>
<ContainerHealth :health="container.health" v-if="container.health" />
<Tag class="mobile-hidden hidden font-mono @3xl:block" size="small">
<Tag class="hidden font-mono max-md:hidden @3xl:block" size="small">
{{ container.image.replace(/@sha.*/, "") }}
</Tag>
</div>
@@ -38,4 +58,42 @@ const pinned = computed({
}
},
});
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,
),
);
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>
+39 -3
View File
@@ -31,9 +31,45 @@ function createFuzzySearchModal() {
initialState: {
container: {
containers: [
new Container("123", new Date(), "image", "test", "command", "host", {}, "running", []),
new Container("345", new Date(), "image", "foo bar", "command", "host", {}, "running", []),
new Container("567", new Date(), "image", "baz", "command", "host", {}, "running", []),
new Container(
"123",
new Date(),
new Date(),
new Date(),
"image",
"test",
"command",
"host",
{},
"running",
[],
),
new Container(
"345",
new Date(),
new Date(),
new Date(),
"image",
"foo bar",
"command",
"host",
{},
"running",
[],
),
new Container(
"567",
new Date(),
new Date(),
new Date(),
"image",
"baz",
"command",
"host",
{},
"running",
[],
),
],
},
},
+20 -10
View File
@@ -1,10 +1,10 @@
<template>
<div class="dropdown dropdown-open w-full">
<div class="input input-primary flex h-auto items-center">
<div class="input input-xl input-primary flex w-full items-center">
<mdi:magnify class="flex size-8" />
<input
tabindex="0"
class="input input-lg input-ghost flex-1 px-1"
class="input-ghost flex-1 px-1"
ref="input"
@keydown.down="selectedIndex = Math.min(selectedIndex + 1, data.length - 1)"
@keydown.up="selectedIndex = Math.max(selectedIndex - 1, 0)"
@@ -14,22 +14,23 @@
:placeholder="$t('placeholder.search-containers')"
/>
<form method="dialog">
<button class="swap swap-rotate hover:swap-active">
<button class="swap hover:swap-active">
<mdi:keyboard-esc class="swap-off" />
<mdi:close class="swap-on" />
</button>
</form>
</div>
<div
class="dropdown-content !relative mt-2 max-h-[calc(100dvh-20rem)] w-full overflow-y-scroll rounded-md border-y-8 border-transparent bg-base-lighter px-2"
class="dropdown-content bg-base-100 relative! mt-2 max-h-[calc(100dvh-20rem)] w-full overflow-y-scroll rounded-md border-y-8 border-transparent px-2"
tabindex="0"
v-if="results.length"
>
<ul tabindex="0" class="menu">
<ul class="menu w-auto">
<li v-for="(result, index) in data" ref="listItems">
<a
class="grid auto-cols-max grid-cols-[min-content,auto] gap-2 py-4"
class="grid auto-cols-max grid-cols-[min-content_auto] gap-2 py-4"
@click.prevent="selected(result.item)"
:class="index === selectedIndex ? 'focus' : ''"
:class="index === selectedIndex ? 'menu-focus' : ''"
>
<div :class="{ 'text-primary': result.item.state === 'running' }">
<template v-if="result.item.type === 'container'">
@@ -85,6 +86,15 @@ const { visibleContainers } = storeToRefs(containerStore);
const swarmStore = useSwarmStore();
const { stacks, services } = storeToRefs(swarmStore);
onMounted(async () => {
const dialog = input.value?.closest("dialog");
if (dialog) {
const animations = dialog.getAnimations();
await Promise.all(animations.map((animation) => animation.finished));
input.value?.focus();
}
});
type Item = {
id: string;
created: Date;
@@ -167,8 +177,6 @@ watch(selectedIndex, () => {
listItems.value?.[selectedIndex.value].scrollIntoView({ block: "end" });
});
useFocus(input, { initialValue: true });
function selected(item: Item) {
if (item.type === "container") {
router.push({ name: "/container/[id]", params: { id: item.id } });
@@ -179,6 +187,7 @@ function selected(item: Item) {
}
close();
}
function addColumn(container: { id: string }) {
pinnedStore.pinContainer(container);
close();
@@ -204,7 +213,8 @@ function matchedName({ item, matches = [] }: FuseResult<Item>) {
}
</script>
<style scoped lang="postcss">
<style scoped>
@import "@/main.css" reference;
:deep(mark) {
@apply bg-transparent text-inherit underline underline-offset-2;
}
+29
View File
@@ -0,0 +1,29 @@
<template>
<ul class="menu w-full p-0 text-[0.95rem]">
<li v-if="customGroups.length > 0">
<details open>
<summary class="text-base-content/80 font-light">
<ph:bounding-box-fill />
{{ $t("label.group-menu") }}
</summary>
<ul>
<li v-for="group in customGroups" :key="group.name">
<router-link :to="{ name: '/group/[name]', params: { name: group.name } }" active-class="menu-active">
<ph:stack-simple />
<div class="truncate">
{{ group.name }}
</div>
</router-link>
</li>
</ul>
</details>
</li>
</ul>
</template>
<script lang="ts" setup>
const store = useSwarmStore();
const { customGroups } = storeToRefs(store);
</script>
<style scoped></style>
@@ -2,13 +2,13 @@
<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="flex flex-1 gap-1.5 truncate @container md:gap-2">
<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">{{ group.containers.length }} containers</div>
<div class="font-semibold">{{ $t("label.container", group.containers.length) }}</div>
</div>
</div>
<MultiContainerStat class="ml-auto" :containers="group.containers" />
<MultiContainerActionToolbar class="mobile-hidden" @clear="viewer?.clear()" />
<MultiContainerActionToolbar class="max-md:hidden" @clear="viewer?.clear()" />
</div>
</template>
<template #default>
@@ -17,7 +17,6 @@
:stream-source="useGroupedStream"
:entity="group"
:visible-keys="new Map<string[], boolean>()"
:show-container-name="true"
/>
</template>
</ScrollableView>
@@ -43,5 +42,8 @@ const { customGroups } = storeToRefs(swarmStore);
const group = computed(() => customGroups.value.find((g) => g.name === name) ?? new GroupedContainers("", []));
provideLoggingContext(toRef(() => group.value.containers));
provideLoggingContext(
toRef(() => group.value.containers),
{ showContainerName: true, showHostname: false },
);
</script>
+13 -11
View File
@@ -1,6 +1,6 @@
<template>
<ul class="grid gap-4 md:grid-cols-[repeat(auto-fill,minmax(480px,1fr))]">
<li v-for="host in hosts" class="card bg-base-lighter">
<li v-for="host in hosts" class="card bg-base-100">
<div class="card-body grid auto-cols-auto grid-flow-col justify-between gap-4">
<div class="flex flex-col gap-2 overflow-hidden">
<div class="flex items-center gap-1 truncate text-xl font-semibold">
@@ -23,15 +23,16 @@
</span>
</div>
<ul class="flex flex-row gap-x-2 text-sm md:gap-3">
<li class="flex items-center gap-1"><ph:cpu /> {{ host.nCPU }} <span class="mobile-hidden">CPUs</span></li>
<li class="flex items-center gap-1"><ph:cpu /> {{ host.nCPU }} <span class="max-md:hidden">CPUs</span></li>
<li class="flex items-center gap-1">
<ph:memory /> {{ formatBytes(host.memTotal) }}
<span class="mobile-hidden">total</span>
<span class="max-md:hidden">total</span>
</li>
</ul>
<ul class="flex flex-row flex-wrap gap-x-2 text-sm md:gap-3">
<li class="flex items-center gap-1">
<octicon:container-24 class="inline-block" /> {{ $t("label.container", hostContainers[host.id]?.length) }}
<octicon:container-24 class="inline-block" />
{{ $t("label.container", hostContainers[host.id]?.length ?? 0) }}
</li>
<li class="flex items-center gap-1"><mdi:docker class="inline-block" /> {{ host.dockerVersion }}</li>
</ul>
@@ -39,15 +40,15 @@
<div class="flex flex-row gap-4 md:gap-8" v-if="weightedStats[host.id]">
<div
class="radial-progress text-sm text-primary [--size:4rem] [--thickness:0.25em] md:text-[1rem] md:[--size:5rem]"
:style="`--value: ${Math.floor((weightedStats[host.id].weighted.totalCPU / (host.nCPU * 100)) * 100)}; `"
class="radial-progress text-primary text-[0.85rem] transition-none [--size:4rem] [--thickness:0.25em] md:text-[0.9rem] md:[--size:5rem]"
:style="`--value: ${Math.floor((weightedStats[host.id].weighted.totalCPU / (host.nCPU * 100)) * 100)};`"
role="progressbar"
>
{{ weightedStats[host.id].weighted.totalCPU.toFixed(0) }}%
</div>
<div
class="radial-progress text-sm text-primary [--size:4rem] [--thickness:0.25em] md:text-[1rem] md:[--size:5rem]"
:style="`--value: ${(weightedStats[host.id].weighted.totalMem / host.memTotal) * 100};`"
class="radial-progress text-primary text-[0.85rem] transition-none [--size:4rem] [--thickness:0.25em] md:text-[0.9rem] md:[--size:5rem]"
:style="`--value: ${Math.floor((weightedStats[host.id].weighted.totalMem / host.memTotal) * 100)};`"
role="progressbar"
>
{{ formatBytes(weightedStats[host.id].weighted.totalMem, 1) }}
@@ -107,12 +108,13 @@ useIntervalFn(
stat.totalCPU += container.stat.cpu;
stat.totalMem += container.stat.memoryUsage;
}
weightedStats[host].mostRecent = stat;
if (weightedStats[host]) {
// TODO fix this init
weightedStats[host].mostRecent = stat;
}
}
},
1000,
{ immediate: true },
);
</script>
<style scoped></style>
+55 -18
View File
@@ -1,20 +1,50 @@
<template>
<div class="breadcrumbs">
<ul>
<li>
<a @click.prevent="setHost(null)" class="link-primary">{{ $t("label.hosts") }}</a>
</li>
<li v-if="sessionHost && hosts[sessionHost]" class="cursor-default">
{{ hosts[sessionHost].name }}
</li>
</ul>
<div class="flex items-center">
<div class="breadcrumbs flex-1">
<ul>
<li>
<a @click.prevent="setHost(null)" class="link-primary">{{ $t("label.hosts") }}</a>
</li>
<li v-if="sessionHost && hosts[sessionHost]" class="cursor-default">
<router-link
:to="{
name: '/host/[id]',
params: { id: hosts[sessionHost].id },
}"
class="btn btn-outline btn-primary btn-xs"
active-class="btn-active"
:title="$t('tooltip.merge-hosts')"
>
<ph:arrows-merge />
{{ hosts[sessionHost].name }}
</router-link>
</li>
</ul>
</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 z-50 w-52 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
</a>
</li>
</ul>
</div>
</div>
</div>
<SlideTransition :slide-right="!!sessionHost">
<template #left>
<ul class="menu p-0">
<li v-for="host in hosts" :key="host.id">
<a @click.prevent="setHost(host.id)" :class="{ 'pointer-events-none text-base-content/50': !host.available }">
<a @click.prevent="setHost(host.id)" :class="{ 'text-base-content/50 pointer-events-none': !host.available }">
<HostIcon :type="host.type" />
{{ host.name }}
<span class="badge badge-error badge-xs p-1.5" v-if="!host.available">offline</span>
@@ -23,10 +53,10 @@
</ul>
</template>
<template #right>
<ul class="containers menu p-0 [&_li.menu-title]:px-0">
<ul class="containers menu w-full p-0 [&_li.menu-title]:px-0">
<li v-for="{ label, containers, icon } in menuItems" :key="label">
<details :open="!collapsedGroups.has(label)" @toggle="updateCollapsedGroups($event, label)">
<summary class="font-light text-base-content/80">
<summary class="text-base-content/80 font-light">
<component :is="icon" />
{{ label.startsWith("label.") ? $t(label) : label }}
@@ -47,17 +77,21 @@
<Popup>
<router-link
:to="{ name: '/container/[id]', params: { id: item.id } }"
active-class="active-primary"
active-class="menu-active"
@click.alt.stop.prevent="pinnedStore.pinContainer(item)"
:title="item.name"
class="group auto-cols-[auto_max-content_max-content]"
class="group auto-cols-[content_max_auto_max-content_max-content]"
>
<div
class="status data-[state=exited]:status-error data-[state=running]:status-success"
:data-state="item.state"
></div>
<div class="truncate">
{{ item.name }}<span class="font-light opacity-70" v-if="item.isSwarm">.{{ item.swarmId }}</span>
{{ item.name }}
</div>
<ContainerHealth :health="item.health" />
<span
class="hidden hover:text-secondary group-hover:inline-block"
class="hover:text-secondary hidden group-hover:inline-block"
@click.stop.prevent="pinnedStore.pinContainer(item)"
v-show="!pinnedStore.isPinned(item)"
:title="$t('tooltip.pin-column')"
@@ -81,6 +115,7 @@
<script lang="ts" setup>
import { Container } from "@/models/Container";
import { sessionHost } from "@/composable/storage";
import { showAllContainers } from "@/stores/settings";
// @ts-ignore
import Pin from "~icons/ph/map-pin-simple";
@@ -175,14 +210,16 @@ watchEffect(() => {
}
}
});
const toggleShowAllContainers = () => (showAllContainers.value = !showAllContainers.value);
</script>
<style scoped lang="postcss">
<style scoped>
.menu {
@apply text-[0.95rem];
}
li.exited {
@apply opacity-50;
@apply opacity-75;
}
li.deleted {
+43
View File
@@ -0,0 +1,43 @@
<template>
<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">
<ph:computer-tower />
<div class="inline-flex font-mono text-sm">
<div class="font-semibold">{{ host.name }}</div>
</div>
<Tag class="font-mono max-md:hidden" size="small">
{{ $t("label.container", containers.length) }}
</Tag>
</div>
<MultiContainerStat class="ml-auto" :containers="containers" />
<MultiContainerActionToolbar class="max-md:hidden" @clear="viewer?.clear()" />
</div>
</template>
<template #default>
<ViewerWithSource
ref="viewer"
:stream-source="useHostStream"
:entity="host"
:visible-keys="new Map<string[], boolean>()"
/>
</template>
</ScrollableView>
</template>
<script lang="ts" setup>
import ViewerWithSource from "@/components/LogViewer/ViewerWithSource.vue";
import { ComponentExposed } from "vue-component-type-helpers";
const { id, scrollable = false } = defineProps<{
id: string;
scrollable?: boolean;
}>();
const store = useContainerStore();
const { containersByHost } = storeToRefs(store);
const { hosts } = useHosts();
const host = computed(() => hosts.value[id]);
const containers = computed(() => containersByHost.value?.[id]?.filter((c) => c.state === "running") ?? []);
const viewer = useTemplateRef<ComponentExposed<typeof ViewerWithSource>>("viewer");
provideLoggingContext(containers, { showContainerName: true, showHostname: false });
</script>
+1 -1
View File
@@ -1,6 +1,6 @@
<template>
<div ref="root" class="flex min-h-[1px] justify-center">
<span class="loading loading-bars loading-md mt-4 text-primary" v-show="isLoading"></span>
<span class="loading loading-bars loading-md text-primary mt-4" v-show="isLoading"></span>
</div>
</template>
+3 -19
View File
@@ -1,20 +1,7 @@
<template>
<div class="flex items-center justify-end gap-4">
<slot name="more-items"></slot>
<Dropdown class="dropdown-end" @closed="latestTag = latest?.tag ?? config.version">
<template #trigger>
<mdi:announcement class="size-6 -rotate-12" />
<span
class="absolute right-px top-0 size-2 rounded-full bg-red"
v-if="hasUpdate && latestTag != latest?.tag"
></span>
</template>
<template #content>
<div class="w-72">
<Releases />
</div>
</template>
</Dropdown>
<Announcements />
<router-link
:to="{ name: '/settings' }"
@@ -28,7 +15,7 @@
<dropdown class="dropdown-end" v-if="config.user">
<template #trigger>
<img
class="size-6 max-w-none rounded-full p-px ring-1 ring-base-content/60"
class="ring-base-content/60 size-6 max-w-none rounded-full p-px ring-1"
:src="withBase('/api/profile/avatar')"
/>
</template>
@@ -43,7 +30,7 @@
</div>
<ul class="menu mt-4 p-0">
<li v-if="config.authProvider === 'simple'">
<button @click.prevent="logout()" class="p-2 text-primary">{{ $t("button.logout") }}</button>
<button @click.prevent="logout()" class="text-primary p-2">{{ $t("button.logout") }}</button>
</li>
</ul>
</template>
@@ -58,7 +45,4 @@ async function logout() {
location.reload();
}
const { hasUpdate, latest } = useReleases();
const latestTag = useProfileStorage("releaseSeen", config.version);
</script>
+16 -34
View File
@@ -1,36 +1,23 @@
<template>
<div class="group/item clickable relative flex w-full gap-x-2" @click="showDrawer(LogDetails, { entry: logEntry })">
<div v-if="showContainerName">
<ContainerName :id="logEntry.containerID" />
</div>
<div v-if="showStd">
<LogStd :std="logEntry.std" />
</div>
<div v-if="showTimestamp">
<LogDate :date="logEntry.date" class="select-none" />
</div>
<div class="flex">
<LogLevel :level="logEntry.level" />
</div>
<div>
<ul class="fields space-x-4">
<li v-for="(value, name) in validValues" :key="name">
<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="value" v-else></span>
</li>
<li class="text-light" v-if="Object.keys(validValues).length === 0">all values are hidden</li>
</ul>
</div>
</div>
<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>
</li>
<li class="text-light" v-if="Object.keys(validValues).length === 0">all values are hidden</li>
</ul>
</LogItem>
</template>
<script lang="ts" setup>
import stripAnsi from "strip-ansi";
import { type ComplexLogEntry } from "@/models/LogEntry";
import LogDetails from "./LogDetails.vue";
const { logEntry, showContainerName = false } = defineProps<{
const { logEntry } = defineProps<{
logEntry: ComplexLogEntry;
showContainerName?: boolean;
}>();
@@ -42,14 +29,9 @@ const validValues = computed(() => {
const showDrawer = useDrawer();
</script>
<style lang="postcss" scoped>
<style scoped>
@import "@/main.css" reference;
.text-light {
@apply text-base-content/70;
}
.fields {
li {
@apply inline-flex;
}
}
</style>
@@ -1,84 +1,19 @@
<template>
<div class="flex w-full flex-col">
<div class="relative flex items-start gap-x-2">
<ContainerName class="flex-none" :id="logEntry.containerID" v-if="showContainerName" />
<LogDate :date="logEntry.date" v-if="showTimestamp" />
<LogLevel class="flex" />
<div class="whitespace-pre-wrap" :data-event="logEntry.event" v-html="logEntry.message"></div>
</div>
<div class="alert alert-info mt-8 w-auto flex-none font-sans text-[1rem] md:mx-auto md:w-1/2" v-if="followEligible">
<carbon:information class="size-6 shrink-0 stroke-current" />
<div>
<h3 class="text-lg font-bold">{{ $t("alert.similar-container-found.title") }}</h3>
{{ $t("alert.similar-container-found.message", { containerId: nextContainer.id }) }}
</div>
<div>
<TimedButton v-if="automaticRedirect" class="btn-primary btn-sm" @finished="redirectNow()">{{
$t("button.cancel")
}}</TimedButton>
<router-link
:to="{ name: '/container/[id]', params: { id: nextContainer.id } }"
class="btn btn-primary btn-sm"
v-else
>
{{ $t("button.redirect") }}
</router-link>
</div>
</div>
</div>
<LogItem :logEntry>
<div class="whitespace-pre-wrap" :data-event="logEntry.event" v-html="logEntry.message"></div>
</LogItem>
</template>
<script lang="ts" setup>
import { ContainerEventLogEntry } from "@/models/LogEntry";
const router = useRouter();
const { showToast } = useToast();
const { t } = useI18n();
const { logEntry } = defineProps<{
logEntry: ContainerEventLogEntry;
showContainerName?: boolean;
}>();
const { containers } = useLoggingContext();
const store = useContainerStore();
const { containers: allContainers } = storeToRefs(store);
const nextContainer = computed(
() =>
[
...allContainers.value.filter(
(c) =>
c.host === containers.value[0].host &&
c.created > logEntry.date &&
c.name === containers.value[0].name &&
c.state === "running",
),
].sort((a, b) => +a.created - +b.created)[0],
);
const followEligible = computed(
() =>
router.currentRoute.value.name === "/container/[id]" &&
logEntry.event === "container-stopped" &&
containers.value.length === 1 &&
nextContainer.value !== undefined,
);
function redirectNow() {
showToast(
{
title: t("alert.redirected.title"),
message: t("alert.redirected.message", { containerId: nextContainer.value?.id }),
type: "info",
},
{ expire: 5000 },
);
router.push({ name: "/container/[id]", params: { id: nextContainer.value?.id } });
}
</script>
<style lang="postcss" scoped>
<style scoped>
@import "@/main.css" reference;
[data-event="container-stopped"] {
@apply text-red;
}
@@ -1,41 +0,0 @@
<template>
<div class="grid w-40 overflow-hidden rounded 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 [direction:rtl]">{{ containerNames[id] }}</div>
</div>
</template>
<script lang="ts">
const colors = [
"#4B0082",
"#FF00FF",
"#FF7F00",
"#FFFF00",
"#00FF00",
"#00FFFF",
"#FF0000",
"#0000FF",
"#FF007F",
"#32CD32",
"#40E0D0",
"#E6E6FA",
"#800080",
"#FFD700",
"#FF4040",
] as const;
</script>
<script lang="ts" setup>
const containerStore = useContainerStore();
const { containerNames } = storeToRefs(containerStore);
const { id } = defineProps<{
id: string;
}>();
const color = computed(() => colors[Math.abs(hashCode(id)) % colors.length]);
</script>
<style lang="postcss" scoped>
.random-color {
background-color: v-bind(color);
}
</style>
@@ -94,7 +94,19 @@ describe("<ContainerEventSource />", () => {
},
props: {
streamSource: useContainerStream,
entity: new Container("abc", new Date(), "image", "name", "command", "localhost", {}, "created", []),
entity: new Container(
"abc",
new Date(), // created
new Date(), // started
new Date(), // finished
"image",
"name",
"command",
"localhost",
{},
"created",
[],
),
},
});
}
@@ -103,7 +115,7 @@ describe("<ContainerEventSource />", () => {
test("renders loading correctly", async () => {
const wrapper = createLogEventSource();
expect(wrapper.html()).toMatchSnapshot();
expect(wrapper.find("ul.animate-pulse").exists()).toBe(true);
});
test("should connect to EventSource", async () => {
+28 -5
View File
@@ -1,6 +1,15 @@
<template>
<InfiniteLoader :onLoadMore="fetchMore" :enabled="!loadingMore && messages.length > 10" />
<slot :messages="messages"></slot>
<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>
<div class="bg-base-content/50 h-3 rounded-full opacity-50" :class="size"></div>
</div>
<span class="sr-only">Loading...</span>
</ul>
<div v-else-if="noLogs && !waitingForMoreLog" class="p-4">Container has no logs yet</div>
<slot :messages="messages" v-else></slot>
<IndeterminateBar :color />
</template>
<script lang="ts" setup generic="T">
@@ -11,10 +20,18 @@ const { entity, streamSource } = $defineProps<{
entity: T;
}>();
const { messages, loadOlderLogs, isLoadingMore } = streamSource($$(entity));
const { messages, loadOlderLogs, isLoadingMore, opened, loading, error, eventSourceURL } = streamSource($$(entity));
const { loadingMore } = useLoggingContext();
const color = computed(() => {
if (error.value) return "error";
if (loading.value) return "secondary";
if (opened.value) return "primary";
return "error";
});
const enabled = ref(true);
const noLogs = computed(() => messages.value.length === 0);
const waitingForMoreLog = refAutoReset(false, 3000);
watchImmediate(loading, () => (waitingForMoreLog.value = true));
defineExpose({
clear: () => (messages.value = []),
@@ -23,10 +40,16 @@ defineExpose({
const fetchMore = async () => {
if (!isLoadingMore.value) {
loadingMore.value = true;
enabled.value = false;
await loadOlderLogs();
loadingMore.value = false;
enabled.value = true;
}
};
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"]),
);
</script>
+6 -6
View File
@@ -1,7 +1,7 @@
<template>
<aside>
<header class="flex items-center gap-4">
<h1 class="mobile-hidden text-2xl">{{ container.name }}</h1>
<h1 class="text-2xl max-md:hidden">{{ container.name }}</h1>
<h2 class="text-sm"><DistanceTime :date="container.created" /></h2>
</header>
@@ -13,12 +13,12 @@
class="textarea textarea-primary w-full font-mono text-lg"
:class="{ 'textarea-error': error }"
></textarea>
<div class="label">
<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></span
>
<template v-if="results.numRows > pageLimit"> . Showing first {{ page.numRows }}. </template>
</span>
</div>
</label>
</section>
@@ -31,7 +31,7 @@
import { Container } from "@/models/Container";
import { type Table } from "@apache-arrow/esnext-esm";
const { container } = defineProps<{ container: Container }>();
const query = ref("SELECT * FROM logs");
const query = ref("SELECT * FROM logs LIMIT 100");
const error = ref<string | null>(null);
const debouncedQuery = debouncedRef(query, 500);
const evaluating = ref(false);
@@ -94,4 +94,4 @@ const page = computed(() =>
results.value.numRows > pageLimit ? results.value.slice(0, pageLimit) : results.value,
) as unknown as ComputedRef<Table<Record<string, any>>>;
</script>
<style lang="postcss" scoped></style>
<style scoped></style>
+2 -2
View File
@@ -1,6 +1,6 @@
<template>
<Tag size="small">
<DateTime :date="date" class="whitespace-nowrap text-blue" />
<Tag size="small" class="items-start!">
<DateTime :date="date" class="text-blue whitespace-nowrap" />
</Tag>
</template>
<script lang="ts" setup>
+39 -30
View File
@@ -1,7 +1,7 @@
<template>
<header class="flex items-center gap-4">
<Tag :data-level="entry.level" class="uppercase text-white" v-if="entry.level">{{ entry.level }}</Tag>
<h1 class="mobile-hidden text-lg">
<Tag :data-level="entry.level" class="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>
@@ -26,37 +26,39 @@
</section>
<section class="flex flex-col gap-2">
<div class="flex gap-2 font-thin">
<div class="flex gap-2">
Raw JSON
<UseClipboard v-slot="{ copy, copied }" :source="JSON.stringify(entry.unfilteredMessage)">
<button class="swap swap-flip outline-none" @click="copy()" :class="{ 'hover:swap-active': copied }">
<button class="swap outline-hidden" @click="copy()" :class="{ 'hover:swap-active': copied }">
<mdi:check class="swap-on" />
<mdi:content-copy class="swap-off" />
</button>
</UseClipboard>
</div>
<div class="max-h-48 overflow-scroll rounded border border-base-lighter bg-base-darker p-2">
<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>
</div>
</section>
<table class="table table-pin-rows table-fixed" v-if="entry instanceof ComplexLogEntry">
<table class="table-pin-rows table table-fixed" v-if="entry instanceof ComplexLogEntry">
<caption class="caption-bottom">
Fields are sortable by dragging and dropping.
</caption>
<thead class="text-lg">
<tr>
<th class="w-60">Field</th>
<th class="mobile-hidden">Value</th>
<th class="w-20"><input type="checkbox" class="toggle toggle-primary" @change="toggleAll($event)" /></th>
<th class="max-md:hidden">Value</th>
<th class="w-20">
<input type="checkbox" class="toggle toggle-primary" v-model="toggleAllFields" title="Toggle all" />
</th>
</tr>
</thead>
<tbody ref="list">
<tr v-for="{ key, value, enabled } in fields" :key="key.join('.')" class="hover">
<td class="cursor-move break-all font-mono">
<td class="cursor-move font-mono break-all">
{{ key.join(".") }}
</td>
<td class="mobile-hidden truncate">
<td class="truncate max-md:hidden">
<code v-html="JSON.stringify(value)"></code>
</td>
<td>
@@ -85,21 +87,11 @@ function toggleField(key: string[]) {
visibleKeys.value = new Map<string[], boolean>(fields.value.map(({ key }) => [key, true]));
}
const enabled = visibleKeys.value.get(key);
const enabled = visibleKeys.value.get(key) ?? true;
visibleKeys.value.set(key, !enabled);
}
function toggleAll(e: Event) {
if (visibleKeys.value.size === 0) {
visibleKeys.value = new Map<string[], boolean>(fields.value.map(({ key }) => [key, true]));
}
const enabled = e.target instanceof HTMLInputElement && e.target.checked;
for (const key of visibleKeys.value.keys()) {
visibleKeys.value.set(key, enabled);
}
}
const fields = computed({
get() {
const fieldsWithValue: { key: string[]; value: any; enabled: boolean }[] = [];
@@ -116,7 +108,7 @@ const fields = computed({
for (const [key, value] of allFields) {
if ([...visibleKeys.value.keys()].findIndex((k) => arrayEquals(k, key)) === -1) {
fieldsWithValue.push({ key, value, enabled: false });
fieldsWithValue.push({ key, value, enabled: true });
}
}
}
@@ -132,6 +124,22 @@ const fields = computed({
},
});
const toggleAllFields = computed({
get: () => fields.value.every(({ enabled }) => enabled),
set(value) {
if (visibleKeys.value.size === 0) {
visibleKeys.value = new Map<string[], boolean>(fields.value.map(({ key }) => [key, true]));
}
for (const key of visibleKeys.value.keys()) {
visibleKeys.value.set(key, value);
}
for (const field of fields.value) {
visibleKeys.value.set(field.key, value);
}
},
});
function syntaxHighlight(json: any) {
json = JSON.stringify(json, null, 2);
return json.replace(
@@ -149,14 +157,15 @@ function syntaxHighlight(json: any) {
} else if (/null/.test(match)) {
cls = "json-null";
}
return '<span class="' + cls + '">' + match + "</span>";
return `<span class="${cls}">${match}</span>`;
},
);
}
useSortable(list, fields);
</script>
<style lang="postcss" scoped>
<style scoped>
@import "@/main.css" reference;
.font-mono {
font-family:
ui-monospace,
@@ -170,19 +179,19 @@ useSortable(list, fields);
}
pre {
:deep(.json-key) {
& :deep(.json-key) {
@apply text-blue;
}
:deep(.json-string) {
& :deep(.json-string) {
@apply text-green;
}
:deep(.json-number) {
& :deep(.json-number) {
@apply text-orange;
}
:deep(.json-boolean) {
& :deep(.json-boolean) {
@apply text-purple;
}
:deep(.json-null) {
& :deep(.json-null) {
@apply text-red;
}
}
+28
View File
@@ -0,0 +1,28 @@
<template>
<div class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
<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" />
<LogLevel
class="flex select-none"
:level="logEntry.level"
:position="logEntry instanceof SimpleLogEntry ? logEntry.position : undefined"
/>
<slot />
</div>
</template>
<script lang="ts" setup>
import { LogEntry, SimpleLogEntry } from "@/models/LogEntry";
const { logEntry } = defineProps<{
logEntry: LogEntry<any>;
}>();
const { showHostname, showContainerName } = useLoggingContext();
const { currentContainer } = useContainerStore();
const { hosts } = useHosts();
const container = currentContainer(toRef(() => logEntry.containerID));
const host = computed(() => hosts.value[container.value.host]);
</script>
+5 -6
View File
@@ -10,7 +10,8 @@ defineProps<{
}>();
</script>
<style lang="postcss" scoped>
<style scoped>
@import "@/main.css" reference;
[data-position="start"] {
border-radius: 0.5em 0.5em 0 0;
height: 70%;
@@ -33,7 +34,8 @@ defineProps<{
align-self: flex-start;
}
</style>
<style lang="postcss">
<style>
@import "@/main.css" reference;
[data-level="debug"],
[data-level="trace"] {
@apply !bg-purple;
@@ -44,14 +46,11 @@ defineProps<{
}
[data-level="error"],
[data-level="severe"],
[data-level="critical"],
[data-level="fatal"] {
@apply !bg-red;
}
[data-level="warn"],
[data-level="warning"] {
[data-level="warn"] {
@apply !bg-orange;
}
</style>
+19 -13
View File
@@ -1,9 +1,5 @@
<template>
<ul
class="events group pt-4"
:class="{ 'disable-wrap': !softWrap, [size]: true, compact }"
v-if="messages.length > 0"
>
<ul class="events group pt-4" :class="{ 'disable-wrap': !softWrap, [size]: true, compact }">
<li
v-for="item in messages"
ref="list"
@@ -12,7 +8,7 @@
:data-time="item.date.getTime()"
class="group/entry"
>
<component :is="item.getComponent()" :log-entry="item" :show-container-name="showContainerName" />
<component :is="item.getComponent()" :log-entry="item" />
</li>
</ul>
</template>
@@ -24,7 +20,6 @@ const { loading, progress, currentDate } = useScrollContext();
const { messages } = defineProps<{
messages: LogEntry<string | JSONObject>[];
showContainerName: boolean;
}>();
watchEffect(() => {
@@ -58,7 +53,8 @@ useIntersectionObserver(
},
);
</script>
<style scoped lang="postcss">
<style scoped>
@import "@/main.css" reference;
.events {
font-family:
ui-monospace,
@@ -71,14 +67,10 @@ useIntersectionObserver(
monospace;
> li {
@apply flex break-words px-2 py-1 last:snap-end odd:bg-gray-400/[0.07] has-[.clickable]:cursor-pointer has-[.clickable]:hover:bg-primary/10 md:px-4;
@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;
&:last-child {
scroll-margin-block-end: 5rem;
}
.jump-context {
@apply mr-2 flex items-center font-sans text-secondary;
}
}
&.small {
@@ -102,5 +94,19 @@ useIntersectionObserver(
@apply rounded-none;
}
}
:deep(mark) {
@apply bg-secondary inline-block rounded-xs;
animation: pops 200ms ease-out;
}
}
@keyframes pops {
0% {
transform: scale(1.5);
}
100% {
transform: scale(1.05);
}
}
</style>
@@ -6,7 +6,7 @@
:title="t('log_actions.copy_log')"
>
<span
class="rounded bg-slate-800/60 px-1.5 py-1 text-primary hover:bg-slate-700"
class="text-primary rounded-sm bg-slate-800/60 px-1.5 py-1 hover:bg-slate-700"
@click.prevent="copyLogMessageToClipBoard()"
>
<carbon:copy-file />
+3 -2
View File
@@ -1,5 +1,5 @@
<template>
<Tag size="small" :std="std">
<Tag size="small" :std="std" class="items-start!">
{{ std }}
</Tag>
</template>
@@ -12,7 +12,8 @@ defineProps<{
}>();
</script>
<style lang="postcss" scoped>
<style scoped>
@import "@/main.css" reference;
[std="stdout"] {
@apply text-blue;
}
+2 -3
View File
@@ -1,5 +1,5 @@
<template>
<LogList :messages="visibleMessages" :show-container-name="showContainerName" />
<LogList :messages="visibleMessages" />
</template>
<script lang="ts" setup>
@@ -8,7 +8,6 @@ import { type JSONObject, LogEntry } from "@/models/LogEntry";
const props = defineProps<{
messages: LogEntry<string | JSONObject>[];
visibleKeys: Map<string[], boolean>;
showContainerName: boolean;
}>();
const { messages, visibleKeys } = toRefs(props);
@@ -40,4 +39,4 @@ watchEffect(() => {
});
});
</script>
<style scoped lang="postcss"></style>
<style scoped></style>
@@ -1,16 +1,19 @@
<template>
<div class="dropdown dropdown-end dropdown-hover">
<label tabindex="0" class="btn btn-ghost btn-sm gap-0.5 px-2">
<carbon:circle-solid class="w-2.5 text-red" v-if="streamConfig.stderr" />
<carbon:circle-solid class="w-2.5 text-blue" v-if="streamConfig.stdout" />
<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 z-50 w-52 rounded-box bg-base p-1 shadow">
<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()">
<octicon:trash-24 /> {{ $t("toolbar.clear") }}
<KeyShortcut char="k" :modifiers="['shift', 'meta']" />
</a>
</li>
<li>
<a :href="downloadUrl" download> <octicon:download-24 /> {{ $t("toolbar.download") }} </a>
</li>
<li>
<a @click.prevent="showSearch = true">
<mdi:magnify /> {{ $t("toolbar.search") }}
@@ -27,8 +30,8 @@
>
<div class="flex size-4 gap-0.5">
<template v-if="streamConfig.stderr && streamConfig.stdout">
<carbon:circle-solid class="w-2 text-red" />
<carbon:circle-solid class="w-2 text-blue" />
<carbon:circle-solid class="text-red w-2" />
<carbon:circle-solid class="text-blue w-2" />
</template>
</div>
{{ $t("toolbar.show-all") }}
@@ -42,7 +45,7 @@
"
>
<div class="flex size-4 flex-col gap-1">
<carbon:circle-solid class="w-2 text-blue" v-if="!streamConfig.stderr && streamConfig.stdout" />
<carbon:circle-solid class="text-blue w-2" v-if="!streamConfig.stderr && streamConfig.stdout" />
</div>
{{ $t("toolbar.show", { std: "STDOUT" }) }}
</a>
@@ -55,11 +58,26 @@
"
>
<div class="flex size-4 flex-col gap-1">
<carbon:circle-solid class="w-2 text-red" v-if="streamConfig.stderr && !streamConfig.stdout" />
<carbon:circle-solid class="text-red w-2" v-if="streamConfig.stderr && !streamConfig.stdout" />
</div>
{{ $t("toolbar.show", { std: "STDERR" }) }}
</a>
</li>
<li class="line"></li>
<li>
<a @click="showHostname = !showHostname">
<mdi:check class="w-4" v-if="showHostname" />
<div v-else class="w-4"></div>
{{ $t("toolbar.show-hostname") }}
</a>
</li>
<li>
<a @click="showContainerName = !showContainerName">
<mdi:check class="w-4" v-if="showContainerName" />
<div v-else class="w-4"></div>
{{ $t("toolbar.show-container-name") }}
</a>
</li>
</ul>
</div>
</template>
@@ -69,12 +87,25 @@ const { showSearch } = useSearchFilter();
const clear = defineEmit();
const { streamConfig } = useLoggingContext();
const { streamConfig, showHostname, showContainerName, containers } = useLoggingContext();
const downloadParams = computed(() =>
Object.entries(toValue(streamConfig))
.filter(([, value]) => value)
.reduce((acc, [key]) => ({ ...acc, [key]: "1" }), {}),
);
const downloadUrl = computed(() =>
withBase(
`/api/containers/${containers.value.map((c) => c.host + "~" + c.id).join(",")}/download?${new URLSearchParams(downloadParams.value).toString()}`,
),
);
</script>
<style scoped lang="postcss">
<style scoped>
@import "@/main.css" reference;
li.line {
@apply h-px bg-base-content/20;
@apply bg-base-content/20 h-px;
}
a {
@@ -0,0 +1,46 @@
<template>
<div class="tag grid w-40 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 }}
</div>
</div>
</template>
<script lang="ts">
const colors = [
"hsl(200, 85%, 65%)", // Vibrant Sky Blue
"hsl(150, 85%, 65%)", // Vibrant Mint
"hsl(300, 85%, 65%)", // Vibrant Purple
"hsl(25, 85%, 65%)", // Vibrant Peach
"hsl(270, 85%, 65%)", // Vibrant Lavender
"hsl(340, 85%, 65%)", // Vibrant Rose
"hsl(170, 85%, 65%)", // Vibrant Aqua
"hsl(50, 85%, 65%)", // Vibrant Yellow
"hsl(235, 85%, 65%)", // Vibrant Periwinkle
"hsl(10, 85%, 65%)", // Vibrant Coral
"hsl(180, 85%, 65%)", // Vibrant Turquoise
"hsl(320, 85%, 65%)", // Vibrant Orchid
"hsl(90, 85%, 65%)", // Vibrant Lime
"hsl(260, 85%, 65%)", // Vibrant Amethyst
"hsl(30, 85%, 65%)", // Vibrant Orange
"hsl(210, 85%, 65%)", // Vibrant Ocean
"hsl(290, 85%, 65%)", // Vibrant Mauve
"hsl(120, 85%, 65%)", // Vibrant Spring
"hsl(350, 85%, 65%)", // Vibrant Strawberry
"hsl(190, 85%, 65%)", // Vibrant Azure
] as const;
</script>
<script lang="ts" setup>
const { value, truncateRight = false } = defineProps<{
value: string;
truncateRight?: boolean;
}>();
const color = computed(() => colors[Math.abs(hashCode(value)) % colors.length]);
</script>
<style scoped>
.random-color {
background-color: v-bind(color);
}
</style>
+4 -4
View File
@@ -1,5 +1,5 @@
<template>
<table class="table table-zebra table-pin-rows table-md" v-if="!loading">
<table class="table-zebra table-pin-rows table-md table" v-if="!loading">
<thead>
<tr>
<th v-for="column in columns" :key="column">{{ column }}</th>
@@ -11,18 +11,18 @@
</tr>
</tbody>
</table>
<table class="table table-md animate-pulse" v-else>
<table class="table-md table animate-pulse" v-else>
<thead>
<tr>
<th v-for="_ in 3">
<div class="h-4 w-20 animate-pulse bg-base-content/50 opacity-50"></div>
<div class="bg-base-content/50 h-4 w-20 animate-pulse opacity-50"></div>
</th>
</tr>
</thead>
<tbody>
<tr v-for="_ in 9">
<td v-for="_ in 3">
<div class="h-4 w-20 bg-base-content/50 opacity-20"></div>
<div class="bg-base-content/50 h-4 w-20 opacity-20"></div>
</td>
</tr>
</tbody>
+13 -15
View File
@@ -1,19 +1,15 @@
<template>
<div class="relative flex w-full items-start gap-x-2">
<LogStd :std="logEntry.std" v-if="showStd" />
<ContainerName class="flex-none" :id="logEntry.containerID" v-if="showContainerName" />
<LogDate :date="logEntry.date" v-if="showTimestamp" class="select-none" />
<LogLevel class="flex" :level="logEntry.level" :position="logEntry.position" />
<LogItem :logEntry>
<div
class="log-wrapper whitespace-pre-wrap [word-break:break-word] group-[.disable-wrap]:whitespace-nowrap"
class="log-wrapper [word-break:break-word] whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap"
v-html="linkify(colorize(logEntry.message))"
></div>
<LogMessageActions
class="duration-250 absolute -right-1 opacity-0 transition-opacity delay-150 group-hover/entry:opacity-100"
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"
/>
</div>
</LogItem>
</template>
<script lang="ts" setup>
import { SimpleLogEntry } from "@/models/LogEntry";
@@ -23,22 +19,24 @@ import stripAnsi from "strip-ansi";
const ansiConvertor = new AnsiConvertor({
escapeXML: false,
fg: "var(--base-content-color)",
bg: "var(--base-color)",
fg: "var(--color-base-content)",
bg: "var(--color-base-100)",
});
const { showContainerName = false } = defineProps<{
defineProps<{
logEntry: SimpleLogEntry;
showContainerName?: boolean;
}>();
const colorize = (value: string) => ansiConvertor.toHtml(value);
const urlPattern = /(https?:\/\/[^\s]+)/g;
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 lang="postcss">
.log-wrapper > :deep(a) {
<style scoped>
@import "@/main.css" reference;
.log-wrapper :deep(a) {
@apply text-primary underline-offset-4 hover:underline;
}
</style>
@@ -2,7 +2,7 @@
<div class="my-4 flex-1 text-center">
<div class="relative">
<ZigZag class="absolute inset-0 mt-2" />
<span class="relative whitespace-pre-wrap bg-base px-4 py-2 font-bold">
<span class="bg-base-200 relative px-4 py-2 font-bold whitespace-pre-wrap">
{{ $t("error.logs-skipped", { total: logEntry.totalSkipped }) }}
</span>
</div>
+5 -5
View File
@@ -1,12 +1,12 @@
<template>
<div class="relative hover:text-secondary" @mouseenter="mouseOver = true" @mouseleave="mouseOver = false">
<div class="hidden overflow-hidden rounded-sm border border-primary px-px pb-px pt-1 md:flex">
<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">
<StatSparkline :data="data" @selected-point="onSelectedPoint" />
</div>
<div class="inline-flex gap-1 rounded bg-base p-px text-xs md:absolute md:-left-0.5 md:-top-2">
<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="select-none font-bold">
{{ mouseOver ? selectedPoint?.value ?? selectedPoint?.y ?? statValue : statValue }}
<div class="font-bold select-none">
{{ mouseOver ? (selectedPoint?.value ?? selectedPoint?.y ?? statValue) : statValue }}
</div>
</div>
</div>
@@ -1,7 +1,7 @@
<template>
<svg :width="width" :height="height" @mousemove="onMove" class="group">
<path :d="path" class="fill-primary" />
<line :x1="lineX" y1="0" :x2="lineX" :y2="height" class="invisible stroke-secondary stroke-2 group-hover:visible" />
<line :x1="lineX" y1="0" :x2="lineX" :y2="height" class="stroke-secondary invisible stroke-2 group-hover:visible" />
</svg>
</template>
@@ -1,6 +1,6 @@
<template>
<EventSource ref="source" #default="{ messages }" :stream-source="streamSource" :entity="entity">
<LogViewer :messages="messages" :visible-keys="visibleKeys" :show-container-name="showContainerName" />
<LogViewer :messages="messages" :visible-keys="visibleKeys" />
</EventSource>
</template>
@@ -9,10 +9,9 @@ import EventSource from "@/components/LogViewer/EventSource.vue";
import { LogStreamSource } from "@/composable/eventStreams";
import { ComponentExposed } from "vue-component-type-helpers";
const { streamSource, visibleKeys, showContainerName, entity } = defineProps<{
const { streamSource, visibleKeys, entity } = defineProps<{
streamSource: (t: Ref<T>) => LogStreamSource;
visibleKeys: Map<string[], boolean>;
showContainerName: boolean;
entity: T;
}>();
@@ -1,17 +1,18 @@
// 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">
"<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">
<div data-v-a49e52d4="" data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
<!--v-if-->
<!--v-if-->
<div data-v-961504e7="" data-v-a49e52d4="" class="tag inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em] select-none" size="small">
<div class="inline-flex gap-2 whitespace-nowrap text-blue"><time datetime="2019-06-12T10:55:42.459Z" class="mobile-hidden">06/12/2019</time><time datetime="2019-06-12T10:55:42.459Z">10:55:42 AM</time></div>
<!--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>
<div data-v-e625cddd="" data-v-a49e52d4="" class="mt-1.5 size-2.5 flex-none rounded-lg flex"></div>
<div data-v-a49e52d4="" class="log-wrapper whitespace-pre-wrap [word-break:break-word] group-[.disable-wrap]:whitespace-nowrap">foo bar</div>
<div data-v-a49e52d4="" class="flex gap-2 duration-250 absolute -right-1 opacity-0 transition-opacity delay-150 group-hover/entry:opacity-100">
<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>
@@ -20,17 +21,18 @@ exports[`<ContainerEventSource /> > render html correctly > should render dates
`;
exports[`<ContainerEventSource /> > render html correctly > should render dates with 24 hour style 1`] = `
"<ul data-v-cf9ff940="" class="events group pt-4 medium">
"<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">
<div data-v-a49e52d4="" data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
<!--v-if-->
<!--v-if-->
<div data-v-961504e7="" data-v-a49e52d4="" class="tag inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em] select-none" size="small">
<div class="inline-flex gap-2 whitespace-nowrap text-blue"><time datetime="2019-06-12T10:55:42.459Z" class="mobile-hidden">06/12/2019</time><time datetime="2019-06-12T10:55:42.459Z">10:55:42</time></div>
<!--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>
<div data-v-e625cddd="" data-v-a49e52d4="" class="mt-1.5 size-2.5 flex-none rounded-lg flex"></div>
<div data-v-a49e52d4="" class="log-wrapper whitespace-pre-wrap [word-break:break-word] group-[.disable-wrap]:whitespace-nowrap">foo bar</div>
<div data-v-a49e52d4="" class="flex gap-2 duration-250 absolute -right-1 opacity-0 transition-opacity delay-150 group-hover/entry:opacity-100">
<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>
@@ -39,17 +41,18 @@ exports[`<ContainerEventSource /> > render html correctly > should render dates
`;
exports[`<ContainerEventSource /> > render html correctly > should render messages 1`] = `
"<ul data-v-cf9ff940="" class="events group pt-4 medium">
"<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">
<div data-v-a49e52d4="" data-v-cf9ff940="" class="relative flex w-full items-start gap-x-2 group-[.compact]:items-stretch">
<!--v-if-->
<!--v-if-->
<div data-v-961504e7="" data-v-a49e52d4="" class="tag inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em] select-none" size="small">
<div class="inline-flex gap-2 whitespace-nowrap text-blue"><time datetime="2019-06-12T10:55:42.459Z" class="mobile-hidden">06/12/2019</time><time datetime="2019-06-12T10:55:42.459Z">10:55:42 AM</time></div>
<!--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>
<div data-v-e625cddd="" data-v-a49e52d4="" class="mt-1.5 size-2.5 flex-none rounded-lg flex"></div>
<div data-v-a49e52d4="" class="log-wrapper whitespace-pre-wrap [word-break:break-word] group-[.disable-wrap]:whitespace-nowrap">This is a message.</div>
<div data-v-a49e52d4="" class="flex gap-2 duration-250 absolute -right-1 opacity-0 transition-opacity delay-150 group-hover/entry:opacity-100">
<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>
@@ -57,11 +60,6 @@ exports[`<ContainerEventSource /> > render html correctly > should render messag
</ul>"
`;
exports[`<ContainerEventSource /> > renders loading correctly 1`] = `
"<div class="flex min-h-[1px] justify-center"><span class="loading loading-bars loading-md mt-4 text-primary" style="display: none;"></span></div>
<!--v-if-->"
`;
exports[`<ContainerEventSource /> > should parse messages 1`] = `
SimpleLogEntry {
"_message": "This is a message.",
@@ -2,13 +2,14 @@
<ScrollableView :scrollable="scrollable" v-if="containers.length && ready">
<template #header>
<div class="mx-2 flex items-center gap-2 md:ml-4">
<div class="flex flex-1 gap-1.5 truncate @container md:gap-2">
<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>
<MultiContainerStat class="ml-auto" :containers="containers" />
<MultiContainerActionToolbar class="mobile-hidden" @clear="viewer?.clear()" />
<MultiContainerActionToolbar class="max-md:hidden" @clear="viewer?.clear()" />
</div>
</template>
<template #default>
@@ -17,7 +18,6 @@
:stream-source="useMergedStream"
:entity="containers"
:visible-keys="new Map<string[], boolean>()"
:show-container-name="true"
/>
</template>
</ScrollableView>
@@ -37,5 +37,5 @@ const viewer = ref<ComponentExposed<typeof ViewerWithSource>>();
const { allContainersById, ready } = storeToRefs(containerStore);
const containers = computed(() => ids.map((id) => allContainersById.value[id]));
provideLoggingContext(containers);
provideLoggingContext(containers, { showContainerName: true, showHostname: false });
</script>
+2 -2
View File
@@ -4,7 +4,7 @@
<transition name="fade">
<div
v-show="show && (delayedShow || glopbalShow)"
class="fixed z-50 rounded border border-base-content/20 bg-base-lighter p-4 shadow"
class="border-base-content/20 bg-base-100 fixed z-50 rounded-sm border p-4 shadow-sm"
ref="content"
>
<slot name="content"></slot>
@@ -45,7 +45,7 @@ useEventListener(() => el.value?.nextElementSibling, "mouseenter", onMouseEnter)
useEventListener(() => el.value?.nextElementSibling, "mouseleave", onMouseLeave);
</script>
<style scoped lang="postcss">
<style scoped>
.fade-enter-active,
.fade-leave-active {
@apply transition-opacity;
-74
View File
@@ -1,74 +0,0 @@
<template>
<ul class="space-y-4 p-2">
<li v-for="release in releases" v-if="releases?.length">
<div class="flex items-baseline gap-1">
<carbon:warning class="self-center stroke-orange" v-if="release.breaking > 0" />
<a :href="release.htmlUrl" class="link-primary text-lg font-bold" target="_blank" rel="noreferrer noopener">
{{ release.name }}
</a>
<span class="ml-1 text-xs"><distance-time :date="new Date(release.createdAt)" /></span>
<Tag class="ml-auto bg-red px-1 py-1 text-xs" v-if="release.tag === latest?.tag">
{{ $t("releases.latest") }}
</Tag>
</div>
<div class="text-sm text-base-content/80">
{{ summary(release) }}
</div>
</li>
<li v-else>
<div class="text-sm text-base-content/80">
{{ $t("releases.no_releases") }}
</div>
</li>
</ul>
</template>
<script setup lang="ts">
const { releases, latest } = useReleases();
const { t } = useI18n();
function summary(release: { features: number; bugFixes: number; breaking: number }) {
if (release.features > 0 && release.bugFixes > 0 && release.breaking > 0) {
return t("releases.three_parts", {
first: t("releases.breaking", { count: release.breaking }),
second: t("releases.features", { count: release.features }),
third: t("releases.bugFixes", { count: release.bugFixes }),
});
}
if (release.features > 0 && release.bugFixes > 0) {
return t("releases.two_parts", {
first: t("releases.features", { count: release.features }),
second: t("releases.bugFixes", { count: release.bugFixes }),
});
}
if (release.features > 0 && release.breaking > 0) {
return t("releases.two_parts", {
first: t("releases.features", { count: release.features }),
second: t("releases.breaking", { count: release.breaking }),
});
}
if (release.bugFixes > 0 && release.breaking > 0) {
return t("releases.two_parts", {
first: t("releases.bugFixes", { count: release.bugFixes }),
second: t("releases.breaking", { count: release.breaking }),
});
}
if (release.features > 0) {
return t("releases.features", { count: release.features });
}
if (release.bugFixes > 0) {
return t("releases.bugFixes", { count: release.bugFixes });
}
if (release.breaking > 0) {
return t("releases.breaking", { count: release.breaking });
}
}
</script>
<style scoped></style>
+3 -3
View File
@@ -3,7 +3,7 @@
<div class="inline-flex flex-col items-end gap-2" ref="root" v-show="!autoHide || show">
<div class="relative inline-block">
<svg width="100" height="100" viewBox="0 0 100 100" :class="{ indeterminate }">
<circle r="44" cx="50" cy="50" class="fill-base-darker stroke-primary" />
<circle r="44" cx="50" cy="50" class="fill-base-300 stroke-primary" />
</svg>
<div class="absolute inset-0 flex items-center justify-center font-light">
<span class="text-4xl">
@@ -12,7 +12,7 @@
<span> % </span>
</div>
</div>
<DistanceTime :date="date" class="whitespace-nowrap text-sm" />
<DistanceTime :date="date" class="text-sm whitespace-nowrap" />
</div>
</transition>
</template>
@@ -41,7 +41,7 @@ watch(
},
);
</script>
<style scoped lang="postcss">
<style scoped>
svg {
filter: drop-shadow(0px 1px 1px rgba(0, 0, 0, 0.2));
margin-top: 5px;
+6 -26
View File
@@ -2,39 +2,33 @@
<section :class="{ 'h-screen min-h-0': scrollable }" class="flex flex-col">
<header
v-if="$slots.header"
class="sticky top-[70px] z-[2] border-b border-base-content/10 bg-base 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-[55px] z-2 border-b py-2 shadow-[1px_1px_2px_0_rgb(0,0,0,0.05)] md:top-0"
>
<slot name="header"></slot>
</header>
<main :data-scrolling="scrollable ? true : undefined" class="snap-y overflow-auto">
<div class="invisible relative md:visible" v-show="scrollContext.paused">
<div class="absolute right-44 top-4">
<div class="absolute top-4 right-44">
<ScrollProgress
:indeterminate="loadingMore"
:auto-hide="!loadingMore"
:progress="scrollContext.progress"
:date="scrollContext.currentDate"
class="!fixed z-10 min-w-40"
class="fixed! z-10 min-w-40"
/>
</div>
</div>
<div ref="scrollableContent">
<slot></slot>
<div v-if="scrollContext.loading" class="m-4 text-center">
<span class="loading loading-ring loading-md text-primary"></span>
</div>
</div>
<div
class="animate-background h-1 w-1/2 bg-gradient-radial from-primary to-transparent to-75%"
v-show="!scrollContext.paused && !scrollContext.loading"
></div>
<div ref="scrollObserver" class="h-px"></div>
</main>
<div class="mr-16 text-right">
<transition name="fade">
<button
class="transition-colorsblur-xs dark btn btn-primary fixed bottom-8 rounded p-3 text-primary-content shadow"
class="transition-colorsblur-xs dark btn btn-primary text-primary-content fixed bottom-8 rounded-sm p-3 shadow-sm"
:class="hasMore ? 'btn-secondary animate-bounce-fast text-secondary-content' : ''"
@click="scrollToBottom()"
v-show="scrollContext.paused"
@@ -83,7 +77,7 @@ function scrollToBottom(behavior: "auto" | "smooth" = "auto") {
hasMore.value = false;
}
</script>
<style scoped lang="postcss">
<style scoped>
.fade-enter-active,
.fade-leave-active {
@apply transition-opacity;
@@ -93,20 +87,6 @@ function scrollToBottom(behavior: "auto" | "smooth" = "auto") {
.fade-leave-to {
@apply opacity-0;
}
.animate-background {
animation: gradient-animation 3s ease-out infinite;
}
@keyframes gradient-animation {
0%,
100% {
transform: translateX(0%);
}
50% {
transform: translateX(100%);
}
}
</style>
<style>
+2 -5
View File
@@ -7,10 +7,7 @@
ref="container"
:style="style"
>
<div
class="input input-primary flex h-auto items-center !shadow-lg"
:class="!isValidQuery ? 'input-warning' : ''"
>
<div class="input input-primary flex items-center shadow-lg" :class="!isValidQuery ? 'input-warning' : ''">
<mdi:magnify />
<input
class="input input-ghost w-72 flex-1"
@@ -57,7 +54,7 @@ onMounted(() => {
onUnmounted(() => resetSearch());
</script>
<style lang="postcss" scoped>
<style scoped>
.slide-enter-active,
.slide-leave-active {
transition: all 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
@@ -2,16 +2,17 @@
<ScrollableView :scrollable="scrollable" v-if="service.name">
<template #header>
<div class="mx-2 flex items-center gap-2 md:ml-4">
<div class="flex flex-1 gap-1.5 truncate @container md:gap-2">
<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="mobile-hidden hidden font-mono @3xl:block" size="small">
<Tag class="hidden font-mono max-md:hidden @3xl:block" size="small">
{{ $t("label.container", service.containers.length) }}
</Tag>
</div>
<MultiContainerStat class="ml-auto" :containers="service.containers" />
<MultiContainerActionToolbar class="mobile-hidden" @clear="viewer?.clear()" />
<MultiContainerActionToolbar class="max-md:hidden" @clear="viewer?.clear()" />
</div>
</template>
<template #default>
@@ -20,7 +21,6 @@
:stream-source="useServiceStream"
:entity="service"
:visible-keys="new Map<string[], boolean>()"
:show-container-name="true"
/>
</template>
</ScrollableView>
@@ -41,5 +41,8 @@ const store = useSwarmStore();
const { services } = storeToRefs(store) as unknown as { services: Ref<Service[]> };
const service = computed(() => services.value.find((s) => s.name === name) ?? new Service("", []));
provideLoggingContext(toRef(() => service.value.containers));
provideLoggingContext(
toRef(() => service.value.containers),
{ showContainerName: true, showHostname: false },
);
</script>
+23 -24
View File
@@ -1,40 +1,39 @@
<template>
<div v-if="ready" data-testid="side-menu">
<Toggle v-model="showSwarm" v-if="services.length > 0 || customGroups.length > 0">
<div class="text-lg font-light">{{ $t("label.swarm-mode") }}</div>
</Toggle>
<SlideTransition :slide-right="showSwarm">
<template #left>
<div v-if="ready" data-testid="side-menu" class="flex min-h-0 flex-col">
<Carousel v-model="selectedCard" class="flex-1">
<CarouselItem :title="$t('label.host-menu')" id="host">
<HostMenu />
</template>
<template #right>
</CarouselItem>
<CarouselItem :title="$t('label.group-menu')" v-if="customGroups.length > 0" id="group">
<GroupMenu />
</CarouselItem>
<CarouselItem :title="$t('label.swarm-menu')" v-if="services.length > 0" id="swarm">
<SwarmMenu />
</template>
</SlideTransition>
</CarouselItem>
</Carousel>
</div>
<div role="status" class="flex animate-pulse flex-col gap-4" v-else>
<div class="h-3 w-full rounded-full bg-base-content/50 opacity-50" v-for="_ in 9"></div>
<div class="bg-base-content/50 h-3 w-full rounded-full opacity-50" v-for="_ in 9"></div>
<span class="sr-only">Loading...</span>
</div>
</template>
<script lang="ts" setup>
import { onBeforeRouteLeave } from "vue-router";
const containerStore = useContainerStore();
const { ready } = storeToRefs(containerStore);
const route = useRoute();
const swarmStore = useSwarmStore();
const { services, customGroups } = storeToRefs(swarmStore);
const selectedCard = ref<"host" | "swarm" | "group">("host");
const showSwarm = useSessionStorage<boolean>("DOZZLE_SWARM_MODE", false);
onBeforeRouteLeave((to) => {
if (to.meta.swarmMode) {
showSwarm.value = true;
} else if (to.meta.containerMode) {
showSwarm.value = false;
}
});
watch(
route,
() => {
if (route.meta.menu && ["host", "swarm", "group"].includes(route.meta.menu as string)) {
selectedCard.value = route.meta.menu as "host" | "swarm" | "group";
}
},
{ immediate: true },
);
</script>
<style scoped lang="postcss"></style>
<style scoped></style>
+7 -6
View File
@@ -1,10 +1,8 @@
<template>
<aside class="fixed h-screen w-[inherit] overflow-auto p-3" data-testid="navigation">
<aside class="fixed flex h-screen w-[inherit] flex-col gap-4 p-3" data-testid="navigation">
<h1>
<router-link :to="{ name: '/' }">
<svg class="h-14 w-28 fill-secondary">
<use href="#logo"></use>
</svg>
<LogoWithText class="logo [&_.secondary-fill]:fill-secondary [&_.content-fill]:fill-base-content h-16 w-40" />
</router-link>
<small class="mb-4 block text-xs font-light" v-if="hostname">
@@ -13,7 +11,7 @@
</h1>
<button
class="input input-sm mt-4 inline-flex cursor-pointer items-center gap-2 font-light hover:border-primary"
class="input input-sm hover:border-primary inline-flex w-auto cursor-pointer items-center gap-2 self-start font-light"
@click="$emit('search')"
:title="$t('tooltip.search')"
data-testid="search"
@@ -23,10 +21,13 @@
<key-shortcut char="k" class="text-base-content/70"></key-shortcut>
</button>
<side-menu class="mt-4"></side-menu>
<SideMenu class="mt-2 flex-1" />
</aside>
</template>
<script lang="ts" setup>
import LogoWithText from "@/logo-text.svg";
const { hostname } = config;
</script>
<style scoped></style>
+10 -7
View File
@@ -2,19 +2,20 @@
<ScrollableView :scrollable="scrollable" v-if="stack.name">
<template #header>
<div class="mx-2 flex items-center gap-2 md:ml-4">
<div class="flex flex-1 gap-1.5 truncate @container md:gap-2">
<div class="@container flex flex-1 gap-1.5 truncate md:gap-2">
<ph:stack />
<div class="inline-flex font-mono text-sm">
<div class="font-semibold">{{ stack.name }}</div>
</div>
<Tag class="mobile-hidden hidden font-mono @3xl:block" size="small">
<Tag class="hidden font-mono max-md:hidden @3xl:block" size="small">
{{ $t("label.container", stack.containers.length) }}
</Tag>
<Tag class="mobile-hidden hidden font-mono @3xl:block" size="small">
{{ $t("label.serivce", stack.services.length) }}
<Tag class="hidden font-mono max-md:hidden @3xl:block" size="small">
{{ $t("label.service", stack.services.length) }}
</Tag>
</div>
<MultiContainerStat class="ml-auto" :containers="stack.containers" />
<MultiContainerActionToolbar class="mobile-hidden" @clear="viewer?.clear()" />
<MultiContainerActionToolbar class="max-md:hidden" @clear="viewer?.clear()" />
</div>
</template>
<template #default>
@@ -23,7 +24,6 @@
:stream-source="useStackStream"
:entity="stack"
:visible-keys="new Map<string[], boolean>()"
:show-container-name="true"
/>
</template>
</ScrollableView>
@@ -42,5 +42,8 @@ const viewer = ref<ComponentExposed<typeof ViewerWithSource>>();
const store = useSwarmStore();
const { stacks } = storeToRefs(store) as unknown as { stacks: Ref<Stack[]> };
const stack = computed(() => stacks.value.find((s) => s.name === name) ?? new Stack("", [], []));
provideLoggingContext(toRef(() => stack.value.containers));
provideLoggingContext(
toRef(() => stack.value.containers),
{ showContainerName: true, showHostname: false },
);
</script>
+9 -39
View File
@@ -1,8 +1,8 @@
<template>
<ul class="menu p-0 text-[0.95rem]">
<ul class="menu w-full p-0 text-[0.95rem]">
<li v-for="{ name, services } in stacks" :key="name">
<details open>
<summary class="font-light text-base-content/80">
<summary class="text-base-content/80 font-light">
<ph:stack />
{{ name }}
@@ -17,10 +17,7 @@
</summary>
<ul>
<li v-for="service in services" :key="service.name">
<router-link
:to="{ name: '/service/[name]', params: { name: service.name } }"
active-class="active-primary"
>
<router-link :to="{ name: '/service/[name]', params: { name: service.name } }" active-class="menu-active">
<ph:stack-simple />
<div class="truncate">
{{ service.name }}
@@ -31,18 +28,15 @@
</details>
</li>
<li v-if="serivcesWithoutStacks.length > 0">
<li v-if="servicesWithoutStacks.length > 0">
<details open>
<summary class="font-light text-base-content/80">
<summary class="text-base-content/80 font-light">
<ph:circles-four />
{{ $t("label.services") }}
</summary>
<ul>
<li v-for="service in serivcesWithoutStacks" :key="service.name">
<router-link
:to="{ name: '/service/[name]', params: { name: service.name } }"
active-class="active-primary"
>
<li v-for="service in servicesWithoutStacks" :key="service.name">
<router-link :to="{ name: '/service/[name]', params: { name: service.name } }" active-class="menu-active">
<ph:stack-simple />
<div class="truncate">
{{ service.name }}
@@ -52,37 +46,13 @@
</ul>
</details>
</li>
<li v-if="customGroups.length > 0">
<details open>
<summary class="font-light text-base-content/80">
<ph:bounding-box-fill />
{{ $t("label.custom-groups") }}
</summary>
<ul>
<li v-for="group in customGroups" :key="group.name">
<router-link :to="{ name: '/group/[name]', params: { name: group.name } }" active-class="active-primary">
<ph:stack-simple />
<div class="truncate">
{{ group.name }}
</div>
</router-link>
</li>
</ul>
</details>
</li>
</ul>
</template>
<script lang="ts" setup>
const store = useSwarmStore();
const { stacks, services, customGroups } = storeToRefs(store);
const { stacks, services } = storeToRefs(store);
const serivcesWithoutStacks = computed(() => services.value.filter((service) => !service.stack));
const servicesWithoutStacks = computed(() => services.value.filter((service) => !service.stack));
</script>
<style scoped lang="postcss">
.menu {
@apply text-[0.95rem];
}
</style>
+97
View File
@@ -0,0 +1,97 @@
<template>
<div class="flex min-h-0 flex-col gap-2">
<div class="flex min-h-0 flex-1 flex-col overflow-auto overscroll-y-contain">
<div
ref="container"
class="scrollbar-hide flex shrink-0 grow snap-x snap-mandatory overflow-x-auto overscroll-x-contain scroll-smooth"
>
<component v-for="(card, index) in providedCards" :key="index" :is="card" ref="cards" />
</div>
</div>
<div class="flex flex-col gap-2">
<h3 class="text-center text-sm font-thin">
{{ cards?.[activeIndex].title }}
</h3>
<div class="flex flex-none justify-center gap-2" v-if="providedCards.length > 1">
<button
v-for="(c, index) in providedCards"
:key="c.props?.id"
@click="scrollToItem(index)"
:class="[
'size-2 cursor-pointer rounded-full transition-all duration-700',
activeIndex === index ? 'bg-primary scale-125' : 'bg-base-content/50 hover:bg-base-content',
]"
:aria-label="c.props?.title"
:title="c.props?.title"
/>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import CarouselItem from "./CarouselItem.vue";
const container = useTemplateRef<HTMLDivElement>("container");
const activeIndex = ref(0);
const activeId = defineModel<string>();
const slots = defineSlots<{ default(): VNode[] }>();
const providedCards = computed(() => slots.default().filter(({ type }) => type === CarouselItem));
const cards = useTemplateRef<InstanceType<typeof CarouselItem>[]>("cards");
const scrollToItem = (index: number) => {
cards.value?.[index].$el.scrollIntoView({
behavior: "smooth",
inline: "start",
});
};
const { pause, resume } = watchPausable(activeId, (v) => {
if (activeId.value) {
const index = cards.value?.map((c) => c.id).indexOf(activeId.value) ?? -1;
if (index !== -1) {
console.log("watching", activeId.value);
scrollToItem(index);
}
}
});
watchOnce(cards, () => {
if (activeId.value) {
const index = cards.value?.map((c) => c.id).indexOf(activeId.value) ?? -1;
if (index !== -1) {
scrollToItem(index);
}
}
});
useIntersectionObserver(
cards as Ref<InstanceType<typeof CarouselItem>[]>,
(entries) => {
entries.forEach(({ isIntersecting, target }) => {
if (isIntersecting) {
const index = cards.value?.map((c) => c.$el).indexOf(target as HTMLDivElement) ?? -1;
if (index !== -1) {
pause();
activeIndex.value = index;
activeId.value = cards.value?.[index].id;
nextTick(() => resume());
}
}
});
},
{
root: container,
threshold: 0.5,
},
);
</script>
<style scoped>
.scrollbar-hide {
scrollbar-width: none;
-ms-overflow-style: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
</style>
+16
View File
@@ -0,0 +1,16 @@
<template>
<div class="w-full min-w-full shrink-0 snap-start snap-always p-0.5">
<slot />
</div>
</template>
<script lang="ts" setup>
const { id, title } = defineProps<{ id: string; title?: string }>();
defineExpose({
id,
title,
});
</script>
<style scoped></style>
+1 -1
View File
@@ -1,6 +1,6 @@
<template>
<div class="inline-flex gap-2">
<time :datetime="date.toISOString()" class="mobile-hidden">{{ dateStr }}</time>
<time :datetime="date.toISOString()" class="max-md:hidden">{{ dateStr }}</time>
<time :datetime="date.toISOString()">{{ timeStr }}</time>
</div>
</template>
+1 -1
View File
@@ -5,7 +5,7 @@
</label>
<div
tabindex="0"
class="dropdown-content z-50 mt-1 min-w-52 rounded-box border border-base-content/20 bg-base p-2 shadow"
class="dropdown-content rounded-box border-base-content/20 bg-base-200 z-50 mt-1 min-w-52 border p-2 shadow-sm"
>
<slot name="content"></slot>
</div>
+13 -9
View File
@@ -3,10 +3,12 @@
<summary class="btn btn-primary flex-nowrap" v-bind="$attrs">
<slot name="trigger"> {{ label }} <carbon:caret-down /></slot>
</summary>
<ul class="menu dropdown-content z-50 mt-1 w-52 rounded-box border border-base-content/20 bg-base p-2 shadow">
<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"
>
<slot>
<li v-for="item in options">
<a @click="modelValue = item.value">
<a @click="update(item.value as T)">
<mdi:check class="w-4" v-if="modelValue == item.value" />
<div v-else class="w-4"></div>
{{ item.label }}
@@ -24,17 +26,19 @@ type DropdownItem = {
value: T;
};
const { modelValue } = defineModels<{
modelValue: T;
}>();
const model = defineModel<T>();
const { options = [], defaultLabel = "" } = defineProps<{
options?: DropdownItem[];
const { options, defaultLabel = "" } = defineProps<{
options: DropdownItem[];
defaultLabel?: string;
}>();
const label = computed(() => options.find((item) => item.value === modelValue.value)?.label ?? defaultLabel);
const label = computed(() => options.find((item) => item.value === model.value)?.label ?? defaultLabel);
const details = ref<HTMLElement | null>(null);
const close = () => details.value?.removeAttribute("open");
watch(modelValue, () => close());
const update = (value: T) => {
model.value = value;
close();
};
</script>
+1
View File
@@ -2,6 +2,7 @@
<mdi:satellite-variant v-if="type == 'agent'" />
<ph:globe-simple v-else-if="type == 'remote'" />
<mdi:hexagon-multiple v-else-if="type == 'swarm'" />
<carbon:logo-kubernetes v-else-if="type == 'k8s'" />
<ph:computer-tower v-else />
</template>
<script setup lang="ts">
@@ -0,0 +1,33 @@
<template>
<div class="animate-background h-1 w-1/2 bg-radial to-transparent to-75%" :class="colorClass"></div>
</template>
<script setup lang="ts">
const { color = "primary" } = defineProps<{ color: "primary" | "error" | "secondary" }>();
const colorClass = computed(() => {
switch (color) {
case "primary":
return "from-primary";
case "error":
return "from-error";
case "secondary":
return "from-secondary";
}
});
</script>
<style scoped>
.animate-background {
animation: gradient-animation 3s ease-out infinite;
}
@keyframes gradient-animation {
0%,
100% {
transform: translateX(0%);
}
50% {
transform: translateX(100%);
}
}
</style>
+2 -2
View File
@@ -1,6 +1,6 @@
<template>
<label class="label cursor-pointer gap-4">
<div class="label-text"><slot name="label" /></div>
<label class="label text-base-content cursor-pointer gap-4">
<div class="flex-1"><slot name="label" /></div>
<slot name="input" />
</label>
</template>
+12 -16
View File
@@ -1,10 +1,8 @@
<template>
<nav class="fixed top-0 z-10 w-full border-b border-base-content/20 bg-base p-2" data-testid="navigation">
<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: '/' }">
<svg class="h-14 w-28 fill-secondary">
<use href="#logo"></use>
</svg>
<Logo class="logo [&_.secondary-fill]:fill-secondary h-8" />
</router-link>
<div class="ml-auto flex items-center gap-2">
@@ -20,14 +18,15 @@
</div>
<transition name="fade">
<div v-show="show" class="h-[calc(100vh-60px)] overflow-auto">
<SideMenu />
<div v-show="show" class="flex h-[calc(100svh-55px)]">
<SideMenu class="flex-1" />
</div>
</transition>
</nav>
</template>
<script lang="ts" setup>
import Logo from "@/logo.svg";
const route = useRoute();
const show = ref(false);
@@ -35,18 +34,15 @@ watch(route, () => {
show.value = false;
});
</script>
<style scoped lang="postcss">
li.exited {
@apply opacity-50;
}
<style scoped>
@import "@/main.css" reference;
.fade-enter-active,
.fade-leave-active {
@apply transition-opacity;
}
.fade-enter-active .menu,
.fade-leave-active .menu {
.fade-enter-active > div,
.fade-leave-active > div {
@apply transition-transform;
}
@@ -55,8 +51,8 @@ li.exited {
@apply opacity-0;
}
.fade-enter-from .menu,
.fade-leave-to .menu {
@apply -translate-y-2;
.fade-enter-from > div,
.fade-leave-to > div {
@apply -translate-y-10;
}
</style>
+6 -4
View File
@@ -1,8 +1,8 @@
<template>
<dialog ref="panel" class="modal-right modal items-start outline-none backdrop:bg-none">
<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 absolute right-4 top-4 outline-none hover:swap-active">
<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>
@@ -32,9 +32,11 @@ defineExpose({
useEventListener(panel, "close", () => (open.value = false));
</script>
<style scoped lang="postcss">
<style scoped>
@import "@/main.css" reference;
.modal-right :where(.modal-box) {
@apply fixed right-0 h-lvh max-h-screen translate-x-24 scale-100 rounded-none bg-base-lighter shadow-none;
@apply bg-base-100 fixed right-0 h-lvh max-h-screen translate-x-24 scale-100 rounded-none shadow-none;
&[width="md"] {
@apply max-w-3xl;
+1 -1
View File
@@ -12,7 +12,7 @@
<script lang="ts" setup>
const { slideRight } = defineProps<{ slideRight: boolean }>();
</script>
<style scoped lang="postcss">
<style scoped>
.slide-left-enter-active,
.slide-left-leave-active,
.slide-right-enter-active,
+4 -7
View File
@@ -1,5 +1,8 @@
<template>
<div class="tag inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em]" :size="size">
<div
class="tag bg-base-100 inline-flex items-center justify-center rounded-sm px-2 py-[0.2em] [[size='small']]:text-[0.8rem]"
:size="size"
>
<slot></slot>
</div>
</template>
@@ -7,9 +10,3 @@
<script lang="ts" setup>
const { size = undefined } = defineProps<{ size?: "small" | undefined }>();
</script>
<style scoped lang="postcss">
[size="small"] {
@apply text-xs;
}
</style>
+1 -1
View File
@@ -40,4 +40,4 @@ const cancel = () => {
};
</script>
<style scoped lang="postcss"></style>
<style scoped></style>
+42
View File
@@ -0,0 +1,42 @@
<template>
<div class="toast toast-end whitespace-normal max-md:m-0 max-md:w-full">
<div
class="alert max-w-xl max-md:rounded-none"
v-for="{ toast, options: { timed } } in toasts"
:key="toast.id"
:class="{
'alert-error': toast.type === 'error',
'alert-info': toast.type === 'info',
'alert-warning': toast.type === 'warning',
}"
>
<carbon:information class="size-6 shrink-0 stroke-current" v-if="toast.type === 'info'" />
<carbon:warning class="size-6 shrink-0 stroke-current" v-else-if="toast.type === 'error'" />
<carbon:warning class="size-6 shrink-0 stroke-current" v-else-if="toast.type === 'warning'" />
<div>
<h3 class="text-lg font-bold" v-if="toast.title">{{ toast.title }}</h3>
<div v-html="toast.message" class="[&>a]:underline"></div>
</div>
<div>
<TimedButton
v-if="timed"
class="btn-primary btn-sm"
:duration="timed"
@finished="
removeToast(toast.id);
toast.action?.handler();
"
@cancelled="removeToast(toast.id)"
>
{{ toast.action?.label }}
</TimedButton>
<button class="btn btn-circle btn-xs" @click="removeToast(toast.id)" v-else>
<mdi:close />
</button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
const { toasts, removeToast } = useToast();
</script>
+1 -3
View File
@@ -10,7 +10,5 @@
</template>
<script lang="ts" setup>
const { modelValue } = defineModels<{
modelValue: boolean;
}>();
const modelValue = defineModel<boolean>();
</script>
+38 -6
View File
@@ -26,6 +26,10 @@ export function useContainerStream(container: Ref<Container>): LogStreamSource {
return useLogStream(url, loadMoreUrl);
}
export function useHostStream(host: Ref<Host>): LogStreamSource {
return useLogStream(computed(() => `/api/hosts/${host.value.id}/logs/stream`));
}
export function useStackStream(stack: Ref<Stack>): LogStreamSource {
return useLogStream(computed(() => `/api/stacks/${stack.value.name}/logs/stream`));
}
@@ -52,6 +56,9 @@ export type LogStreamSource = ReturnType<typeof useLogStream>;
function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
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();
function flushNow() {
@@ -122,18 +129,25 @@ function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
function connect({ clear } = { clear: true }) {
close();
if (clear) clearMessages();
opened.value = false;
loading.value = true;
error.value = false;
es = new EventSource(urlWithParams.value);
es.addEventListener("container-event", (e) => {
const event = JSON.parse((e as MessageEvent).data) as { actorId: string; name: string };
const event = JSON.parse((e as MessageEvent).data) as {
actorId: string;
name: "container-stopped" | "container-started";
time: string;
};
const containerEvent = new ContainerEventLogEntry(
event.name == "container-started" ? "Container started" : "Container stopped",
event.actorId,
new Date(),
event.name as "container-stopped" | "container-started",
new Date(event.time),
event.name,
);
buffer.value = [...buffer.value, containerEvent];
flushBuffer();
flushBuffer.flush();
});
@@ -149,7 +163,14 @@ function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
flushBuffer();
}
};
es.onerror = () => clearMessages();
es.onerror = () => {
error.value = true;
};
es.onopen = () => {
loading.value = false;
opened.value = true;
error.value = false;
};
}
watch(urlWithParams, () => connect(), { immediate: true });
@@ -161,6 +182,7 @@ function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
if (isLoadingMore.value) return;
const to = messages.value[0].date;
const lastSeenId = messages.value[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);
@@ -174,6 +196,7 @@ function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
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()}`);
});
@@ -203,5 +226,14 @@ function useLogStream(url: Ref<string>, loadMoreUrl?: Ref<string>) {
}
});
return { messages, loadOlderLogs, isLoadingMore, hasComplexLogs };
return {
messages,
loadOlderLogs,
isLoadingMore,
hasComplexLogs,
opened,
error,
loading,
eventSourceURL: urlWithParams,
};
}
+11 -12
View File
@@ -7,19 +7,11 @@ type LogContext = {
loadingMore: boolean;
hasComplexLogs: boolean;
levels: Set<Level>;
showContainerName: boolean;
showHostname: boolean;
};
export const allLevels: Level[] = [
"info",
"debug",
"warn",
"error",
"fatal",
"trace",
"warning",
"critical",
"unknown",
];
export const allLevels: Level[] = ["info", "debug", "warn", "error", "fatal", "trace", "unknown"];
// export for testing
export const loggingContextKey = Symbol("loggingContext") as InjectionKey<LogContext>;
@@ -27,7 +19,10 @@ const searchParams = new URLSearchParams(window.location.search);
const stdout = searchParams.has("stdout") ? searchParams.get("stdout") === "true" : true;
const stderr = searchParams.has("stderr") ? searchParams.get("stderr") === "true" : true;
export const provideLoggingContext = (containers: Ref<Container[]>) => {
export const provideLoggingContext = (
containers: Ref<Container[]>,
{ showContainerName = false, showHostname = false } = {},
) => {
provide(
loggingContextKey,
reactive({
@@ -36,6 +31,8 @@ export const provideLoggingContext = (containers: Ref<Container[]>) => {
loadingMore: false,
hasComplexLogs: false,
levels: new Set<Level>(allLevels),
showContainerName,
showHostname,
}),
);
};
@@ -49,6 +46,8 @@ export const useLoggingContext = () => {
loadingMore: false,
hasComplexLogs: false,
levels: new Set<Level>(allLevels),
showContainerName: false,
showHostname: false,
}),
);
+26 -7
View File
@@ -4,36 +4,55 @@ type Toast = {
title?: string;
message: string;
type: "success" | "error" | "warning" | "info";
action?: {
label: string;
handler: () => void;
};
};
type ToastOptions = {
expire?: number;
once?: boolean;
timed?: number;
};
const toasts = ref<Toast[]>([]);
const toasts = ref<
{
toast: Toast;
options: ToastOptions;
}[]
>([]);
const showToast = (
toast: Omit<Toast, "id" | "createdAt"> & { id?: string },
{ expire = -1, once = false }: ToastOptions = { expire: -1, once: false },
{ expire = -1, once = false, timed }: ToastOptions = { expire: -1, once: false },
) => {
if (once && toasts.value.some((t) => t.id === toast.id)) {
if (once && !toast.id) {
throw new Error("Toast id is required when once is true");
}
if (once && toasts.value.some((t) => t.toast.id === toast.id)) {
return;
}
toasts.value.push({
const toastWithId = {
id: Date.now().toString(),
createdAt: new Date(),
...toast,
createdAt: new Date(),
};
toasts.value.push({
toast: toastWithId,
options: { expire, once, timed },
});
if (expire > 0) {
setTimeout(() => {
removeToast(toasts.value[0].id);
removeToast(toastWithId.id);
}, expire);
}
};
const removeToast = (id: Toast["id"]) => {
toasts.value = toasts.value.filter((toast) => toast.id !== id);
toasts.value = toasts.value.filter((instance) => instance.toast.id !== id);
};
export const useToast = () => {
-5
View File
@@ -1,5 +0,0 @@
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="128" height="128" rx="6" fill="#222222"/>
<path d="M82.3248 94.3863H123V104.093H67.8025V95.3506L106.164 44.3736H68.3808V34.5382H121.072V42.9594L82.3248 94.3863Z" fill="#FFDD57"/>
<path d="M8 107.107L17.5656 14L43.8372 16.7013C51.9339 17.5338 58.9091 20.0604 64.7629 24.2812C70.6166 28.5019 74.8873 34.0893 77.5749 41.0432C80.3052 48.0016 81.2514 55.7674 80.4137 64.3407L79.8027 70.2877C78.9005 79.0698 76.4053 86.5894 72.3173 92.8468C68.2719 99.1084 62.914 103.684 56.2436 106.574C49.6158 109.468 42.1213 110.529 33.7602 109.755L8 107.107ZM28.8005 25.3655L21.3043 98.3288L34.2164 99.6565C43.6767 100.629 51.3299 98.4435 57.1758 93.0993C63.0644 87.7595 66.5671 79.6542 67.684 68.7832L68.2424 63.3477C69.3286 52.7752 67.6788 44.3123 63.293 37.9592C58.9542 31.5678 52.2295 27.8607 43.1188 26.8377L28.8005 25.3655Z" fill="#FFDD57"/>
</svg>

Before

Width:  |  Height:  |  Size: 949 B

+25 -42
View File
@@ -1,17 +1,17 @@
<template>
<div>
<mobile-menu v-if="isMobile && !forceMenuHidden" @search="showFuzzySearch"></mobile-menu>
<splitpanes @resized="onResized($event)">
<pane min-size="10" :size="menuWidth" v-if="!isMobile && !collapseNav && !forceMenuHidden">
<side-panel @search="showFuzzySearch"></side-panel>
</pane>
<pane min-size="10">
<splitpanes>
<pane class="router-view min-h-screen">
<Splitpanes @resized="onResized($event)">
<Pane min-size="10" :size="menuWidth" v-if="!isMobile && !collapseNav && !forceMenuHidden">
<SidePanel @search="showFuzzySearch" />
</Pane>
<Pane min-size="10">
<Splitpanes>
<Pane class="router-view min-h-screen">
<router-view></router-view>
</pane>
</Pane>
<template v-if="!isMobile">
<pane v-for="other in pinnedLogs" :key="other.id">
<Pane v-for="other in pinnedLogs" :key="other.id">
<ContainerLog
:id="other.id"
show-title
@@ -19,14 +19,14 @@
closable
@close="pinnedLogsStore.unPinContainer(other)"
/>
</pane>
</Pane>
</template>
</splitpanes>
</pane>
</splitpanes>
</Splitpanes>
</Pane>
</Splitpanes>
<label
class="btn btn-circle swap btn-neutral swap-rotate fixed -left-12 bottom-4 w-16 transition-all hover:-left-4"
:class="{ '!-left-6': collapseNav }"
class="btn btn-circle swap bg-base-100 swap-rotate fixed bottom-4 -left-12 w-16 transition-all hover:-left-4"
:class="{ '-left-6!': collapseNav }"
v-if="!isMobile && !forceMenuHidden"
>
<input type="checkbox" v-model="collapseNav" />
@@ -34,7 +34,11 @@
<mdi:chevron-left class="swap-off" />
</label>
</div>
<dialog ref="modal" class="modal items-start bg-white/20 backdrop:backdrop-blur-sm" @close="open = false">
<dialog
ref="modal"
class="modal items-start bg-white/20 transition-none backdrop:backdrop-blur-xs"
@close="open = false"
>
<div class="modal-box max-w-2xl bg-transparent pt-20 shadow-none">
<FuzzySearchModal @close="open = false" v-if="open" />
</div>
@@ -48,29 +52,7 @@
<template #fallback> Loading dependencies... </template>
</Suspense>
</SideDrawer>
<div class="toast toast-end whitespace-normal">
<div
class="alert max-w-xl"
v-for="toast in toasts"
:key="toast.id"
:class="{
'alert-error': toast.type === 'error',
'alert-info': toast.type === 'info',
'alert-warning': toast.type === 'warning',
}"
>
<carbon:information class="size-6 shrink-0 stroke-current" v-if="toast.type === 'info'" />
<carbon:warning class="size-6 shrink-0 stroke-current" v-else-if="toast.type === 'error'" />
<carbon:warning class="size-6 shrink-0 stroke-current" v-else-if="toast.type === 'warning'" />
<div>
<h3 class="text-lg font-bold" v-if="toast.title">{{ toast.title }}</h3>
<div v-html="toast.message" class="[&>a]:underline"></div>
</div>
<div>
<button class="btn btn-circle btn-xs" @click="removeToast(toast.id)"><mdi:close /></button>
</div>
</div>
</div>
<ToastModal />
</template>
<script lang="ts" setup>
@@ -82,7 +64,6 @@ import SideDrawer from "@/components/common/SideDrawer.vue";
const pinnedLogsStore = usePinnedLogsStore();
const { pinnedLogs } = storeToRefs(pinnedLogsStore);
const { toasts, removeToast } = useToast();
const drawer = useTemplateRef<InstanceType<typeof SideDrawer>>("drawer") as Ref<InstanceType<typeof SideDrawer>>;
const { component: drawerComponent, properties: drawerProperties, width: drawerWidth } = createDrawer(drawer);
@@ -117,9 +98,11 @@ function onResized(e: any) {
}
</script>
<style scoped lang="postcss">
<style scoped>
@import "@/main.css" reference;
:deep(.splitpanes--vertical > .splitpanes__splitter) {
@apply min-w-[3px] bg-base-lighter hover:bg-secondary;
@apply bg-base-100 hover:bg-secondary min-w-[3px];
}
@media screen and (max-width: 768px) {
+1
View File
@@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 2.4 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 97 76" fill="none"><path class="secondary-fill" d="M70.091 27.16a6.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.98M9.182 18.977a6.5 6.5 0 0 1-1.898-4.592 6.474 6.474 0 0 1 6.49-6.49c1.664 0 3.327.631 4.593 1.896l-1.517 1.12A51.67 51.67 0 0 1 48.672 0C60.671 0 71.7 4.08 80.495 10.911l-1.512-1.115A6.46 6.46 0 0 1 83.575 7.9c1.659 0 3.324.63 4.587 1.897a6.51 6.51 0 0 1 1.893 4.593 6.47 6.47 0 0 1-1.893 4.594l1.515 1.113c21.944 21.942-15.406 31.822-15.406 31.822H23.075c-1.077 0-38.88-8.35-15.406-31.822zM82.897 75.094a7.894 7.894 0 0 1-7.894-7.895 7.893 7.893 0 1 1 15.787 0 7.893 7.893 0 0 1-7.893 7.895M18.038 75.812a7.894 7.894 0 0 1-7.895-7.895 7.894 7.894 0 0 1 7.895-7.894 7.894 7.894 0 0 1 7.896 7.894 7.894 7.894 0 0 1-7.896 7.895M25.055 53.549h47.402s-22.981 7.899-47.402 0"/></svg>

After

Width:  |  Height:  |  Size: 934 B

+119 -101
View File
@@ -1,110 +1,128 @@
@import "splitpanes/dist/splitpanes.css";
@import "tailwindcss";
@import "splitpanes/dist/splitpanes.css" layer(base);
@plugin "daisyui";
@plugin "@tailwindcss/typography";
@tailwind base;
@tailwind components;
@tailwind utilities;
@theme {
--color-green: oklch(69% 0.119722 188.479048);
--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);
}
@plugin "daisyui/theme" {
name: "dark";
default: false;
prefersdark: false;
color-scheme: dark;
--color-base-100: oklch(25% 0 0);
--color-base-200: oklch(18% 0 0);
--color-base-300: oklch(11% 0 0);
--color-base-content: oklch(89.23% 0 0);
--color-primary: oklch(70.96% 0.143 176.65);
--color-primary-content: oklch(98% 0.01 240);
--color-secondary: oklch(81.38% 0.1448 90.1243);
--color-secondary-content: oklch(98% 0.01 200);
--color-neutral: oklch(50% 0.05 240);
--color-neutral-content: oklch(98% 0.01 240);
--color-info: var(--color-blue);
--color-info-content: oklch(98% 0.01 220);
--color-success: var(--color-green);
--color-success-content: oklch(98% 0.01 140);
--color-warning: var(--color-orange);
--color-success-content: oklch(98% 0.01 140);
--color-error: var(--color-red);
--color-error-content: oklch(98% 0.01 30);
--radius-selector: 1rem;
--radius-field: 0.25rem;
--radius-box: 0.5rem;
--border: 1px;
--depth: 0;
--noise: 1;
}
@plugin "daisyui/theme" {
name: "light";
default: false;
prefersdark: false;
color-scheme: light;
--color-base-100: oklch(100% 0 0);
--color-base-200: oklch(97% 0 0);
--color-base-300: oklch(90% 0 0);
--color-base-content: oklch(33% 0 0);
--color-primary: oklch(79.96% 0.143 176.65);
--color-primary-content: oklch(98% 0.01 240);
--color-secondary: oklch(82.67% 0.15380739746702807 77.82);
--color-secondary-content: oklch(98% 0.01 200);
--color-neutral: oklch(50% 0.05 240);
--color-neutral-content: oklch(98% 0.01 240);
--color-info: var(--color-blue);
--color-info-content: oklch(98% 0.01 220);
--color-success: var(--color-green);
--color-success-content: oklch(98% 0.01 140);
--color-warning: var(--color-orange);
--color-success-content: oklch(98% 0.01 140);
--color-error: var(--color-red);
--color-error-content: oklch(98% 0.01 30);
--radius-selector: 1rem;
--radius-field: 0.25rem;
--radius-box: 0.5rem;
--border: 1px;
--depth: 0;
--noise: 1;
}
@utility input {
@apply outline-hidden!;
&.input-ghost {
@apply focus:border-transparent;
}
}
@utility btn {
&:not(.btn-primary):not(.btn-secondary) {
@apply hover:bg-base-100;
}
}
@utility menu-active {
&,
&:active {
--menu-active-bg: var(--color-primary);
}
}
@utility animate-bounce-fast {
animation: bounce 0.5s 2 both;
}
@utility link-primary {
@apply underline-offset-4 hover:underline;
}
@layer base {
:root {
--p: var(--primary-color);
--pc: 89.23% 0;
--s: var(--secondary-color);
--sc: 14.57% 0 0;
--b1: var(--base-lighter-color);
--b2: var(--base-color);
--b3: var(--base-darker-color);
--bc: var(--base-content-color);
--in: 65% 0.171 249.5;
--inc: 100% 0 0;
--er: 64% 0.218 28.85;
--su: 56% 0.119722 164.12;
--erc: 100% 0 0;
--wa: 70% 0.186 48.13;
--wac: 100% 0 0;
--n: var(--base-lighter-color);
}
html[data-theme="dark"] {
--base-lighter-color: 25% 0 0;
--base-color: 18% 0 0;
--base-darker-color: 11% 0 0;
--base-content-color: 89.23% 0 0;
--primary-color: 70.96% 0.143 176.65;
--secondary-color: 91.36% 0.11098 90.1243;
}
html[data-theme="light"] {
--base-lighter-color: 100% 0 0;
--base-color: 97% 0 0;
--base-darker-color: 90% 0 0;
--base-content-color: 33% 0 0;
--primary-color: 79.96% 0.143 176.65;
--secondary-color: 82.67% 0.15380739746702807 77.82;
}
@media screen and (max-device-width: 480px) {
body {
-webkit-text-size-adjust: 100%;
}
}
h1 {
@apply text-3xl;
}
h2 {
@apply text-2xl;
}
h3 {
@apply text-lg;
}
.events mark {
@apply inline-block rounded-sm bg-secondary;
animation: pops 200ms ease-out;
}
@keyframes pops {
0% {
transform: scale(1.5);
}
100% {
transform: scale(1.05);
}
}
}
@layer components {
.input {
@apply !outline-none;
&.input-ghost {
@apply focus:border-transparent;
}
}
.btn {
@apply font-normal normal-case;
}
.btn:not(.btn-primary):not(.btn-secondary) {
@apply hover:bg-base-lighter;
}
}
@layer utilities {
@media (max-width: 768px) {
.mobile-hidden {
display: none;
}
}
.active-primary {
--n: var(--p);
--nc: var(--pc);
@apply active;
}
.link-primary {
@apply underline-offset-4 hover:underline;
}
}
-1
View File
@@ -6,5 +6,4 @@ const app = createApp(App);
Object.values(import.meta.glob<{ install: (app: VueApp) => void }>("./modules/*.ts", { eager: true })).forEach((i) =>
i.install?.(app),
);
app.mount("#app");
+7 -1
View File
@@ -23,13 +23,15 @@ export class GroupedContainers {
export class Container {
private _stat: Ref<Stat>;
private _name: string;
private readonly _statsHistory: Ref<Stat[]>;
private readonly movingAverageStat: Ref<Stat>;
private readonly _name: string;
constructor(
public readonly id: string,
public readonly created: Date,
public startedAt: Date,
public finishedAt: Date,
public readonly image: string,
name: string,
public readonly command: string,
@@ -76,6 +78,10 @@ export class Container {
return this.group;
}
set name(name: string) {
this._name = name;
}
get name() {
return this.isSwarm
? this.labels["com.docker.swarm.task.name"]
+14 -6
View File
@@ -1,5 +1,5 @@
import { Component, ComputedRef, Ref } from "vue";
import { flattenJSON, getDeep } from "@/utils";
import { flattenJSON } from "@/utils";
import ComplexLogItem from "@/components/LogViewer/ComplexLogItem.vue";
import SimpleLogItem from "@/components/LogViewer/SimpleLogItem.vue";
import ContainerEventLogItem from "@/components/LogViewer/ContainerEventLogItem.vue";
@@ -85,10 +85,18 @@ export class ComplexLogEntry extends LogEntry<JSONObject> {
if (visibleKeys.value.size === 0) {
return flattenJSON(message);
} else {
const keys = Array.from(visibleKeys.value.entries())
.filter(([, value]) => value)
.map(([key]) => key);
return keys.reduce((acc, attr) => ({ ...acc, [attr.join(".")]: getDeep(message, attr) }), {});
const flatJSON = flattenJSON(message);
const filteredJSON: Record<string, any> = {};
for (const [keys, enabled] of visibleKeys.value.entries()) {
const key = keys.join(".");
if (!enabled) {
delete flatJSON[key];
continue;
}
filteredJSON[key] = flatJSON[key];
delete flatJSON[key];
}
return { ...filteredJSON, ...flatJSON };
}
});
} else {
@@ -127,7 +135,7 @@ export class ContainerEventLogEntry extends LogEntry<string> {
date: Date,
public readonly event: "container-stopped" | "container-started",
) {
super(message, containerID, date.getTime(), date, "stderr", "info");
super(message, containerID, date.getTime(), date, "stderr", "unknown");
}
getComponent(): Component {
return ContainerEventLogItem;
+3 -1
View File
@@ -42,7 +42,9 @@ await loadLanguage("en", false); // load default language
const userLocale = computed(
() =>
locale.value ||
[navigator.language, navigator.language.slice(0, 2)].find((l) => availableLocales.includes(l)) ||
[navigator.language.toLowerCase(), navigator.language.toLowerCase().slice(0, 2)].find((l) =>
availableLocales.includes(l),
) ||
"en",
);
+1 -1
View File
@@ -1,6 +1,6 @@
<template>
<PageWithLinks>
<div class="hero min-h-screen bg-base-200">
<div 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.page-not-found") }}</p>
+49 -4
View File
@@ -1,7 +1,7 @@
<template>
<Search />
<ContainerLog :id="id" :show-title="true" :scrollable="pinnedLogs.length > 0" v-if="currentContainer" />
<div v-else-if="ready" class="hero min-h-screen bg-base-200">
<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>
@@ -11,15 +11,18 @@
</template>
<script lang="ts" setup>
import { type Container } from "@/models/Container";
const route = useRoute("/container/[id]");
const id = toRef(() => route.params.id);
const containerStore = useContainerStore();
const currentContainer = containerStore.currentContainer(id);
const { ready } = storeToRefs(containerStore);
const pinnedLogsStore = usePinnedLogsStore();
const { pinnedLogs } = storeToRefs(pinnedLogsStore);
const { containers: allContainers } = storeToRefs(containerStore) as unknown as { containers: Ref<Container[]> };
const { showToast } = useToast();
const { t } = useI18n();
const router = useRouter();
watchEffect(() => {
if (ready.value) {
@@ -30,8 +33,50 @@ watchEffect(() => {
}
}
});
const redirectTrigger = ref(false);
watch(currentContainer, () => (redirectTrigger.value = false));
watchEffect(() => {
if (redirectTrigger.value) return;
if (!currentContainer.value) return;
if (currentContainer.value.state === "running") return;
if (Date.now() - +currentContainer.value.finishedAt > 5 * 60 * 1000) return;
const nextContainer = allContainers.value
.filter((c) => c.startedAt > currentContainer.value.startedAt && c.name === currentContainer.value.name)
.sort((a, b) => +a.created - +b.created)[0];
if (!nextContainer) return;
if (automaticRedirect.value) {
redirectTrigger.value = true;
showToast(
{
title: t("alert.similar-container-found.title"),
message: t("alert.similar-container-found.message", { containerId: nextContainer.id }),
type: "info",
action: {
label: t("button.cancel"),
handler: () => {
showToast(
{
title: t("alert.redirected.title"),
message: t("alert.redirected.message", { containerId: nextContainer.id }),
type: "info",
},
{ expire: 5000 },
);
router.push({ name: "/container/[id]", params: { id: nextContainer.id } });
},
},
},
{ timed: 4000 },
);
}
});
</script>
<route lang="yaml">
meta:
containerMode: true
menu: host
</route>
+1 -1
View File
@@ -22,5 +22,5 @@ watchEffect(() => {
</script>
<route lang="yaml">
meta:
swarmMode: true
menu: group
</route>
+30
View File
@@ -0,0 +1,30 @@
<template>
<Search />
<HostLog :id="route.params.id" :scrollable="pinnedLogs.length > 0" />
</template>
<script lang="ts" setup>
const route = useRoute("/host/[id]");
const containerStore = useContainerStore();
const { ready } = storeToRefs(containerStore);
const pinnedLogsStore = usePinnedLogsStore();
const { pinnedLogs } = storeToRefs(pinnedLogsStore);
const { hosts } = useHosts();
const host = computed(() => hosts.value[route.params.id]);
watchEffect(() => {
if (ready.value) {
if (host.value?.name) {
setTitle(host.value.name);
} else {
setTitle("Not Found");
}
}
});
</script>
<route lang="yaml">
meta:
menu: host
</route>

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