Compare commits

...

549 Commits

Author SHA1 Message Date
Amir Raminfar 5946e56ccb chore: release v4.10.25 2023-08-09 09:42:58 -07:00
Amir Raminfar 8f77400f8b fix: reverts unneeded css files from oruga (#2341) 2023-08-09 09:41:09 -07:00
renovate[bot] 19cc2f30c4 chore(deps): update dependency unplugin-vue-macros to ^2.4.4 2023-08-07 18:50:53 +00:00
Amir Raminfar 298993289c chore: changes renovate to bump 2023-08-07 11:44:33 -07:00
Amir Raminfar a06f197015 chore: changes renovate to pin 2023-08-07 11:43:29 -07:00
Amir Raminfar 39917518b9 chore: updates modules 2023-08-07 09:34:46 -07:00
Amir Raminfar cbccc1ac4c feat: shows running / total containers (#2339) 2023-08-07 09:34:08 -07:00
renovate[bot] 5cc5e517e6 fix(deps): update all non-major dependencies 2023-08-07 00:21:43 +00:00
Amir Raminfar 44e21cba83 chore: cleans up types in typescript (#2335) 2023-08-03 09:23:58 -07:00
Amir Raminfar ac886b0e9a feat: adds bar charts back to dashboard table (#2331) 2023-08-02 11:29:50 -07:00
Amir Raminfar 1c74a46f3f chore: release v4.10.24 2023-08-01 12:42:22 -07:00
Amir Raminfar 8778500aa6 chore: updates modules 2023-08-01 12:36:08 -07:00
Amir Raminfar 3eb222066c feat: updates home dashboard to support mobile view (#2334)
* feat: updates home dashboard to support mobile view

* updates created translation

* adds ellipses
2023-08-01 12:35:05 -07:00
yldrmali 5d326189a8 fix: cap scrollProgress value to 1. (#2333) 2023-08-01 09:32:30 -07:00
Amir Raminfar b722fae909 chore: release v4.10.23 2023-07-31 10:12:48 -07:00
Amir Raminfar 8e21cbd51c fix: changes default direction to desc 2023-07-31 10:06:49 -07:00
Amir Raminfar 166e547f05 chore(cleanup): cleans up if statements 2023-07-31 09:57:47 -07:00
Amir Raminfar ada1a98d9c chore: removes console 2023-07-31 09:55:39 -07:00
Amir Raminfar 40c259b1be feat: adds table sort to homepage (#2330)
* feat: adds table sort to homepage

* feat: adds sorting to dashboard

* fixes colors
2023-07-31 16:54:27 +00:00
renovate[bot] ac0b9b157e chore(deps): update all non-major dependencies 2023-07-31 00:14:29 +00:00
Amir Raminfar 0be7005ab4 feat: adds pagination for dashboard page. see #2322 (#2328) 2023-07-28 20:30:15 +00:00
Amir Raminfar 97f50d696c fix: fixes JSON encoding when nested array is found. see #2325 (#2327) 2023-07-28 20:16:05 +00:00
Amir Raminfar 3c53e218d1 chore: release v4.10.22 2023-07-27 19:21:05 -07:00
Amir Raminfar 11487003f2 fix(performance): remove table component and uses own table component to improve performance (#2326)
* fix: remove table component and uses own table component to improve performance

* adds id
2023-07-28 02:20:23 +00:00
Amir Raminfar c1b8d0aed0 chore: release v4.10.21 2023-07-27 10:25:29 -07:00
Amir Raminfar 699181ec7c fix: removes sorting from homepage temporary until a better fix is found (#2323)
* fix: removes sorting from homepage temporary until a better fix is found

* adds pagination and remove bar charts
2023-07-27 10:21:32 -07:00
Amir Raminfar 62f9da0536 chore: release v4.10.20 2023-07-26 10:08:21 -07:00
Amir Raminfar 10de475a64 fix: adds new languages for dashboard (#2320) 2023-07-26 17:07:18 +00:00
Amir Raminfar 14bf3a1417 chore: updates modules (#2321) 2023-07-26 17:06:55 +00:00
Amir Raminfar 0ff031e361 chore: adds visual comparison for aside menu only (#2319)
* chore: adds visual comparison for aside menu only

* adds missing files
2023-07-26 10:03:07 -07:00
Amir Raminfar baf3cc70f8 chore: updates modules 2023-07-25 09:28:38 -07:00
Amir Raminfar 1b056346c0 feat: adds exponential average and a new dashboard showing all containers (#2317)
* chore: updates modules

* feat: adds support for exponential moving average

* feat: add expoentital moving avg

* adds index page

* cleans up table

* fixes typecheck

* adds bar chart

* updates dashboard to orgua table

* wip

* cleans up ui

* remove screenshot tests for playwright

* adds more tests

* fixes icon

* fixes default sort

* removes unused var

* adds vscode settings
2023-07-24 15:06:42 -07:00
Amir Raminfar 18005fcd23 chore: updates modules 2023-07-24 08:40:48 -07:00
renovate[bot] 425ab449a6 chore(deps): update all non-major dependencies 2023-07-24 02:10:38 +00:00
Amir Raminfar ff0ceea29d chore: fixes renovate json 2023-07-21 12:07:45 -07:00
renovate[bot] 5b509d26d4 chore(deps): update dependency vite to v4.4.6 2023-07-21 11:34:03 +00:00
renovate[bot] f2dbda3dde chore(deps): update dependency vite to v4.4.5 2023-07-20 15:13:50 +00:00
Amir Raminfar 6b1b7099b1 go cleanup (#2313)
* chore: cleans waitgroups

* chore: adds benchmark
2023-07-18 10:58:59 -07:00
Amir Raminfar 93347a6888 chore(docs): improve top menu on home layout 2023-07-18 08:40:00 -07:00
renovate[bot] 80800674bd chore(deps): update pnpm to v8.6.9 2023-07-18 13:14:58 +00:00
Amir Raminfar 6f8cf9045e chore(docs): adds link to github on homepage 2023-07-17 14:31:54 -07:00
renovate[bot] 5988b1d0df chore(deps): update pnpm to v8.6.8 2023-07-17 12:11:22 +00:00
Amir Raminfar c774ba7edb chore: adds more gotests 2023-07-15 13:07:05 -07:00
Amir Raminfar 079ac56442 fix: uses json.valid instead 2023-07-15 12:25:16 -07:00
renovate[bot] 178ade4d20 chore(deps): update mcr.microsoft.com/playwright docker tag to v1.36.1 2023-07-15 01:05:29 +00:00
Amir Raminfar ffa1fb976e chore(test): updates playwright (#2310) 2023-07-14 10:22:33 -07:00
renovate[bot] b0b1793874 chore(deps): update dependency vite to v4.4.4 2023-07-14 07:33:44 +00:00
renovate[bot] 30491b10ff fix(deps): update module github.com/go-chi/chi/v5 to v5.0.10 2023-07-13 16:46:21 +00:00
Amir Raminfar 991588a42e chore(typo): fixes typo in docs 2023-07-12 14:29:04 -07:00
Amir Raminfar 1a899a18e8 chore: release v4.10.19 2023-07-12 09:47:21 -07:00
Amir Raminfar 53ad92c60d chore(docs): adds a page about logs on file 2023-07-12 09:39:02 -07:00
renovate[bot] a9eb17cce3 chore(deps): update docker/setup-buildx-action action to v2.9.1 2023-07-12 12:51:08 +00:00
Amir Raminfar 8c8987b666 chore(refactor): refactors docker client for better testing (#2302)
* chore(refactor): refactors docker client for better testing

* more refactoring and clenaing up tests
2023-07-11 13:21:46 -07:00
renovate[bot] bbc6853f96 chore(deps): update golang docker tag to v1.20.6 2023-07-11 19:54:26 +00:00
Amir Raminfar c083c8f0c9 chore: updates modules (#2300) 2023-07-11 10:34:09 -07:00
renovate[bot] 33e5a1560f chore(deps): update dependency vite to v4.4.3 2023-07-11 16:57:01 +00:00
Amir Raminfar 1bb1081f1a fix(performance): improves streaming of logs by using the Docker library to demultiplex (#2295)
* fix(performance): improves streaming of logs by using the Docker library to demultiplex

* fixes go tests

* changes channel to unbuffered

* fixes golang race test

* fixes stopped containers

* wip

* uses different strategy

* cleans tests

* cleans tests

* adds more tests

* fixes possible bug

* fixes download

* adds more tests for download

* uses pool

* adds more tests
2023-07-11 09:52:30 -07:00
renovate[bot] ad7b3a2b52 chore(deps): update pnpm to v8.6.7 2023-07-09 22:59:02 +00:00
renovate[bot] e00c918025 fix(deps): update module github.com/docker/docker to v24.0.4+incompatible 2023-07-08 05:13:46 +00:00
renovate[bot] 35455d845a chore(deps): update docker/setup-buildx-action action to v2.9.0 (#2294)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-07 08:44:31 -07:00
renovate[bot] 4b73fb8413 chore(deps): update dependency vite to v4.4.2 2023-07-07 13:41:22 +00:00
renovate[bot] fc97b1dcf9 fix(deps): update module github.com/docker/docker to v24.0.3+incompatible (#2292)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-06 12:50:37 -07:00
renovate[bot] b7085dda2b chore(deps): update dependency vite to v4.4.1 (#2290)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-06 11:43:15 -07:00
Amir Raminfar 901785dc20 chore: release v4.10.18 2023-07-06 09:30:22 -07:00
Amir Raminfar 2c909dab09 fix: adds port and schema to connection again to fix clashes between same host. fixes #2279 (#2289)
* fix: adds port and schema to connection again to fix clashes between same host. fixes #2279

* fixes tests
2023-07-06 09:22:22 -07:00
Amir Raminfar e4ef784be7 chore(format): updates js files to updates prettier 2023-07-06 08:41:04 -07:00
Amir Raminfar d2bb0cd31d chore: updates modules 2023-07-06 08:39:42 -07:00
renovate[bot] 0562b60df4 chore(deps): update dependency prettier to v3 (#2286)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-06 08:32:01 -07:00
renovate[bot] d1e63155c9 chore(deps): update dependency vitest to ^0.33.0 (#2288)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-06 08:30:06 -07:00
renovate[bot] 7fed87fa79 chore(deps): update dependency vite to v4.4.0 2023-07-06 14:46:02 +00:00
renovate[bot] 90c4b8a68b chore(deps): update pnpm to v8.6.6 2023-07-03 18:49:48 +00:00
renovate[bot] c85ce593a1 chore(deps): update dependency vitepress to v1.0.0-beta.5 2023-07-03 09:14:05 +00:00
renovate[bot] 0fe9278a29 chore(deps): update dependency vitepress to v1.0.0-beta.4 2023-07-02 19:14:15 +00:00
renovate[bot] 8a616b6bc0 fix(deps): update dependency @intlify/unplugin-vue-i18n to ^0.12.0 2023-07-01 22:38:37 +00:00
renovate[bot] f3750fd7d0 chore(deps): update docker/setup-buildx-action action to v2.8.0 2023-06-28 21:56:04 +00:00
Amir Raminfar 5af162ddda chore: updates modules 2023-06-28 12:31:33 -07:00
Amir Raminfar b2a6ea7ba1 fix: improves performance by making initital requests in parallel (#2277) 2023-06-28 12:26:47 -07:00
Amir Raminfar 70acaa64d8 feat: adds ability to support labels with | delimeter (#2276)
* feat: adds ability to support labels with | delimeter

* fixes tests

* updates docs
2023-06-28 12:14:17 -07:00
Amir Raminfar f6807db592 chore: release v4.10.17 2023-06-27 15:43:22 -07:00
Amir Raminfar 6e394bad19 fix: fixes download link not working (#2274) 2023-06-27 13:25:51 -07:00
Amir Raminfar 657e6314c0 chore: release v4.10.16 2023-06-27 11:54:43 -07:00
Amir Raminfar a6fff56997 fix: cleans up config in template (#2273) 2023-06-27 11:35:55 -07:00
Amir Raminfar 05a4b7c25a feat(dev): moves vite integeration to backend (#2272)
* feat(dev): moves vite integeration to backend

* fixes dockerbuild

* fixes docker build

* fixes custom base

* fixes base path again

* uses concurrently instead

* fixes go tests
2023-06-27 09:20:24 -07:00
renovate[bot] 263029d77e chore(deps): update pnpm to v8.6.5 2023-06-26 13:47:57 +00:00
renovate[bot] 32be87d245 chore(deps): update pnpm to v8.6.4 2023-06-26 03:30:58 +00:00
Amir Raminfar 14fc1190a8 feat: supports multiple hosts in parallel and update hosts menu (#2269)
* feat: updates host menu to be part of side menu

* updates routes to be host/id

* fixes go tests

* fixes js tests

* fixes typescheck

* fixes int tests

* fixes mobile

* fixes bug in merging containers

* fixed minor bug with menu
2023-06-24 19:13:39 +00:00
Amir Raminfar 9f90d1ccfa chore: release v4.10.15 2023-06-20 15:43:09 -07:00
Amir Raminfar 8b3ddcfca7 chore: adds more e2e tests (#2266) 2023-06-20 15:36:33 -07:00
Amir Raminfar 50f3c0b7a4 fix: fixes UI when no localhost is provided 2023-06-20 14:38:12 -07:00
Amir Raminfar 31063fa1b4 feat: removes localhost as a required client. fixes #2259 (#2263)
* feat: removes localhost as a required connection

* refactors code

* fixes tests

* adds more tests

* adds more tests

* refactors

* cleans up logs
2023-06-20 17:43:47 +00:00
renovate[bot] 3aa874e399 chore(deps): update dependency vitepress to v1.0.0-beta.3 2023-06-20 13:19:07 +00:00
renovate[bot] ef7bb3fbcb chore(deps): update pnpm to v8.6.3 (#2262)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-19 12:59:34 -07:00
Amir Raminfar a019705bae fix: downgrade json parsing errors to warn. see #2260 2023-06-19 08:32:32 -07:00
Amir Raminfar e59f9106cc fix: fixes route update with hash 2023-06-18 13:35:04 -07:00
Amir Raminfar b3357e9843 chore(logs): prints the original log when json unmarshalling fails 2023-06-18 13:25:26 -07:00
Amir Raminfar 3511739008 chore: updates modules 2023-06-18 13:24:30 -07:00
renovate[bot] 608a39d841 chore(deps): update mcr.microsoft.com/playwright docker tag to v1.35.1 2023-06-15 21:47:49 +00:00
renovate[bot] 360985e8e1 chore(deps): update docker/setup-buildx-action action to v2.7.0 2023-06-14 04:19:01 +00:00
renovate[bot] 82f78582b5 chore(deps): update pnpm to v8.6.2 2023-06-14 01:37:18 +00:00
renovate[bot] 10bcb7812a chore(deps): update docker/build-push-action action to v4.1.1 (#2255)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-13 12:50:19 -07:00
Amir Raminfar a7693f8a6b chore: updates modules and playwright (#2253) 2023-06-13 12:26:36 -07:00
Amir Raminfar 93297649bc chore: release v4.10.14 2023-06-09 14:15:39 -07:00
renovate[bot] d25f87390c chore(deps): update docker/build-push-action action to v4.1.0 2023-06-09 14:31:59 +00:00
Amir Raminfar 5a713dc88c feat: adds chinese translations (#2249)
* feat: adds chinese translations

* updates language for zh
2023-06-08 22:38:13 +00:00
Amir Raminfar b23daa4158 chore: release v4.10.13 2023-06-08 11:28:01 -07:00
Amir Raminfar 41da3f0de4 fix: fixes searching non-ascii characters by also escaping search entities (#2248) 2023-06-08 18:25:26 +00:00
Amir Raminfar cb5712f7e0 chore: fixes german yaml file 2023-06-08 11:21:04 -07:00
Amir Raminfar 69723962fd fix: german language for buttons was incorrect 2023-06-08 11:14:28 -07:00
Amir Raminfar 81cfdbec42 feat: adds german to locales 2023-06-08 10:38:03 -07:00
Amir Raminfar 7477b1aecc fix: fixes std stream when type is unknown. see #2243 (#2247)
* fix: fixes std stream when type is unknown. see #2243

* fixes tests
2023-06-08 10:23:02 -07:00
Amir Raminfar 8912339044 chore: fixes import 2023-06-07 12:07:39 -07:00
Amir Raminfar 3f328a0ebf chore: removes logging for routes 2023-06-07 11:28:33 -07:00
renovate[bot] 4ea51fc06f chore(deps): update docker/login-action action to v2.2.0 (#2244)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-07 10:02:44 -07:00
renovate[bot] f4ebd12fbf chore(deps): update docker/setup-buildx-action action to v2.6.0 (#2245)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-07 10:02:32 -07:00
Amir Raminfar 8df12a4f3d chore: updates modules 2023-06-07 09:53:28 -07:00
renovate[bot] ab8441ac01 chore(deps): update golang docker tag to v1.20.5 2023-06-07 02:39:31 +00:00
renovate[bot] 17125c67ed chore(deps): update dependency vitest to ^0.32.0 2023-06-06 22:22:46 +00:00
renovate[bot] 8fd8e82994 chore(deps): update pnpm to v8.6.1 2023-06-05 18:18:53 +00:00
renovate[bot] 31ddc941ff chore(deps): update dependency vite-plugin-pages to ^0.31.0 2023-06-05 11:44:53 +00:00
renovate[bot] 94d4610b08 chore(deps): update dependency unocss to ^0.53.0 2023-06-05 03:29:19 +00:00
renovate[bot] d9a810111b chore(deps): update dependency @unocss/transformer-directives to ^0.53.0 2023-06-05 02:59:22 +00:00
renovate[bot] bdf77b7f8a chore(deps): update dependency @unocss/reset to ^0.53.0 2023-06-04 22:28:42 +00:00
renovate[bot] 19c2bc8274 chore(deps): update dependency @unocss/preset-typography to ^0.53.0 2023-06-04 19:39:49 +00:00
renovate[bot] b431ce16c5 fix(deps): update module github.com/sirupsen/logrus to v1.9.3 2023-06-04 00:37:51 +00:00
Amir Raminfar 38a6a99096 fix: fixes nested array displaying as object instead of actual value (#2233) 2023-06-02 15:33:59 -07:00
Amir Raminfar 88442edee5 chore(deps): remove gorilla mux in favor of using go-chi (#2232)
* chore(deps): remove gorilla mux in favor of using go-chi

* fixes broken redirect and tests

* chore: clean up

* reverts changes
2023-06-02 11:55:06 -07:00
Amir Raminfar d7710f736f chore: fixes typing for emits 2023-06-01 15:24:41 -07:00
Amir Raminfar c9bb793e9b chore: release v4.10.12 2023-06-01 10:16:09 -07:00
Amir Raminfar c522a66d1a fix: fixes stream colors by swapping (#2231) 2023-06-01 16:59:47 +00:00
Amir Raminfar d0edc42d1b feat: adds better icons for streams in the drop down (#2230) 2023-06-01 09:45:29 -07:00
Amir Raminfar 5f92e84d9d feat: adds support for different std out and err streams (#2229)
* feat: adds support for different std out and err streams

* feat: adds std to json

* fixes tests

* fixes deprecated code

* fixes download

* adds defineEmit as an option

* chore: updates modules

* adds ui elements

* fixes tests

* updates languages
2023-05-31 15:19:40 -07:00
renovate[bot] 84b8e24ca3 chore(deps): update dependency unplugin-vue-components to ^0.25.0 (#2228)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-30 10:11:32 -07:00
renovate[bot] c225a837df fix(deps): update module github.com/stretchr/testify to v1.8.4 2023-05-30 16:19:04 +00:00
renovate[bot] 3b6ad12c93 chore(deps): update pnpm to v8.6.0 2023-05-28 14:26:44 +00:00
Amir Raminfar 2581150346 chore: release v4.10.11 2023-05-26 12:09:21 -07:00
Amir Raminfar 3d93bb69cb fix: fixes cpu monitor when all values are 0. fixes #2215 (#2222)
* fix: fixes cpu monitor when all values are 0. fixes #2215

* chore: remove unused imports
2023-05-26 12:07:41 -07:00
Amir Raminfar cc7d834e35 feat: adds warning as possible level 2023-05-26 10:58:08 -07:00
renovate[bot] ecf5fda53e fix(deps): update dependency @intlify/unplugin-vue-i18n to ^0.11.0 (#2221)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-26 08:44:08 -07:00
Amir Raminfar 73aa1d9010 fix: improves graceful shutdown 2023-05-26 08:42:04 -07:00
renovate[bot] 066248c531 fix(deps): update module github.com/docker/docker to v24.0.2+incompatible (#2220)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-26 08:34:26 -07:00
renovate[bot] 8c29954caf chore(deps): update dependency vite to v4.3.9 (#2219)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-26 08:34:18 -07:00
Amir Raminfar ce63996954 chore: remove debug statement 2023-05-25 11:35:48 -07:00
Amir Raminfar ad46d53ef2 feat: removes colors to find the level (#2218) 2023-05-25 10:21:07 -07:00
renovate[bot] a2d208adf7 chore(deps): update mcr.microsoft.com/playwright docker tag to v1.34.3 2023-05-25 09:27:27 +00:00
Amir Raminfar c2110e322f chore: updates modules (#2216) 2023-05-24 10:11:01 -07:00
renovate[bot] d9376b303a chore(deps): update mcr.microsoft.com/playwright docker tag to v1.34.2 2023-05-24 05:27:49 +00:00
Amir Raminfar 3baed9da3d chore: release v4.10.10 2023-05-23 12:21:20 -07:00
Amir Raminfar 8a88b332c5 chore: fixes int test for release 2023-05-23 12:21:07 -07:00
Amir Raminfar 770f5e63ff chore: release v4.10.9 2023-05-23 12:19:05 -07:00
Amir Raminfar f0424e1e94 feat: shows error message when container is not found (#2213) 2023-05-23 12:11:54 -07:00
Amir Raminfar fc0c189e38 feat: adds a sub header to display stats on mobile view (#2212) 2023-05-23 12:07:19 -07:00
renovate[bot] 8586f16ea5 chore(deps): update mcr.microsoft.com/playwright docker tag to v1.34.1 2023-05-23 11:52:56 +00:00
Amir Raminfar 7427d005c8 chore: vue updates (#2211)
* chore: updates node modules

* chore: fixes reactivity warning
2023-05-22 18:51:50 +00:00
Amir Raminfar e7174dea69 chore: updates playwright 2023-05-22 11:48:50 -07:00
Amir Raminfar 5b12a3cc18 chore: updates node modules 2023-05-22 11:34:51 -07:00
Amir Raminfar b2844469d6 chore(e2e): updates testing to use playwright (#2181)
* chore(e2e): updates testing to use playwright

* chore: updates workflows

* fixes spaces

* fixes space again

* fixes int test to add docker

* chore: ignore e2e tests for vite

* updates screenshots for linux

* updates screenshots again

* chore: uses docker compose for e2e

* adds PWTEST_SKIP_TEST_OUTPUT

* add ci

* updates screenshots again

* updates with  css

* adds more tests

* updates tests
2023-05-22 11:33:25 -07:00
Amir Raminfar 0d3bd7246a chore: updates go modules 2023-05-22 10:20:10 -07:00
renovate[bot] d0c080c7ae chore(deps): update dependency vitepress to v1.0.0-beta.1 (#2209)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-22 10:12:18 -07:00
Amir Raminfar 48aa0afa3a chore: updates all other modules (#2208) 2023-05-21 09:50:13 -07:00
renovate[bot] a9ff1e9335 chore(deps): update dependency @unocss/reset to ^0.52.0 (#2207) 2023-05-21 07:37:32 -07:00
renovate[bot] 9eb0242544 fix(deps): update module github.com/docker/docker to v24.0.1+incompatible 2023-05-20 05:00:43 +00:00
renovate[bot] 6b41b7acef chore(deps): update dependency vite-plugin-pages to ^0.30.0 2023-05-19 11:44:56 +00:00
renovate[bot] d80d8d4484 fix(deps): update module github.com/stretchr/testify to v1.8.3 2023-05-19 04:33:01 +00:00
renovate[bot] d268d3e2ca chore(deps): update dependency vitepress to v1.0.0-alpha.76 2023-05-18 21:55:35 +00:00
Agneev Mukherjee d867c08aae fix: use ui-monospace as the default font for log viewer (#2200)
* Use ui-monospace as the default font for log body

* Add SF Mono
2023-05-18 07:38:55 -07:00
renovate[bot] 969fdf62d0 chore(deps): update dependency vite to v4.3.8 (#2199)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-18 07:36:14 -07:00
renovate[bot] 8d3f501bac fix(deps): update module github.com/sirupsen/logrus to v1.9.2 2023-05-18 00:48:57 +00:00
renovate[bot] 9ab7f0acca fix(deps): update module github.com/docker/docker to v24 (#2196) 2023-05-16 20:54:50 -07:00
renovate[bot] ce4ab3a249 fix(deps): update module github.com/sirupsen/logrus to v1.9.1 2023-05-17 03:49:30 +00:00
renovate[bot] 82e37c7397 chore(deps): update dependency vite to v4.3.7 2023-05-17 01:35:58 +00:00
renovate[bot] ea23891ac6 chore(deps): update dependency unplugin-auto-import to ^0.16.0 2023-05-16 12:18:22 +00:00
renovate[bot] 0e9edd8bf2 chore(deps): update dependency vite to v4.3.6 2023-05-15 21:13:02 +00:00
renovate[bot] 23c53a8e3a chore(deps): update pnpm to v8.5.1 2023-05-15 05:33:58 +00:00
Amir Raminfar 86fc556fb9 chore: release v4.10.8 2023-05-12 12:44:44 -07:00
renovate[bot] f5f268b677 chore(deps): update dependency @vitejs/plugin-vue to v4.2.3 (#2190)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-12 08:25:34 -07:00
Amir Raminfar 352d815255 Revert "chore: updates vue"
This reverts commit e7e2089f4f.
2023-05-11 13:33:56 -07:00
Amir Raminfar e7e2089f4f chore: updates vue 2023-05-11 11:42:01 -07:00
Amir Raminfar d4762908be fix: fixes incorrect regex group match. fixes #2187 (#2188) 2023-05-11 18:36:45 +00:00
renovate[bot] 71f9963a75 chore(deps): update dependency @vitejs/plugin-vue to v4.2.2 2023-05-11 15:43:39 +00:00
Amir Raminfar 5056298704 chore: updates modules 2023-05-10 12:19:07 -07:00
Amir Raminfar 491b1cb944 chore: release v4.10.7 2023-05-10 12:15:05 -07:00
Amir Raminfar b7d5229534 fix: fixes logic for extracing swarm ids. fixed #2184 (#2185) 2023-05-10 10:54:41 -07:00
renovate[bot] 1ffa17ef48 chore(deps): update cypress/included docker tag to v12.12.0 2023-05-10 06:04:38 +00:00
renovate[bot] b55243a6b9 chore(deps): update pnpm to v8.5.0 2023-05-10 02:29:33 +00:00
renovate[bot] 002d8dedee chore(deps): update dependency @pinia/testing to ^0.1.0 2023-05-09 00:57:26 +00:00
renovate[bot] 4348ba6323 fix(deps): update module github.com/docker/docker to v23.0.6+incompatible 2023-05-08 21:20:51 +00:00
Amir Raminfar 0b76f1185b chore: release v4.10.6 2023-05-08 11:03:04 -07:00
Amir Raminfar 4fc8109048 feat: respects fg color with css color using ansi (#2180) 2023-05-08 11:02:30 -07:00
Amir Raminfar e0e4292d1c chore: release v4.10.5 2023-05-08 09:19:42 -07:00
Amir Raminfar 18c57066c7 fix: fixes colors on side menu to be less bold and updates the regex to support longer hashes. see #2144 (#2177) 2023-05-08 09:17:34 -07:00
renovate[bot] 6f1b3a1dd1 chore(deps): update dependency @vue/compiler-sfc to ^3.3.0-beta 2023-05-08 12:10:46 +00:00
Amir Raminfar 370e7b238e fix: shows a message when all keys in json logs are hidden. see #2122 (#2174) 2023-05-05 14:54:37 -07:00
Amir Raminfar f89e2cc6a7 chore: tidy go mod 2023-05-05 11:26:17 -07:00
Amir Raminfar 049ae108de chore: cleans up types 2023-05-05 10:07:29 -07:00
renovate[bot] be47fdab12 chore(deps): update dependency vite to v4.3.5 2023-05-05 14:03:20 +00:00
renovate[bot] d0ae1db50b chore(deps): update dependency vitest to ^0.31.0 2023-05-04 03:08:50 +00:00
Amir Raminfar 6b32920d33 chore: cleans up types for config 2023-05-03 10:11:09 -07:00
renovate[bot] 25d7c14783 chore(deps): update golang docker tag to v1.20.4 2023-05-02 23:40:02 +00:00
Amir Raminfar 7c13637870 chore: release v4.10.4 2023-05-02 09:31:03 -07:00
renovate[bot] 1b969380bc chore(deps): update pnpm to v8.4.0 (#2167)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-02 16:11:09 +00:00
Amir Raminfar cfe616a79e feat: improves popup timing (#2166) 2023-05-02 09:09:13 -07:00
Amir Raminfar 764d176455 feat: moves mobile host to mobile menu. see #2157 (#2165)
* Revert "feat: adds host drop down to mobile view. see #2157 (#2160)"

This reverts commit 088d96cb0d.

* feat: moves mobiles host drop down to mobile menu
2023-05-02 16:08:26 +00:00
Amir Raminfar 786aaedc78 chore: updates npm modules 2023-05-02 09:06:13 -07:00
renovate[bot] 847daed5e8 chore(deps): update dependency jsdom to v22 (#2164)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-02 09:03:51 -07:00
renovate[bot] f410b9dd1d chore(deps): update dependency vite to v4.3.4 2023-05-02 12:22:45 +00:00
renovate[bot] 0b83fb4356 chore(deps): update dependency vitepress to v1.0.0-alpha.75 2023-04-30 14:37:47 +00:00
Amir Raminfar 088d96cb0d feat: adds host drop down to mobile view. see #2157 (#2160)
* feat: adds host drop down to mobile view. see #2157

* fix: removes unused imports
2023-04-27 16:32:48 -07:00
Amir Raminfar 9aed4ba19f chore: release v4.10.3 2023-04-27 16:24:22 -07:00
Amir Raminfar 30a4d9a6be chore: updates all modules 2023-04-27 16:09:45 -07:00
Amir Raminfar 36b4bb65e7 feat: adds a new popup to show uptime and stats on the side menu (#2159) 2023-04-27 12:47:23 -07:00
Amir Raminfar 8767d3461d Merge branch 'master' into tooltips 2023-04-27 12:40:33 -07:00
renovate[bot] cd9f5971a4 chore(deps): update cypress/included docker tag to v12.11.0 2023-04-27 19:39:54 +00:00
Amir Raminfar 3c7021af42 fix: removes unused imports 2023-04-27 12:37:36 -07:00
Amir Raminfar 0b9b9f12da chore: removes tooltip from bulma 2023-04-27 12:35:28 -07:00
Amir Raminfar 16eb71e0c0 chore: cleans up d.ts file 2023-04-27 12:32:38 -07:00
Amir Raminfar 17dfcf3d9a chore(refactor): creates new popup component for containers 2023-04-27 12:31:43 -07:00
Amir Raminfar 2dbbe47c5c chore(refactor): creates new popup component for containers 2023-04-27 12:31:12 -07:00
Amir Raminfar 6076c4eb63 chore: cleans up code 2023-04-27 12:27:48 -07:00
Amir Raminfar 818fec244a chore(refactor): create docker health component 2023-04-27 12:23:13 -07:00
Amir Raminfar 1f85f046cf fix: uses getBoundingClientRect 2023-04-27 11:49:46 -07:00
Amir Raminfar e6efe6ff59 feat: adds on hover popup on the side menu 2023-04-27 11:07:08 -07:00
Amir Raminfar e96ab50486 feat: adds styles to popup 2023-04-27 09:31:19 -07:00
renovate[bot] f74b1c91f2 fix(deps): update module github.com/docker/docker to v23.0.5+incompatible 2023-04-27 12:12:24 +00:00
renovate[bot] 0cbfcb6097 chore(deps): update dependency vite to v4.3.3 2023-04-27 03:46:57 +00:00
Amir Raminfar 27d0244e5d fix: removes extra space between name and swarm id 2023-04-26 13:50:25 -07:00
Amir Raminfar 7b19e83fea chore: release v4.10.2 2023-04-26 12:30:53 -07:00
Amir Raminfar 832594532a fix: fixes mobile nav not showing up when container does not exist. fixes #2153 2023-04-26 12:22:38 -07:00
renovate[bot] 298b39f6b9 chore(deps): update dependency @vitejs/plugin-vue to v4.2.1 2023-04-26 14:00:54 +00:00
renovate[bot] e04fbbb51a fix(deps): update dependency @oruga-ui/oruga-next to ^0.6.0 2023-04-26 08:12:39 +00:00
Amir Raminfar 685b17ef4e chore: release v4.10.1 2023-04-25 14:48:52 -07:00
Amir Raminfar 582aa207f1 chore: updates all modules (#2151) 2023-04-25 14:48:13 -07:00
Amir Raminfar 8116868669 feat: makes swarm id lighter (#2145)
* feat: makes swarm id lighter

* fix: add swarm to title id
2023-04-25 14:41:45 -07:00
renovate[bot] cd125e83be chore(deps): update dependency vite to v4.3.2 2023-04-25 20:23:14 +00:00
renovate[bot] 6ba0f6037f chore(deps): update dependency vitepress to v1.0.0-alpha.74 2023-04-25 08:37:32 +00:00
Amir Raminfar 5bc02e49f2 chore: release v4.10.0 2023-04-22 13:05:24 -07:00
Amir Raminfar bf634382e5 feat: sorts running conttainers at top (#2142) 2023-04-22 13:04:52 -07:00
Amir Raminfar 551fb1579d chore: removes semver in favor of localeCompare (#2141)
* chore: removes semver in favor of localeCompare

* chore: removes v
2023-04-21 13:52:38 -07:00
Amir Raminfar 12891b17ea chore(dev): adds https to localhost 2023-04-21 13:21:02 -07:00
Amir Raminfar 96b7189c44 chore: fix typecheck 2023-04-21 13:17:16 -07:00
Amir Raminfar 8489426540 chore: updates npm modules 2023-04-20 16:43:16 -07:00
renovate[bot] c592553db2 chore(deps): update dependency vite to v4.3.1 (#2135)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-20 16:41:21 -07:00
renovate[bot] 15e4cc7bce chore(deps): update node.js to v20 (#2139)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-20 16:41:08 -07:00
renovate[bot] 023a3490dc chore(deps): update dependency vitepress to v1.0.0-alpha.73 2023-04-20 18:01:01 +00:00
Amir Raminfar ee920b119e chore: update pnpm 2023-04-19 10:13:20 -07:00
Amir Raminfar fbcd52bcdd chore: updates npm modules 2023-04-18 09:49:05 -07:00
renovate[bot] 8b3bc673d0 chore(deps): update docker/bake-action action to v3 (#2136)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-18 09:45:17 -07:00
renovate[bot] 25341e2b7c chore(deps): update pnpm to v8.3.0 2023-04-18 02:47:07 +00:00
renovate[bot] 5c8437436e fix(deps): update module github.com/docker/docker to v23.0.4+incompatible 2023-04-18 01:45:28 +00:00
renovate[bot] 879dbb5ad0 chore(deps): update cypress/included docker tag to v12.10.0 2023-04-17 23:37:11 +00:00
renovate[bot] 7b4d084cb2 chore(deps): update dependency vitepress to v1.0.0-alpha.72 2023-04-17 20:17:04 +00:00
renovate[bot] c93ecbfdae chore(deps): update dependency vitepress to v1.0.0-alpha.71 2023-04-17 04:46:23 +00:00
renovate[bot] d79fcc2774 chore(deps): update dependency vitepress to v1.0.0-alpha.70 2023-04-16 17:49:28 +00:00
renovate[bot] 98b5c106ec chore(deps): update dependency vitepress to v1.0.0-alpha.69 2023-04-16 02:49:21 +00:00
renovate[bot] c44610fd14 chore(deps): update dependency vitepress to v1.0.0-alpha.66 2023-04-15 15:37:25 +00:00
Amir Raminfar 24a34146f2 docs: fixes document error before mounting 2023-04-14 09:13:15 -07:00
Amir Raminfar af96a5b18d chore: updates npm modules 2023-04-14 09:09:11 -07:00
Amir Raminfar f98bdc054a Updates release to bake 2023-04-14 08:59:58 -07:00
Amir Raminfar d92d153175 chore(docs): adds caching 2023-04-13 12:01:58 -07:00
Amir Raminfar 82e201973b chore: remove .netlify dir 2023-04-13 10:16:33 -07:00
Amir Raminfar 52b1425a41 docs: adds netlify function to fetch pull count 2023-04-13 10:15:34 -07:00
Amir Raminfar 547c3917c5 chore: updates npm modules 2023-04-12 13:27:27 -07:00
Amir Raminfar 2800421e8d ci: use docker bake instead (#2119)
* Updates to bake instead

* Removes buildx from compose

* Cleans up build again

* Removes load

* More clean up

* Tries to use bake correctly

* ci: use bake plugin again
2023-04-12 13:14:15 -07:00
Amir Raminfar 57d1c290d9 chore: release v4.9.4 2023-04-12 09:55:24 -07:00
Amir Raminfar 650413d510 Merge pull request #2123 from amir20/escape-html
fix: correctly escape XML entities on client side should fix #2120
2023-04-12 09:54:41 -07:00
Amir Raminfar 0e677bdee7 fix: correctly escape XML entities on client side 2023-04-12 09:47:27 -07:00
renovate[bot] 4455cc15cc chore(deps): update pnpm to v8.2.0 2023-04-10 22:04:50 +00:00
Amir Raminfar bc48387749 chore(renovate): updates renovate to auto merge 2023-04-10 09:25:57 -07:00
Amir Raminfar 8c625a3113 fix: fixes healthcheck when containers are stopped 2023-04-09 13:47:03 -07:00
Amir Raminfar 9775df895b docs: updates healthcheck docs 2023-04-09 13:27:14 -07:00
Amir Raminfar 5fc2d29e4f chore: cleans up e2e package.json file 2023-04-09 13:17:03 -07:00
Amir Raminfar 60cfdd4a70 chore: release v4.9.3 2023-04-09 13:00:00 -07:00
Amir Raminfar ad52a14d42 chore: updates all other modules 2023-04-09 12:55:53 -07:00
Amir Raminfar 792f640e41 chore: updates unocss 2023-04-09 12:55:21 -07:00
Amir Raminfar eb47420444 chore: uses npx changelogithub going forward with conventional commits 2023-04-09 12:49:04 -07:00
renovate[bot] 8492d73714 Update dependency @pinia/testing to ^0.0.16 (#2111) 2023-04-08 06:53:48 -07:00
Amir Raminfar c8565e08c9 Release 4.9.2 2023-04-06 09:53:52 -07:00
Amir Raminfar 63a0d544e4 Adds tooltip to healthcheck. Fixes #2104 (#2110) 2023-04-06 16:50:44 +00:00
renovate[bot] e7591f9a64 Update module github.com/docker/docker to v23.0.3+incompatible [SECURITY] (#2109) 2023-04-04 19:12:37 -07:00
renovate[bot] f242776772 Update golang Docker tag to v1.20.3 (#2108)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-04 13:26:28 -07:00
Amir Raminfar 51008ba604 Updates modules 2023-04-04 13:25:41 -07:00
renovate[bot] 96c68686bf Update pnpm to v8.1.1 (#2106)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-04 13:19:26 -07:00
renovate[bot] 93ddbca73a Update dependency vitepress to v1.0.0-alpha.65 (#2107)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-04 13:19:18 -07:00
Amir Raminfar a30de127ca Fixes spaces 2023-04-04 11:32:58 -07:00
Amir Raminfar 8e5611bc50 Updates docs to have analytics and dashboard 2023-04-03 12:00:15 -07:00
Amir Raminfar 9e2d6a484a Tries to remove unneeded npmrc changes 2023-03-31 08:32:02 -07:00
Amir Raminfar 7be7b17cc1 Uses corepack (#2103)
* Uses corepack

* Adds corepack to Docker
2023-03-30 10:15:18 -07:00
Amir Raminfar a619149412 Adds strict-peer-dependencies 2023-03-29 14:18:56 -07:00
Amir Raminfar 488156df89 Revert netlify 2023-03-29 14:12:54 -07:00
Amir Raminfar 1c068cc6f9 Temprorary fix for pnpm 2023-03-29 14:09:26 -07:00
renovate[bot] c6f2f8ccb2 Update dependency vitepress to v1.0.0-alpha.64 (#2093)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-29 12:49:27 -07:00
Amir Raminfar 518c78c3d1 Release 4.9.1 2023-03-29 12:38:13 -07:00
Amir Raminfar fe034d1a38 Removes -- 2023-03-29 12:29:26 -07:00
Amir Raminfar 49e6cd1c9b Changes to 1.20 (#2102) 2023-03-29 12:27:35 -07:00
Amir Raminfar 3e78f81a53 Release 4.9.0 2023-03-29 11:56:00 -07:00
Amir Raminfar e84f0cdb97 Adds typechecking with typescript (#2101)
* Fixes all other vue errors

* Adds typescheck

* Fix pnpm

* Fix pnpm again for cache
2023-03-29 11:53:15 -07:00
Amir Raminfar 804bd5b9e1 Fixes mobile bug 2023-03-29 09:59:21 -07:00
Amir Raminfar 9c9f247e0c cleans up typescript errors 2023-03-29 09:38:02 -07:00
Amir Raminfar db43f7a62d Fixes vue-tsc errors 2023-03-29 09:27:35 -07:00
Amir Raminfar efef1dca6b Fixes component d file 2023-03-29 09:20:47 -07:00
Amir Raminfar 3417ec3e99 Cleans up icons 2023-03-29 09:16:38 -07:00
Amir Raminfar bda832a8a3 Updates modules 2023-03-29 09:01:36 -07:00
renovate[bot] 30e9ab68d7 Update cypress/included Docker tag to v12.9.0 (#2100)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-29 08:59:02 -07:00
Amir Raminfar 9ba1a0f76a Adds health status by parsing the label from status (#2099)
* Adds healthcheck to status in menu

* Fixes bug with crated

* Moves colors to vars

* Updates icons
2023-03-29 08:58:12 -07:00
Amir Raminfar 0521f343e3 Also checks for level between [] 2023-03-28 16:04:56 -07:00
renovate[bot] 9ecc21dcb2 Update module github.com/docker/docker to v23.0.2+incompatible (#2098)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-28 09:55:33 -07:00
Amir Raminfar d99f4b2766 Updates pnpm 2023-03-28 09:08:38 -07:00
Amir Raminfar 5c83e33ace Updates circles 2023-03-27 19:01:25 -07:00
Amir Raminfar a4ac2c60ac Updates modules 2023-03-27 15:29:13 -07:00
Amir Raminfar bbb7fb8ba9 Updates modules 2023-03-26 09:01:49 -07:00
renovate[bot] 0ebd399768 Update dependency vitepress to v1.0.0-alpha.62 (#2092) 2023-03-25 08:40:57 -07:00
Amir Raminfar 9971b41434 Updates modules 2023-03-24 14:33:31 -07:00
Amir Raminfar 7f4d005a67 Update README.md 2023-03-24 13:43:45 -07:00
Amir Raminfar 2709417ba3 Release 4.8.2 2023-03-23 16:12:38 -07:00
Amir Raminfar c4386d2d84 Updates modules 2023-03-23 12:55:43 -07:00
Amir Raminfar fbcf989885 Updates with dark and light video 2023-03-21 15:25:03 -07:00
Amir Raminfar 07352f6e16 Adds dark and light for browser 2023-03-21 12:41:22 -07:00
Amir Raminfar 6fecfce3c7 Updates color on video 2023-03-21 12:35:32 -07:00
Amir Raminfar 2884ffafeb Adds blur 2023-03-21 12:33:06 -07:00
Amir Raminfar 67ac202b04 Updates videos 2023-03-20 13:08:18 -07:00
Amir Raminfar 839afd8fcf Fixes bug for level 2023-03-20 13:06:31 -07:00
Amir Raminfar 202b07bfb4 Updates logic to find ERROR in logs 2023-03-20 11:13:53 -07:00
renovate[bot] 15bd0999a2 Update dependency vite to v4.2.1 (#2088) 2023-03-20 06:08:57 -07:00
Amir Raminfar 4450c2527f node latest (#2089)
* Fixes tests

* Fixes tests

* Updates tests

* Updates node to latest

* Updates modules
2023-03-16 13:48:42 -07:00
renovate[bot] 614e62b782 Update cypress/included Docker tag to v12.8.1 (#2086) 2023-03-16 13:03:49 -07:00
Amir Raminfar 77cce86e29 Fixes file formatting (#2085)
* Updates prettier ignore

* Updates prettier ignore

* Updates formatting

* Fixes tests

* Fixes tests
2023-03-15 10:46:32 -07:00
renovate[bot] 93dc547b9d Update actions/setup-go action to v4 (#2084)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-15 09:14:43 -07:00
renovate[bot] 0681a3c0c6 Update cypress/included Docker tag to v12.8.0 (#2063)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-15 08:50:58 -07:00
renovate[bot] 9bf35e3e1f Update dependency vitepress to v1.0.0-alpha.60 (#2082)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-15 08:50:49 -07:00
Amir Raminfar 087e8694d1 Uses node lts instead (#2083)
* Uses node lts instead

* Uses node 18
2023-03-15 08:39:41 -07:00
renovate[bot] 34b034c822 Update dependency vitepress to v1.0.0-alpha.58 (#2080)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-14 14:43:29 -07:00
Amir Raminfar bd598699e3 Adds docker social link 2023-03-13 13:55:51 -07:00
Amir Raminfar 8867b2aa7c Updates docs 2023-03-13 09:51:53 -07:00
Amir Raminfar 296e8fc50e Fixes docker build 2023-03-13 09:19:35 -07:00
Amir Raminfar da065a1966 Fixes space 2023-03-13 09:15:41 -07:00
Amir Raminfar 5262a54463 Removes husky 2023-03-13 09:12:29 -07:00
Amir Raminfar f37c5d09ed Updates lint stage 2023-03-13 09:09:26 -07:00
Amir Raminfar 298e030db0 Uses simple-git-hooks 2023-03-13 09:08:09 -07:00
Amir Raminfar 6f2ef4499b Updates deps 2023-03-13 08:43:06 -07:00
Amir Raminfar e4d1064843 Updates npmrc 2023-03-13 08:30:24 -07:00
Amir Raminfar 0c1ad7c0c7 Adds include-workspace-root 2023-03-12 13:26:40 -07:00
Amir Raminfar fb9333a11b Fixes workspaces and int tests 2023-03-12 13:23:21 -07:00
Amir Raminfar ed67d7d085 Updates libs for dozzle 2023-03-12 12:46:14 -07:00
Amir Raminfar d34136589b Revert "Update dependency vitepress to v1.0.0-alpha.52 (#2078)" (#2079)
This reverts commit ac7b120176.
2023-03-11 16:45:22 -08:00
Amir Raminfar 5226c92f78 Adds sitemap 2023-03-11 15:18:22 -08:00
Amir Raminfar 9e507b31ec Fixes max width for video 2023-03-11 15:11:38 -08:00
renovate[bot] ac7b120176 Update dependency vitepress to v1.0.0-alpha.52 (#2078)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-11 15:06:31 -08:00
Amir Raminfar c15a680797 Adds more docs 2023-03-10 16:05:48 -08:00
Amir Raminfar 1c07142bce Adds workspace 2023-03-10 14:09:57 -08:00
renovate[bot] c886071559 Update docker/setup-buildx-action action to v2.5.0 (#2075)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-10 12:23:11 -08:00
renovate[bot] 44b6809abe Update dependency vitepress to v1.0.0-alpha.51 (#2077)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-10 12:23:01 -08:00
Amir Raminfar 13db2cfb58 Fixes style on mobile 2023-03-10 12:19:56 -08:00
Amir Raminfar a1af2c60cd vitepress (#2076)
* Uses vitepress

* migrates to vitepress

* Remove api link

* Adds gitignore

* Adds gitignore

* Removes unused colors

* Removes optional for docker
2023-03-10 12:01:26 -08:00
Amir Raminfar 37d322bec8 Adds gtag 2023-03-08 13:22:57 -08:00
Amir Raminfar ccd61ce095 Fixes js warn 2023-03-08 13:06:04 -08:00
Amir Raminfar 01c63a3650 Updates colors and adds button 2023-03-08 13:03:36 -08:00
Amir Raminfar 5a99378845 Updates dockerfile for int test 2023-03-08 10:34:08 -08:00
renovate[bot] dfed83df0b Update pnpm to v7.29.1 (#2068)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-08 10:22:00 -08:00
renovate[bot] 75b0d5c4fb Update module github.com/spf13/afero to v1.9.5 (#2071)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-08 10:21:41 -08:00
renovate[bot] d654f3eceb Update golang Docker tag to v1.20.2 (#2072)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-08 10:21:30 -08:00
renovate[bot] 6cdcbd8244 Update dependency @intlify/unplugin-vue-i18n to ^0.9.0 (#2073)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-08 10:21:19 -08:00
Amir Raminfar cb18e42602 Fixes pre 2023-03-06 11:45:13 -08:00
Amir Raminfar 37d78117f1 Fixes menu 2023-03-06 09:58:25 -08:00
Amir Raminfar 0cf4e6d2b6 Creates header 2023-03-06 09:45:32 -08:00
Amir Raminfar ec5196ea6e Removes comma 2023-03-06 09:31:37 -08:00
Amir Raminfar 089c2e9b17 Finishes menu 2023-03-06 09:27:33 -08:00
Amir Raminfar 5f2420960b Makes improves to colors and menu 2023-03-05 14:24:43 -08:00
Amir Raminfar e73ee6735e Adds guide link 2023-03-04 11:17:50 -08:00
Amir Raminfar 03b4c5adb9 Updates link colors 2023-03-03 15:20:11 -08:00
Amir Raminfar b92703c7bb Updates title and adds more content 2023-03-03 13:12:18 -08:00
Amir Raminfar 2bb4d10b66 Adds docs layouts 2023-03-03 09:50:01 -08:00
Amir Raminfar e02b08c21d Removes unused components 2023-03-02 15:57:12 -08:00
Amir Raminfar 539ace566c Adds icons for github and docker 2023-03-01 12:15:04 -08:00
Amir Raminfar a29373d1a2 Adds poster fix 2023-02-28 16:48:42 -08:00
Amir Raminfar 3c0eac4541 Adds poster 2023-02-28 16:32:10 -08:00
Amir Raminfar beee7ea9f8 Fixes grid bug 2023-02-28 15:51:08 -08:00
Amir Raminfar 770416805e Adds color to dozzle 2023-02-28 15:48:06 -08:00
Amir Raminfar c2fcecaec0 Updates modules 2023-02-28 13:45:25 -08:00
Amir Raminfar 3a30bcf937 Fixes grid 2023-02-28 13:42:39 -08:00
Amir Raminfar 54f9a04b9d Fixes grid problem 2023-02-28 13:10:24 -08:00
Amir Raminfar 62b7c38f88 Adds dockerignore 2023-02-28 12:02:31 -08:00
Amir Raminfar b474128a5f Fixes vscode settings 2023-02-28 12:00:07 -08:00
Amir Raminfar cb46130880 Moves netlify again 2023-02-28 11:51:34 -08:00
Amir Raminfar 8cf9fbe77c Moves netlify 2023-02-28 11:48:43 -08:00
Amir Raminfar 9c5da96eb4 Moves netlify 2023-02-28 11:48:43 -08:00
Amir Raminfar 5abf538cb4 Updates docs 2023-02-28 11:48:43 -08:00
renovate[bot] a1d79d4fc6 Update dependency vitest to ^0.29.0 (#2064)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-27 10:56:07 -08:00
renovate[bot] 468c8982b0 Update module github.com/stretchr/testify to v1.8.2 (#2065)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-27 10:55:51 -08:00
Amir Raminfar a9b4be43c8 Release 4.8.1 2023-02-26 08:41:39 -08:00
Amir Raminfar 1350895676 Updates logic when no TLS certs are found to use plan HTTP. See #2066 (#2067) 2023-02-25 12:57:35 -08:00
Amir Raminfar 591bee332f Release 4.8.0 2023-02-24 09:51:21 -08:00
Amir Raminfar 3257f4b04f Updates remadme file 2023-02-24 09:46:21 -08:00
Amir Raminfar 872729a93b Adds support for multiple hosts (#2059)
* Adds support for multiple hosts

* Adds UI for drop down

* Adds support for TLS and remove SSH

* Changes dropdown to only show up with >1 hosts

* Fixes js tests

* Fixes go tests

* Fixes download link

* Updates readme

* Removes unused imports

* Fixes spaces
2023-02-24 09:42:58 -08:00
renovate[bot] a7d6a5088a Update dependency vite to v4.1.4 (#2060)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-23 15:08:26 -08:00
renovate[bot] ebf832b501 Update module github.com/spf13/afero to v1.9.4 (#2061)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-23 15:08:14 -08:00
renovate[bot] 11fd3e14bb Update dependency unplugin-auto-import to ^0.15.0 (#2062)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-23 15:07:32 -08:00
Amir Raminfar eb4df34d42 Updates go modules 2023-02-21 08:17:49 -08:00
Amir Raminfar 6bc6dd5c03 Updates modules 2023-02-21 08:16:52 -08:00
renovate[bot] 460f1167b0 Update dependency vite to v4.1.3 (#2057) 2023-02-20 07:08:27 -08:00
renovate[bot] f04563e868 Update dependency @pinia/testing to ^0.0.15 (#2056) 2023-02-20 07:07:59 -08:00
renovate[bot] c4881cf407 Update cypress/included Docker tag to v12.6.0 (#2048) 2023-02-19 07:05:17 -08:00
renovate[bot] 9945f31e7c Update dependency vite to v4.1.2 (#2052) 2023-02-19 07:05:10 -08:00
renovate[bot] c13fd882ed Update dependency vite-plugin-vue-layouts to ^0.8.0 (#2053) 2023-02-19 06:16:37 -08:00
Amir Raminfar da6257c233 Updates modules 2023-02-16 13:24:36 -08:00
Amir Raminfar 0b5da03ffb Release 4.7.5 2023-02-15 12:25:48 -08:00
renovate[bot] 617fc3d387 Update golang Docker tag to v1.20.1 (#2046)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-15 12:24:20 -08:00
Amir Raminfar d82f9e4db2 Updates modules 2023-02-14 14:54:29 -08:00
renovate[bot] efabc926cf Update module github.com/docker/docker to v23.0.1+incompatible (#2043) 2023-02-14 06:17:35 -08:00
renovate[bot] c5505e9ac5 Update dependency unplugin-auto-import to ^0.14.0 (#2040)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-09 10:30:21 -08:00
Amir Raminfar bb14f9121e Properly handles casting of string. Fixes #2041 (#2042) 2023-02-09 09:46:46 -08:00
Amir Raminfar 050666a39c Uses 127.0.0.1 instead 2023-02-08 10:24:05 -08:00
renovate[bot] a03d83676f Update docker/setup-buildx-action action to v2.4.1 (#2035) 2023-02-06 06:30:41 -08:00
Amir Raminfar 6d4bff0ce8 Adds coverage to ignore 2023-02-03 08:58:34 -08:00
renovate[bot] 5e1b532f52 Update cypress/included Docker tag to v12.5.1 (#2034)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-03 08:38:28 -08:00
Amir Raminfar 4ea19f97da Adds more tests 2023-02-02 12:23:46 -08:00
Amir Raminfar 3420eb968e Updates go modules 2023-02-02 12:08:08 -08:00
Amir Raminfar f9f48c3f81 Updates npm modules and tests 2023-02-02 12:06:18 -08:00
renovate[bot] 5fad7b1efc Update dependency vite to v4.1.1 (#2033) 2023-02-02 07:09:46 -08:00
renovate[bot] 1337ad97f1 Update golang Docker tag to v1.20.0 (#2030) 2023-02-02 07:01:36 -08:00
renovate[bot] 520d6c05b1 Update dependency vite to v4.1.0 (#2031) 2023-02-02 07:01:27 -08:00
renovate[bot] 52bd643b63 Update cypress/included Docker tag to v12.5.0 (#2018) 2023-01-31 16:42:19 -08:00
Amir Raminfar 928136cb62 Release 4.7.4 2023-01-31 12:07:24 -08:00
Amir Raminfar 4c5bd9a7be Reverts back previous fix and uses proper error handeling (#2029)
* Reverts back previous fix and uses proper error handeling

* Adds more tests and refactors existing tests
2023-01-31 12:03:41 -08:00
renovate[bot] 9378bb5f51 Update docker/build-push-action action to v4 (#2026) 2023-01-31 05:27:45 -08:00
Amir Raminfar c036debd17 Release 4.7.3 2023-01-30 12:51:51 -08:00
Amir Raminfar d330ca4746 Fixes error when paginating (#2027) 2023-01-30 12:51:39 -08:00
Amir Raminfar 645b9a8311 Release 4.7.2 2023-01-30 11:17:29 -08:00
Amir Raminfar 862bd449fd Fixes int tests 2023-01-30 11:14:18 -08:00
Amir Raminfar 0a7ea0251a Uses docker compose instead docker-compose 2023-01-30 10:09:09 -08:00
renovate[bot] 5809a5478b Update docker/setup-buildx-action action to v2.4.0 (#2023)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-30 09:26:20 -08:00
Amir Raminfar 8bbdfb4a9e Uses gha cache 2023-01-30 09:16:57 -08:00
Amir Raminfar 74b5ff48fc Updates cypress 2023-01-30 09:16:39 -08:00
Amir Raminfar c6c24aaf9f Changes minimum ms to 10ms 2023-01-30 09:00:53 -08:00
Amir Raminfar e133b59969 Updates tests 2023-01-30 08:54:46 -08:00
Amir Raminfar 61fea7afaa Hide date when mobile 2023-01-29 13:39:53 -08:00
renovate[bot] 46aa4ab005 Update dependency unplugin-auto-import to ^0.13.0 (#2022) 2023-01-29 13:18:38 -08:00
renovate[bot] 8a8b2607a1 Update docker/setup-buildx-action action to v2.3.0 (#2020)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-28 06:42:39 -08:00
renovate[bot] 5df604e480 Update dependency unplugin-vue-components to ^0.23.0 (#2021)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-28 06:42:31 -08:00
Amir Raminfar 7a169dd555 Release 4.7.1 2023-01-27 13:30:55 -08:00
Amir Raminfar af49e6fd6f Does not center multiline 2023-01-27 12:40:05 -08:00
Amir Raminfar 36a03d91fc Fixes error position to use flex instead (#2019) 2023-01-27 11:03:22 -08:00
Amir Raminfar 143246237d Fixes prefix bug 2023-01-25 13:02:33 -08:00
Amir Raminfar a0dbfeaba5 Release 4.7.0 2023-01-25 11:04:13 -08:00
Amir Raminfar d19d6efa8b Release 4.6.5 2023-01-25 10:54:08 -08:00
Amir Raminfar fd97c50da8 Fixes release 2023-01-25 10:54:00 -08:00
Amir Raminfar de109abd76 Release 4.6.4 2023-01-25 10:52:56 -08:00
Amir Raminfar 0e5830df20 Makes changes to log streamer to guess the event level (#2013)
* Makes changes to log streamer to guess the event level

* Updates diff

* Adds log class for level

* Groups messages by level

* Fixes bugs for grouping

* Fixes tests

* Fixes json bug

* Updates logic to support other kind of levels

* Fixes mobile view
2023-01-25 10:39:21 -08:00
Amir Raminfar 20e158c7bd Uses latest 2023-01-25 10:26:10 -08:00
Amir Raminfar b77c908719 Updates node 2023-01-25 10:24:08 -08:00
Amir Raminfar 5f9dc2780c Adds -- 2023-01-25 10:09:02 -08:00
Amir Raminfar 9c905da702 Adds --outputTruncateLength=500 2023-01-25 10:07:26 -08:00
Amir Raminfar 4729e7f29e Fixes tests 2023-01-25 09:50:07 -08:00
renovate[bot] 10c5a2739c Update dependency vitest to ^0.28.0 (#2016)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-23 08:51:39 -08:00
renovate[bot] 8d75729bca Update module github.com/docker/docker to v20.10.23+incompatible (#2014)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-23 08:51:29 -08:00
Amir Raminfar 3f61b8cfdc Release 4.6.3 2023-01-19 11:37:53 -08:00
Amir Raminfar 3a55fdfff0 Updates modules 2023-01-19 11:37:46 -08:00
renovate[bot] 941fb60336 Update module github.com/dustin/go-humanize to v1.0.1 (#2010)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-17 11:46:46 -08:00
renovate[bot] 133261fba2 Update docker/build-push-action action to v3.3.0 (#2011) 2023-01-14 08:06:59 -08:00
renovate[bot] 9fc01567a2 Update golang Docker tag to v1.19.5 (#2009)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-11 10:03:11 -08:00
renovate[bot] 4118204948 Update dependency vitest to ^0.27.0 (#2007) 2023-01-09 07:11:38 -08:00
dependabot[bot] 12bb8d3f68 Bump github.com/docker/distribution (#2004)
Bumps [github.com/docker/distribution](https://github.com/docker/distribution) from 2.7.1+incompatible to 2.8.0+incompatible.
- [Release notes](https://github.com/docker/distribution/releases)
- [Commits](https://github.com/docker/distribution/compare/v2.7.1...v2.8.0)

---
updated-dependencies:
- dependency-name: github.com/docker/distribution
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-07 13:39:14 -08:00
dependabot[bot] 88517ff0f7 Bump github.com/opencontainers/image-spec from 1.0.1 to 1.0.2 (#2005) 2023-01-07 12:25:33 -08:00
renovate[bot] 6dd3758f98 Update dependency jsdom to v21 (#2003) 2023-01-07 09:58:42 -08:00
Amir Raminfar e3630465ed Updates deps 2023-01-06 09:59:26 -08:00
renovate[bot] 4715f18c07 Update cypress/included Docker tag to v12.3.0 (#2002)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-06 09:46:19 -08:00
Amir Raminfar b1d4520360 Updates deps 2023-01-03 10:04:13 -08:00
renovate[bot] 467b5e8e19 Update dependency unplugin-icons to ^0.15.0 (#2001)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-03 10:03:27 -08:00
Amir Raminfar ce39d3837f Release 4.6.2 2023-01-02 10:06:09 -08:00
Amir Raminfar 884dd94242 Adds analytics for requests made (#1999) 2023-01-02 09:28:27 -08:00
Amir Raminfar df2ff662dd Removes outdated i18n plugin (#1998) 2022-12-29 08:27:54 -08:00
renovate[bot] f602d4e69d Update dependency vite to v4.0.3 (#1996) 2022-12-25 08:02:53 -08:00
renovate[bot] 27f898b696 Update cypress/included Docker tag to v12.2.0 (#1995) 2022-12-25 08:02:46 -08:00
renovate[bot] 400afddc45 Update dependency vitest to ^0.26.0 (#1994) 2022-12-20 07:06:03 -08:00
Amir Raminfar edf7edced3 Release 4.6.1 2022-12-18 14:44:29 -08:00
Amir Raminfar 0ef5c36900 Fixes #1990 by only coming list when relative fields change (#1993) 2022-12-18 14:38:29 -08:00
renovate[bot] a008ede927 Update dependency vite to v4.0.2 (#1992)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-18 14:18:39 -08:00
renovate[bot] b4e88937cc Update module github.com/docker/docker to v20.10.22+incompatible (#1989)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-18 14:18:32 -08:00
renovate[bot] fd3992cabd Update cypress/included Docker tag to v12.1.0 (#1984)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-14 11:00:42 -08:00
renovate[bot] 47bed89fe2 Update dependency vite to v4.0.1 (#1979)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-12 13:31:39 -08:00
Amir Raminfar 361fff7803 Release 4.6.0 2022-12-12 09:38:17 -08:00
Amir Raminfar a6ba94e03f Adds hostname config for setting UI parameter (#1983)
* Adds hostname config to setting a hostname in title

* Also updates title for hostname
2022-12-12 09:37:54 -08:00
renovate[bot] d39e2514b2 Update dependency vite-plugin-pages to ^0.28.0 (#1982)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-12 09:14:35 -08:00
Amir Raminfar 155757dd74 Removes tail size (#1981)
* Removes tail size

* Removes tail from analytics
2022-12-12 09:14:29 -08:00
Amir Raminfar cc97e90a3e Release 4.5.0 2022-12-09 10:20:28 -08:00
Amir Raminfar 68160dd9c4 Updates vite to v4 (#1980) 2022-12-09 10:17:28 -08:00
renovate[bot] 7694c95e1a Update module github.com/magiconair/properties to v1.8.7 (#1977)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-08 09:56:48 -08:00
renovate[bot] 5b91a4a9e8 Update cypress/included Docker tag to v12.0.2 (#1976)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-08 09:56:41 -08:00
renovate[bot] 7c56449874 Update cypress/included Docker tag to v12.0.1 (#1975)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-07 09:50:21 -08:00
renovate[bot] 3dbe83fd3e Update dependency vite to v3.2.5 (#1971)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-06 15:01:01 -08:00
renovate[bot] 2a3bce8cd8 Update cypress/included Docker tag to v12 (#1972)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-06 15:00:50 -08:00
renovate[bot] d3303b2436 Update dependency cypress to v12 (#1973)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-06 15:00:43 -08:00
renovate[bot] 5d2717d584 Update golang Docker tag to v1.19.4 (#1974)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-06 15:00:35 -08:00
Amir Raminfar e22fd87d45 Fixes #1963 2022-11-30 08:49:21 -08:00
Amir Raminfar 3a6f1bf92c Updates modules 2022-11-29 14:06:19 -08:00
IrwenXYZ ae3283adda Update readme for memory consumption on ARM devices (#1967) 2022-11-25 20:19:39 -08:00
renovate[bot] 145158a925 Update dependency unplugin-auto-import to ^0.12.0 (#1966) 2022-11-25 09:56:19 -08:00
renovate[bot] e46e1a0814 Update cypress/included Docker tag to v11.2.0 (#1961) 2022-11-23 06:34:10 -08:00
renovate[bot] 7a624cd065 Update dependency jest-serializer-vue to v3 (#1959) 2022-11-23 06:33:56 -08:00
Amir Raminfar fe7c8743b9 Removes depedenbot 2022-11-15 15:40:58 -08:00
renovate[bot] ccc1aae78e Update cypress/included Docker tag to v11.1.0 (#1958)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-11-15 13:05:01 -08:00
renovate[bot] 935e17c200 Update dependency vite to v3.2.4 (#1957) 2022-11-15 07:11:03 -08:00
renovate[bot] 63bb1f24a3 Update module github.com/spf13/afero to v1.9.3 (#1954) 2022-11-14 20:11:08 -08:00
renovate[bot] cdc8189ad1 Update module go to 1.19 (#1955) 2022-11-14 20:09:59 -08:00
renovate[bot] f6daab2111 Add renovate.json (#1953) 2022-11-14 19:43:38 -08:00
Amir Raminfar 7ed461e6ef Removes npmrc and hositing is not needed anymore (#1952) 2022-11-14 09:42:01 -08:00
Amir Raminfar 15636348b5 Updates modules 2022-11-10 09:35:11 -08:00
kodiakhq[bot] a370ad48d5 Merge pull request #1950 from amir20/dependabot/docker/e2e/cypress/included-11.0.1
Bump cypress/included from 11.0.0 to 11.0.1 in /e2e
2022-11-10 09:11:52 +00:00
dependabot[bot] 8900631b20 Bump cypress/included from 11.0.0 to 11.0.1 in /e2e
Bumps cypress/included from 11.0.0 to 11.0.1.

---
updated-dependencies:
- dependency-name: cypress/included
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-10 09:06:19 +00:00
Amir Raminfar fa3ef24999 Updates cypress to 11.x (#1949) 2022-11-09 09:11:02 -08:00
kodiakhq[bot] deaf58c47b Merge pull request #1948 from amir20/dependabot/docker/e2e/cypress/included-11.0.0
Bump cypress/included from 10.11.0 to 11.0.0 in /e2e
2022-11-09 09:11:24 +00:00
dependabot[bot] 1d4573a657 Bump cypress/included from 10.11.0 to 11.0.0 in /e2e
Bumps cypress/included from 10.11.0 to 11.0.0.

---
updated-dependencies:
- dependency-name: cypress/included
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-09 09:07:24 +00:00
Amir Raminfar 84300fc734 Release 4.4.1 2022-11-08 15:10:14 -08:00
Dmitry Mazurov 2b9873e76b Update mobile menu (#1946) 2022-11-08 15:09:57 -08:00
Amir Raminfar c589c599fc Release 4.4.0 2022-11-08 12:48:48 -08:00
Amir Raminfar 2702e80314 Adds hover for buttons 2022-11-08 12:47:10 -08:00
Dmitry Mazurov bb1dda41ff Add save collapse to localstorage (#1945) 2022-11-08 12:40:13 -08:00
Amir Raminfar ddafd95b69 Fixes int tests 2022-11-08 11:54:37 -08:00
Amir Raminfar d6e87a7137 Fixes collapseNav 2022-11-08 11:49:40 -08:00
Dmitry Mazurov 54eca89c4e Moved logout button to sidebar and fix default width sidebar. (#1942)
* Moved logout button to sidebar and fix default width sidebar.

Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>

* Fix logout url. Save collapse to localstorage.

Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>

* Clear code.

Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>

* Update logout button if collapse

Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>

* Fix dark mode for collapse button. Update toolbar. Remove localstorage.

* Fix collapse auto-import

* Remove collapse from settings

Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>
2022-11-08 11:45:11 -08:00
Amir Raminfar fbe34d7377 Updates modules 2022-11-07 09:08:29 -08:00
Dmitry Mazurov e2b497f158 Fix search modal for mobile view (#1941)
Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>

Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>
2022-11-04 10:36:19 -07:00
Amir Raminfar ca9fcf8e1d Updates snapshots 2022-11-03 13:43:24 -07:00
Dmitry Mazurov 4fb012c0e4 Merge branch 'amir20:master' into responsible 2022-11-03 22:48:44 +03:00
Dmitry Mazurov b77448f2a9 Fix responsible list containers
Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>
2022-11-03 22:09:56 +03:00
Amir Raminfar 3bc921c8d7 Updates auto import 2022-11-03 09:51:17 -07:00
Amir Raminfar 54c08cf79b Removes duplicate case sensistive files (#1939) 2022-11-03 09:41:59 -07:00
Amir Raminfar 199c51722c Also skip docker login 2022-11-02 10:43:08 -07:00
Amir Raminfar 379af164dd Skips only the push int tests (#1937) 2022-11-02 09:16:45 -07:00
Dmitry Mazurov dc4ba652b4 Skip Integration Tests and Push branches and PRs from fork (#1936)
Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>

Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>
2022-11-02 07:24:39 -07:00
kodiakhq[bot] 0e8561a1c5 Merge pull request #1935 from amir20/dependabot/docker/golang-1.19.3-alpine
Bump golang from 1.19.2-alpine to 1.19.3-alpine
2022-11-02 09:29:57 +00:00
dependabot[bot] 60ec928a8e Bump golang from 1.19.2-alpine to 1.19.3-alpine
Bumps golang from 1.19.2-alpine to 1.19.3-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-02 09:26:23 +00:00
Dmitry Mazurov 26f3ab65a7 Update ru locale (#1934) 2022-11-01 11:36:02 -07:00
Amir Raminfar 0cc209484d Use trim instead 2022-11-01 10:57:49 -07:00
Amir Raminfar f09a7815af Cleans up DockerSecret 2022-11-01 09:08:05 -07:00
Amir Raminfar 70b573cec0 Fixes spacing 2022-10-31 15:11:33 -07:00
Amir Raminfar 0d79196eda Updates cypress 2022-10-31 10:21:41 -07:00
Amir Raminfar 8eb4e05055 Release 4.3.0 2022-10-31 09:33:26 -07:00
Amir Raminfar cd793a9b53 Updates modules 2022-10-31 09:32:21 -07:00
Dmitry Mazurov fad75e2f5b Fix read username and password from file (#1930)
* Fix read username and password from file

* Update Readme

* Add ru locale

* Update read the file from UnmarshalText

* Fix if not set username and password

* Fix if content in file empty

* Update ru locale

* Fix errorMsg for auth

* Removed local ru

Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>

* Remove struct for username and password

Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>
2022-10-31 09:31:27 -07:00
Dmitry Mazurov d6710453e5 Add ru locale (#1932)
Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>

Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>
2022-10-31 09:03:47 -07:00
Amir Raminfar 3a8c9cbcfa Release 4.2.3 2022-10-28 14:42:46 -07:00
Dmitry Mazurov 8586fee5ab Add docker secrets env (#1929) 2022-10-28 14:31:48 -07:00
Amir Raminfar 8ccae473ed Updates modules 2022-10-28 12:24:42 -07:00
kodiakhq[bot] 02434afef3 Merge pull request #1923 from amir20/dependabot/docker/e2e/cypress/included-10.11.0
Bump cypress/included from 10.10.0 to 10.11.0 in /e2e
2022-10-26 09:45:31 +00:00
dependabot[bot] 192ee98dd1 Bump cypress/included from 10.10.0 to 10.11.0 in /e2e
Bumps cypress/included from 10.10.0 to 10.11.0.

---
updated-dependencies:
- dependency-name: cypress/included
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-26 09:41:20 +00:00
kodiakhq[bot] 79e9dc7be6 Merge pull request #1922 from amir20/dependabot/go_modules/github.com/docker/docker-20.10.21incompatible
Bump github.com/docker/docker from 20.10.20+incompatible to 20.10.21+incompatible
2022-10-26 09:15:01 +00:00
dependabot[bot] 19fe7e8d18 Bump github.com/docker/docker
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 20.10.20+incompatible to 20.10.21+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Changelog](https://github.com/moby/moby/blob/master/CHANGELOG.md)
- [Commits](https://github.com/docker/docker/compare/v20.10.20...v20.10.21)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-26 09:11:08 +00:00
Amir Raminfar e4ecfca5be Updats modules 2022-10-24 16:08:59 -07:00
kodiakhq[bot] f37925b637 Merge pull request #1920 from amir20/dependabot/go_modules/github.com/stretchr/testify-1.8.1
Bump github.com/stretchr/testify from 1.8.0 to 1.8.1
2022-10-24 09:15:06 +00:00
dependabot[bot] 822b783063 Bump github.com/stretchr/testify from 1.8.0 to 1.8.1
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.0...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-24 09:11:05 +00:00
189 changed files with 9577 additions and 7179 deletions
+1 -1
View File
@@ -5,4 +5,4 @@ node_modules
dist
.git
e2e
docs
-2
View File
@@ -1,2 +0,0 @@
version = 1
merge.notify_on_conflict = false
+8 -7
View File
@@ -1,10 +1,9 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
title: ""
labels: ""
assignees: amir20
---
**Describe the bug**
@@ -12,6 +11,7 @@ A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
@@ -24,9 +24,10 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS, Windows or Mac Os]
- Docker version [e.g. `docker version`'s output]
- Browser & version [e.g. chrome, safari]
- Version [e.g. 1.26.1. Can be found at `<Dozzle-host>/version`]
- OS: [e.g. iOS, Windows or Mac Os]
- Docker version [e.g. `docker version`'s output]
- Browser & version [e.g. chrome, safari]
- Version [e.g. 1.26.1. Can be found at `<Dozzle-host>/version`]
**If applicable include logs with `--level debug` and browser logs**
+1 -2
View File
@@ -1,10 +1,9 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
title: ""
labels: enhancement
assignees: amir20
---
**Is your feature request related to a problem? Please describe.**
-47
View File
@@ -1,47 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
labels:
- "dependencies"
- "automerge"
schedule:
interval: "daily"
- package-ecosystem: "docker"
directory: "/"
labels:
- "dependencies"
- "automerge"
schedule:
interval: "daily"
- package-ecosystem: gomod
directory: "/"
labels:
- "gomod"
- "dependencies"
- "automerge"
schedule:
interval: daily
- package-ecosystem: npm
directory: "/"
labels:
- "npm"
- "dependencies"
- "automerge"
schedule:
interval: daily
- package-ecosystem: npm
directory: "/e2e"
labels:
- "npm"
- "dependencies"
- "automerge"
schedule:
interval: daily
- package-ecosystem: "docker"
directory: "/e2e"
labels:
- "dependencies"
- "automerge"
schedule:
interval: "daily"
+5
View File
@@ -0,0 +1,5 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base", "schedule:weekly", "group:allNonMajor", ":automergeMinor"],
"rangeStrategy": "bump"
}
+53 -29
View File
@@ -8,40 +8,70 @@ jobs:
name: JavaScript Tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install Node
uses: actions/setup-node@v3
- name: Install pnpm
uses: pnpm/action-setup@v2.2.4
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
name: Install Node
with:
version: 6.20.1
node-version: latest
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v3
with:
node-version: latest
cache: "pnpm"
cache-dependency-path: "**/pnpm-lock.yaml"
- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile --prefer-offline
- name: Run Tests
run: pnpm run test
run: pnpm run test -- --outputTruncateLength=500
go-test:
name: Go Tests
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
go-version: 1.18.x
go-version: 1.20.x
- name: Checkout code
uses: actions/checkout@v3
- name: Run Go Tests with Coverage
run: make test SKIP_ASSET=1
int-test:
name: Integration Tests
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Build images
run: COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f e2e/docker-compose.yml build
- name: Run tests
run: COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f e2e/docker-compose.yml up --build --force-recreate --exit-code-from cypress
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
name: Install Node
with:
node-version: latest
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v3
with:
node-version: latest
cache: "pnpm"
cache-dependency-path: "**/pnpm-lock.yaml"
- name: Install dependencies
run: pnpm install
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build
uses: docker/bake-action@v3
with:
load: true
set: |
*.cache-from=type=gha
*.cache-to=type=gha,mode=max
- name: Run Playwright tests
run: docker compose up --exit-code-from playwright
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
buildx:
needs: [go-test, npm-test, int-test]
name: Release
@@ -53,22 +83,22 @@ jobs:
with:
images: amir20/dozzle
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.2.1
uses: docker/setup-buildx-action@v2.9.1
- name: Login to DockerHub
uses: docker/login-action@v2.1.0
uses: docker/login-action@v2.2.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v3.2.0
uses: docker/build-push-action@v4.1.1
with:
push: true
platforms: linux/amd64,linux/arm/v7,linux/arm64/v8
tags: ${{ steps.meta.outputs.tags }}
build-args: TAG=${{ steps.meta.outputs.version }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=amir20/dozzle:latest
cache-to: type=inline
cache-from: type=gha
cache-to: type=gha,mode=max
git-release:
needs: [buildx]
name: Github Release
@@ -80,13 +110,7 @@ jobs:
fetch-depth: 0
- name: Install Node
uses: actions/setup-node@v3
- name: Install pnpm
uses: pnpm/action-setup@v2.2.4
with:
version: 6.20.1
- name: Install dependencies
run: pnpm install
- name: Release to Github
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: pnpm run release -- --github.release --no-increment --no-git --ci
run: npx changelogithub
+6 -6
View File
@@ -10,7 +10,7 @@ jobs:
buildx:
name: Push branches and PRs
runs-on: ubuntu-latest
if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == 'amir20/dozzle' }}
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: Docker meta
id: meta
@@ -18,19 +18,19 @@ jobs:
with:
images: amir20/dozzle
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.2.1
uses: docker/setup-buildx-action@v2.9.1
- name: Login to DockerHub
uses: docker/login-action@v2.1.0
uses: docker/login-action@v2.2.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v3.2.0
uses: docker/build-push-action@v4.1.1
with:
push: true
platforms: linux/amd64,linux/arm/v7,linux/arm64/v8
tags: ${{ steps.meta.outputs.tags }}
build-args: TAG=${{ steps.meta.outputs.version }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=amir20/dozzle:master
cache-to: type=inline
cache-from: type=gha
cache-to: type=gha,mode=max
+64 -37
View File
@@ -7,64 +7,91 @@ on:
- master
name: Test
jobs:
typecheck:
name: Typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
name: Install Node
with:
node-version: latest
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v3
with:
node-version: latest
cache: "pnpm"
cache-dependency-path: "**/pnpm-lock.yaml"
- name: Install dependencies
run: pnpm install --frozen-lockfile --prefer-offline
- name: Run Typecheck
run: pnpm run typecheck
npm-test:
name: JavaScript Tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install Node
uses: actions/setup-node@v3
- name: Install pnpm
uses: pnpm/action-setup@v2.2.4
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
name: Install Node
with:
version: 6.20.1
node-version: latest
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v3
with:
node-version: latest
cache: "pnpm"
cache-dependency-path: "**/pnpm-lock.yaml"
- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile --prefer-offline
- name: Run Tests
run: pnpm run test
run: pnpm run test -- --outputTruncateLength=500
go-test:
name: Go Tests
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
go-version: 1.18.x
go-version: 1.20.x
- name: Checkout code
uses: actions/checkout@v3
- name: Run Go Tests with Coverage
run: make test SKIP_ASSET=1
int-test:
name: Integration Tests
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
name: Install Node
with:
fetch-depth: 2
node-version: latest
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v3
with:
node-version: latest
cache: "pnpm"
cache-dependency-path: "**/pnpm-lock.yaml"
- name: Install dependencies
run: pnpm install
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.2.1
- name: Login to DockerHub
uses: docker/login-action@v2.1.0
uses: docker/setup-buildx-action@v2
- name: Build
uses: docker/bake-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build images
run: COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f e2e/docker-compose.yml build --build-arg BUILDKIT_INLINE_CACHE=1
- name: Push images
run: COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f e2e/docker-compose.yml push
- name: Set commit message for push
if: github.event_name == 'push'
run: |
echo "GIT_LOG_MESSAGE<<EOF" >> $GITHUB_ENV
git log -1 --pretty=%B ${GITHUB_SHA} >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
- name: Set commit message for pull request
if: github.event_name == 'pull_request'
run: |
echo "GIT_LOG_MESSAGE<<EOF" >> $GITHUB_ENV
git log -1 --pretty=%B ${{github.event.pull_request.head.sha}} >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
- name: Run tests
run: COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f e2e/docker-compose.yml up --build --force-recreate --exit-code-from cypress
load: true
set: |
*.cache-from=type=gha
*.cache-to=type=gha,mode=max
- name: Run Playwright tests
run: docker compose up --exit-code-from playwright
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
+6 -2
View File
@@ -1,4 +1,4 @@
certs
dist
node_modules
.cache
@@ -6,4 +6,8 @@ static
dozzle
coverage
.pnpm-debug.log
.vscode
coverage.out
.netlify
/test-results/
/playwright-report/
/playwright/.cache/
-1
View File
@@ -1 +0,0 @@
_
-4
View File
@@ -1,4 +0,0 @@
#!/bin/sh
. "$(dirname $0)/_/husky.sh"
pnpm lint-staged
+3 -1
View File
@@ -1 +1,3 @@
shamefully-hoist=true
shared-workspace-lockfile=false
include-workspace-root=true
ignore-workspace-root-check=true
+5
View File
@@ -0,0 +1,5 @@
auto-imports.d.ts
components.d.ts
docs/.vitepress/cache
docs/.vitepress/dist
dist
+1 -1
View File
@@ -1 +1 @@
-r '\.(go)$' -R 'node_modules' -G '*_test.go' -s -- go run main.go --level debug
-r '\.(go)$' -R 'node_modules' -G '\*\_test.go' -s -- go run main.go --level debug --remote-host tcp://64.225.88.189:2376|clashleaders.com
+6
View File
@@ -0,0 +1,6 @@
{
"i18n-ally.localesPaths": ["locales"],
"i18n-ally.keystyle": "nested",
"cSpell.words": ["healthcheck"],
"editor.formatOnSave": true
}
+13 -13
View File
@@ -14,22 +14,22 @@ appearance, race, religion, or sexual identity and orientation.
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
- The use of sexualized language or imagery and unwelcome sexual attention or
advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic
address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
+8 -8
View File
@@ -1,28 +1,28 @@
# Build assets
FROM --platform=$BUILDPLATFORM node:19-alpine as node
RUN npm install -g pnpm
FROM --platform=$BUILDPLATFORM node:20-alpine as node
RUN corepack enable
WORKDIR /build
# Install dependencies from lock file
COPY pnpm-lock.yaml ./
RUN pnpm fetch
COPY pnpm-*.yaml ./
RUN pnpm fetch --ignore-scripts --no-optional
# Copy package.json and install dependencies
COPY package.json ./
RUN pnpm install -r --offline --ignore-scripts
RUN pnpm install --offline --ignore-scripts --no-optional
# Copy assets and translations to build
COPY .* vite.config.ts index.html ./
COPY .* vite.config.ts ./
COPY assets ./assets
COPY locales ./locales
COPY public ./public
# Build assets
RUN pnpm build
FROM --platform=$BUILDPLATFORM golang:1.19.2-alpine AS builder
FROM --platform=$BUILDPLATFORM golang:1.20.7-alpine AS builder
RUN apk add --no-cache ca-certificates && mkdir /dozzle
+1 -1
View File
@@ -31,4 +31,4 @@ dev:
.PHONY: int
int:
docker-compose -f e2e/docker-compose.yml up --build --force-recreate --exit-code-from cypress
docker compose up --build --force-recreate --exit-code-from playwright
+66 -25
View File
@@ -2,7 +2,7 @@
Dozzle is a small lightweight application with a web based interface to monitor Docker logs. It doesnt store any log files. It is for live monitoring of your container logs only.
![Image](https://github.com/amir20/dozzle/blob/master/.github/demo.gif?raw=true)
https://user-images.githubusercontent.com/260667/227634771-9ebbe381-16a8-465a-b28a-450c5cd20c94.mp4
[![Go Report Card](https://goreportcard.com/badge/github.com/amir20/dozzle)](https://goreportcard.com/report/github.com/amir20/dozzle)
[![Docker Pulls](https://img.shields.io/docker/pulls/amir20/dozzle.svg)](https://hub.docker.com/r/amir20/dozzle/)
@@ -37,16 +37,7 @@ The simplest way to use dozzle is to run the docker container. Also, mount the D
Dozzle will be available at [http://localhost:8888/](http://localhost:8888/). You can change `-p 8888:8080` to any port. For example, if you want to view dozzle over port 4040 then you would do `-p 4040:8080`.
### With Docker swarm
docker service create \
--name=dozzle \
--publish=8888:8080 \
--constraint=node.role==manager \
--mount=type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock \
amir20/dozzle:latest
### With Docker compose
### Connecting with Docker compose
version: "3"
services:
@@ -58,8 +49,35 @@ Dozzle will be available at [http://localhost:8888/](http://localhost:8888/). Yo
ports:
- 9999:8080
### Connecting to remote hosts
### Enabling health check
Dozzle supports connecting to multiple remote hosts via `tcp://` using TLS or without. Appropriate certs need to be mounted for Dozzle to be able to successfully connect. At this point, `ssh://` is not supported because Dozzle docker image does not ship with any ssh clients.
To configure remote hosts, `--remote-host` or `DOZZLE_REMOTE_HOST` need to provided and the `pem` files need to be mounted to `/cert` directory. The `/cert` directory expects to have `/certs/{ca,cert,key}.pem` or `/certs/{host}/{ca,cert,key}.pem` in case of multiple hosts.
Below are examples of using `--remote-host` via CLI:
$ docker run -v /var/run/docker.sock:/var/run/docker.sock -v /path/to/certs:/certs -p 8080:8080 amir20/dozzle --remote-host tcp://167.99.1.1:2376
Multiple `--remote-host` flags can be used to specify multiple hosts.
Or to use compose:
version: "3"
services:
dozzle:
image: amir20/dozzle:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /path/to/certs:/certs
ports:
- 8080:8080
environment:
DOZZLE_REMOTE_HOST: tcp://167.99.1.1:2376,tcp://167.99.1.2:2376
You need to make sure appropriate certs are provided in `/certs/167.99.1.1/{ca,cert,key}.pem` and `/certs/167.99.1.2/{ca,cert,key}.pem` for both hosts to work.
### Adding health check
Dozzle doesn't enable healthcheck by default as it adds extra CPU usage. `healthcheck` can be enabled manually.
@@ -81,7 +99,6 @@ Dozzle doesn't enable healthcheck by default as it adds extra CPU usage. `health
retries: 5
start_period: 30s
#### Security
You can control the device Dozzle binds to by passing `--addr` parameter. For example,
@@ -98,7 +115,7 @@ this would then only allow you to view containers with a name starting with "foo
#### Authentication
Dozzle supports a very simple authentication out of the box with just username and password. You should deploy using SSL to keep the credentials safe. See configuration to use `--username` and `--password`.
Dozzle supports a very simple authentication out of the box with just username and password. You should deploy using SSL to keep the credentials safe. See configuration to use `--username` and `--password`. You can also use [docker secrets](https://docs.docker.com/engine/swarm/secrets/) `--usernamefile` and `--passwordfile`.
#### Changing base URL
@@ -119,17 +136,19 @@ If you do not want to be tracked at all, see the `--no-analytics` flag below.
Dozzle follows the [12-factor](https://12factor.net/) model. Configurations can use the CLI flags or environment variables. The table below outlines all supported options and their respective env vars.
| Flag | Env Variable | Default |
| ---------------- | --------------------- | ------- |
| `--addr` | `DOZZLE_ADDR` | `:8080` |
| `--base` | `DOZZLE_BASE` | `/` |
| `--level` | `DOZZLE_LEVEL` | `info` |
| n/a | `DOCKER_API_VERSION` | not set |
| `--tailSize` | `DOZZLE_TAILSIZE` | `300` |
| `--filter` | `DOZZLE_FILTER` | `""` |
| `--username` | `DOZZLE_USERNAME` | `""` |
| `--password` | `DOZZLE_PASSWORD` | `""` |
| `--no-analytics` | `DOZZLE_NO_ANALYTICS` | false |
| Flag | Env Variable | Default |
| ---------------- | ---------------------- | ------- |
| `--addr` | `DOZZLE_ADDR` | `:8080` |
| `--base` | `DOZZLE_BASE` | `/` |
| `--hostname` | `DOZZLE_HOSTNAME` | `""` |
| `--level` | `DOZZLE_LEVEL` | `info` |
| `--filter` | `DOZZLE_FILTER` | `""` |
| `--username` | `DOZZLE_USERNAME` | `""` |
| `--password` | `DOZZLE_PASSWORD` | `""` |
| `--usernamefile` | `DOZZLE_USERNAME_FILE` | `""` |
| `--passwordfile` | `DOZZLE_PASSWORD_FILE` | `""` |
| `--no-analytics` | `DOZZLE_NO_ANALYTICS` | false |
| `--remote-host` | `DOZZLE_REMOTE_HOST` | |
## Troubleshooting and FAQs
@@ -181,6 +200,28 @@ Dozzle has a [special route](https://github.com/amir20/dozzle/blob/master/assets
</details>
<details>
<summary>I installed Dozzle but memory consumption doesn't show up!</summary>
_This is an issue specific to ARM devices_
Dozzle uses the Docker API to gather information about the containers' memory usage. If the memory usage is not showing up, then it is likely that the Docker API is not returning the memory usage.
You can verify this by running `docker info`, and you should see the following:
```
WARNING: No memory limit support
WARNING: No swap limit support
```
In this case, you'll need to add the following line to your `/boot/cmdline.txt` file and reboot your device.
```
cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1
```
</details>
## License
[MIT](LICENSE)
+19 -1
View File
@@ -21,7 +21,25 @@ func SendStartEvent(se StartEvent) error {
},
}
jsonValue, err := json.Marshal(postBody)
return doRequest(postBody)
}
func SendRequestEvent(re RequestEvent) error {
postBody := map[string]interface{}{
"client_id": re.ClientId,
"events": []map[string]interface{}{
{
"name": "request",
"params": re,
},
},
}
return doRequest(postBody)
}
func doRequest(body map[string]interface{}) error {
jsonValue, err := json.Marshal(body)
if err != nil {
return err
}
+14 -7
View File
@@ -1,11 +1,18 @@
package analytics
type StartEvent struct {
ClientId string `json:"-"`
Version string `json:"version"`
FilterLength int `json:"filterLength"`
CustomAddress bool `json:"customAddress"`
CustomBase bool `json:"customBase"`
TailSize int `json:"tailSize"`
Protected bool `json:"protected"`
ClientId string `json:"-"`
Version string `json:"version"`
FilterLength int `json:"filterLength"`
RemoteHostLength int `json:"remoteHostLength"`
CustomAddress bool `json:"customAddress"`
CustomBase bool `json:"customBase"`
Protected bool `json:"protected"`
HasHostname bool `json:"hasHostname"`
}
type RequestEvent struct {
ClientId string `json:"-"`
TotalContainers int `json:"totalContainers"`
RunningContainers int `json:"runningContainers"`
}
+391 -5
View File
@@ -1,8 +1,12 @@
// Generated by 'unplugin-auto-import'
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
export {}
declare global {
const $$: typeof import('vue/macros')['$$']
const $: typeof import('vue/macros')['$']
const $$: typeof import('vue/macros')['$$']
const $computed: typeof import('vue/macros')['$computed']
const $customRef: typeof import('vue/macros')['$customRef']
const $ref: typeof import('vue/macros')['$ref']
@@ -14,6 +18,7 @@ declare global {
const arrayEquals: typeof import('./utils/index')['arrayEquals']
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
const collapseNav: typeof import('./composables/settings')['collapseNav']
const computed: typeof import('vue')['computed']
const computedAsync: typeof import('@vueuse/core')['computedAsync']
const computedEager: typeof import('@vueuse/core')['computedEager']
@@ -28,7 +33,9 @@ declare global {
const createInjectionState: typeof import('@vueuse/core')['createInjectionState']
const createPinia: typeof import('pinia')['createPinia']
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate']
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise']
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
const customRef: typeof import('vue')['customRef']
const debouncedRef: typeof import('@vueuse/core')['debouncedRef']
@@ -45,6 +52,7 @@ declare global {
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const getDeep: typeof import('./utils/index')['getDeep']
const globalShowPopup: typeof import('./composables/popup')['globalShowPopup']
const h: typeof import('vue')['h']
const hourStyle: typeof import('./composables/settings')['hourStyle']
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
@@ -68,6 +76,8 @@ declare global {
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
@@ -84,7 +94,7 @@ declare global {
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
const persistentVisibleKeys: typeof import('./utils/index')['persistentVisibleKeys']
const persistentVisibleKeys: typeof import('./composables/storage')['persistentVisibleKeys']
const provide: typeof import('vue')['provide']
const reactify: typeof import('@vueuse/core')['reactify']
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
@@ -103,6 +113,7 @@ declare global {
const resolveRef: typeof import('@vueuse/core')['resolveRef']
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
const search: typeof import('./composables/settings')['search']
const sessionHost: typeof import('./composables/storage')['sessionHost']
const setActivePinia: typeof import('pinia')['setActivePinia']
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
const setTitle: typeof import('./composables/title')['setTitle']
@@ -111,6 +122,7 @@ declare global {
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const showAllContainers: typeof import('./composables/settings')['showAllContainers']
const showStd: typeof import('./composables/settings')['showStd']
const showTimestamp: typeof import('./composables/settings')['showTimestamp']
const size: typeof import('./composables/settings')['size']
const smallerScrollbars: typeof import('./composables/settings')['smallerScrollbars']
@@ -126,6 +138,7 @@ declare global {
const toReactive: typeof import('@vueuse/core')['toReactive']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
@@ -136,14 +149,19 @@ declare global {
const unrefElement: typeof import('@vueuse/core')['unrefElement']
const until: typeof import('@vueuse/core')['until']
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
const useAnimate: typeof import('@vueuse/core')['useAnimate']
const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery']
const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter']
const useArrayFind: typeof import('@vueuse/core')['useArrayFind']
const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex']
const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast']
const useArrayIncludes: typeof import('@vueuse/core')['useArrayIncludes']
const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin']
const useArrayMap: typeof import('@vueuse/core')['useArrayMap']
const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce']
const useArraySome: typeof import('@vueuse/core')['useArraySome']
const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique']
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
const useAttrs: typeof import('vue')['useAttrs']
@@ -186,6 +204,7 @@ declare global {
const useEventBus: typeof import('@vueuse/core')['useEventBus']
const useEventListener: typeof import('@vueuse/core')['useEventListener']
const useEventSource: typeof import('@vueuse/core')['useEventSource']
const useExponentialMovingAverage: typeof import('./utils/index')['useExponentialMovingAverage']
const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper']
const useFavicon: typeof import('@vueuse/core')['useFavicon']
const useFetch: typeof import('@vueuse/core')['useFetch']
@@ -207,6 +226,7 @@ declare global {
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
const useLink: typeof import('vue-router')['useLink']
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
const useLogStream: typeof import('./composables/eventsource')['useLogStream']
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
@@ -228,14 +248,18 @@ declare global {
const useOnline: typeof import('@vueuse/core')['useOnline']
const usePageLeave: typeof import('@vueuse/core')['usePageLeave']
const useParallax: typeof import('@vueuse/core')['useParallax']
const useParentElement: typeof import('@vueuse/core')['useParentElement']
const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver']
const usePermission: typeof import('@vueuse/core')['usePermission']
const usePointer: typeof import('@vueuse/core')['usePointer']
const usePointerLock: typeof import('@vueuse/core')['usePointerLock']
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast']
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
const usePrevious: typeof import('@vueuse/core')['usePrevious']
const useRafFn: typeof import('@vueuse/core')['useRafFn']
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
@@ -247,6 +271,7 @@ declare global {
const useScroll: typeof import('@vueuse/core')['useScroll']
const useScrollLock: typeof import('@vueuse/core')['useScrollLock']
const useSearchFilter: typeof import('./composables/search')['useSearchFilter']
const useSeoMeta: typeof import('@vueuse/head')['useSeoMeta']
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
const useShare: typeof import('@vueuse/core')['useShare']
const useSlots: typeof import('vue')['useSlots']
@@ -295,8 +320,10 @@ declare global {
const watchArray: typeof import('@vueuse/core')['watchArray']
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
const watchDebounced: typeof import('@vueuse/core')['watchDebounced']
const watchDeep: typeof import('@vueuse/core')['watchDeep']
const watchEffect: typeof import('vue')['watchEffect']
const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable']
const watchImmediate: typeof import('@vueuse/core')['watchImmediate']
const watchOnce: typeof import('@vueuse/core')['watchOnce']
const watchPausable: typeof import('@vueuse/core')['watchPausable']
const watchPostEffect: typeof import('vue')['watchPostEffect']
@@ -306,9 +333,14 @@ declare global {
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
const whenever: typeof import('@vueuse/core')['whenever']
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode } from 'vue'
}
// for vue template auto import
import { UnwrapRef } from 'vue'
declare module '@vue/runtime-core' {
declare module 'vue' {
interface ComponentCustomProperties {
readonly $$: UnwrapRef<typeof import('vue/macros')['$$']>
readonly $: UnwrapRef<typeof import('vue/macros')['$']>
@@ -323,6 +355,7 @@ declare module '@vue/runtime-core' {
readonly arrayEquals: UnwrapRef<typeof import('./utils/index')['arrayEquals']>
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
readonly collapseNav: UnwrapRef<typeof import('./composables/settings')['collapseNav']>
readonly computed: UnwrapRef<typeof import('vue')['computed']>
readonly computedAsync: UnwrapRef<typeof import('@vueuse/core')['computedAsync']>
readonly computedEager: UnwrapRef<typeof import('@vueuse/core')['computedEager']>
@@ -337,7 +370,9 @@ declare module '@vue/runtime-core' {
readonly createInjectionState: UnwrapRef<typeof import('@vueuse/core')['createInjectionState']>
readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
readonly createReactiveFn: UnwrapRef<typeof import('@vueuse/core')['createReactiveFn']>
readonly createReusableTemplate: UnwrapRef<typeof import('@vueuse/core')['createReusableTemplate']>
readonly createSharedComposable: UnwrapRef<typeof import('@vueuse/core')['createSharedComposable']>
readonly createTemplatePromise: UnwrapRef<typeof import('@vueuse/core')['createTemplatePromise']>
readonly createUnrefFn: UnwrapRef<typeof import('@vueuse/core')['createUnrefFn']>
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
readonly debouncedRef: UnwrapRef<typeof import('@vueuse/core')['debouncedRef']>
@@ -354,6 +389,7 @@ declare module '@vue/runtime-core' {
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
readonly getDeep: UnwrapRef<typeof import('./utils/index')['getDeep']>
readonly globalShowPopup: UnwrapRef<typeof import('./composables/popup')['globalShowPopup']>
readonly h: UnwrapRef<typeof import('vue')['h']>
readonly hourStyle: UnwrapRef<typeof import('./composables/settings')['hourStyle']>
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
@@ -377,6 +413,8 @@ declare module '@vue/runtime-core' {
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router')['onBeforeRouteLeave']>
readonly onBeforeRouteUpdate: UnwrapRef<typeof import('vue-router')['onBeforeRouteUpdate']>
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
readonly onClickOutside: UnwrapRef<typeof import('@vueuse/core')['onClickOutside']>
@@ -393,7 +431,7 @@ declare module '@vue/runtime-core' {
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
readonly persistentVisibleKeys: UnwrapRef<typeof import('./utils/index')['persistentVisibleKeys']>
readonly persistentVisibleKeys: UnwrapRef<typeof import('./composables/storage')['persistentVisibleKeys']>
readonly provide: UnwrapRef<typeof import('vue')['provide']>
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
@@ -412,6 +450,7 @@ declare module '@vue/runtime-core' {
readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
readonly resolveUnref: UnwrapRef<typeof import('@vueuse/core')['resolveUnref']>
readonly search: UnwrapRef<typeof import('./composables/settings')['search']>
readonly sessionHost: UnwrapRef<typeof import('./composables/storage')['sessionHost']>
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
readonly setTitle: UnwrapRef<typeof import('./composables/title')['setTitle']>
@@ -420,6 +459,7 @@ declare module '@vue/runtime-core' {
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
readonly showAllContainers: UnwrapRef<typeof import('./composables/settings')['showAllContainers']>
readonly showStd: UnwrapRef<typeof import('./composables/settings')['showStd']>
readonly showTimestamp: UnwrapRef<typeof import('./composables/settings')['showTimestamp']>
readonly size: UnwrapRef<typeof import('./composables/settings')['size']>
readonly smallerScrollbars: UnwrapRef<typeof import('./composables/settings')['smallerScrollbars']>
@@ -435,6 +475,7 @@ declare module '@vue/runtime-core' {
readonly toReactive: UnwrapRef<typeof import('@vueuse/core')['toReactive']>
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
readonly tryOnBeforeMount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeMount']>
readonly tryOnBeforeUnmount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeUnmount']>
@@ -445,14 +486,19 @@ declare module '@vue/runtime-core' {
readonly unrefElement: UnwrapRef<typeof import('@vueuse/core')['unrefElement']>
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 useArrayDifference: UnwrapRef<typeof import('@vueuse/core')['useArrayDifference']>
readonly useArrayEvery: UnwrapRef<typeof import('@vueuse/core')['useArrayEvery']>
readonly useArrayFilter: UnwrapRef<typeof import('@vueuse/core')['useArrayFilter']>
readonly useArrayFind: UnwrapRef<typeof import('@vueuse/core')['useArrayFind']>
readonly useArrayFindIndex: UnwrapRef<typeof import('@vueuse/core')['useArrayFindIndex']>
readonly useArrayFindLast: UnwrapRef<typeof import('@vueuse/core')['useArrayFindLast']>
readonly useArrayIncludes: UnwrapRef<typeof import('@vueuse/core')['useArrayIncludes']>
readonly useArrayJoin: UnwrapRef<typeof import('@vueuse/core')['useArrayJoin']>
readonly useArrayMap: UnwrapRef<typeof import('@vueuse/core')['useArrayMap']>
readonly useArrayReduce: UnwrapRef<typeof import('@vueuse/core')['useArrayReduce']>
readonly useArraySome: UnwrapRef<typeof import('@vueuse/core')['useArraySome']>
readonly useArrayUnique: UnwrapRef<typeof import('@vueuse/core')['useArrayUnique']>
readonly useAsyncQueue: UnwrapRef<typeof import('@vueuse/core')['useAsyncQueue']>
readonly useAsyncState: UnwrapRef<typeof import('@vueuse/core')['useAsyncState']>
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
@@ -495,6 +541,7 @@ declare module '@vue/runtime-core' {
readonly useEventBus: UnwrapRef<typeof import('@vueuse/core')['useEventBus']>
readonly useEventListener: UnwrapRef<typeof import('@vueuse/core')['useEventListener']>
readonly useEventSource: UnwrapRef<typeof import('@vueuse/core')['useEventSource']>
readonly useExponentialMovingAverage: UnwrapRef<typeof import('./utils/index')['useExponentialMovingAverage']>
readonly useEyeDropper: UnwrapRef<typeof import('@vueuse/core')['useEyeDropper']>
readonly useFavicon: UnwrapRef<typeof import('@vueuse/core')['useFavicon']>
readonly useFetch: UnwrapRef<typeof import('@vueuse/core')['useFetch']>
@@ -516,6 +563,7 @@ declare module '@vue/runtime-core' {
readonly useIntervalFn: UnwrapRef<typeof import('@vueuse/core')['useIntervalFn']>
readonly useKeyModifier: UnwrapRef<typeof import('@vueuse/core')['useKeyModifier']>
readonly useLastChanged: UnwrapRef<typeof import('@vueuse/core')['useLastChanged']>
readonly useLink: UnwrapRef<typeof import('vue-router')['useLink']>
readonly useLocalStorage: UnwrapRef<typeof import('@vueuse/core')['useLocalStorage']>
readonly useLogStream: UnwrapRef<typeof import('./composables/eventsource')['useLogStream']>
readonly useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']>
@@ -537,14 +585,18 @@ declare module '@vue/runtime-core' {
readonly useOnline: UnwrapRef<typeof import('@vueuse/core')['useOnline']>
readonly usePageLeave: UnwrapRef<typeof import('@vueuse/core')['usePageLeave']>
readonly useParallax: UnwrapRef<typeof import('@vueuse/core')['useParallax']>
readonly useParentElement: UnwrapRef<typeof import('@vueuse/core')['useParentElement']>
readonly usePerformanceObserver: UnwrapRef<typeof import('@vueuse/core')['usePerformanceObserver']>
readonly usePermission: UnwrapRef<typeof import('@vueuse/core')['usePermission']>
readonly usePointer: UnwrapRef<typeof import('@vueuse/core')['usePointer']>
readonly usePointerLock: UnwrapRef<typeof import('@vueuse/core')['usePointerLock']>
readonly usePointerSwipe: UnwrapRef<typeof import('@vueuse/core')['usePointerSwipe']>
readonly usePreferredColorScheme: UnwrapRef<typeof import('@vueuse/core')['usePreferredColorScheme']>
readonly usePreferredContrast: UnwrapRef<typeof import('@vueuse/core')['usePreferredContrast']>
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 usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
readonly useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
@@ -556,6 +608,7 @@ declare module '@vue/runtime-core' {
readonly useScroll: UnwrapRef<typeof import('@vueuse/core')['useScroll']>
readonly useScrollLock: UnwrapRef<typeof import('@vueuse/core')['useScrollLock']>
readonly useSearchFilter: UnwrapRef<typeof import('./composables/search')['useSearchFilter']>
readonly useSeoMeta: UnwrapRef<typeof import('@vueuse/head')['useSeoMeta']>
readonly useSessionStorage: UnwrapRef<typeof import('@vueuse/core')['useSessionStorage']>
readonly useShare: UnwrapRef<typeof import('@vueuse/core')['useShare']>
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
@@ -604,8 +657,341 @@ declare module '@vue/runtime-core' {
readonly watchArray: UnwrapRef<typeof import('@vueuse/core')['watchArray']>
readonly watchAtMost: UnwrapRef<typeof import('@vueuse/core')['watchAtMost']>
readonly watchDebounced: UnwrapRef<typeof import('@vueuse/core')['watchDebounced']>
readonly watchDeep: UnwrapRef<typeof import('@vueuse/core')['watchDeep']>
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
readonly watchIgnorable: UnwrapRef<typeof import('@vueuse/core')['watchIgnorable']>
readonly watchImmediate: UnwrapRef<typeof import('@vueuse/core')['watchImmediate']>
readonly watchOnce: UnwrapRef<typeof import('@vueuse/core')['watchOnce']>
readonly watchPausable: UnwrapRef<typeof import('@vueuse/core')['watchPausable']>
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
readonly watchThrottled: UnwrapRef<typeof import('@vueuse/core')['watchThrottled']>
readonly watchTriggerable: UnwrapRef<typeof import('@vueuse/core')['watchTriggerable']>
readonly watchWithFilter: UnwrapRef<typeof import('@vueuse/core')['watchWithFilter']>
readonly whenever: UnwrapRef<typeof import('@vueuse/core')['whenever']>
}
}
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
readonly $$: UnwrapRef<typeof import('vue/macros')['$$']>
readonly $: UnwrapRef<typeof import('vue/macros')['$']>
readonly $computed: UnwrapRef<typeof import('vue/macros')['$computed']>
readonly $customRef: UnwrapRef<typeof import('vue/macros')['$customRef']>
readonly $ref: UnwrapRef<typeof import('vue/macros')['$ref']>
readonly $shallowRef: UnwrapRef<typeof import('vue/macros')['$shallowRef']>
readonly $toRef: UnwrapRef<typeof import('vue/macros')['$toRef']>
readonly DEFAULT_SETTINGS: UnwrapRef<typeof import('./composables/settings')['DEFAULT_SETTINGS']>
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
readonly arrayEquals: UnwrapRef<typeof import('./utils/index')['arrayEquals']>
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
readonly collapseNav: UnwrapRef<typeof import('./composables/settings')['collapseNav']>
readonly computed: UnwrapRef<typeof import('vue')['computed']>
readonly computedAsync: UnwrapRef<typeof import('@vueuse/core')['computedAsync']>
readonly computedEager: UnwrapRef<typeof import('@vueuse/core')['computedEager']>
readonly computedInject: UnwrapRef<typeof import('@vueuse/core')['computedInject']>
readonly computedWithControl: UnwrapRef<typeof import('@vueuse/core')['computedWithControl']>
readonly config: UnwrapRef<typeof import('./stores/config')['default']>
readonly controlledComputed: UnwrapRef<typeof import('@vueuse/core')['controlledComputed']>
readonly controlledRef: UnwrapRef<typeof import('@vueuse/core')['controlledRef']>
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
readonly createEventHook: UnwrapRef<typeof import('@vueuse/core')['createEventHook']>
readonly createGlobalState: UnwrapRef<typeof import('@vueuse/core')['createGlobalState']>
readonly createInjectionState: UnwrapRef<typeof import('@vueuse/core')['createInjectionState']>
readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
readonly createReactiveFn: UnwrapRef<typeof import('@vueuse/core')['createReactiveFn']>
readonly createReusableTemplate: UnwrapRef<typeof import('@vueuse/core')['createReusableTemplate']>
readonly createSharedComposable: UnwrapRef<typeof import('@vueuse/core')['createSharedComposable']>
readonly createTemplatePromise: UnwrapRef<typeof import('@vueuse/core')['createTemplatePromise']>
readonly createUnrefFn: UnwrapRef<typeof import('@vueuse/core')['createUnrefFn']>
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
readonly debouncedRef: UnwrapRef<typeof import('@vueuse/core')['debouncedRef']>
readonly debouncedWatch: UnwrapRef<typeof import('@vueuse/core')['debouncedWatch']>
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
readonly defineStore: UnwrapRef<typeof import('pinia')['defineStore']>
readonly eagerComputed: UnwrapRef<typeof import('@vueuse/core')['eagerComputed']>
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
readonly extendRef: UnwrapRef<typeof import('@vueuse/core')['extendRef']>
readonly flattenJSON: UnwrapRef<typeof import('./utils/index')['flattenJSON']>
readonly formatBytes: UnwrapRef<typeof import('./utils/index')['formatBytes']>
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
readonly getDeep: UnwrapRef<typeof import('./utils/index')['getDeep']>
readonly globalShowPopup: UnwrapRef<typeof import('./composables/popup')['globalShowPopup']>
readonly h: UnwrapRef<typeof import('vue')['h']>
readonly hourStyle: UnwrapRef<typeof import('./composables/settings')['hourStyle']>
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
readonly inject: UnwrapRef<typeof import('vue')['inject']>
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
readonly isMobile: UnwrapRef<typeof import('./composables/media')['isMobile']>
readonly isObject: UnwrapRef<typeof import('./utils/index')['isObject']>
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
readonly lightTheme: UnwrapRef<typeof import('./composables/settings')['lightTheme']>
readonly makeDestructurable: UnwrapRef<typeof import('@vueuse/core')['makeDestructurable']>
readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>
readonly mapState: UnwrapRef<typeof import('pinia')['mapState']>
readonly mapStores: UnwrapRef<typeof import('pinia')['mapStores']>
readonly mapWritableState: UnwrapRef<typeof import('pinia')['mapWritableState']>
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
readonly menuWidth: UnwrapRef<typeof import('./composables/settings')['menuWidth']>
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router')['onBeforeRouteLeave']>
readonly onBeforeRouteUpdate: UnwrapRef<typeof import('vue-router')['onBeforeRouteUpdate']>
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
readonly onClickOutside: UnwrapRef<typeof import('@vueuse/core')['onClickOutside']>
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
readonly onKeyStroke: UnwrapRef<typeof import('@vueuse/core')['onKeyStroke']>
readonly onLongPress: UnwrapRef<typeof import('@vueuse/core')['onLongPress']>
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
readonly onStartTyping: UnwrapRef<typeof import('@vueuse/core')['onStartTyping']>
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
readonly persistentVisibleKeys: UnwrapRef<typeof import('./composables/storage')['persistentVisibleKeys']>
readonly provide: UnwrapRef<typeof import('vue')['provide']>
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
readonly reactiveComputed: UnwrapRef<typeof import('@vueuse/core')['reactiveComputed']>
readonly reactiveOmit: UnwrapRef<typeof import('@vueuse/core')['reactiveOmit']>
readonly reactivePick: UnwrapRef<typeof import('@vueuse/core')['reactivePick']>
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
readonly ref: UnwrapRef<typeof import('vue')['ref']>
readonly refAutoReset: UnwrapRef<typeof import('@vueuse/core')['refAutoReset']>
readonly refDebounced: UnwrapRef<typeof import('@vueuse/core')['refDebounced']>
readonly refDefault: UnwrapRef<typeof import('@vueuse/core')['refDefault']>
readonly refThrottled: UnwrapRef<typeof import('@vueuse/core')['refThrottled']>
readonly refWithControl: UnwrapRef<typeof import('@vueuse/core')['refWithControl']>
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
readonly resolveUnref: UnwrapRef<typeof import('@vueuse/core')['resolveUnref']>
readonly search: UnwrapRef<typeof import('./composables/settings')['search']>
readonly sessionHost: UnwrapRef<typeof import('./composables/storage')['sessionHost']>
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
readonly setTitle: UnwrapRef<typeof import('./composables/title')['setTitle']>
readonly settings: UnwrapRef<typeof import('./composables/settings')['settings']>
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
readonly showAllContainers: UnwrapRef<typeof import('./composables/settings')['showAllContainers']>
readonly showStd: UnwrapRef<typeof import('./composables/settings')['showStd']>
readonly showTimestamp: UnwrapRef<typeof import('./composables/settings')['showTimestamp']>
readonly size: UnwrapRef<typeof import('./composables/settings')['size']>
readonly smallerScrollbars: UnwrapRef<typeof import('./composables/settings')['smallerScrollbars']>
readonly softWrap: UnwrapRef<typeof import('./composables/settings')['softWrap']>
readonly storeToRefs: UnwrapRef<typeof import('pinia')['storeToRefs']>
readonly stripVersion: UnwrapRef<typeof import('./utils/index')['stripVersion']>
readonly syncRef: UnwrapRef<typeof import('@vueuse/core')['syncRef']>
readonly syncRefs: UnwrapRef<typeof import('@vueuse/core')['syncRefs']>
readonly templateRef: UnwrapRef<typeof import('@vueuse/core')['templateRef']>
readonly throttledRef: UnwrapRef<typeof import('@vueuse/core')['throttledRef']>
readonly throttledWatch: UnwrapRef<typeof import('@vueuse/core')['throttledWatch']>
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
readonly toReactive: UnwrapRef<typeof import('@vueuse/core')['toReactive']>
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
readonly tryOnBeforeMount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeMount']>
readonly tryOnBeforeUnmount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeUnmount']>
readonly tryOnMounted: UnwrapRef<typeof import('@vueuse/core')['tryOnMounted']>
readonly tryOnScopeDispose: UnwrapRef<typeof import('@vueuse/core')['tryOnScopeDispose']>
readonly tryOnUnmounted: UnwrapRef<typeof import('@vueuse/core')['tryOnUnmounted']>
readonly unref: UnwrapRef<typeof import('vue')['unref']>
readonly unrefElement: UnwrapRef<typeof import('@vueuse/core')['unrefElement']>
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 useArrayDifference: UnwrapRef<typeof import('@vueuse/core')['useArrayDifference']>
readonly useArrayEvery: UnwrapRef<typeof import('@vueuse/core')['useArrayEvery']>
readonly useArrayFilter: UnwrapRef<typeof import('@vueuse/core')['useArrayFilter']>
readonly useArrayFind: UnwrapRef<typeof import('@vueuse/core')['useArrayFind']>
readonly useArrayFindIndex: UnwrapRef<typeof import('@vueuse/core')['useArrayFindIndex']>
readonly useArrayFindLast: UnwrapRef<typeof import('@vueuse/core')['useArrayFindLast']>
readonly useArrayIncludes: UnwrapRef<typeof import('@vueuse/core')['useArrayIncludes']>
readonly useArrayJoin: UnwrapRef<typeof import('@vueuse/core')['useArrayJoin']>
readonly useArrayMap: UnwrapRef<typeof import('@vueuse/core')['useArrayMap']>
readonly useArrayReduce: UnwrapRef<typeof import('@vueuse/core')['useArrayReduce']>
readonly useArraySome: UnwrapRef<typeof import('@vueuse/core')['useArraySome']>
readonly useArrayUnique: UnwrapRef<typeof import('@vueuse/core')['useArrayUnique']>
readonly useAsyncQueue: UnwrapRef<typeof import('@vueuse/core')['useAsyncQueue']>
readonly useAsyncState: UnwrapRef<typeof import('@vueuse/core')['useAsyncState']>
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
readonly useBase64: UnwrapRef<typeof import('@vueuse/core')['useBase64']>
readonly useBattery: UnwrapRef<typeof import('@vueuse/core')['useBattery']>
readonly useBluetooth: UnwrapRef<typeof import('@vueuse/core')['useBluetooth']>
readonly useBreakpoints: UnwrapRef<typeof import('@vueuse/core')['useBreakpoints']>
readonly useBroadcastChannel: UnwrapRef<typeof import('@vueuse/core')['useBroadcastChannel']>
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
readonly useContainerStore: UnwrapRef<typeof import('./stores/container')['useContainerStore']>
readonly useCounter: UnwrapRef<typeof import('@vueuse/core')['useCounter']>
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
readonly useCssVar: UnwrapRef<typeof import('@vueuse/core')['useCssVar']>
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
readonly useCurrentElement: UnwrapRef<typeof import('@vueuse/core')['useCurrentElement']>
readonly useCycleList: UnwrapRef<typeof import('@vueuse/core')['useCycleList']>
readonly useDark: UnwrapRef<typeof import('@vueuse/core')['useDark']>
readonly useDateFormat: UnwrapRef<typeof import('@vueuse/core')['useDateFormat']>
readonly useDebounce: UnwrapRef<typeof import('@vueuse/core')['useDebounce']>
readonly useDebounceFn: UnwrapRef<typeof import('@vueuse/core')['useDebounceFn']>
readonly useDebouncedRefHistory: UnwrapRef<typeof import('@vueuse/core')['useDebouncedRefHistory']>
readonly useDeviceMotion: UnwrapRef<typeof import('@vueuse/core')['useDeviceMotion']>
readonly useDeviceOrientation: UnwrapRef<typeof import('@vueuse/core')['useDeviceOrientation']>
readonly useDevicePixelRatio: UnwrapRef<typeof import('@vueuse/core')['useDevicePixelRatio']>
readonly useDevicesList: UnwrapRef<typeof import('@vueuse/core')['useDevicesList']>
readonly useDisplayMedia: UnwrapRef<typeof import('@vueuse/core')['useDisplayMedia']>
readonly useDocumentVisibility: UnwrapRef<typeof import('@vueuse/core')['useDocumentVisibility']>
readonly useDraggable: UnwrapRef<typeof import('@vueuse/core')['useDraggable']>
readonly useDropZone: UnwrapRef<typeof import('@vueuse/core')['useDropZone']>
readonly useElementBounding: UnwrapRef<typeof import('@vueuse/core')['useElementBounding']>
readonly useElementByPoint: UnwrapRef<typeof import('@vueuse/core')['useElementByPoint']>
readonly useElementHover: UnwrapRef<typeof import('@vueuse/core')['useElementHover']>
readonly useElementSize: UnwrapRef<typeof import('@vueuse/core')['useElementSize']>
readonly useElementVisibility: UnwrapRef<typeof import('@vueuse/core')['useElementVisibility']>
readonly useEventBus: UnwrapRef<typeof import('@vueuse/core')['useEventBus']>
readonly useEventListener: UnwrapRef<typeof import('@vueuse/core')['useEventListener']>
readonly useEventSource: UnwrapRef<typeof import('@vueuse/core')['useEventSource']>
readonly useExponentialMovingAverage: UnwrapRef<typeof import('./utils/index')['useExponentialMovingAverage']>
readonly useEyeDropper: UnwrapRef<typeof import('@vueuse/core')['useEyeDropper']>
readonly useFavicon: UnwrapRef<typeof import('@vueuse/core')['useFavicon']>
readonly useFetch: UnwrapRef<typeof import('@vueuse/core')['useFetch']>
readonly useFileDialog: UnwrapRef<typeof import('@vueuse/core')['useFileDialog']>
readonly useFileSystemAccess: UnwrapRef<typeof import('@vueuse/core')['useFileSystemAccess']>
readonly useFocus: UnwrapRef<typeof import('@vueuse/core')['useFocus']>
readonly useFocusWithin: UnwrapRef<typeof import('@vueuse/core')['useFocusWithin']>
readonly useFps: UnwrapRef<typeof import('@vueuse/core')['useFps']>
readonly useFullscreen: UnwrapRef<typeof import('@vueuse/core')['useFullscreen']>
readonly useGamepad: UnwrapRef<typeof import('@vueuse/core')['useGamepad']>
readonly useGeolocation: UnwrapRef<typeof import('@vueuse/core')['useGeolocation']>
readonly useHead: UnwrapRef<typeof import('@vueuse/head')['useHead']>
readonly useI18n: UnwrapRef<typeof import('vue-i18n')['useI18n']>
readonly useIdle: UnwrapRef<typeof import('@vueuse/core')['useIdle']>
readonly useImage: UnwrapRef<typeof import('@vueuse/core')['useImage']>
readonly useInfiniteScroll: UnwrapRef<typeof import('@vueuse/core')['useInfiniteScroll']>
readonly useIntersectionObserver: UnwrapRef<typeof import('@vueuse/core')['useIntersectionObserver']>
readonly useInterval: UnwrapRef<typeof import('@vueuse/core')['useInterval']>
readonly useIntervalFn: UnwrapRef<typeof import('@vueuse/core')['useIntervalFn']>
readonly useKeyModifier: UnwrapRef<typeof import('@vueuse/core')['useKeyModifier']>
readonly useLastChanged: UnwrapRef<typeof import('@vueuse/core')['useLastChanged']>
readonly useLink: UnwrapRef<typeof import('vue-router')['useLink']>
readonly useLocalStorage: UnwrapRef<typeof import('@vueuse/core')['useLocalStorage']>
readonly useLogStream: UnwrapRef<typeof import('./composables/eventsource')['useLogStream']>
readonly useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']>
readonly useManualRefHistory: UnwrapRef<typeof import('@vueuse/core')['useManualRefHistory']>
readonly useMediaControls: UnwrapRef<typeof import('@vueuse/core')['useMediaControls']>
readonly useMediaQuery: UnwrapRef<typeof import('@vueuse/core')['useMediaQuery']>
readonly useMemoize: UnwrapRef<typeof import('@vueuse/core')['useMemoize']>
readonly useMemory: UnwrapRef<typeof import('@vueuse/core')['useMemory']>
readonly useMounted: UnwrapRef<typeof import('@vueuse/core')['useMounted']>
readonly useMouse: UnwrapRef<typeof import('@vueuse/core')['useMouse']>
readonly useMouseInElement: UnwrapRef<typeof import('@vueuse/core')['useMouseInElement']>
readonly useMousePressed: UnwrapRef<typeof import('@vueuse/core')['useMousePressed']>
readonly useMutationObserver: UnwrapRef<typeof import('@vueuse/core')['useMutationObserver']>
readonly useNavigatorLanguage: UnwrapRef<typeof import('@vueuse/core')['useNavigatorLanguage']>
readonly useNetwork: UnwrapRef<typeof import('@vueuse/core')['useNetwork']>
readonly useNow: UnwrapRef<typeof import('@vueuse/core')['useNow']>
readonly useObjectUrl: UnwrapRef<typeof import('@vueuse/core')['useObjectUrl']>
readonly useOffsetPagination: UnwrapRef<typeof import('@vueuse/core')['useOffsetPagination']>
readonly useOnline: UnwrapRef<typeof import('@vueuse/core')['useOnline']>
readonly usePageLeave: UnwrapRef<typeof import('@vueuse/core')['usePageLeave']>
readonly useParallax: UnwrapRef<typeof import('@vueuse/core')['useParallax']>
readonly useParentElement: UnwrapRef<typeof import('@vueuse/core')['useParentElement']>
readonly usePerformanceObserver: UnwrapRef<typeof import('@vueuse/core')['usePerformanceObserver']>
readonly usePermission: UnwrapRef<typeof import('@vueuse/core')['usePermission']>
readonly usePointer: UnwrapRef<typeof import('@vueuse/core')['usePointer']>
readonly usePointerLock: UnwrapRef<typeof import('@vueuse/core')['usePointerLock']>
readonly usePointerSwipe: UnwrapRef<typeof import('@vueuse/core')['usePointerSwipe']>
readonly usePreferredColorScheme: UnwrapRef<typeof import('@vueuse/core')['usePreferredColorScheme']>
readonly usePreferredContrast: UnwrapRef<typeof import('@vueuse/core')['usePreferredContrast']>
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 usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
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 useScreenOrientation: UnwrapRef<typeof import('@vueuse/core')['useScreenOrientation']>
readonly useScreenSafeArea: UnwrapRef<typeof import('@vueuse/core')['useScreenSafeArea']>
readonly useScriptTag: UnwrapRef<typeof import('@vueuse/core')['useScriptTag']>
readonly useScroll: UnwrapRef<typeof import('@vueuse/core')['useScroll']>
readonly useScrollLock: UnwrapRef<typeof import('@vueuse/core')['useScrollLock']>
readonly useSearchFilter: UnwrapRef<typeof import('./composables/search')['useSearchFilter']>
readonly useSeoMeta: UnwrapRef<typeof import('@vueuse/head')['useSeoMeta']>
readonly useSessionStorage: UnwrapRef<typeof import('@vueuse/core')['useSessionStorage']>
readonly useShare: UnwrapRef<typeof import('@vueuse/core')['useShare']>
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
readonly useSorted: UnwrapRef<typeof import('@vueuse/core')['useSorted']>
readonly useSpeechRecognition: UnwrapRef<typeof import('@vueuse/core')['useSpeechRecognition']>
readonly useSpeechSynthesis: UnwrapRef<typeof import('@vueuse/core')['useSpeechSynthesis']>
readonly useStepper: UnwrapRef<typeof import('@vueuse/core')['useStepper']>
readonly useStorage: UnwrapRef<typeof import('@vueuse/core')['useStorage']>
readonly useStorageAsync: UnwrapRef<typeof import('@vueuse/core')['useStorageAsync']>
readonly useStyleTag: UnwrapRef<typeof import('@vueuse/core')['useStyleTag']>
readonly useSupported: UnwrapRef<typeof import('@vueuse/core')['useSupported']>
readonly useSwipe: UnwrapRef<typeof import('@vueuse/core')['useSwipe']>
readonly useTemplateRefsList: UnwrapRef<typeof import('@vueuse/core')['useTemplateRefsList']>
readonly useTextDirection: UnwrapRef<typeof import('@vueuse/core')['useTextDirection']>
readonly useTextSelection: UnwrapRef<typeof import('@vueuse/core')['useTextSelection']>
readonly useTextareaAutosize: UnwrapRef<typeof import('@vueuse/core')['useTextareaAutosize']>
readonly useThrottle: UnwrapRef<typeof import('@vueuse/core')['useThrottle']>
readonly useThrottleFn: UnwrapRef<typeof import('@vueuse/core')['useThrottleFn']>
readonly useThrottledRefHistory: UnwrapRef<typeof import('@vueuse/core')['useThrottledRefHistory']>
readonly useTimeAgo: UnwrapRef<typeof import('@vueuse/core')['useTimeAgo']>
readonly useTimeout: UnwrapRef<typeof import('@vueuse/core')['useTimeout']>
readonly useTimeoutFn: UnwrapRef<typeof import('@vueuse/core')['useTimeoutFn']>
readonly useTimeoutPoll: UnwrapRef<typeof import('@vueuse/core')['useTimeoutPoll']>
readonly useTimestamp: UnwrapRef<typeof import('@vueuse/core')['useTimestamp']>
readonly useTitle: UnwrapRef<typeof import('@vueuse/core')['useTitle']>
readonly useToNumber: UnwrapRef<typeof import('@vueuse/core')['useToNumber']>
readonly useToString: UnwrapRef<typeof import('@vueuse/core')['useToString']>
readonly useToggle: UnwrapRef<typeof import('@vueuse/core')['useToggle']>
readonly useTransition: UnwrapRef<typeof import('@vueuse/core')['useTransition']>
readonly useUrlSearchParams: UnwrapRef<typeof import('@vueuse/core')['useUrlSearchParams']>
readonly useUserMedia: UnwrapRef<typeof import('@vueuse/core')['useUserMedia']>
readonly useVModel: UnwrapRef<typeof import('@vueuse/core')['useVModel']>
readonly useVModels: UnwrapRef<typeof import('@vueuse/core')['useVModels']>
readonly useVibrate: UnwrapRef<typeof import('@vueuse/core')['useVibrate']>
readonly useVirtualList: UnwrapRef<typeof import('@vueuse/core')['useVirtualList']>
readonly useVisibleFilter: UnwrapRef<typeof import('./composables/visible')['useVisibleFilter']>
readonly useWakeLock: UnwrapRef<typeof import('@vueuse/core')['useWakeLock']>
readonly useWebNotification: UnwrapRef<typeof import('@vueuse/core')['useWebNotification']>
readonly useWebSocket: UnwrapRef<typeof import('@vueuse/core')['useWebSocket']>
readonly useWebWorker: UnwrapRef<typeof import('@vueuse/core')['useWebWorker']>
readonly useWebWorkerFn: UnwrapRef<typeof import('@vueuse/core')['useWebWorkerFn']>
readonly useWindowFocus: UnwrapRef<typeof import('@vueuse/core')['useWindowFocus']>
readonly useWindowScroll: UnwrapRef<typeof import('@vueuse/core')['useWindowScroll']>
readonly useWindowSize: UnwrapRef<typeof import('@vueuse/core')['useWindowSize']>
readonly watch: UnwrapRef<typeof import('vue')['watch']>
readonly watchArray: UnwrapRef<typeof import('@vueuse/core')['watchArray']>
readonly watchAtMost: UnwrapRef<typeof import('@vueuse/core')['watchAtMost']>
readonly watchDebounced: UnwrapRef<typeof import('@vueuse/core')['watchDebounced']>
readonly watchDeep: UnwrapRef<typeof import('@vueuse/core')['watchDeep']>
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
readonly watchIgnorable: UnwrapRef<typeof import('@vueuse/core')['watchIgnorable']>
readonly watchImmediate: UnwrapRef<typeof import('@vueuse/core')['watchImmediate']>
readonly watchOnce: UnwrapRef<typeof import('@vueuse/core')['watchOnce']>
readonly watchPausable: UnwrapRef<typeof import('@vueuse/core')['watchPausable']>
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
+35 -20
View File
@@ -1,18 +1,28 @@
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'
export {}
declare module '@vue/runtime-core' {
declare module 'vue' {
export interface GlobalComponents {
CarbonCaretDown: typeof import('~icons/carbon/caret-down')['default']
CilColumns: typeof import('~icons/cil/columns')['default']
CilFindInPage: typeof import('~icons/cil/find-in-page')['default']
BarChart: typeof import('./components/BarChart.vue')['default']
'Carbon:caretDown': typeof import('~icons/carbon/caret-down')['default']
'Carbon:circleSolid': typeof import('~icons/carbon/circle-solid')['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']
'Cil:findInPage': typeof import('~icons/cil/find-in-page')['default']
'Cil:xCircle': typeof import('~icons/cil/x-circle')['default']
ComplexLogItem: typeof import('./components/LogViewer/ComplexLogItem.vue')['default']
ContainerHealth: typeof import('./components/LogViewer/ContainerHealth.vue')['default']
ContainerPopup: typeof import('./components/LogViewer/ContainerPopup.vue')['default']
ContainerStat: typeof import('./components/LogViewer/ContainerStat.vue')['default']
ContainerTable: typeof import('./components/ContainerTable.vue')['default']
ContainerTitle: typeof import('./components/LogViewer/ContainerTitle.vue')['default']
DateTime: typeof import('./components/common/DateTime.vue')['default']
DistanceTime: typeof import('./components/common/DistanceTime.vue')['default']
DockerEventLogItem: typeof import('./components/LogViewer/DockerEventLogItem.vue')['default']
DropdownMenu: typeof import('./components/DropdownMenu.vue')['default']
FieldList: typeof import('./components/LogViewer/FieldList.vue')['default']
@@ -22,30 +32,35 @@ declare module '@vue/runtime-core' {
LogContainer: typeof import('./components/LogViewer/LogContainer.vue')['default']
LogDate: typeof import('./components/LogViewer/LogDate.vue')['default']
LogEventSource: typeof import('./components/LogViewer/LogEventSource.vue')['default']
LogLevel: typeof import('./components/LogViewer/LogLevel.vue')['default']
LogStd: typeof import('./components/LogViewer/LogStd.vue')['default']
LogViewer: typeof import('./components/LogViewer/LogViewer.vue')['default']
LogViewerWithSource: typeof import('./components/LogViewer/LogViewerWithSource.vue')['default']
MdiDotsVertical: typeof import('~icons/mdi/dots-vertical')['default']
MdiLightChevronDoubleDown: typeof import('~icons/mdi-light/chevron-double-down')['default']
MdiLightChevronLeft: typeof import('~icons/mdi-light/chevron-left')['default']
MdiLightChevronRight: typeof import('~icons/mdi-light/chevron-right')['default']
MdiLightCog: typeof import('~icons/mdi-light/cog')['default']
MdiLightMagnify: typeof import('~icons/mdi-light/magnify')['default']
MobileMenu: typeof import('./components/MobileMenu.vue')['default']
OcticonContainer24: typeof import('~icons/octicon/container24')['default']
OcticonDownload24: typeof import('~icons/octicon/download24')['default']
OcticonTrash24: typeof import('~icons/octicon/trash24')['default']
PastTime: typeof import('./components/PastTime.vue')['default']
RelativeTime: typeof import('./components/RelativeTime.vue')['default']
'Mdi:arrowUp': typeof import('~icons/mdi/arrow-up')['default']
'Mdi:dotsVertical': typeof import('~icons/mdi/dots-vertical')['default']
'Mdi:lightChevronDoubleDown': typeof import('~icons/mdi-light/chevron-double-down')['default']
'Mdi:lightChevronLeft': typeof import('~icons/mdi-light/chevron-left')['default']
'Mdi:lightChevronRight': typeof import('~icons/mdi-light/chevron-right')['default']
'Mdi:lightCog': typeof import('~icons/mdi-light/cog')['default']
'Mdi:lightLogout': typeof import('~icons/mdi-light/logout')['default']
'Mdi:lightMagnify': typeof import('~icons/mdi-light/magnify')['default']
MobileMenu: typeof import('./components/common/MobileMenu.vue')['default']
'Octicon:container24': typeof import('~icons/octicon/container24')['default']
'Octicon:download24': typeof import('~icons/octicon/download24')['default']
'Octicon:trash24': typeof import('~icons/octicon/trash24')['default']
Popup: typeof import('./components/Popup.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
ScrollableView: typeof import('./components/ScrollableView.vue')['default']
ScrollProgress: typeof import('./components/ScrollProgress.vue')['default']
Search: typeof import('./components/Search.vue')['default']
SideMenu: typeof import('./components/SideMenu.vue')['default']
SidePanel: typeof import('./components/SidePanel.vue')['default']
SimpleLogItem: typeof import('./components/LogViewer/SimpleLogItem.vue')['default']
SkippedEntriesLogItem: typeof import('./components/LogViewer/SkippedEntriesLogItem.vue')['default']
StatMonitor: typeof import('./components/LogViewer/StatMonitor.vue')['default']
StatSparkline: typeof import('./components/LogViewer/StatSparkline.vue')['default']
Tag: typeof import('./components/common/Tag.vue')['default']
ZigZag: typeof import('./components/LogViewer/ZigZag.vue')['default']
}
}
+31
View File
@@ -0,0 +1,31 @@
<template>
<div class="is-relative">
<div class="bar"></div>
<div class="is-overlay">
<slot></slot>
</div>
</div>
</template>
<script lang="ts" setup>
const { value } = defineProps<{ value: number }>();
const minValue = computed(() => Math.min(value, 1));
</script>
<style scoped>
.bar {
height: 1.5em;
background-color: var(--primary-color);
transform-origin: left;
transform: scaleX(v-bind(minValue));
transition: transform 0.2s ease-out;
border-top-right-radius: 0.2em;
border-bottom-right-radius: 0.2em;
}
.is-overlay {
font-size: 0.9em;
padding: 0 0.5em;
}
</style>
+150
View File
@@ -0,0 +1,150 @@
!
<template>
<table class="table is-fullwidth">
<thead>
<tr :data-direction="direction > 0 ? 'asc' : 'desc'">
<th
v-for="(value, key) in fields"
:key="key"
@click.prevent="sort(key)"
:class="{ 'selected-sort': key === sortField }"
v-show="isVisible(key)"
>
<a>
<span class="icon-text">
<span>{{ $t(value.label) }}</span>
<span class="icon">
<mdi:arrow-up />
</span>
</span>
</a>
</th>
</tr>
</thead>
<tbody>
<tr v-for="container in paginated" :key="container.id">
<td v-if="isVisible('name')">
<router-link :to="{ name: 'container-id', params: { id: container.id } }" :title="container.name">
{{ container.name }}
</router-link>
</td>
<td v-if="isVisible('state')">{{ container.state }}</td>
<td v-if="isVisible('created')">
<distance-time :date="container.created" strict :suffix="false"></distance-time>
</td>
<td v-if="isVisible('cpu')">
<bar-chart :value="container.movingAverage.cpu / 100">
{{ (container.movingAverage.cpu / 100).toLocaleString(undefined, { style: "percent" }) }}
</bar-chart>
</td>
<td v-if="isVisible('mem')">
<bar-chart :value="container.movingAverage.memory / 100">
{{ (container.movingAverage.memory / 100).toLocaleString(undefined, { style: "percent" }) }}
</bar-chart>
</td>
</tr>
</tbody>
</table>
<nav class="pagination is-right" role="navigation" aria-label="pagination" v-if="isPaginated">
<ul class="pagination-list">
<li v-for="i in totalPages">
<a class="pagination-link" :class="{ 'is-current': i === currentPage }" @click.prevent="currentPage = i">{{
i
}}</a>
</li>
</ul>
</nav>
</template>
<script setup lang="ts">
import { Container } from "@/models/Container";
const fields = {
name: {
label: "label.container-name",
sortFunc: (a: Container, b: Container) => a.name.localeCompare(b.name) * direction.value,
mobileVisible: true,
},
state: {
label: "label.status",
sortFunc: (a: Container, b: Container) => a.state.localeCompare(b.state) * direction.value,
mobileVisible: false,
},
created: {
label: "label.created",
sortFunc: (a: Container, b: Container) => (a.created.getTime() - b.created.getTime()) * direction.value,
mobileVisible: true,
},
cpu: {
label: "label.avg-cpu",
sortFunc: (a: Container, b: Container) => (a.movingAverage.cpu - b.movingAverage.cpu) * direction.value,
mobileVisible: false,
},
mem: {
label: "label.avg-mem",
sortFunc: (a: Container, b: Container) => (a.movingAverage.memory - b.movingAverage.memory) * direction.value,
mobileVisible: false,
},
};
const { containers, perPage = 15 } = defineProps<{
containers: Container[];
perPage?: number;
}>();
const sortField: Ref<keyof typeof fields> = ref("created");
const direction = ref<1 | -1>(-1);
const sortedContainers = computedWithControl(
() => [containers.length, sortField.value, direction.value],
() => {
return containers.sort((a, b) => {
return fields[sortField.value].sortFunc(a, b);
});
},
);
const totalPages = computed(() => Math.ceil(sortedContainers.value.length / perPage));
const isPaginated = computed(() => totalPages.value > 1);
const currentPage = ref(1);
const paginated = computed(() => {
const start = (currentPage.value - 1) * perPage;
const end = start + perPage;
return sortedContainers.value.slice(start, end);
});
function sort(field: keyof typeof fields) {
if (sortField.value === field) {
direction.value *= -1;
} else {
sortField.value = field;
direction.value = 1;
}
}
function isVisible(field: keyof typeof fields) {
return fields[field].mobileVisible || !isMobile.value;
}
</script>
<style lang="scss" scoped>
.icon {
display: none;
transition: transform 0.2s ease-in-out;
[data-direction="desc"] & {
transform: rotate(180deg);
}
}
.selected-sort {
font-weight: bold;
border-color: var(--primary-color);
.icon {
display: inline-block;
}
}
tbody td {
max-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
+9 -5
View File
@@ -1,11 +1,13 @@
<template>
<div class="dropdown is-hoverable">
<div class="dropdown-trigger">
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu">
<span class="icon">
<mdi-dots-vertical />
</span>
</button>
<slot name="trigger">
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu">
<span class="icon">
<mdi:dots-vertical />
</span>
</button>
</slot>
</div>
<div class="dropdown-menu" id="dropdown-menu" role="menu">
<div class="dropdown-content">
@@ -26,12 +28,14 @@
& > .icon {
height: 100%;
& > svg {
align-self: flex-start;
height: 0.85em;
}
}
}
.is-top {
& .dropdown-menu {
top: 0;
+19 -11
View File
@@ -14,19 +14,17 @@
<div class="media">
<div class="media-left">
<span class="icon is-small" :class="item.state">
<octicon-container-24 />
<octicon:container-24 />
</span>
</div>
<div class="media-content">
{{ item.name }}
</div>
<div class="media-content">{{ item.host }} / {{ item.name }}</div>
<div class="media-right">
<span
class="icon is-small column-icon"
@click.stop.prevent="addColumn(item)"
:title="$t('tooltip.pin-column')"
>
<cil-columns />
<cil:columns />
</span>
</div>
</div>
@@ -43,9 +41,7 @@ const { maxResults: resultLimit = 20 } = defineProps<{
maxResults?: number;
}>();
const emit = defineEmits<{
(e: "close"): void;
}>();
const close = defineEmit();
const query = ref("");
const autocomplete = ref<HTMLElement>();
@@ -54,12 +50,13 @@ const store = useContainerStore();
const { containers } = storeToRefs(store);
const list = computed(() => {
return containers.value.map(({ id, created, name, state }) => {
return containers.value.map(({ id, created, name, state, host }) => {
return {
id,
created,
name,
state,
host,
};
});
});
@@ -91,11 +88,11 @@ watchOnce(autocomplete, () => autocomplete.value?.focus());
function selected({ id }: { id: string }) {
router.push({ name: "container-id", params: { id } });
emit("close");
close();
}
function addColumn(container: Container) {
store.appendActiveContainer(container);
emit("close");
close();
}
</script>
@@ -105,6 +102,15 @@ function addColumn(container: Container) {
width: 580px;
}
@media screen and (max-width: 768px) {
.panel {
min-height: 200px;
width: auto;
margin-left: 0.25rem !important;
margin-right: 0.25rem !important;
}
}
.running {
color: var(--primary-color);
}
@@ -121,9 +127,11 @@ function addColumn(container: Container) {
:deep(a.dropdown-item) {
padding-right: 1em;
.media-right {
visibility: hidden;
}
&:hover .media-right {
visibility: visible;
}
+22 -9
View File
@@ -1,14 +1,25 @@
<template>
<div class="columns is-1 is-variable">
<div class="columns is-1 is-variable is-mobile">
<div class="column is-narrow" v-if="showStd">
<log-std :std="logEntry.std"></log-std>
</div>
<div class="column is-narrow" v-if="showTimestamp">
<log-date :date="logEntry.date"></log-date>
</div>
<div class="column is-narrow is-flex">
<log-level :level="logEntry.level"></log-level>
</div>
<div class="column">
<ul class="fields" :class="{ expanded }" @click="expanded = !expanded">
<li v-for="(value, name) in validValues(logEntry.message)">
<span class="has-text-grey">{{ name }}=</span>
<span class="has-text-weight-bold" v-html="markSearch(value)"></span>
<ul class="fields" :class="{ expanded }" @click="expandToggle()">
<li v-for="(value, name) in validValues">
<span class="has-text-grey">{{ name }}=</span
><span class="has-text-weight-bold" v-if="value === null">&lt;null&gt;</span>
<template v-else-if="Array.isArray(value)">
<span class="has-text-weight-bold" v-html="markSearch(JSON.stringify(value))"> </span>
</template>
<span class="has-text-weight-bold" v-html="markSearch(value)" v-else></span>
</li>
<li class="has-text-grey" v-if="Object.keys(validValues).length === 0">all values are hidden</li>
</ul>
<field-list :fields="logEntry.unfilteredMessage" :expanded="expanded" :visible-keys="visibleKeys"></field-list>
</div>
@@ -24,11 +35,11 @@ const { logEntry } = defineProps<{
visibleKeys: string[][];
}>();
let expanded = $ref(false);
const [expanded, expandToggle] = useToggle();
function validValues(obj: Record<string, any>) {
return Object.fromEntries(Object.entries(obj).filter(([_, value]) => value !== undefined));
}
const validValues = computed(() => {
return Object.fromEntries(Object.entries(logEntry.message).filter(([_, value]) => value !== undefined));
});
</script>
<style lang="scss" scoped>
@@ -38,6 +49,7 @@ function validValues(obj: Record<string, any>) {
&:hover {
cursor: pointer;
&::after {
content: "expand json";
color: var(--secondary-color);
@@ -55,6 +67,7 @@ function validValues(obj: Record<string, any>) {
li {
display: inline-block;
& + li {
margin-left: 1em;
}
@@ -0,0 +1,27 @@
<template>
<div class="icon is-small health" :health="health" v-if="health" :title="health">
<cil:check-circle v-if="health == 'healthy'" />
<cil:x-circle v-else-if="health == 'unhealthy'" />
<cil:circle v-else />
</div>
</template>
<script lang="ts" setup>
import { ContainerHealth } from "@/types/Container";
defineProps<{
health: ContainerHealth | undefined;
}>();
</script>
<style lang="scss" scoped>
.health {
&[health="unhealthy"] {
color: var(--red-color);
}
&[health="healthy"] {
color: var(--green-color);
}
}
</style>
@@ -0,0 +1,26 @@
<template>
<div>
<span class="has-text-weight-light"> RUNNING </span>
<span class="has-text-weight-semibold">
<distance-time :date="container.created" strict :suffix="false"></distance-time>
</span>
</div>
<div>
<span class="has-text-weight-light"> LOAD </span>
<span class="has-text-weight-semibold"> {{ container.stat.cpu }}% </span>
</div>
<div>
<span class="has-text-weight-light"> MEM </span>
<span class="has-text-weight-semibold"> {{ formatBytes(container.stat.memoryUsage) }} </span>
</div>
</template>
<script lang="ts" setup>
import { Container } from "@/models/Container";
const { container } = defineProps<{
container: Container;
}>();
</script>
<style lang="scss" scoped></style>
+8 -14
View File
@@ -4,13 +4,13 @@
class="column is-narrow"
:data="memoryData"
label="mem"
:stat-value="formatBytes(container.stat.memoryUsage)"
:stat-value="formatBytes(unref(container.stat).memoryUsage)"
></stat-monitor>
<stat-monitor
class="column is-narrow"
:data="cpuData"
label="load"
:stat-value="container.stat.cpu + '%'"
:stat-value="unref(container.stat).cpu + '%'"
></stat-monitor>
</div>
</template>
@@ -22,39 +22,33 @@ import { type ComputedRef } from "vue";
const container = inject("container") as ComputedRef<Container>;
const cpuData = computedWithControl(
() => container.value.getLastStat(),
() => container.value.stat,
() => {
const history = container.value.getStatHistory();
const history = container.value.statHistory;
const points: Point<unknown>[] = history.map((stat, i) => ({
x: i,
y: stat.snapshot.cpu,
value: stat.snapshot.cpu + "%",
}));
return points;
}
},
);
const memoryData = computedWithControl(
() => container.value.getLastStat(),
() => container.value.stat,
() => {
const history = container.value.getStatHistory();
const history = container.value.statHistory;
const points: Point<string>[] = history.map((stat, i) => ({
x: i,
y: stat.snapshot.memory,
value: formatBytes(stat.snapshot.memoryUsage),
}));
return points;
}
},
);
</script>
<style lang="scss" scoped>
.has-spacer {
&::after {
content: " ";
}
}
.has-border {
border: 1px solid var(--primary-color);
border-radius: 3px;
+12 -5
View File
@@ -1,9 +1,12 @@
<template>
<div class="columns is-marginless has-text-weight-bold is-family-monospace">
<span class="column is-ellipsis">
{{ container.name }}
<span class="tag is-dark">{{ container.image.replace(/@sha.*/, "") }}</span>
</span>
<div class="column is-narrow is-hidden-mobile" v-if="container.health">
<container-health :health="container.health"></container-health>
</div>
<div class="column is-ellipsis">
{{ container.name }}<span v-if="container.isSwarm">{{ container.swarmId }}</span>
<tag class="is-hidden-mobile">{{ container.image.replace(/@sha.*/, "") }}</tag>
</div>
</div>
</template>
@@ -14,4 +17,8 @@ import { type ComputedRef } from "vue";
const container = inject("container") as ComputedRef<Container>;
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.icon {
vertical-align: middle;
}
</style>
+2 -3
View File
@@ -14,14 +14,13 @@
<a @click="toggleField(name)"> {{ hasField(name) ? "remove" : "add" }}&nbsp;</a>
<span class="has-text-grey">{{ name }}=</span>[
<span class="has-text-weight-bold" v-for="(item, index) in value">
{{ item }}
<span v-if="index !== value.length - 1">,</span>
<span v-html="JSON.stringify(item)"></span><span v-if="index !== value.length - 1">,</span>
</span>
]
</template>
<template v-else>
<a @click="toggleField(name)"> {{ hasField(name) ? "remove" : "add" }}&nbsp;</a>
<span class="has-text-grey">{{ name }}=</span><span class="has-text-weight-bold">{{ value }}</span>
<span class="has-text-grey">{{ name }}=</span><span class="has-text-weight-bold" v-html="value"></span>
</template>
</li>
</ul>
@@ -1,10 +1,18 @@
<template>
<dropdown-menu class="is-right">
<a class="dropdown-item" @click="onClearClicked">
<template #trigger>
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu">
<span class="icon">
<carbon:circle-solid class="is-red is-small" v-if="streamConfig.stderr" />
<carbon:circle-solid class="is-blue is-small" v-if="streamConfig.stdout" />
</span>
</button>
</template>
<a class="dropdown-item" @click="clear()">
<div class="level is-justify-content-start">
<div class="level-left">
<div class="level-item">
<octicon-trash-24 class="mr-4" />
<octicon:trash-24 />
</div>
</div>
<div class="level-right">
@@ -12,11 +20,11 @@
</div>
</div>
</a>
<a class="dropdown-item" :href="`${base}/api/logs/download?id=${container.id}`">
<a class="dropdown-item" :href="`${base}/api/logs/download/${sessionHost}/${container.id}`">
<div class="level is-justify-content-start">
<div class="level-left">
<div class="level-item">
<octicon-download-24 class="mr-4" />
<octicon:download-24 />
</div>
</div>
<div class="level-right">
@@ -29,7 +37,7 @@
<div class="level is-justify-content-start">
<div class="level-left">
<div class="level-item">
<mdi-light-magnify class="mr-4" />
<mdi:light-magnify />
</div>
</div>
<div class="level-right">
@@ -37,6 +45,67 @@
</div>
</div>
</a>
<hr class="dropdown-divider" />
<a
class="dropdown-item"
@click="
streamConfig.stdout = true;
streamConfig.stderr = true;
"
>
<div class="level is-justify-content-start">
<div class="level-left">
<div class="level-item">
<template v-if="streamConfig.stderr && streamConfig.stdout">
<carbon:circle-solid class="is-red is-small" />
<carbon:circle-solid class="is-blue is-small" />
</template>
</div>
</div>
<div class="level-right">
{{ $t("toolbar.show-all") }}
</div>
</div>
</a>
<a
class="dropdown-item"
@click="
streamConfig.stdout = true;
streamConfig.stderr = false;
"
>
<div class="level is-justify-content-start">
<div class="level-left">
<div class="level-item">
<carbon:circle-solid class="is-blue is-small" v-if="!streamConfig.stderr && streamConfig.stdout" />
</div>
</div>
<div class="level-right">
{{ $t("toolbar.show", { std: "STDOUT" }) }}
</div>
</div>
</a>
<a
class="dropdown-item"
@click="
streamConfig.stdout = false;
streamConfig.stderr = true;
"
>
<div class="level is-justify-content-start">
<div class="level-left">
<div class="level-item">
<carbon:circle-solid class="is-red is-small" v-if="streamConfig.stderr && !streamConfig.stdout" />
</div>
</div>
<div class="level-right">
<div class="level-item">
{{ $t("toolbar.show", { std: "STDERR" }) }}
</div>
</div>
</div>
</a>
</dropdown-menu>
</template>
@@ -47,23 +116,20 @@ import { Container } from "@/models/Container";
const { showSearch } = useSearchFilter();
const { base } = config;
const { onClearClicked = (e: Event) => {} } = defineProps<{
onClearClicked: (e: Event) => void;
}>();
const clear = defineEmit();
const container = inject("container") as ComputedRef<Container>;
const streamConfig = inject("stream-config") as { stdout: boolean; stderr: boolean };
</script>
<style lang="scss" scoped>
#download.button,
#clear.button {
.icon {
height: 80%;
}
.level-left .level-item {
width: 2.2em;
align-items: center;
margin-right: 0.5em;
}
&:hover {
color: var(--primary-color);
border-color: var(--primary-color);
}
.is-small {
width: 0.6em;
}
</style>
+8 -7
View File
@@ -1,7 +1,7 @@
<template>
<scrollable-view :scrollable="scrollable" v-if="container">
<template #header v-if="showTitle">
<div class="mr-0 columns is-vcentered is-marginless is-hidden-mobile has-boxshadow">
<div class="mr-0 columns is-mobile is-vcentered is-marginless has-boxshadow">
<div class="column is-clipped is-paddingless">
<container-title @close="$emit('close')" />
</div>
@@ -9,11 +9,11 @@
<container-stat />
</div>
<div class="mr-2 column is-narrow is-paddingless">
<log-actions-toolbar :onClearClicked="onClearClicked" />
<div class="mr-2 column is-narrow is-paddingless is-hidden-mobile">
<log-actions-toolbar @clear="onClearClicked()" />
</div>
<div class="mr-2 column is-narrow is-paddingless" v-if="closable">
<button class="delete is-medium" @click="emit('close')"></button>
<button class="delete is-medium" @click="close()"></button>
</div>
</div>
</template>
@@ -38,15 +38,15 @@ const {
closable?: boolean;
}>();
const emit = defineEmits<{
(event: "close"): void;
}>();
const close = defineEmit();
const store = useContainerStore();
const container = store.currentContainer($$(id));
const config = reactive({ stdout: true, stderr: true });
provide("container", container);
provide("stream-config", config);
const viewer = ref<InstanceType<typeof LogViewerWithSource>>();
@@ -58,6 +58,7 @@ function onClearClicked() {
button.delete {
background-color: var(--scheme-main-ter);
opacity: 0.6;
&:after,
&:before {
background-color: var(--text-color);
+1 -1
View File
@@ -1,5 +1,5 @@
<template>
<relative-time :date="date" class="date"></relative-time>
<date-time :date="date" class="date"></date-time>
</template>
<script lang="ts" setup>
defineProps<{
@@ -12,7 +12,7 @@ import { createRouter, createWebHistory } from "vue-router";
vi.mock("@/stores/config", () => ({
__esModule: true,
default: { base: "" },
default: { base: "", hosts: ["localhost"] },
}));
/**
@@ -44,11 +44,11 @@ describe("<LogEventSource />", () => {
hourStyle = "auto",
}: { searchFilter?: string | undefined; hourStyle?: "auto" | "24" | "12" } = {
hourStyle: "auto",
}
},
) {
settings.value.hourStyle = hourStyle;
search.searchFilter.value = searchFilter;
if(searchFilter){
if (searchFilter) {
search.showSearch.value = true;
}
@@ -71,7 +71,8 @@ describe("<LogEventSource />", () => {
LogViewer,
},
provide: {
container: computed(() => ({ id: "abc", image: "test:v123" })),
container: computed(() => ({ id: "abc", image: "test:v123", host: "localhost" })),
"stream-config": reactive({ stdout: true, stderr: true }),
scrollingPaused: computed(() => false),
},
},
@@ -84,6 +85,8 @@ describe("<LogEventSource />", () => {
});
}
const sourceUrl = "/api/logs/stream/localhost/abc?lastEventId=&stdout=1&stderr=1";
test("renders correctly", async () => {
const wrapper = createLogEventSource();
expect(wrapper.html()).toMatchSnapshot();
@@ -91,22 +94,22 @@ describe("<LogEventSource />", () => {
test("should connect to EventSource", async () => {
const wrapper = createLogEventSource();
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
expect(sources["/api/logs/stream?id=abc&lastEventId="].readyState).toBe(1);
sources[sourceUrl].emitOpen();
expect(sources[sourceUrl].readyState).toBe(1);
wrapper.unmount();
});
test("should close EventSource", async () => {
const wrapper = createLogEventSource();
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
sources[sourceUrl].emitOpen();
wrapper.unmount();
expect(sources["/api/logs/stream?id=abc&lastEventId="].readyState).toBe(2);
expect(sources[sourceUrl].readyState).toBe(2);
});
test("should parse messages", async () => {
const wrapper = createLogEventSource();
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
sources[sourceUrl].emitOpen();
sources[sourceUrl].emitMessage({
data: `{"ts":1560336942459, "m":"This is a message.", "id":1}`,
});
@@ -121,8 +124,8 @@ describe("<LogEventSource />", () => {
describe("render html correctly", () => {
test("should render messages", async () => {
const wrapper = createLogEventSource();
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
sources[sourceUrl].emitOpen();
sources[sourceUrl].emitMessage({
data: `{"ts":1560336942459, "m":"This is a message.", "id":1}`,
});
@@ -134,8 +137,8 @@ describe("<LogEventSource />", () => {
test("should render messages with color", async () => {
const wrapper = createLogEventSource();
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
sources[sourceUrl].emitOpen();
sources[sourceUrl].emitMessage({
data: '{"ts":1560336942459,"m":"\\u001b[30mblack\\u001b[37mwhite", "id":1}',
});
@@ -147,8 +150,8 @@ describe("<LogEventSource />", () => {
test("should render messages with html entities", async () => {
const wrapper = createLogEventSource();
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
sources[sourceUrl].emitOpen();
sources[sourceUrl].emitMessage({
data: `{"ts":1560336942459, "m":"<test>foo bar</test>", "id":1}`,
});
@@ -160,8 +163,8 @@ describe("<LogEventSource />", () => {
test("should render dates with 12 hour style", async () => {
const wrapper = createLogEventSource({ hourStyle: "12" });
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
sources[sourceUrl].emitOpen();
sources[sourceUrl].emitMessage({
data: `{"ts":1560336942459, "m":"<test>foo bar</test>", "id":1}`,
});
@@ -173,8 +176,8 @@ describe("<LogEventSource />", () => {
test("should render dates with 24 hour style", async () => {
const wrapper = createLogEventSource({ hourStyle: "24" });
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
sources[sourceUrl].emitOpen();
sources[sourceUrl].emitMessage({
data: `{"ts":1560336942459, "m":"<test>foo bar</test>", "id":1}`,
});
@@ -186,11 +189,11 @@ describe("<LogEventSource />", () => {
test("should render messages with filter", async () => {
const wrapper = createLogEventSource({ searchFilter: "test" });
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
sources[sourceUrl].emitOpen();
sources[sourceUrl].emitMessage({
data: `{"ts":1560336942459, "m":"foo bar", "id":1}`,
});
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
sources[sourceUrl].emitMessage({
data: `{"ts":1560336942459, "m":"test bar", "id":2}`,
});
@@ -7,15 +7,14 @@
import { Container } from "@/models/Container";
import { type ComputedRef } from "vue";
const emit = defineEmits<{
(e: "loading-more", value: boolean): void;
}>();
const loadingMore = defineEmit<[value: boolean]>();
const container = inject("container") as ComputedRef<Container>;
const { messages, loadOlderLogs } = useLogStream(container);
const config = inject("stream-config") as { stdout: boolean; stderr: boolean };
const { messages, loadOlderLogs } = useLogStream(container, config);
const beforeLoading = () => emit("loading-more", true);
const afterLoading = () => emit("loading-more", false);
const beforeLoading = () => loadingMore(true);
const afterLoading = () => loadingMore(false);
defineExpose({
clear: () => (messages.value = []),
+63
View File
@@ -0,0 +1,63 @@
<template>
<div :class="level" :data-position="position"></div>
</template>
<script lang="ts" setup>
import { Position } from "@/models/LogEntry";
defineProps<{
level?: string;
position?: Position;
}>();
</script>
<style lang="scss" scoped>
div {
display: inline-block;
width: 0.7em;
height: 0.7em;
border-radius: 0.5em;
align-self: auto;
margin-top: 0.4em;
&[data-position="start"] {
border-radius: 0.5em 0.5em 0 0;
height: 70%;
margin-bottom: -0.2em;
margin-top: auto;
align-self: flex-end;
}
&[data-position="middle"] {
border-radius: 0;
height: auto;
margin: -0.2em 0;
align-self: auto;
}
&[data-position="end"] {
border-radius: 0 0 0.5em 0.5em;
height: 70%;
margin-top: -0.2em;
align-self: flex-start;
}
&.debug,
&.trace {
background-color: var(--purple-color);
}
&.info {
background-color: var(--green-color);
}
&.error,
&.fatal {
background-color: var(--red-color);
}
&.warn,
&.warning {
background-color: var(--orange-color);
}
}
</style>
+25
View File
@@ -0,0 +1,25 @@
<template>
<tag size="small" :std="std">
{{ std }}
</tag>
</template>
<script lang="ts" setup>
import { Std } from "@/models/LogEntry";
defineProps<{
std: Std;
}>();
</script>
<style lang="scss" scoped>
.tag {
&[std="stdout"] {
color: var(--blue-color);
}
&[std="stderr"] {
color: var(--red-color);
}
}
</style>
+16 -4
View File
@@ -12,7 +12,7 @@
<div class="level is-justify-content-start">
<div class="level-left">
<div class="level-item">
<cil-find-in-page class="mr-4" />
<cil:find-in-page class="mr-4" />
</div>
</div>
<div class="level-right">
@@ -58,24 +58,36 @@ const routeHash = useRouteHash();
watch(
routeHash,
(hash) => {
document.querySelector(`[data-key="${hash.substring(1)}"]`)?.scrollIntoView({ block: "center" });
if (hash) {
document.querySelector(`[data-key="${hash.substring(1)}"]`)?.scrollIntoView({ block: "center" });
}
},
{ immediate: true, flush: "post" }
{ immediate: true, flush: "post" },
);
</script>
<style scoped lang="scss">
.events {
padding: 1em 0;
font-family: SFMono-Regular, Consolas, Liberation Mono, monaco, Menlo, monospace;
font-family:
ui-monospace,
SFMono-Regular,
SF Mono,
Consolas,
Liberation Mono,
monaco,
Menlo,
monospace;
& > li {
display: flex;
word-wrap: break-word;
padding: 0.2em 1em;
&:last-child {
scroll-snap-align: end;
scroll-margin-block-end: 5rem;
}
&:nth-child(odd) {
background-color: rgba(125, 125, 125, 0.08);
}
@@ -1,5 +1,5 @@
<template>
<log-event-source ref="source" #default="{ messages }" @loading-more="emit('loading-more', $event)">
<log-event-source ref="source" #default="{ messages }" @loading-more="loadingMore($event)">
<log-viewer :messages="messages"></log-viewer>
</log-event-source>
</template>
@@ -7,10 +7,7 @@
<script lang="ts" setup>
import LogEventSource from "./LogEventSource.vue";
const emit = defineEmits<{
(e: "loading-more", value: boolean): void;
}>();
const loadingMore = defineEmit<[value: boolean]>();
const source = $ref<InstanceType<typeof LogEventSource>>();
function clear() {
source?.clear();
@@ -1,8 +1,14 @@
<template>
<div class="columns is-1 is-variable">
<div class="columns is-1 is-variable is-mobile">
<div class="column is-narrow" v-if="showStd">
<log-std :std="logEntry.std"></log-std>
</div>
<div class="column is-narrow" v-if="showTimestamp">
<log-date :date="logEntry.date"></log-date>
</div>
<div class="column is-narrow is-flex">
<log-level :level="logEntry.level" :position="logEntry.position"></log-level>
</div>
<div class="text column" v-html="colorize(logEntry.message)"></div>
</div>
</template>
@@ -10,7 +16,7 @@
import { SimpleLogEntry } from "@/models/LogEntry";
import AnsiConvertor from "ansi-to-html";
const ansiConvertor = new AnsiConvertor({ escapeXML: true });
const ansiConvertor = new AnsiConvertor({ escapeXML: false, fg: "var(--text-color)" });
defineProps<{
logEntry: SimpleLogEntry;
}>();
+9 -13
View File
@@ -1,10 +1,10 @@
<template>
<div class="has-text-centered is-relative host" @mouseenter="mouseOver = true" @mouseleave="mouseOver = false">
<div class="has-border has-boxshadow">
<div class="has-border has-boxshadow is-hidden-mobile">
<stat-sparkline :data="data" @selected-point="onSelectedPoint"></stat-sparkline>
</div>
<div class="has-background-body-color is-top-left">
<span class="has-text-weight-light has-spacer">{{ label }}</span>
<span class="has-text-weight-light">{{ label }}</span>
<span class="has-text-weight-bold">
{{ mouseOver ? selectedPoint?.value ?? selectedPoint?.y ?? statValue : statValue }}
</span>
@@ -15,7 +15,7 @@
<script lang="ts" setup>
const { data, label, statValue } = defineProps<{ data: Point<unknown>[]; label: string; statValue: string | number }>();
let selectedPoint: Point<unknown> = $ref();
let selectedPoint: Point<unknown> | undefined = $ref();
function onSelectedPoint(point: Point<unknown>) {
selectedPoint = point;
@@ -25,12 +25,6 @@ let mouseOver = $ref(false);
</script>
<style lang="scss" scoped>
.has-spacer {
&::after {
content: " ";
}
}
.has-border {
border: 1px solid var(--primary-color);
border-radius: 3px;
@@ -48,9 +42,11 @@ let mouseOver = $ref(false);
color: var(--secondary-color);
}
.is-top-left {
position: absolute;
top: 0;
left: 0.75em;
@media screen and (min-width: 768px) {
.is-top-left {
position: absolute;
top: 0;
left: 0.75em;
}
}
</style>
@@ -15,9 +15,7 @@ const { data, width = 150, height = 30 } = defineProps<{ data: Point<unknown>[];
const x = d3.scaleLinear().range([width, 0]);
const y = d3.scaleLinear().range([height, 0]);
const emit = defineEmits<{
(event: "selected-point", value: Point<unknown>): void;
}>();
const selectedPoint = defineEmit<[value: Point<unknown>]>();
const shape = d3
.area<Point<unknown>>()
@@ -28,7 +26,7 @@ const shape = d3
const path = computed(() => {
x.domain(d3.extent(data, (d) => d.x) as [number, number]);
y.domain(d3.extent(data, (d) => d.y) as [number, number]);
y.domain(d3.extent([...data, { y: 1 }], (d) => d.y) as [number, number]);
return shape(data) ?? "";
});
@@ -41,7 +39,7 @@ function onMove(e: MouseEvent) {
const index = Math.round(xValue);
lineX = x(index);
const point = data[index];
emit("selected-point", point);
selectedPoint(point);
}
</script>
@@ -1,200 +1,237 @@
// Vitest Snapshot v1
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`<LogEventSource /> > render html correctly > should render dates with 12 hour style 1`] = `
"<ul class=\\"events medium\\" data-v-2e92daca=\\"\\">
<li data-key=\\"1\\" class=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"line-options\\" data-v-2e92daca=\\"\\" style=\\"display: none;\\">
<div class=\\"dropdown is-hoverable is-last is-top minimal\\" data-v-539164cb=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"dropdown-trigger\\" data-v-539164cb=\\"\\"><button class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\" data-v-539164cb=\\"\\"><span class=\\"icon\\" data-v-539164cb=\\"\\"><svg viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\" data-v-539164cb=\\"\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div class=\\"dropdown-menu\\" id=\\"dropdown-menu\\" role=\\"menu\\" data-v-539164cb=\\"\\">
<div class=\\"dropdown-content\\" data-v-539164cb=\\"\\"><a class=\\"dropdown-item\\" href=\\"#1\\" data-v-2e92daca=\\"\\">
<div class=\\"level is-justify-content-start\\" data-v-2e92daca=\\"\\">
<div class=\\"level-left\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\"><svg viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-2e92daca=\\"\\">
"<ul data-v-2e92daca=\\"\\" class=\\"events medium\\">
<li data-v-2e92daca=\\"\\" data-key=\\"1\\" class=\\"\\">
<div data-v-2e92daca=\\"\\" class=\\"line-options\\" style=\\"display: none;\\">
<div data-v-539164cb=\\"\\" data-v-2e92daca=\\"\\" class=\\"dropdown is-hoverable is-last is-top minimal\\">
<div data-v-539164cb=\\"\\" class=\\"dropdown-trigger\\"><button data-v-539164cb=\\"\\" class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\"><span data-v-539164cb=\\"\\" class=\\"icon\\"><svg data-v-539164cb=\\"\\" viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div data-v-539164cb=\\"\\" class=\\"dropdown-menu\\" id=\\"dropdown-menu\\" role=\\"menu\\">
<div data-v-539164cb=\\"\\" class=\\"dropdown-content\\"><a data-v-2e92daca=\\"\\" class=\\"dropdown-item\\" href=\\"#1\\">
<div data-v-2e92daca=\\"\\" class=\\"level is-justify-content-start\\">
<div data-v-2e92daca=\\"\\" class=\\"level-left\\">
<div data-v-2e92daca=\\"\\" class=\\"level-item\\"><svg data-v-2e92daca=\\"\\" viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\">
<path fill=\\"currentColor\\" d=\\"M334.627 16H48v480h424V153.373ZM440 464H80V48h241.373L440 166.627Z\\"></path>
<path fill=\\"currentColor\\" d=\\"M239.861 152a95.861 95.861 0 1 0 53.624 175.284l68.03 68.029l22.627-22.626l-67.5-67.5A95.816 95.816 0 0 0 239.861 152ZM176 247.861a63.862 63.862 0 1 1 63.861 63.861A63.933 63.933 0 0 1 176 247.861Z\\"></path>
</svg></div>
</div>
<div class=\\"level-right\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\">Jump to Context</div>
<div data-v-2e92daca=\\"\\" class=\\"level-right\\">
<div data-v-2e92daca=\\"\\" class=\\"level-item\\">Jump to Context</div>
</div>
</div>
</a></div>
</div>
</div>
</div>
<div class=\\"columns is-1 is-variable\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\">06/12/2019 10:55:42 AM</time></div>
<div class=\\"text column\\" data-v-a49e52d4=\\"\\">&lt;test&gt;foo bar&lt;/test&gt;</div>
<div data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\" class=\\"columns is-1 is-variable is-mobile\\" visible-keys=\\"\\">
<!--v-if-->
<div data-v-a49e52d4=\\"\\" class=\\"column is-narrow\\">
<div data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\" class=\\"date\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"is-hidden-mobile\\">06/12/2019</time> <time datetime=\\"2019-06-12T10:55:42.459Z\\">10:55:42 AM</time></div>
</div>
<div data-v-a49e52d4=\\"\\" class=\\"column is-narrow is-flex\\">
<div data-v-e625cddd=\\"\\" data-v-a49e52d4=\\"\\" class=\\"\\"></div>
</div>
<div data-v-a49e52d4=\\"\\" class=\\"text column\\">&lt;test&gt;foo bar&lt;/test&gt;</div>
</div>
</li>
</ul>"
`;
exports[`<LogEventSource /> > render html correctly > should render dates with 24 hour style 1`] = `
"<ul class=\\"events medium\\" data-v-2e92daca=\\"\\">
<li data-key=\\"1\\" class=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"line-options\\" data-v-2e92daca=\\"\\" style=\\"display: none;\\">
<div class=\\"dropdown is-hoverable is-last is-top minimal\\" data-v-539164cb=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"dropdown-trigger\\" data-v-539164cb=\\"\\"><button class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\" data-v-539164cb=\\"\\"><span class=\\"icon\\" data-v-539164cb=\\"\\"><svg viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\" data-v-539164cb=\\"\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div class=\\"dropdown-menu\\" id=\\"dropdown-menu\\" role=\\"menu\\" data-v-539164cb=\\"\\">
<div class=\\"dropdown-content\\" data-v-539164cb=\\"\\"><a class=\\"dropdown-item\\" href=\\"#1\\" data-v-2e92daca=\\"\\">
<div class=\\"level is-justify-content-start\\" data-v-2e92daca=\\"\\">
<div class=\\"level-left\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\"><svg viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-2e92daca=\\"\\">
"<ul data-v-2e92daca=\\"\\" class=\\"events medium\\">
<li data-v-2e92daca=\\"\\" data-key=\\"1\\" class=\\"\\">
<div data-v-2e92daca=\\"\\" class=\\"line-options\\" style=\\"display: none;\\">
<div data-v-539164cb=\\"\\" data-v-2e92daca=\\"\\" class=\\"dropdown is-hoverable is-last is-top minimal\\">
<div data-v-539164cb=\\"\\" class=\\"dropdown-trigger\\"><button data-v-539164cb=\\"\\" class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\"><span data-v-539164cb=\\"\\" class=\\"icon\\"><svg data-v-539164cb=\\"\\" viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div data-v-539164cb=\\"\\" class=\\"dropdown-menu\\" id=\\"dropdown-menu\\" role=\\"menu\\">
<div data-v-539164cb=\\"\\" class=\\"dropdown-content\\"><a data-v-2e92daca=\\"\\" class=\\"dropdown-item\\" href=\\"#1\\">
<div data-v-2e92daca=\\"\\" class=\\"level is-justify-content-start\\">
<div data-v-2e92daca=\\"\\" class=\\"level-left\\">
<div data-v-2e92daca=\\"\\" class=\\"level-item\\"><svg data-v-2e92daca=\\"\\" viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\">
<path fill=\\"currentColor\\" d=\\"M334.627 16H48v480h424V153.373ZM440 464H80V48h241.373L440 166.627Z\\"></path>
<path fill=\\"currentColor\\" d=\\"M239.861 152a95.861 95.861 0 1 0 53.624 175.284l68.03 68.029l22.627-22.626l-67.5-67.5A95.816 95.816 0 0 0 239.861 152ZM176 247.861a63.862 63.862 0 1 1 63.861 63.861A63.933 63.933 0 0 1 176 247.861Z\\"></path>
</svg></div>
</div>
<div class=\\"level-right\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\">Jump to Context</div>
<div data-v-2e92daca=\\"\\" class=\\"level-right\\">
<div data-v-2e92daca=\\"\\" class=\\"level-item\\">Jump to Context</div>
</div>
</div>
</a></div>
</div>
</div>
</div>
<div class=\\"columns is-1 is-variable\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\">06/12/2019 10:55:42</time></div>
<div class=\\"text column\\" data-v-a49e52d4=\\"\\">&lt;test&gt;foo bar&lt;/test&gt;</div>
<div data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\" class=\\"columns is-1 is-variable is-mobile\\" visible-keys=\\"\\">
<!--v-if-->
<div data-v-a49e52d4=\\"\\" class=\\"column is-narrow\\">
<div data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\" class=\\"date\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"is-hidden-mobile\\">06/12/2019</time> <time datetime=\\"2019-06-12T10:55:42.459Z\\">10:55:42</time></div>
</div>
<div data-v-a49e52d4=\\"\\" class=\\"column is-narrow is-flex\\">
<div data-v-e625cddd=\\"\\" data-v-a49e52d4=\\"\\" class=\\"\\"></div>
</div>
<div data-v-a49e52d4=\\"\\" class=\\"text column\\">&lt;test&gt;foo bar&lt;/test&gt;</div>
</div>
</li>
</ul>"
`;
exports[`<LogEventSource /> > render html correctly > should render messages 1`] = `
"<ul class=\\"events medium\\" data-v-2e92daca=\\"\\">
<li data-key=\\"1\\" class=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"line-options\\" data-v-2e92daca=\\"\\" style=\\"display: none;\\">
<div class=\\"dropdown is-hoverable is-last is-top minimal\\" data-v-539164cb=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"dropdown-trigger\\" data-v-539164cb=\\"\\"><button class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\" data-v-539164cb=\\"\\"><span class=\\"icon\\" data-v-539164cb=\\"\\"><svg viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\" data-v-539164cb=\\"\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div class=\\"dropdown-menu\\" id=\\"dropdown-menu\\" role=\\"menu\\" data-v-539164cb=\\"\\">
<div class=\\"dropdown-content\\" data-v-539164cb=\\"\\"><a class=\\"dropdown-item\\" href=\\"#1\\" data-v-2e92daca=\\"\\">
<div class=\\"level is-justify-content-start\\" data-v-2e92daca=\\"\\">
<div class=\\"level-left\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\"><svg viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-2e92daca=\\"\\">
"<ul data-v-2e92daca=\\"\\" class=\\"events medium\\">
<li data-v-2e92daca=\\"\\" data-key=\\"1\\" class=\\"\\">
<div data-v-2e92daca=\\"\\" class=\\"line-options\\" style=\\"display: none;\\">
<div data-v-539164cb=\\"\\" data-v-2e92daca=\\"\\" class=\\"dropdown is-hoverable is-last is-top minimal\\">
<div data-v-539164cb=\\"\\" class=\\"dropdown-trigger\\"><button data-v-539164cb=\\"\\" class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\"><span data-v-539164cb=\\"\\" class=\\"icon\\"><svg data-v-539164cb=\\"\\" viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div data-v-539164cb=\\"\\" class=\\"dropdown-menu\\" id=\\"dropdown-menu\\" role=\\"menu\\">
<div data-v-539164cb=\\"\\" class=\\"dropdown-content\\"><a data-v-2e92daca=\\"\\" class=\\"dropdown-item\\" href=\\"#1\\">
<div data-v-2e92daca=\\"\\" class=\\"level is-justify-content-start\\">
<div data-v-2e92daca=\\"\\" class=\\"level-left\\">
<div data-v-2e92daca=\\"\\" class=\\"level-item\\"><svg data-v-2e92daca=\\"\\" viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\">
<path fill=\\"currentColor\\" d=\\"M334.627 16H48v480h424V153.373ZM440 464H80V48h241.373L440 166.627Z\\"></path>
<path fill=\\"currentColor\\" d=\\"M239.861 152a95.861 95.861 0 1 0 53.624 175.284l68.03 68.029l22.627-22.626l-67.5-67.5A95.816 95.816 0 0 0 239.861 152ZM176 247.861a63.862 63.862 0 1 1 63.861 63.861A63.933 63.933 0 0 1 176 247.861Z\\"></path>
</svg></div>
</div>
<div class=\\"level-right\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\">Jump to Context</div>
<div data-v-2e92daca=\\"\\" class=\\"level-right\\">
<div data-v-2e92daca=\\"\\" class=\\"level-item\\">Jump to Context</div>
</div>
</div>
</a></div>
</div>
</div>
</div>
<div class=\\"columns is-1 is-variable\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\">06/12/2019 10:55:42 AM</time></div>
<div class=\\"text column\\" data-v-a49e52d4=\\"\\">This is a message.</div>
<div data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\" class=\\"columns is-1 is-variable is-mobile\\" visible-keys=\\"\\">
<!--v-if-->
<div data-v-a49e52d4=\\"\\" class=\\"column is-narrow\\">
<div data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\" class=\\"date\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"is-hidden-mobile\\">06/12/2019</time> <time datetime=\\"2019-06-12T10:55:42.459Z\\">10:55:42 AM</time></div>
</div>
<div data-v-a49e52d4=\\"\\" class=\\"column is-narrow is-flex\\">
<div data-v-e625cddd=\\"\\" data-v-a49e52d4=\\"\\" class=\\"\\"></div>
</div>
<div data-v-a49e52d4=\\"\\" class=\\"text column\\">This is a message.</div>
</div>
</li>
</ul>"
`;
exports[`<LogEventSource /> > render html correctly > should render messages with color 1`] = `
"<ul class=\\"events medium\\" data-v-2e92daca=\\"\\">
<li data-key=\\"1\\" class=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"line-options\\" data-v-2e92daca=\\"\\" style=\\"display: none;\\">
<div class=\\"dropdown is-hoverable is-last is-top minimal\\" data-v-539164cb=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"dropdown-trigger\\" data-v-539164cb=\\"\\"><button class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\" data-v-539164cb=\\"\\"><span class=\\"icon\\" data-v-539164cb=\\"\\"><svg viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\" data-v-539164cb=\\"\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div class=\\"dropdown-menu\\" id=\\"dropdown-menu\\" role=\\"menu\\" data-v-539164cb=\\"\\">
<div class=\\"dropdown-content\\" data-v-539164cb=\\"\\"><a class=\\"dropdown-item\\" href=\\"#1\\" data-v-2e92daca=\\"\\">
<div class=\\"level is-justify-content-start\\" data-v-2e92daca=\\"\\">
<div class=\\"level-left\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\"><svg viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-2e92daca=\\"\\">
"<ul data-v-2e92daca=\\"\\" class=\\"events medium\\">
<li data-v-2e92daca=\\"\\" data-key=\\"1\\" class=\\"\\">
<div data-v-2e92daca=\\"\\" class=\\"line-options\\" style=\\"display: none;\\">
<div data-v-539164cb=\\"\\" data-v-2e92daca=\\"\\" class=\\"dropdown is-hoverable is-last is-top minimal\\">
<div data-v-539164cb=\\"\\" class=\\"dropdown-trigger\\"><button data-v-539164cb=\\"\\" class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\"><span data-v-539164cb=\\"\\" class=\\"icon\\"><svg data-v-539164cb=\\"\\" viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div data-v-539164cb=\\"\\" class=\\"dropdown-menu\\" id=\\"dropdown-menu\\" role=\\"menu\\">
<div data-v-539164cb=\\"\\" class=\\"dropdown-content\\"><a data-v-2e92daca=\\"\\" class=\\"dropdown-item\\" href=\\"#1\\">
<div data-v-2e92daca=\\"\\" class=\\"level is-justify-content-start\\">
<div data-v-2e92daca=\\"\\" class=\\"level-left\\">
<div data-v-2e92daca=\\"\\" class=\\"level-item\\"><svg data-v-2e92daca=\\"\\" viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\">
<path fill=\\"currentColor\\" d=\\"M334.627 16H48v480h424V153.373ZM440 464H80V48h241.373L440 166.627Z\\"></path>
<path fill=\\"currentColor\\" d=\\"M239.861 152a95.861 95.861 0 1 0 53.624 175.284l68.03 68.029l22.627-22.626l-67.5-67.5A95.816 95.816 0 0 0 239.861 152ZM176 247.861a63.862 63.862 0 1 1 63.861 63.861A63.933 63.933 0 0 1 176 247.861Z\\"></path>
</svg></div>
</div>
<div class=\\"level-right\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\">Jump to Context</div>
<div data-v-2e92daca=\\"\\" class=\\"level-right\\">
<div data-v-2e92daca=\\"\\" class=\\"level-item\\">Jump to Context</div>
</div>
</div>
</a></div>
</div>
</div>
</div>
<div class=\\"columns is-1 is-variable\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\">06/12/2019 10:55:42 AM</time></div>
<div class=\\"text column\\" data-v-a49e52d4=\\"\\"><span style=\\"color:#000\\">black<span style=\\"color:#AAA\\">white</span></span></div>
<div data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\" class=\\"columns is-1 is-variable is-mobile\\" visible-keys=\\"\\">
<!--v-if-->
<div data-v-a49e52d4=\\"\\" class=\\"column is-narrow\\">
<div data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\" class=\\"date\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"is-hidden-mobile\\">06/12/2019</time> <time datetime=\\"2019-06-12T10:55:42.459Z\\">10:55:42 AM</time></div>
</div>
<div data-v-a49e52d4=\\"\\" class=\\"column is-narrow is-flex\\">
<div data-v-e625cddd=\\"\\" data-v-a49e52d4=\\"\\" class=\\"\\"></div>
</div>
<div data-v-a49e52d4=\\"\\" class=\\"text column\\"><span style=\\"color:#000\\">black<span style=\\"color:#AAA\\">white</span></span></div>
</div>
</li>
</ul>"
`;
exports[`<LogEventSource /> > render html correctly > should render messages with filter 1`] = `
"<ul class=\\"events medium\\" data-v-2e92daca=\\"\\">
<li data-key=\\"2\\" class=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"line-options\\" data-v-2e92daca=\\"\\">
<div class=\\"dropdown is-hoverable is-last is-top minimal\\" data-v-539164cb=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"dropdown-trigger\\" data-v-539164cb=\\"\\"><button class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\" data-v-539164cb=\\"\\"><span class=\\"icon\\" data-v-539164cb=\\"\\"><svg viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\" data-v-539164cb=\\"\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div class=\\"dropdown-menu\\" id=\\"dropdown-menu\\" role=\\"menu\\" data-v-539164cb=\\"\\">
<div class=\\"dropdown-content\\" data-v-539164cb=\\"\\"><a class=\\"dropdown-item\\" href=\\"#2\\" data-v-2e92daca=\\"\\">
<div class=\\"level is-justify-content-start\\" data-v-2e92daca=\\"\\">
<div class=\\"level-left\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\"><svg viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-2e92daca=\\"\\">
"<ul data-v-2e92daca=\\"\\" class=\\"events medium\\">
<li data-v-2e92daca=\\"\\" data-key=\\"2\\" class=\\"\\">
<div data-v-2e92daca=\\"\\" class=\\"line-options\\">
<div data-v-539164cb=\\"\\" data-v-2e92daca=\\"\\" class=\\"dropdown is-hoverable is-last is-top minimal\\">
<div data-v-539164cb=\\"\\" class=\\"dropdown-trigger\\"><button data-v-539164cb=\\"\\" class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\"><span data-v-539164cb=\\"\\" class=\\"icon\\"><svg data-v-539164cb=\\"\\" viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div data-v-539164cb=\\"\\" class=\\"dropdown-menu\\" id=\\"dropdown-menu\\" role=\\"menu\\">
<div data-v-539164cb=\\"\\" class=\\"dropdown-content\\"><a data-v-2e92daca=\\"\\" class=\\"dropdown-item\\" href=\\"#2\\">
<div data-v-2e92daca=\\"\\" class=\\"level is-justify-content-start\\">
<div data-v-2e92daca=\\"\\" class=\\"level-left\\">
<div data-v-2e92daca=\\"\\" class=\\"level-item\\"><svg data-v-2e92daca=\\"\\" viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\">
<path fill=\\"currentColor\\" d=\\"M334.627 16H48v480h424V153.373ZM440 464H80V48h241.373L440 166.627Z\\"></path>
<path fill=\\"currentColor\\" d=\\"M239.861 152a95.861 95.861 0 1 0 53.624 175.284l68.03 68.029l22.627-22.626l-67.5-67.5A95.816 95.816 0 0 0 239.861 152ZM176 247.861a63.862 63.862 0 1 1 63.861 63.861A63.933 63.933 0 0 1 176 247.861Z\\"></path>
</svg></div>
</div>
<div class=\\"level-right\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\">Jump to Context</div>
<div data-v-2e92daca=\\"\\" class=\\"level-right\\">
<div data-v-2e92daca=\\"\\" class=\\"level-item\\">Jump to Context</div>
</div>
</div>
</a></div>
</div>
</div>
</div>
<div class=\\"columns is-1 is-variable\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\">06/12/2019 10:55:42 AM</time></div>
<div class=\\"text column\\" data-v-a49e52d4=\\"\\"><mark>test</mark> bar</div>
<div data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\" class=\\"columns is-1 is-variable is-mobile\\" visible-keys=\\"\\">
<!--v-if-->
<div data-v-a49e52d4=\\"\\" class=\\"column is-narrow\\">
<div data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\" class=\\"date\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"is-hidden-mobile\\">06/12/2019</time> <time datetime=\\"2019-06-12T10:55:42.459Z\\">10:55:42 AM</time></div>
</div>
<div data-v-a49e52d4=\\"\\" class=\\"column is-narrow is-flex\\">
<div data-v-e625cddd=\\"\\" data-v-a49e52d4=\\"\\" class=\\"\\"></div>
</div>
<div data-v-a49e52d4=\\"\\" class=\\"text column\\"><mark>test</mark> bar</div>
</div>
</li>
</ul>"
`;
exports[`<LogEventSource /> > render html correctly > should render messages with html entities 1`] = `
"<ul class=\\"events medium\\" data-v-2e92daca=\\"\\">
<li data-key=\\"1\\" class=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"line-options\\" data-v-2e92daca=\\"\\" style=\\"display: none;\\">
<div class=\\"dropdown is-hoverable is-last is-top minimal\\" data-v-539164cb=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"dropdown-trigger\\" data-v-539164cb=\\"\\"><button class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\" data-v-539164cb=\\"\\"><span class=\\"icon\\" data-v-539164cb=\\"\\"><svg viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\" data-v-539164cb=\\"\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div class=\\"dropdown-menu\\" id=\\"dropdown-menu\\" role=\\"menu\\" data-v-539164cb=\\"\\">
<div class=\\"dropdown-content\\" data-v-539164cb=\\"\\"><a class=\\"dropdown-item\\" href=\\"#1\\" data-v-2e92daca=\\"\\">
<div class=\\"level is-justify-content-start\\" data-v-2e92daca=\\"\\">
<div class=\\"level-left\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\"><svg viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-2e92daca=\\"\\">
"<ul data-v-2e92daca=\\"\\" class=\\"events medium\\">
<li data-v-2e92daca=\\"\\" data-key=\\"1\\" class=\\"\\">
<div data-v-2e92daca=\\"\\" class=\\"line-options\\" style=\\"display: none;\\">
<div data-v-539164cb=\\"\\" data-v-2e92daca=\\"\\" class=\\"dropdown is-hoverable is-last is-top minimal\\">
<div data-v-539164cb=\\"\\" class=\\"dropdown-trigger\\"><button data-v-539164cb=\\"\\" class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\"><span data-v-539164cb=\\"\\" class=\\"icon\\"><svg data-v-539164cb=\\"\\" viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div data-v-539164cb=\\"\\" class=\\"dropdown-menu\\" id=\\"dropdown-menu\\" role=\\"menu\\">
<div data-v-539164cb=\\"\\" class=\\"dropdown-content\\"><a data-v-2e92daca=\\"\\" class=\\"dropdown-item\\" href=\\"#1\\">
<div data-v-2e92daca=\\"\\" class=\\"level is-justify-content-start\\">
<div data-v-2e92daca=\\"\\" class=\\"level-left\\">
<div data-v-2e92daca=\\"\\" class=\\"level-item\\"><svg data-v-2e92daca=\\"\\" viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\">
<path fill=\\"currentColor\\" d=\\"M334.627 16H48v480h424V153.373ZM440 464H80V48h241.373L440 166.627Z\\"></path>
<path fill=\\"currentColor\\" d=\\"M239.861 152a95.861 95.861 0 1 0 53.624 175.284l68.03 68.029l22.627-22.626l-67.5-67.5A95.816 95.816 0 0 0 239.861 152ZM176 247.861a63.862 63.862 0 1 1 63.861 63.861A63.933 63.933 0 0 1 176 247.861Z\\"></path>
</svg></div>
</div>
<div class=\\"level-right\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\">Jump to Context</div>
<div data-v-2e92daca=\\"\\" class=\\"level-right\\">
<div data-v-2e92daca=\\"\\" class=\\"level-item\\">Jump to Context</div>
</div>
</div>
</a></div>
</div>
</div>
</div>
<div class=\\"columns is-1 is-variable\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\">06/12/2019 10:55:42 AM</time></div>
<div class=\\"text column\\" data-v-a49e52d4=\\"\\">&lt;test&gt;foo bar&lt;/test&gt;</div>
<div data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\" class=\\"columns is-1 is-variable is-mobile\\" visible-keys=\\"\\">
<!--v-if-->
<div data-v-a49e52d4=\\"\\" class=\\"column is-narrow\\">
<div data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\" class=\\"date\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"is-hidden-mobile\\">06/12/2019</time> <time datetime=\\"2019-06-12T10:55:42.459Z\\">10:55:42 AM</time></div>
</div>
<div data-v-a49e52d4=\\"\\" class=\\"column is-narrow is-flex\\">
<div data-v-e625cddd=\\"\\" data-v-a49e52d4=\\"\\" class=\\"\\"></div>
</div>
<div data-v-a49e52d4=\\"\\" class=\\"text column\\">&lt;test&gt;foo bar&lt;/test&gt;</div>
</div>
</li>
</ul>"
`;
exports[`<LogEventSource /> > renders correctly 1`] = `
"<div class=\\"infinte-loader\\" data-v-1cd63c6e=\\"\\">
<div class=\\"spinner\\" data-v-1cd63c6e=\\"\\" style=\\"display: none;\\">
<div class=\\"bounce1\\" data-v-1cd63c6e=\\"\\"></div>
<div class=\\"bounce2\\" data-v-1cd63c6e=\\"\\"></div>
<div class=\\"bounce3\\" data-v-1cd63c6e=\\"\\"></div>
"<div data-v-1cd63c6e=\\"\\" class=\\"infinte-loader\\">
<div data-v-1cd63c6e=\\"\\" class=\\"spinner\\" style=\\"display: none;\\">
<div data-v-1cd63c6e=\\"\\" class=\\"bounce1\\"></div>
<div data-v-1cd63c6e=\\"\\" class=\\"bounce2\\"></div>
<div data-v-1cd63c6e=\\"\\" class=\\"bounce3\\"></div>
</div>
</div>
<ul class=\\"events medium\\" data-v-2e92daca=\\"\\"></ul>"
<ul data-v-2e92daca=\\"\\" class=\\"events medium\\"></ul>"
`;
exports[`<LogEventSource /> > should parse messages 1`] = `
@@ -202,5 +239,8 @@ SimpleLogEntry {
"_message": "This is a message.",
"date": 2019-06-12T10:55:42.459Z,
"id": 1,
"level": undefined,
"position": undefined,
"std": "stderr",
}
`;
-96
View File
@@ -1,96 +0,0 @@
<template>
<aside>
<div class="columns is-marginless is-gapless is-mobile is-vcentered">
<div class="column is-narrow">
<router-link :to="{ name: 'index' }">
<svg class="logo">
<use href="#logo"></use>
</svg>
</router-link>
</div>
<div class="column ml-4 is-family-monospace is-ellipsis" v-if="$route.name == 'container'">
{{ allContainersById[route.params.id].name }}
</div>
<div class="column is-narrow push-right">
<a
role="button"
class="navbar-burger burger is-hidden-tablet is-pulled-right"
@click="showNav = !showNav"
:class="{ 'is-active': showNav }"
>
<span></span> <span></span> <span></span>
</a>
</div>
</div>
<p class="menu-label is-hidden-mobile" :class="{ 'is-active': showNav }">{{ $t("label.containers") }}</p>
<ul class="menu-list is-hidden-mobile" :class="{ 'is-active': showNav }">
<li v-for="item in visibleContainers" :key="item.id">
<router-link
:to="{ name: 'container-id', params: { id: item.id } }"
active-class="is-active"
:title="item.name"
>
<div class="is-ellipsis">
{{ item.name }}
</div>
</router-link>
</li>
</ul>
</aside>
</template>
<script lang="ts" setup>
const store = useContainerStore();
const route = useRoute();
const { visibleContainers, allContainersById } = storeToRefs(store);
let showNav = $ref(false);
watch(route, () => {
showNav = false;
});
</script>
<style scoped lang="scss">
aside {
padding: 1em;
position: fixed;
left: 0;
right: 0;
background: var(--scheme-main-ter);
z-index: 10;
max-height: 100vh;
overflow: auto;
.menu-label {
margin-top: 1em;
}
.title {
text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.2);
}
.burger {
color: var(--body-color);
}
.is-hidden-mobile.is-active {
display: block !important;
}
.navbar-burger {
height: 2.35rem;
}
.logo {
width: 82px;
height: 36px;
fill: var(--logo-color);
}
.column.push-right {
margin-left: auto;
}
}
</style>
-19
View File
@@ -1,19 +0,0 @@
<template>
<time :datetime="date.toISOString()">{{ text }}</time>
</template>
<script lang="ts" setup>
import formatDistance from "date-fns/formatDistance";
const { date } = defineProps<{
date: Date;
}>();
const text = ref<string>();
function updateFromNow() {
text.value = formatDistance(date, new Date(), {
addSuffix: true,
});
}
useIntervalFn(updateFromNow, 30_000, { immediateCallback: true });
</script>
+51
View File
@@ -0,0 +1,51 @@
<template>
<div @mouseenter="onMouseEnter" @mouseleave="onMouseLeave" ref="trigger"><slot></slot></div>
<Teleport to="body">
<Transition name="fade">
<div v-show="show && (delayedShow || glopbalShow)" class="content" ref="content">
<slot name="content"></slot>
</div>
</Transition>
</Teleport>
</template>
<script lang="ts" setup>
import { globalShowPopup } from "@/composables/popup";
let glopbalShow = globalShowPopup();
let show = ref(glopbalShow.value);
let delayedShow = refDebounced(show, 1000);
let content: HTMLElement | null = $ref(null);
let trigger: HTMLElement | null = $ref(null);
function onMouseEnter(e: MouseEvent) {
show.value = true;
glopbalShow.value = true;
if (e.target && content && e.target instanceof Element) {
const { left, top, width } = e.target.getBoundingClientRect();
const x = left + width + 10;
const y = top;
content.style.left = `${x}px`;
content.style.top = `${y}px`;
}
}
function onMouseLeave() {
show.value = false;
glopbalShow.value = false;
}
</script>
<style scoped>
.content {
position: fixed;
z-index: 9999;
background: var(--scheme-main-ter);
border-radius: 0.5em;
padding: 1em;
box-shadow: 0 0 0.5em rgba(0, 0, 0, 0.5);
border: 1px solid var(--border-color);
}
</style>
-21
View File
@@ -1,21 +0,0 @@
<template>
<time :datetime="date.toISOString()">{{ format(date) }}</time>
</template>
<script lang="ts" setup>
defineProps<{
date: Date;
}>();
const dateFormatter = new Intl.DateTimeFormat(undefined, { day: "2-digit", month: "2-digit", year: "numeric" });
const use12Hour = $computed(() => ({ auto: undefined, "12": true, "24": false }[hourStyle.value]));
const timeFormatter = $computed(
() => new Intl.DateTimeFormat(undefined, { hour: "numeric", minute: "2-digit", second: "2-digit", hour12: use12Hour })
);
function format(date: Date) {
const dateStr = dateFormatter.format(date);
const timeStr = timeFormatter.format(date);
return `${dateStr} ${timeStr}`;
}
</script>
+4 -4
View File
@@ -29,7 +29,7 @@ const root = ref<HTMLElement>();
const store = useContainerStore();
const { activeContainers } = storeToRefs(store);
const scrollElement = ref<HTMLElement | Document>((root.value?.closest("[data-scrolling]") as HTMLElement) ?? document);
const { y: scrollY } = useScroll(scrollElement, { throttle: 100 });
const { y: scrollY } = useScroll(scrollElement as Ref<HTMLElement | Document>, { throttle: 100 });
onMounted(() => {
watch(
@@ -37,7 +37,7 @@ onMounted(() => {
() => {
scrollElement.value = (root.value?.closest("[data-scrolling]") as HTMLElement) ?? document;
},
{ immediate: true, flush: "post" }
{ immediate: true, flush: "post" },
);
});
@@ -46,7 +46,7 @@ watchPostEffect(() => {
scrollElement.value === document
? (scrollElement.value as Document).documentElement
: (scrollElement.value as HTMLElement);
scrollProgress.value = scrollY.value / (parent.scrollHeight - parent.clientHeight);
scrollProgress.value = Math.min(scrollY.value / (parent.scrollHeight - parent.clientHeight), 1);
animation.value.cancel();
if (autoHide && root.value) {
animation.value = root.value.animate(
@@ -56,7 +56,7 @@ watchPostEffect(() => {
delay: 2000,
fill: "both",
easing: "ease-out",
}
},
);
}
});
+20 -2
View File
@@ -22,7 +22,7 @@
@click="scrollToBottom()"
v-show="paused"
>
<mdi-light-chevron-double-down />
<mdi:light-chevron-double-down />
</button>
</transition>
</div>
@@ -45,7 +45,8 @@ const mutationObserver = new MutationObserver((e) => {
scrollToBottom();
} else {
const record = e[e.length - 1];
if (record.target.children[record.target.children.length - 1] == record.addedNodes[record.addedNodes.length - 1]) {
const children = (record.target as HTMLElement).children;
if (children[children.length - 1] == record.addedNodes[record.addedNodes.length - 1]) {
hasMore = true;
}
}
@@ -81,6 +82,10 @@ section {
background: var(--body-background-color);
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
z-index: 1;
@media screen and (max-width: 768px) {
top: 70px;
}
}
&.is-full-height-scrollable {
@@ -97,6 +102,7 @@ section {
.is-scrollbar-progress {
text-align: right;
margin-right: 110px;
.scroll-progress {
position: fixed;
top: 60px;
@@ -111,6 +117,7 @@ section {
.is-scrollbar-notification {
text-align: right;
margin-right: 65px;
button {
position: fixed;
bottom: 30px;
@@ -138,9 +145,11 @@ section {
100% {
transform: translateY(0);
}
40% {
transform: translateY(-30px);
}
60% {
transform: translateY(-15px);
}
@@ -150,9 +159,18 @@ section {
.fade-leave-active {
transition: opacity 0.15s ease-in;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
}
</style>
<style>
@media screen and (max-width: 768px) {
.splitpanes__pane {
overflow: unset !important;
}
}
</style>
+6 -2
View File
@@ -11,7 +11,7 @@
@keyup.esc="resetSearch()"
/>
<span class="icon is-left">
<mdi-light-magnify />
<mdi:light-magnify />
</span>
</p>
</div>
@@ -46,12 +46,15 @@ onUnmounted(() => resetSearch());
right: 0;
border-radius: 0 0 0 5px;
z-index: 10;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
box-shadow:
0 1px 3px rgba(0, 0, 0, 0.12),
0 1px 2px rgba(0, 0, 0, 0.24);
button.delete {
margin-left: 1em;
background-color: var(--scheme-main-ter);
opacity: 0.6;
&:after,
&:before {
background-color: var(--text-color);
@@ -68,6 +71,7 @@ onUnmounted(() => resetSearch());
.input {
color: var(--body-color);
&::placeholder {
color: var(--border-color);
}
+141 -88
View File
@@ -1,115 +1,168 @@
<template>
<aside>
<div class="columns is-marginless">
<div class="column is-paddingless">
<router-link :to="{ name: 'index' }">
<svg class="logo">
<use href="#logo"></use>
</svg>
</router-link>
</div>
<div class="column is-narrow has-text-right px-1">
<button class="button is-rounded" @click="$emit('search')" :title="$t('tooltip.search')">
<span class="icon">
<mdi-light-magnify />
</span>
</button>
</div>
<div class="column is-narrow has-text-right px-0">
<router-link :to="{ name: 'settings' }" active-class="is-active" class="button is-rounded">
<span class="icon">
<mdi-light-cog />
</span>
</router-link>
</div>
</div>
<p class="menu-label is-hidden-mobile">{{ $t("label.containers") }}</p>
<ul class="menu-list is-hidden-mobile" v-if="ready">
<li v-for="item in visibleContainers" :key="item.id" :class="item.state">
<router-link
:to="{ name: 'container-id', params: { id: item.id } }"
active-class="is-active"
:title="item.name"
>
<div class="container is-flex is-align-items-center">
<div class="is-flex-grow-1 is-ellipsis">
{{ item.name }}
</div>
<div class="is-flex-shrink-1 column-icon">
<span
class="icon is-small"
@click.stop.prevent="store.appendActiveContainer(item)"
v-show="!activeContainersById[item.id]"
:title="$t('tooltip.pin-column')"
>
<cil-columns />
</span>
</div>
</div>
</router-link>
</li>
</ul>
<ul class="menu-list is-hidden-mobile loading" v-else>
<li v-for="index in 7" class="my-4"><o-skeleton animated size="large" :key="index"></o-skeleton></li>
</ul>
</aside>
<div v-if="ready">
<nav class="breadcrumb menu-label" aria-label="breadcrumbs">
<ul v-if="sessionHost">
<li>
<a href="#" @click.prevent="setHost(null)">{{ hosts[sessionHost].name }}</a>
</li>
<li class="is-active">
<a href="#" aria-current="page">{{ $t("label.containers") }}</a>
</li>
</ul>
<ul v-else>
<li>Hosts</li>
</ul>
</nav>
<transition :name="sessionHost ? 'slide-left' : 'slide-right'" mode="out-in">
<ul class="menu-list" v-if="!sessionHost">
<li v-for="host in config.hosts">
<a @click.prevent="setHost(host.id)">{{ host.name }}</a>
</li>
</ul>
<ul class="menu-list" v-else>
<li v-for="item in sortedContainers" :key="item.id" :class="item.state">
<popup>
<router-link
:to="{ name: 'container-id', params: { id: item.id } }"
active-class="is-active"
:title="item.name"
>
<div class="container is-flex is-align-items-center">
<div class="is-flex-grow-1 is-ellipsis">
<span>{{ item.name }}</span
><span class="has-text-weight-light has-light-opacity" v-if="item.isSwarm">{{ item.swarmId }}</span>
</div>
<div class="is-flex-shrink-1 is-flex icons">
<div
class="icon is-small pin"
@click.stop.prevent="store.appendActiveContainer(item)"
v-show="!activeContainersById[item.id]"
:title="$t('tooltip.pin-column')"
>
<cil:columns />
</div>
<container-health :health="item.health"></container-health>
</div>
</div>
</router-link>
<template #content>
<container-popup :container="item"></container-popup>
</template>
</popup>
</li>
</ul>
</transition>
</div>
<ul class="menu-list is-hidden-mobile has-light-opacity" v-else>
<li v-for="index in 7" class="my-4"><o-skeleton animated size="large" :key="index"></o-skeleton></li>
</ul>
</template>
<script lang="ts" setup>
import type { Container } from "@/types/Container";
import { Container } from "@/models/Container";
import { sessionHost } from "@/composables/storage";
const store = useContainerStore();
const { activeContainers, visibleContainers, ready } = storeToRefs(store);
function setHost(host: string | null) {
sessionHost.value = host;
}
const sortedContainers = computed(() =>
visibleContainers.value
.filter((c) => c.host === sessionHost.value)
.sort((a, b) => {
if (a.state === "running" && b.state !== "running") {
return -1;
} else if (a.state !== "running" && b.state === "running") {
return 1;
} else {
return a.name.localeCompare(b.name);
}
}),
);
const hosts = computed(() =>
config.hosts.reduce(
(acc, item) => {
acc[item.id] = item;
return acc;
},
{} as Record<string, { name: string; id: string }>,
),
);
const activeContainersById = computed(() =>
activeContainers.value.reduce((acc, item) => {
acc[item.id] = item;
return acc;
}, {} as Record<string, Container>)
activeContainers.value.reduce(
(acc, item) => {
acc[item.id] = item;
return acc;
},
{} as Record<string, Container>,
),
);
</script>
<style scoped lang="scss">
aside {
padding: 1em;
height: 100vh;
overflow: auto;
position: fixed;
width: inherit;
.is-hidden-mobile.is-active {
display: block !important;
}
}
.loading {
.has-light-opacity {
opacity: 0.5;
}
li.exited a {
.has-light-opacity {
opacity: 0.5;
}
li.exited a,
li.dead a {
color: #777;
}
.logo {
width: 122px;
height: 54px;
fill: var(--logo-color);
.icons {
column-gap: 0.35em;
align-items: baseline;
}
a {
.pin {
display: none;
.menu-list li {
.column-icon {
visibility: hidden;
& > span {
vertical-align: middle;
}
}
&:hover .column-icon {
visibility: visible;
&:hover {
color: var(--secondary-color);
}
}
&:hover {
.pin {
display: block;
}
}
}
.slide-left-enter-active,
.slide-left-leave-active,
.slide-right-enter-active,
.slide-right-leave-active {
transition: all 0.1s ease-out;
}
.slide-left-enter-from {
opacity: 0;
transform: translateX(100%);
}
.slide-right-enter-from {
opacity: 0;
transform: translateX(-100%);
}
.slide-left-leave-to {
opacity: 0;
transform: translateX(-100%);
}
.slide-right-leave-to {
opacity: 0;
transform: translateX(100%);
}
</style>
+71
View File
@@ -0,0 +1,71 @@
<template>
<aside>
<div class="columns is-marginless">
<div class="column is-paddingless">
<h1>
<router-link :to="{ name: 'index' }">
<svg class="logo">
<use href="#logo"></use>
</svg>
</router-link>
<small class="subtitle is-6 is-block mb-4" v-if="hostname">
{{ hostname }}
</small>
</h1>
</div>
</div>
<div class="columns is-marginless">
<div class="column is-narrow py-0 pl-0 pr-1">
<button class="button is-rounded is-small" @click="$emit('search')" :title="$t('tooltip.search')">
<span class="icon">
<mdi:light-magnify />
</span>
</button>
</div>
<div class="column is-narrow py-0" :class="secured ? 'pl-0 pr-1' : 'px-0'">
<router-link
:to="{ name: 'settings' }"
active-class="is-active"
class="button is-rounded is-small"
:aria-label="$t('title.settings')"
>
<span class="icon">
<mdi:light-cog />
</span>
</router-link>
</div>
<div class="column is-narrow py-0 px-0" v-if="secured">
<a class="button is-rounded is-small" :href="`${base}/logout`" :title="$t('button.logout')">
<span class="icon">
<mdi:light-logout />
</span>
</a>
</div>
</div>
<side-menu class="mt-4"></side-menu>
</aside>
</template>
<script lang="ts" setup>
const { base, secured, hostname } = config;
</script>
<style scoped lang="scss">
aside {
padding: 1em;
height: 100vh;
overflow: auto;
position: fixed;
width: inherit;
.is-hidden-mobile.is-active {
display: block !important;
}
}
.logo {
width: 122px;
height: 54px;
fill: var(--logo-color);
}
</style>
+22
View File
@@ -0,0 +1,22 @@
<template>
<div>
<time :datetime="date.toISOString()" class="is-hidden-mobile">{{ dateStr }}</time>
<time :datetime="date.toISOString()">{{ timeStr }}</time>
</div>
</template>
<script lang="ts" setup>
const props = defineProps<{
date: Date;
}>();
const dateFormatter = new Intl.DateTimeFormat(undefined, { day: "2-digit", month: "2-digit", year: "numeric" });
const use12Hour = $computed(() => ({ auto: undefined, "12": true, "24": false })[hourStyle.value]);
const timeFormatter = $computed(
() =>
new Intl.DateTimeFormat(undefined, { hour: "numeric", minute: "2-digit", second: "2-digit", hour12: use12Hour }),
);
const dateStr = $computed(() => dateFormatter.format(props.date));
const timeStr = $computed(() => timeFormatter.format(props.date));
</script>
+30
View File
@@ -0,0 +1,30 @@
<template>
<time :datetime="date.toISOString()" data-ci-skip>{{ text }}</time>
</template>
<script lang="ts" setup>
import formatDistanceToNow from "date-fns/formatDistanceToNow";
import formatDistanceToNowStrict from "date-fns/formatDistanceToNowStrict";
const {
date,
strict = false,
suffix = true,
} = defineProps<{
date: Date;
strict?: boolean;
suffix?: boolean;
}>();
const text = ref<string>();
watch($$(date), updateFromNow, { immediate: true });
function updateFromNow() {
const fn = strict ? formatDistanceToNowStrict : formatDistanceToNow;
text.value = fn(date, {
addSuffix: suffix,
});
}
useIntervalFn(updateFromNow, 30_000, { immediateCallback: true });
</script>
+170
View File
@@ -0,0 +1,170 @@
<template>
<aside>
<div class="columns is-marginless is-gapless is-mobile is-vcentered">
<div class="column is-narrow">
<router-link :to="{ name: 'index' }">
<svg class="logo">
<use href="#logo"></use>
</svg>
</router-link>
</div>
<div class="column is-narrow push-right">
<a
role="button"
class="navbar-burger burger is-hidden-tablet is-pulled-right"
@click="showNav = !showNav"
:class="{ 'is-active': showNav }"
>
<span></span> <span></span> <span></span>
</a>
</div>
</div>
<div class="menu-label level is-mobile is-hidden-mobile" :class="{ 'is-active': showNav }">
<div v-if="config.hosts.length > 1">
<o-dropdown v-model="sessionHost" aria-role="list">
<template #trigger>
<o-button variant="primary" type="button" size="small">
<span>{{ sessionHost ? hosts[sessionHost].name : "" }}</span>
<span class="icon">
<carbon:caret-down />
</span>
</o-button>
</template>
<o-dropdown-item :value="value.id" aria-role="listitem" v-for="value in config.hosts" :key="value">
<span>{{ value.name }}</span>
</o-dropdown-item>
</o-dropdown>
</div>
<div class="level-item has-text-centered">
<div>
<button class="button is-small is-rounded" @click="$emit('search')" :title="$t('tooltip.search')">
<span class="icon">
<mdi:light-magnify />
</span>
</button>
</div>
</div>
<div class="level-item has-text-centered">
<div>
<router-link :to="{ name: 'settings' }" active-class="is-active" class="button is-small is-rounded">
<span class="icon">
<mdi:light-cog />
</span>
</router-link>
</div>
</div>
<div class="level-item has-text-centered" v-if="secured">
<div>
<a class="button is-small is-rounded" :href="`${base}/logout`" :title="$t('button.logout')">
<span class="icon">
<mdi:light-logout />
</span>
</a>
</div>
</div>
</div>
<p class="menu-label is-hidden-mobile" :class="{ 'is-active': showNav }">{{ $t("label.containers") }}</p>
<ul class="menu-list is-hidden-mobile" :class="{ 'is-active': showNav }">
<li v-for="item in sortedContainers" :key="item.id">
<router-link
:to="{ name: 'container-id', params: { id: item.id } }"
active-class="is-active"
:title="item.name"
>
<div class="is-ellipsis">
{{ item.name }}
</div>
</router-link>
</li>
</ul>
</aside>
</template>
<script lang="ts" setup>
const { base, secured } = config;
import { sessionHost } from "@/composables/storage";
const store = useContainerStore();
const route = useRoute();
const { visibleContainers } = storeToRefs(store);
let showNav = $ref(false);
watch(route, () => {
showNav = false;
});
const sortedContainers = computed(() =>
visibleContainers.value
.filter((c) => c.host === sessionHost.value)
.sort((a, b) => {
if (a.state === "running" && b.state !== "running") {
return -1;
} else if (a.state !== "running" && b.state === "running") {
return 1;
} else {
return a.name.localeCompare(b.name);
}
}),
);
const hosts = computed(() =>
config.hosts.reduce(
(acc, item) => {
acc[item.id] = item;
return acc;
},
{} as Record<string, { name: string; id: string }>,
),
);
</script>
<style scoped lang="scss">
aside {
padding: 1em;
position: fixed;
left: 0;
right: 0;
background: var(--scheme-main-ter);
z-index: 10;
max-height: 100vh;
overflow: auto;
.level.is-hidden-mobile.is-active {
display: flex !important;
}
.menu-label {
margin-top: 1em;
}
.title {
text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.2);
}
.burger {
color: var(--body-color);
}
.is-hidden-mobile.is-active {
display: block !important;
}
.navbar-burger {
height: 2.35rem;
}
.logo {
width: 82px;
height: 36px;
fill: var(--logo-color);
}
.column.push-right {
margin-left: auto;
}
}
</style>
+19
View File
@@ -0,0 +1,19 @@
<template>
<div class="tag" :size="size">
<slot></slot>
</div>
</template>
<script lang="ts" setup>
const { size = undefined } = defineProps<{ size?: "small" | undefined }>();
</script>
<style scoped lang="scss">
.tag {
background-color: var(--scheme-main-ter);
border: 1px solid var(--border-color);
&[size="small"] {
font-size: 0.61rem;
}
}
</style>
+51 -6
View File
@@ -1,4 +1,5 @@
import { type ComputedRef, type Ref } from "vue";
import { encodeXML } from "entities";
import debounce from "lodash.debounce";
import {
type LogEvent,
@@ -11,11 +12,21 @@ import {
import { Container } from "@/models/Container";
function parseMessage(data: string): LogEntry<string | JSONObject> {
const e = JSON.parse(data) as LogEvent;
const e = JSON.parse(data, (key, value) => {
if (typeof value === "string") {
return encodeXML(value);
}
return value;
}) as LogEvent;
return asLogEntry(e);
}
export function useLogStream(container: ComputedRef<Container>) {
type LogStreamConfig = {
stdout: boolean;
stderr: boolean;
};
export function useLogStream(container: ComputedRef<Container>, streamConfig: LogStreamConfig) {
let messages: LogEntry<string | JSONObject>[] = $ref([]);
let buffer: LogEntry<string | JSONObject>[] = $ref([]);
const scrollingPaused = $ref(inject("scrollingPaused") as Ref<boolean>);
@@ -58,7 +69,22 @@ export function useLogStream(container: ComputedRef<Container>) {
lastEventId = "";
}
es = new EventSource(`${config.base}/api/logs/stream?id=${container.value.id}&lastEventId=${lastEventId}`);
const params = {
lastEventId,
} as { lastEventId: string; stdout?: string; stderr?: string };
if (streamConfig.stdout) {
params.stdout = "1";
}
if (streamConfig.stderr) {
params.stderr = "1";
}
es = new EventSource(
`${config.base}/api/logs/stream/${container.value.host}/${container.value.id}?${new URLSearchParams(
params,
).toString()}`,
);
es.addEventListener("container-stopped", () => {
es?.close();
es = null;
@@ -85,8 +111,25 @@ export function useLogStream(container: ComputedRef<Container>) {
const last = messages[299].date;
const delta = to.getTime() - last.getTime();
const from = new Date(to.getTime() + delta);
const params = {
from: from.toISOString(),
to: to.toISOString(),
} as { from: string; to: string; stdout?: string; stderr?: string };
if (streamConfig.stdout) {
params.stdout = "1";
}
if (streamConfig.stderr) {
params.stderr = "1";
}
const logs = await (
await fetch(`${config.base}/api/logs?id=${container.value.id}&from=${from.toISOString()}&to=${to.toISOString()}`)
await fetch(
`${config.base}/api/logs/${container.value.host}/${container.value.id}?${new URLSearchParams(
params,
).toString()}`,
)
).text();
if (logs) {
const newMessages = logs
@@ -106,7 +149,7 @@ export function useLogStream(container: ComputedRef<Container>) {
buffer.push(new DockerEventLogEntry("Container started", new Date(), "container-started"));
connect({ clear: false });
}
}
},
);
onUnmounted(() => {
@@ -118,8 +161,10 @@ export function useLogStream(container: ComputedRef<Container>) {
watch(
() => container.value.id,
() => connect(),
{ immediate: true }
{ immediate: true },
);
watch(streamConfig, () => connect());
return { ...$$({ messages }), loadOlderLogs };
}
+13
View File
@@ -0,0 +1,13 @@
const show = ref(false);
const debouncedShow = debouncedRef(show, 1000);
const delayedShow = computed({
set(newVal: boolean) {
show.value = newVal;
},
get() {
return debouncedShow.value;
},
});
export const globalShowPopup = () => delayedShow;
+9 -5
View File
@@ -1,5 +1,6 @@
import { type Ref } from "vue";
import { type LogEntry, type JSONObject, SimpleLogEntry, ComplexLogEntry } from "@/models/LogEntry";
import { encodeXML } from "entities";
const searchFilter = ref<string>("");
const debouncedSearchFilter = useDebounce(searchFilter);
@@ -11,6 +12,9 @@ function matchRecord(record: Record<string, any>, regex: RegExp): boolean {
if (typeof value === "string" && regex.test(value)) {
return true;
}
if (isObject(value) && regex.test(JSON.stringify(value))) {
return true;
}
if (Array.isArray(value) && matchRecord(value, regex)) {
return true;
}
@@ -19,9 +23,9 @@ function matchRecord(record: Record<string, any>, regex: RegExp): boolean {
}
export function useSearchFilter() {
const regex = computed(() => {
const regex = $computed(() => {
const isSmartCase = debouncedSearchFilter.value === debouncedSearchFilter.value.toLowerCase();
return isSmartCase ? new RegExp(debouncedSearchFilter.value, "i") : new RegExp(debouncedSearchFilter.value);
return new RegExp(encodeXML(debouncedSearchFilter.value), isSmartCase ? "i" : undefined);
});
function filteredMessages(messages: Ref<LogEntry<string | JSONObject>[]>) {
@@ -30,9 +34,9 @@ export function useSearchFilter() {
try {
return messages.value.filter((d) => {
if (d instanceof SimpleLogEntry) {
return regex.value.test(d.message);
return regex.test(d.message);
} else if (d instanceof ComplexLogEntry) {
return matchRecord(d.message, regex.value);
return matchRecord(d.message, regex);
}
return false;
});
@@ -59,7 +63,7 @@ export function useSearchFilter() {
return log.map((d) => markSearch(d));
}
return log.toString().replace(regex.value, (match) => `<mark>${match}</mark>`);
return log.toString().replace(regex, (match) => `<mark>${match}</mark>`);
}
function resetSearch() {
+17 -1
View File
@@ -6,20 +6,24 @@ export const DEFAULT_SETTINGS: {
menuWidth: number;
smallerScrollbars: boolean;
showTimestamp: boolean;
showStd: boolean;
showAllContainers: boolean;
lightTheme: "auto" | "dark" | "light";
hourStyle: "auto" | "24" | "12";
softWrap: boolean;
collapseNav: boolean;
} = {
search: true,
size: "medium",
menuWidth: 15,
smallerScrollbars: false,
showTimestamp: true,
showStd: false,
showAllContainers: false,
lightTheme: "auto",
hourStyle: "auto",
softWrap: true,
collapseNav: false,
};
const settings = useStorage(DOZZLE_SETTINGS_KEY, DEFAULT_SETTINGS);
@@ -49,6 +53,11 @@ const showTimestamp = computed({
set: (value) => (settings.value.showTimestamp = value),
});
const showStd = computed({
get: () => settings.value.showStd,
set: (value) => (settings.value.showStd = value),
});
const showAllContainers = computed({
get: () => settings.value.showAllContainers,
set: (value) => (settings.value.showAllContainers = value),
@@ -69,15 +78,22 @@ const softWrap = computed({
set: (value) => (settings.value.softWrap = value),
});
const collapseNav = computed({
get: () => settings.value.collapseNav,
set: (value) => (settings.value.collapseNav = value),
});
export {
collapseNav,
softWrap,
hourStyle,
lightTheme,
showAllContainers,
showTimestamp,
showStd,
smallerScrollbars,
menuWidth,
size,
search,
settings
settings,
};
+13
View File
@@ -0,0 +1,13 @@
import { Container } from "@/models/Container";
const sessionHost = useSessionStorage<string | null>("host", null);
if (config.hosts.length === 1 && !sessionHost.value) {
sessionHost.value = config.hosts[0].id;
}
function persistentVisibleKeys(container: ComputedRef<Container>) {
return computed(() => useStorage(stripVersion(container.value.image) + ":" + container.value.command, []));
}
export { sessionHost, persistentVisibleKeys };
+2 -1
View File
@@ -1,5 +1,6 @@
const { hostname } = config;
let subtitle = $ref("");
const title = $computed(() => `${subtitle} - Dozzle`);
const title = $computed(() => `${subtitle} - Dozzle` + (hostname ? ` @ ${hostname}` : ""));
useTitle($$(title));
+13 -8
View File
@@ -1,9 +1,9 @@
<template>
<main v-if="!authorizationNeeded">
<mobile-menu v-if="isMobile"></mobile-menu>
<mobile-menu v-if="isMobile" @search="showFuzzySearch"></mobile-menu>
<splitpanes @resized="onResized($event)">
<pane min-size="10" :size="menuWidth" v-if="!isMobile && !collapseNav">
<side-menu @search="showFuzzySearch"></side-menu>
<side-panel @search="showFuzzySearch"></side-panel>
</pane>
<pane min-size="10">
<splitpanes>
@@ -25,17 +25,17 @@
</pane>
</splitpanes>
<button
@click="collapseNav = !collapseNav"
class="button is-rounded"
@click="collapse"
class="button is-small is-rounded"
:class="{ collapsed: collapseNav }"
id="hide-nav"
v-if="!isMobile"
>
<span class="icon ml-2" v-if="collapseNav">
<mdi-light-chevron-right />
<mdi:light-chevron-right />
</span>
<span class="icon" v-else>
<mdi-light-chevron-left />
<mdi:light-chevron-left />
</span>
</button>
</main>
@@ -47,7 +47,6 @@ import { Splitpanes, Pane } from "splitpanes";
import { useProgrammatic } from "@oruga-ui/oruga-next";
import FuzzySearchModal from "@/components/FuzzySearchModal.vue";
const collapseNav = ref(false);
const { oruga } = useProgrammatic();
const { authorizationNeeded } = config;
@@ -74,6 +73,9 @@ function showFuzzySearch() {
active: true,
});
}
function collapse() {
collapseNav.value = !collapseNav.value;
}
function onResized(e: any) {
if (e.length == 2) {
menuWidth.value = e[0].size;
@@ -85,6 +87,7 @@ function onResized(e: any) {
:deep(.splitpanes--vertical > .splitpanes__splitter) {
min-width: 3px;
background: var(--border-color);
&:hover {
background: var(--border-hover-color);
}
@@ -108,11 +111,13 @@ function onResized(e: any) {
position: fixed;
left: 10px;
bottom: 10px;
&.collapsed {
left: -40px;
width: 60px;
padding-left: 40px;
background: rgba(0, 0, 0, 0.95);
color: var(--text-strong-color);
background: var(--scheme-main);
&:hover {
left: -25px;
+2 -2
View File
@@ -3,8 +3,8 @@ import { createApp, App as VueApp } from "vue";
import App from "./App.vue";
const app = createApp(App);
Object.values(import.meta.glob<{ install: (app: VueApp) => void }>("./modules/*.ts", { eager: true })).forEach((i) =>
i.install?.(app)
Object.values(import.meta.glob<{ install: (app: VueApp) => void }>("./modules/*.ts", { eager: true })).forEach(
(i) => i.install?.(app),
);
app.mount("#app");
+19
View File
@@ -0,0 +1,19 @@
import { describe, expect, test } from "vitest";
import { Container } from "./Container";
describe("Container", () => {
const names = [
[
"foo.gb1cto7gaq68fp4refnsr5hep.byqr1prci82zyfoos6gx1yhz0",
"foo",
".gb1cto7gaq68fp4refnsr5hep.byqr1prci82zyfoos6gx1yhz0",
],
["bar.gb1cto7gaq68fp4refnsr5hep", "bar", ".gb1cto7gaq68fp4refnsr5hep"],
["baz", "baz", null],
];
test.each(names)("name %s should be %s and %s", (name, expectedName, expectedSwarmId) => {
const c = new Container("id", new Date(), "image", name!, "command", "host", "status", "created");
expect(c.name).toBe(expectedName);
expect(c.swarmId).toBe(expectedSwarmId);
});
});
+38 -9
View File
@@ -1,31 +1,60 @@
import type { ContainerStat, ContainerState } from "@/types/Container";
import type { ContainerHealth, ContainerStat, ContainerState } from "@/types/Container";
import type { UseThrottledRefHistoryReturn } from "@vueuse/core";
import { useExponentialMovingAverage } from "@/utils";
import { Ref } from "vue";
type Stat = Omit<ContainerStat, "id">;
const SWARM_ID_REGEX = /(\.[a-z0-9]{25})+$/i;
export class Container {
public stat: Ref<Stat>;
private _stat: Ref<Stat>;
private readonly throttledStatHistory: UseThrottledRefHistoryReturn<Stat, Stat>;
public readonly swarmId: string | null = null;
public readonly isSwarm: boolean = false;
private readonly movingAverageStat: Ref<Stat>;
constructor(
public readonly id: string,
public readonly created: number,
public readonly created: Date,
public readonly image: string,
public readonly name: string,
public readonly command: string,
public readonly host: string,
public status: string,
public state: ContainerState
public state: ContainerState,
public health?: ContainerHealth,
) {
this.stat = ref({ cpu: 0, memory: 0, memoryUsage: 0 });
this.throttledStatHistory = useThrottledRefHistory(this.stat, { capacity: 300, deep: true, throttle: 1000 });
this._stat = ref({ cpu: 0, memory: 0, memoryUsage: 0 });
this.throttledStatHistory = useThrottledRefHistory(this._stat, { capacity: 300, deep: true, throttle: 1000 });
this.movingAverageStat = useExponentialMovingAverage(this._stat, 0.2);
const match = name.match(SWARM_ID_REGEX);
if (match) {
this.swarmId = match[0];
this.name = name.replace(`${this.swarmId}`, "");
this.isSwarm = true;
}
}
public getStatHistory() {
get statHistory() {
return unref(this.throttledStatHistory.history);
}
public getLastStat() {
return unref(this.throttledStatHistory.last);
get movingAverage() {
return unref(this.movingAverageStat);
}
get stat() {
return unref(this._stat);
}
public updateStat(stat: Stat) {
if (isRef(this._stat)) {
this._stat.value = stat;
} else {
// @ts-ignore
this._stat = stat;
}
}
}
+59 -11
View File
@@ -11,16 +11,26 @@ export interface HasComponent {
export type JSONValue = string | number | boolean | JSONObject | Array<JSONValue>;
export type JSONObject = { [x: string]: JSONValue };
export type Position = "start" | "end" | "middle" | undefined;
export type Std = "stdout" | "stderr";
export interface LogEvent {
readonly m: string | JSONObject;
readonly ts: number;
readonly id: number;
readonly l: string;
readonly p: Position;
readonly s: "stdout" | "stderr" | "unknown";
}
export abstract class LogEntry<T extends string | JSONObject> implements HasComponent {
protected readonly _message: T;
constructor(message: T, public readonly id: number, public readonly date: Date) {
constructor(
message: T,
public readonly id: number,
public readonly date: Date,
public readonly std: Std,
public readonly level?: string,
) {
this._message = message;
}
@@ -32,6 +42,16 @@ export abstract class LogEntry<T extends string | JSONObject> implements HasComp
}
export class SimpleLogEntry extends LogEntry<string> {
constructor(
message: string,
id: number,
date: Date,
public readonly level: string,
public readonly position: Position,
public readonly std: Std,
) {
super(message, id, date, std, level);
}
getComponent(): Component {
return SimpleLogItem;
}
@@ -40,8 +60,15 @@ export class SimpleLogEntry extends LogEntry<string> {
export class ComplexLogEntry extends LogEntry<JSONObject> {
private readonly filteredMessage: ComputedRef<JSONObject>;
constructor(message: JSONObject, id: number, date: Date, visibleKeys?: Ref<string[][]>) {
super(message, id, date);
constructor(
message: JSONObject,
id: number,
date: Date,
public readonly level: string,
public readonly std: Std,
visibleKeys?: Ref<string[][]>,
) {
super(message, id, date, std, level);
if (visibleKeys) {
this.filteredMessage = computed(() => {
if (!visibleKeys.value.length) {
@@ -67,13 +94,17 @@ export class ComplexLogEntry extends LogEntry<JSONObject> {
}
static fromLogEvent(event: ComplexLogEntry, visibleKeys: Ref<string[][]>): ComplexLogEntry {
return new ComplexLogEntry(event._message, event.id, event.date, visibleKeys);
return new ComplexLogEntry(event._message, event.id, event.date, event.level, event.std, visibleKeys);
}
}
export class DockerEventLogEntry extends LogEntry<string> {
constructor(message: string, date: Date, public readonly event: string) {
super(message, date.getTime(), date);
constructor(
message: string,
date: Date,
public readonly event: string,
) {
super(message, date.getTime(), date, "stderr", "info");
}
getComponent(): Component {
return DockerEventLogItem;
@@ -88,9 +119,9 @@ export class SkippedLogsEntry extends LogEntry<string> {
date: Date,
totalSkipped: number,
public readonly firstSkipped: LogEntry<string | JSONObject>,
lastSkipped: LogEntry<string | JSONObject>
lastSkipped: LogEntry<string | JSONObject>,
) {
super("", date.getTime(), date);
super("", date.getTime(), date, "stderr", "info");
this._totalSkipped = totalSkipped;
this.lastSkipped = lastSkipped;
}
@@ -110,12 +141,29 @@ export class SkippedLogsEntry extends LogEntry<string> {
public get totalSkipped(): number {
return this._totalSkipped;
}
public get lastSkippedItem(): LogEntry<string | JSONObject> {
return this.lastSkipped;
}
}
export function asLogEntry(event: LogEvent): LogEntry<string | JSONObject> {
if (typeof event.m === "string") {
return new SimpleLogEntry(event.m, event.id, new Date(event.ts));
return new SimpleLogEntry(
event.m,
event.id,
new Date(event.ts),
event.l,
event.p,
event.s === "unknown" ? "stderr" : event.s ?? "stderr",
);
} else {
return new ComplexLogEntry(event.m, event.id, new Date(event.ts));
return new ComplexLogEntry(
event.m,
event.id,
new Date(event.ts),
event.l,
event.s === "unknown" ? "stderr" : event.s ?? "stderr",
);
}
}
+2 -2
View File
@@ -7,8 +7,8 @@ export const install = (app: App) => {
([key, value]) => {
const yaml = key.endsWith(".yaml");
return [key.slice(14, yaml ? -5 : -4), value.default];
}
)
},
),
);
const i18n = createI18n({
legacy: false,
@@ -1,16 +1,5 @@
import { type App } from "vue";
import {
Autocomplete,
Button,
Dropdown,
Switch,
Radio,
Skeleton,
Field,
Tooltip,
Modal,
Config,
} from "@oruga-ui/oruga-next";
import { Autocomplete, Button, Dropdown, Switch, Skeleton, Field, Table, Modal, Config } from "@oruga-ui/oruga-next";
import { bulmaConfig } from "@oruga-ui/theme-bulma";
export const install = (app: App) => {
@@ -19,10 +8,9 @@ export const install = (app: App) => {
.use(Button)
.use(Dropdown)
.use(Switch)
.use(Tooltip)
.use(Modal)
.use(Radio)
.use(Field)
.use(Skeleton)
.use(Table)
.use(Config, bulmaConfig);
};
+1 -2
View File
@@ -1,8 +1,7 @@
import { type App } from "vue";
import { createPinia } from "pinia";
export const install = (app:App) => {
export const install = (app: App) => {
const pinia = createPinia();
app.use(pinia);
};
-170
View File
@@ -1,170 +0,0 @@
<template>
<div>
<section class="hero is-small mt-4">
<div class="hero-body">
<div class="container">
<div class="columns">
<div class="column is-narrow" v-if="secured">
<a class="button is-primary is-small" :href="`${base}/logout`">{{ $t("button.logout") }}</a>
</div>
</div>
</div>
</div>
</section>
<section class="level section">
<div class="level-item has-text-centered">
<div>
<p class="title">{{ containers.length }}</p>
<p class="heading">{{ $t("label.total-containers") }}</p>
</div>
</div>
<div class="level-item has-text-centered">
<div>
<p class="title">{{ runningContainers.length }}</p>
<p class="heading">{{ $t("label.running") }}</p>
</div>
</div>
<div class="level-item has-text-centered">
<div>
<p class="title" data-ci-skip>{{ totalCpu }}%</p>
<p class="heading">{{ $t("label.total-cpu-usage") }}</p>
</div>
</div>
<div class="level-item has-text-centered">
<div>
<p class="title" data-ci-skip>{{ formatBytes(totalMem) }}</p>
<p class="heading">{{ $t("label.total-mem-usage") }}</p>
</div>
</div>
<div class="level-item has-text-centered">
<div>
<p class="title">{{ version }}</p>
<p class="heading">{{ $t("label.dozzle-version") }}</p>
</div>
</div>
</section>
<section class="columns is-centered section is-marginless">
<div class="column is-4">
<div class="panel">
<p class="panel-heading">{{ $t("label.containers") }}</p>
<div class="panel-block">
<p class="control has-icons-left">
<input
class="input"
type="text"
:placeholder="$t('placeholder.search-containers')"
v-model="query"
@keyup.esc="query = ''"
@keyup.enter="onEnter()"
/>
<span class="icon is-left">
<search-icon />
</span>
</p>
</div>
<p class="panel-tabs" v-if="query === ''">
<a :class="{ 'is-active': sort === 'running' }" @click="sort = 'running'">{{ $t("label.running") }}</a>
<a :class="{ 'is-active': sort === 'all' }" @click="sort = 'all'">{{ $t("label.all") }}</a>
</p>
<router-link
:to="{ name: 'container-id', params: { id: item.id } }"
v-for="item in data.slice(0, 10)"
:key="item.id"
class="panel-block"
>
<span class="name">{{ item.name }}</span>
<div class="subtitle is-7 status">
<past-time :date="new Date(item.created * 1000)"></past-time>
</div>
</router-link>
</div>
</div>
</section>
</div>
</template>
<script lang="ts" setup>
import SearchIcon from "~icons/mdi-light/magnify";
import { useFuse } from "@vueuse/integrations/useFuse";
const { base, version, secured } = config;
const containerStore = useContainerStore();
const { containers } = storeToRefs(containerStore);
const router = useRouter();
const sort = $ref("running");
const query = ref("");
const mostRecentContainers = $computed(() => [...containers.value].sort((a, b) => b.created - a.created));
const runningContainers = $computed(() => mostRecentContainers.filter((c) => c.state === "running"));
const { results } = useFuse(query, containers, {
fuseOptions: { keys: ["name"] },
matchAllWhenSearchEmpty: false,
});
const data = computed(() => {
if (results.value.length) {
return results.value.map(({ item }) => item);
}
switch (sort) {
case "all":
return mostRecentContainers;
case "running":
return runningContainers;
default:
throw `Invalid sort order: ${sort}`;
}
});
let totalCpu = $ref(0);
useIntervalFn(
() => {
totalCpu = runningContainers.reduce((acc, c) => acc + (c.stat?.cpu ?? 0), 0);
},
1000,
{ immediate: true }
);
let totalMem = $ref(0);
useIntervalFn(
() => {
totalMem = runningContainers.reduce((acc, c) => acc + (c.stat?.memoryUsage ?? 0), 0);
},
1000,
{ immediate: true }
);
function onEnter() {
if (data.value.length > 0) {
const item = data.value[0];
router.push({ name: "container-id", params: { id: item.id } });
}
}
</script>
<style lang="scss" scoped>
.panel {
border: 1px solid var(--border-color);
.panel-block,
.panel-tabs {
border-color: var(--border-color);
.is-active {
border-color: var(--border-hover-color);
}
.name {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.status {
margin-left: auto;
white-space: nowrap;
}
}
}
.icon {
padding: 10px 3px;
}
</style>
-79
View File
@@ -1,79 +0,0 @@
<template>
<div class="hero is-halfheight">
<div class="hero-body">
<div class="container">
<section class="columns is-centered section">
<div class="column is-4">
<div class="card">
<div class="card-content">
<form action="" method="post" @submit.prevent="onLogin" ref="form">
<div class="field">
<label class="label">{{ $t("label.username") }}</label>
<div class="control">
<input
class="input"
type="text"
name="username"
autocomplete="username"
v-model="username"
autofocus
/>
</div>
</div>
<div class="field">
<label class="label">{{ $t("label.password") }}</label>
<div class="control">
<input
class="input"
type="password"
name="password"
autocomplete="current-password"
v-model="password"
/>
</div>
<p class="help is-danger" v-if="error">{{ $t("error.invalid-auth") }}</p>
</div>
<div class="field is-grouped is-grouped-centered mt-5">
<p class="control">
<button class="button is-primary" type="submit">{{ $t("button.login") }}</button>
</p>
</div>
</form>
</div>
</div>
</div>
</section>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
const { t } = useI18n();
setTitle(t("title.login"));
let error = $ref(false);
let username = $ref("");
let password = $ref("");
let form: HTMLFormElement = $ref();
async function onLogin() {
const response = await fetch(`${config.base}/api/validateCredentials`, {
body: new FormData(form),
method: "post",
});
if (response.status == 200) {
error = false;
window.location.href = `${config.base}/`;
} else {
error = true;
}
}
</script>
<route lang="yaml">
meta:
layout: splash
</route>
-203
View File
@@ -1,203 +0,0 @@
<template>
<div>
<section class="section">
<div class="has-underline">
<h2 class="title is-4">{{ $t("settings.about") }}</h2>
</div>
<div>
<span v-html="$t('settings.using-version', { version: currentVersion })"></span>
<div
v-if="hasUpdate"
v-html="$t('settings.update-available', { nextVersion: nextRelease.name, href: nextRelease.html_url })"
></div>
</div>
</section>
<section class="section">
<div class="has-underline">
<h2 class="title is-4">{{ $t("settings.display") }}</h2>
</div>
<div class="item">
<o-switch v-model="smallerScrollbars"> {{ $t("settings.small-scrollbars") }} </o-switch>
</div>
<div class="item">
<o-switch v-model="showTimestamp"> {{ $t("settings.show-timesamps") }} </o-switch>
</div>
<div class="item">
<o-switch v-model="softWrap"> {{ $t("settings.soft-wrap") }}</o-switch>
</div>
<div class="item">
<div class="columns is-vcentered">
<div class="column is-narrow">
<o-field>
<o-dropdown v-model="hourStyle" aria-role="list">
<template #trigger>
<o-button variant="primary" type="button">
<span class="is-capitalized">{{ hourStyle }}</span>
<span class="icon">
<carbon-caret-down />
</span>
</o-button>
</template>
<o-dropdown-item :value="value" aria-role="listitem" v-for="value in ['auto', '12', '24']" :key="value">
<span class="is-capitalized">{{ value }}</span>
</o-dropdown-item>
</o-dropdown>
</o-field>
</div>
<div class="column">
{{ $t("settings.12-24-format") }}
</div>
</div>
</div>
<div class="item">
<div class="columns is-vcentered">
<div class="column is-narrow">
<o-field>
<o-dropdown v-model="size" aria-role="list">
<template #trigger>
<o-button variant="primary" type="button">
<span class="is-capitalized">{{ size }}</span>
<span class="icon">
<carbon-caret-down />
</span>
</o-button>
</template>
<o-dropdown-item
:value="value"
aria-role="listitem"
v-for="value in ['small', 'medium', 'large']"
:key="value"
>
<span class="is-capitalized">{{ value }}</span>
</o-dropdown-item>
</o-dropdown>
</o-field>
</div>
<div class="column">{{ $t("settings.font-size") }}</div>
</div>
</div>
<div class="item">
<div class="columns is-vcentered">
<div class="column is-narrow">
<o-field>
<o-dropdown v-model="lightTheme" aria-role="list">
<template #trigger>
<o-button variant="primary" type="button">
<span class="is-capitalized">{{ lightTheme }}</span>
<span class="icon">
<carbon-caret-down />
</span>
</o-button>
</template>
<o-dropdown-item
:value="value"
aria-role="listitem"
v-for="value in ['auto', 'dark', 'light']"
:key="value"
>
<span class="is-capitalized">{{ value }}</span>
</o-dropdown-item>
</o-dropdown>
</o-field>
</div>
<div class="column">{{ $t("settings.color-scheme") }}</div>
</div>
</div>
</section>
<section class="section">
<div class="has-underline">
<h2 class="title is-4">{{ $t("settings.options") }}</h2>
</div>
<div class="item">
<o-switch v-model="search">
<span v-html="$t('settings.search')"></span>
</o-switch>
</div>
<div class="item">
<o-switch v-model="showAllContainers"> {{ $t("settings.show-stopped-containers") }} </o-switch>
</div>
</section>
</div>
</template>
<script lang="ts" setup>
import gt from "semver/functions/gt";
import {
search,
lightTheme,
smallerScrollbars,
showTimestamp,
hourStyle,
showAllContainers,
size,
softWrap,
} from "@/composables/settings";
const { t } = useI18n();
setTitle(t("title.settings"));
const currentVersion = $ref(config.version);
let nextRelease = $ref({ html_url: "", name: "" });
let hasUpdate = $ref(false);
async function fetchNextRelease() {
if (!["dev", "master"].includes(currentVersion)) {
const response = await fetch("https://api.github.com/repos/amir20/dozzle/releases/latest");
if (response.ok) {
const release = await response.json();
hasUpdate = gt(release.tag_name, currentVersion);
nextRelease = release;
}
} else {
hasUpdate = true;
nextRelease = {
html_url: "",
name: "master",
};
}
}
fetchNextRelease();
</script>
<style lang="scss" scoped>
.title {
color: var(--title-color);
}
a.next-release {
text-decoration: underline;
&:hover {
text-decoration: none;
}
}
.section {
padding: 1rem 1.5rem;
}
.has-underline {
border-bottom: 1px solid var(--border-color);
padding: 1em 0px;
margin-bottom: 1em;
}
.item {
padding: 1em 0;
}
code {
border-radius: 4px;
background-color: #444;
}
</style>
-25
View File
@@ -1,25 +0,0 @@
<script lang="ts" setup>
const router = useRouter();
const route = useRoute();
const store = useContainerStore();
const { visibleContainers } = storeToRefs(store);
watch(visibleContainers, (newValue) => {
if (newValue) {
if (route.query.name) {
const [container, _] = visibleContainers.value.filter((c) => c.name == route.query.name);
if (container) {
router.push({ name: "container-id", params: { id: container.id } });
} else {
console.error(`No containers found matching name=${route.query.name}. Redirecting to /`);
router.push({ name: "index" });
}
} else {
console.error(`Expection query parameter name to be set. Redirecting to /`);
router.push({ name: "index" });
}
}
});
</script>
<template></template>
+8 -2
View File
@@ -1,14 +1,20 @@
<template>
<search></search>
<log-container :id="id" show-title :scrollable="activeContainers.length > 0"> </log-container>
<log-container :id="id" show-title :scrollable="activeContainers.length > 0" v-if="currentContainer"></log-container>
<div v-else-if="ready" class="notification is-warning is-light m-6">
<h1 class="title">
{{ $t("error.container-not-found") }}
</h1>
</div>
</template>
<script lang="ts" setup>
import search from "@/components/Search.vue";
const store = useContainerStore();
const { id } = defineProps<{ id: string }>();
const currentContainer = store.currentContainer($$(id));
const { activeContainers } = storeToRefs(store);
const { activeContainers, ready } = storeToRefs(store);
setTitle("loading");
+76 -115
View File
@@ -1,162 +1,100 @@
<template>
<div>
<section class="hero is-small mt-4">
<div class="hero-body">
<div class="container">
<div class="columns">
<div class="column is-narrow" v-if="secured">
<a class="button is-primary is-small" :href="`${base}/logout`">{{ $t("button.logout") }}</a>
</div>
<div class="section tile is-ancestor">
<div class="tile is-parent">
<div class="tile is-child box">
<div class="level-item has-text-centered">
<div>
<p class="title">{{ runningContainers.length }} / {{ containers.length }}</p>
<p class="heading">{{ $t("label.running") }} / {{ $t("label.total-containers") }}</p>
</div>
</div>
</div>
</section>
<section class="level section">
<div class="level-item has-text-centered">
<div>
<p class="title">{{ containers.length }}</p>
<p class="heading">{{ $t("label.total-containers") }}</p>
</div>
</div>
<div class="level-item has-text-centered">
<div>
<p class="title">{{ runningContainers.length }}</p>
<p class="heading">{{ $t("label.running") }}</p>
</div>
</div>
<div class="level-item has-text-centered">
<div>
<p class="title" data-ci-skip>{{ totalCpu }}%</p>
<p class="heading">{{ $t("label.total-cpu-usage") }}</p>
</div>
</div>
<div class="level-item has-text-centered">
<div>
<p class="title" data-ci-skip>{{ formatBytes(totalMem) }}</p>
<p class="heading">{{ $t("label.total-mem-usage") }}</p>
</div>
</div>
<div class="level-item has-text-centered">
<div>
<p class="title">{{ version }}</p>
<p class="heading">{{ $t("label.dozzle-version") }}</p>
</div>
</div>
</section>
<section class="columns is-centered section is-marginless">
<div class="column is-4">
<div class="panel">
<p class="panel-heading">{{ $t("label.containers") }}</p>
<div class="panel-block">
<p class="control has-icons-left">
<input
class="input"
type="text"
:placeholder="$t('placeholder.search-containers')"
v-model="query"
@keyup.esc="query = ''"
@keyup.enter="onEnter()"
/>
<span class="icon is-left">
<search-icon />
</span>
</p>
</div>
<div class="tile is-parent">
<div class="tile is-child box">
<div class="level-item has-text-centered">
<div>
<p class="title">{{ totalCpu }}%</p>
<p class="heading">{{ $t("label.total-cpu-usage") }}</p>
</div>
<p class="panel-tabs" v-if="query === ''">
<a :class="{ 'is-active': sort === 'running' }" @click="sort = 'running'">{{ $t("label.running") }}</a>
<a :class="{ 'is-active': sort === 'all' }" @click="sort = 'all'">{{ $t("label.all") }}</a>
</p>
<router-link
:to="{ name: 'container-id', params: { id: item.id } }"
v-for="item in data.slice(0, 10)"
:key="item.id"
class="panel-block"
>
<span class="name">{{ item.name }}</span>
<div class="subtitle is-7 status">
<past-time :date="new Date(item.created * 1000)"></past-time>
</div>
</router-link>
</div>
</div>
</section>
</div>
<div class="tile is-parent">
<div class="tile is-child box">
<div class="level-item has-text-centered">
<div>
<p class="title">{{ formatBytes(totalMem) }}</p>
<p class="heading">{{ $t("label.total-mem-usage") }}</p>
</div>
</div>
</div>
</div>
<div class="tile is-parent">
<div class="tile is-child box">
<div class="level-item has-text-centered">
<div>
<p class="title">{{ version }}</p>
<p class="heading">{{ $t("label.dozzle-version") }}</p>
</div>
</div>
</div>
</div>
</div>
<section class="section table-container">
<div class="box">
<container-table :containers="runningContainers"></container-table>
</div>
</section>
</template>
<script lang="ts" setup>
import SearchIcon from "~icons/mdi-light/magnify";
import { useFuse } from "@vueuse/integrations/useFuse";
import { Container } from "@/models/Container";
const { base, version, secured } = config;
const { version } = config;
const containerStore = useContainerStore();
const { containers } = storeToRefs(containerStore);
const router = useRouter();
const { containers } = storeToRefs(containerStore) as { containers: unknown } as { containers: Ref<Container[]> };
const sort = $ref("running");
const query = ref("");
const mostRecentContainers = $computed(() => [...containers.value].sort((a, b) => b.created - a.created));
const mostRecentContainers = $computed(() => [...containers.value].sort((a, b) => +b.created - +a.created));
const runningContainers = $computed(() => mostRecentContainers.filter((c) => c.state === "running"));
const { results } = useFuse(query, containers, {
fuseOptions: { keys: ["name"] },
matchAllWhenSearchEmpty: false,
});
const data = computed(() => {
if (results.value.length) {
return results.value.map(({ item }) => item);
}
switch (sort) {
case "all":
return mostRecentContainers;
case "running":
return runningContainers;
default:
throw `Invalid sort order: ${sort}`;
}
});
let totalCpu = $ref(0);
useIntervalFn(
() => {
totalCpu = runningContainers.reduce((acc, c) => acc + (c.stat?.cpu ?? 0), 0);
totalCpu = runningContainers.reduce((acc, c) => acc + c.stat.cpu, 0);
},
1000,
{ immediate: true }
{ immediate: true },
);
let totalMem = $ref(0);
useIntervalFn(
() => {
totalMem = runningContainers.reduce((acc, c) => acc + (c.stat?.memoryUsage ?? 0), 0);
totalMem = runningContainers.reduce((acc, c) => acc + c.stat.memoryUsage, 0);
},
1000,
{ immediate: true }
{ immediate: true },
);
function onEnter() {
if (data.value.length > 0) {
const item = data.value[0];
router.push({ name: "container-id", params: { id: item.id } });
}
}
</script>
<style lang="scss" scoped>
.panel {
border: 1px solid var(--border-color);
.panel-block,
.panel-tabs {
border-color: var(--border-color);
.is-active {
border-color: var(--border-hover-color);
}
.name {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.status {
margin-left: auto;
white-space: nowrap;
@@ -164,7 +102,30 @@ function onEnter() {
}
}
@media screen and (max-width: 768px) {
.pb-0-is-mobile {
padding-bottom: 0 !important;
}
.pt-0-is-mobile {
padding-top: 0 !important;
}
}
.icon {
padding: 10px 3px;
}
.bar-chart {
height: 1.5em;
.bar-text {
font-size: 0.9em;
padding: 0 0.5em;
}
}
:deep(tr td) {
padding-top: 1em;
padding-bottom: 1em;
}
</style>
+1 -1
View File
@@ -57,7 +57,7 @@ setTitle(t("title.login"));
let error = $ref(false);
let username = $ref("");
let password = $ref("");
let form: HTMLFormElement = $ref();
let form: HTMLFormElement | undefined = $ref();
async function onLogin() {
const response = await fetch(`${config.base}/api/validateCredentials`, {
+11 -6
View File
@@ -25,6 +25,9 @@
<div class="item">
<o-switch v-model="showTimestamp"> {{ $t("settings.show-timesamps") }} </o-switch>
</div>
<div class="item">
<o-switch v-model="showStd"> {{ $t("settings.show-std") }} </o-switch>
</div>
<div class="item">
<o-switch v-model="softWrap"> {{ $t("settings.soft-wrap") }}</o-switch>
@@ -39,7 +42,7 @@
<o-button variant="primary" type="button">
<span class="is-capitalized">{{ hourStyle }}</span>
<span class="icon">
<carbon-caret-down />
<carbon:caret-down />
</span>
</o-button>
</template>
@@ -64,7 +67,7 @@
<o-button variant="primary" type="button">
<span class="is-capitalized">{{ size }}</span>
<span class="icon">
<carbon-caret-down />
<carbon:caret-down />
</span>
</o-button>
</template>
@@ -92,7 +95,7 @@
<o-button variant="primary" type="button">
<span class="is-capitalized">{{ lightTheme }}</span>
<span class="icon">
<carbon-caret-down />
<carbon:caret-down />
</span>
</o-button>
</template>
@@ -131,12 +134,12 @@
</template>
<script lang="ts" setup>
import gt from "semver/functions/gt";
import {
search,
lightTheme,
smallerScrollbars,
showTimestamp,
showStd,
hourStyle,
showAllContainers,
size,
@@ -147,7 +150,7 @@ const { t } = useI18n();
setTitle(t("title.settings"));
const currentVersion = $ref(config.version);
const currentVersion = config.version;
let nextRelease = $ref({ html_url: "", name: "" });
let hasUpdate = $ref(false);
@@ -156,7 +159,8 @@ async function fetchNextRelease() {
const response = await fetch("https://api.github.com/repos/amir20/dozzle/releases/latest");
if (response.ok) {
const release = await response.json();
hasUpdate = gt(release.tag_name, currentVersion);
hasUpdate =
release.tag_name.slice(1).localeCompare(currentVersion, undefined, { numeric: true, sensitivity: "base" }) > 0;
nextRelease = release;
}
} else {
@@ -177,6 +181,7 @@ fetchNextRelease();
a.next-release {
text-decoration: underline;
&:hover {
text-decoration: none;
}
+6 -13
View File
@@ -3,9 +3,11 @@ const text = document.querySelector("script#config__json")?.textContent || "{}";
interface Config {
version: string;
base: string;
authorizationNeeded: boolean | "false" | "true";
secured: boolean | "false" | "true";
authorizationNeeded: boolean;
secured: boolean;
maxLogs: number;
hostname: string;
hosts: { name: string; id: string }[];
}
const pageConfig = JSON.parse(text);
@@ -15,15 +17,6 @@ const config: Config = {
...pageConfig,
};
if (config.version == "{{ .Version }}") {
config.version = "master";
config.base = "";
config.authorizationNeeded = false;
config.secured = false;
} else {
config.version = config.version.replace(/^v/, "");
config.authorizationNeeded = config.authorizationNeeded === "true";
config.secured = config.secured === "true";
}
config.version = config.version.replace(/^v/, "");
export default config as Config;
export default config;
+71 -35
View File
@@ -1,17 +1,22 @@
import { acceptHMRUpdate, defineStore } from "pinia";
import { Ref, UnwrapNestedRefs } from "vue";
import type { ContainerJson, ContainerStat } from "@/types/Container";
import type { ContainerHealth, ContainerJson, ContainerStat } from "@/types/Container";
import { Container } from "@/models/Container";
export const useContainerStore = defineStore("container", () => {
const containers: Ref<Container[]> = ref([]);
const activeContainerIds: Ref<string[]> = ref([]);
let es: EventSource | null = null;
const ready = ref(false);
const allContainersById = computed(() =>
containers.value.reduce((acc, container) => {
acc[container.id] = container;
return acc;
}, {} as Record<string, Container>)
containers.value.reduce(
(acc, container) => {
acc[container.id] = container;
return acc;
},
{} as Record<string, Container>,
),
);
const visibleContainers = computed(() => {
@@ -21,36 +26,70 @@ export const useContainerStore = defineStore("container", () => {
const activeContainers = computed(() => activeContainerIds.value.map((id) => allContainersById.value[id]));
const es = new EventSource(`${config.base}/api/events/stream`);
es.addEventListener("containers-changed", (e: Event) =>
setContainers(JSON.parse((e as MessageEvent).data) as ContainerJson[])
);
es.addEventListener("container-stat", (e) => {
const stat = JSON.parse((e as MessageEvent).data) as ContainerStat;
const container = allContainersById.value[stat.id] as unknown as UnwrapNestedRefs<Container>;
if (container) {
const { id, ...rest } = stat;
container.stat = rest;
}
});
es.addEventListener("container-die", (e) => {
const event = JSON.parse((e as MessageEvent).data) as { actorId: string };
const container = allContainersById.value[event.actorId];
if (container) {
container.state = "dead";
}
});
function connect() {
es?.close();
ready.value = false;
es = new EventSource(`${config.base}/api/events/stream`);
const setContainers = (newContainers: ContainerJson[]) => {
containers.value = newContainers.map((c) => {
const existing = allContainersById.value[c.id];
if (existing) {
existing.status = c.status;
existing.state = c.state;
return existing;
es.addEventListener("containers-changed", (e: Event) =>
updateContainers(JSON.parse((e as MessageEvent).data) as ContainerJson[]),
);
es.addEventListener("container-stat", (e) => {
const stat = JSON.parse((e as MessageEvent).data) as ContainerStat;
const container = allContainersById.value[stat.id] as unknown as UnwrapNestedRefs<Container>;
if (container) {
const { id, ...rest } = stat;
container.updateStat(rest);
}
return new Container(c.id, c.created, c.image, c.name, c.command, c.status, c.state);
});
es.addEventListener("container-die", (e) => {
const event = JSON.parse((e as MessageEvent).data) as { actorId: string };
const container = allContainersById.value[event.actorId];
if (container) {
container.state = "dead";
}
});
es.addEventListener("container-health", (e) => {
const event = JSON.parse((e as MessageEvent).data) as { actorId: string; health: ContainerHealth };
const container = allContainersById.value[event.actorId];
if (container) {
container.health = event.health;
}
});
watchOnce(containers, () => (ready.value = true));
}
connect();
const updateContainers = (containersPayload: ContainerJson[]) => {
const existingContainers = containersPayload.filter((c) => allContainersById.value[c.id]);
const newContainers = containersPayload.filter((c) => !allContainersById.value[c.id]);
existingContainers.forEach((c) => {
const existing = allContainersById.value[c.id];
existing.status = c.status;
existing.state = c.state;
existing.health = c.health;
});
containers.value = [
...containers.value,
...newContainers.map((c) => {
return new Container(
c.id,
new Date(c.created * 1000),
c.image,
c.name,
c.command,
c.host,
c.status,
c.state,
c.health,
);
}),
];
};
const currentContainer = (id: Ref<string>) => computed(() => allContainersById.value[id.value]);
@@ -58,9 +97,6 @@ export const useContainerStore = defineStore("container", () => {
const removeActiveContainer = ({ id }: Container) =>
activeContainerIds.value.splice(activeContainerIds.value.indexOf(id), 1);
const ready = ref(false);
watchOnce(containers, () => (ready.value = true));
return {
containers,
activeContainerIds,
+40 -3
View File
@@ -28,13 +28,12 @@ $link-active: $grey-dark;
$dark-toolbar-color: rgba($black-bis, 0.7);
$light-toolbar-color: rgba($grey-darker, 0.7);
@import "bulma/bulma.sass";
@import "bulma/bulma";
@import "@oruga-ui/theme-bulma/dist/scss/components/utils/all.scss";
@import "@oruga-ui/theme-bulma/dist/scss/components/autocomplete.scss";
@import "@oruga-ui/theme-bulma/dist/scss/components/button.scss";
@import "@oruga-ui/theme-bulma/dist/scss/components/modal.scss";
@import "@oruga-ui/theme-bulma/dist/scss/components/switch.scss";
@import "@oruga-ui/theme-bulma/dist/scss/components/tooltip.scss";
@import "@oruga-ui/theme-bulma/dist/scss/components/dropdown.scss";
@import "@oruga-ui/theme-bulma/dist/scss/components/skeleton.scss";
@import "splitpanes/dist/splitpanes.css";
@@ -117,6 +116,34 @@ $light-toolbar-color: rgba($grey-darker, 0.7);
}
}
:root {
--green-color: #00b5ad;
--red-color: #f44336;
--purple-color: #9c27b0;
--orange-color: #ff9800;
--blue-color: #2196f3;
}
.is-red {
color: var(--red-color);
}
.is-green {
color: var(--green-color);
}
.is-purple {
color: var(--purple-color);
}
.is-orange {
color: var(--orange-color);
}
.is-blue {
color: var(--blue-color);
}
html {
overflow-x: unset;
overflow-y: unset;
@@ -177,6 +204,9 @@ html.has-custom-scrollbars {
.modal {
z-index: 1000;
.modal-background {
backdrop-filter: blur(4px);
}
}
.button .button-wrapper > span {
@@ -188,7 +218,9 @@ html.has-custom-scrollbars {
}
.has-boxshadow {
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
box-shadow:
0 1px 3px 0 rgb(0 0 0 / 0.1),
0 1px 2px -1px rgb(0 0 0 / 0.1);
}
mark {
@@ -206,3 +238,8 @@ mark {
transform: scale(1.05);
}
}
.button.is-rounded:hover {
color: var(--text-strong-color);
background: var(--scheme-main-ter);
}
+3
View File
@@ -13,6 +13,9 @@ export type ContainerJson = {
readonly command: string;
readonly status: string;
readonly state: ContainerState;
readonly host: string;
readonly health?: ContainerHealth;
};
export type ContainerState = "created" | "running" | "exited" | "dead" | "paused" | "restarting";
export type ContainerHealth = "healthy" | "unhealthy" | "starting";
+14 -8
View File
@@ -1,7 +1,3 @@
import { Container } from "@/models/Container";
import { useStorage } from "@vueuse/core";
import { computed, ComputedRef } from "vue";
export function formatBytes(bytes: number, decimals = 2) {
if (bytes === 0) return "0 Bytes";
const k = 1024;
@@ -37,11 +33,21 @@ export function arrayEquals(a: string[], b: string[]): boolean {
return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((val, index) => val === b[index]);
}
export function persistentVisibleKeys(container: ComputedRef<Container>) {
return computed(() => useStorage(stripVersion(container.value.image) + ":" + container.value.command, []));
}
export function stripVersion(label: string) {
const [name, _] = label.split(":");
return name;
}
export function useExponentialMovingAverage<T extends Record<string, number>>(source: Ref<T>, alpha: number = 0.2) {
const ema = ref<T>(source.value) as Ref<T>;
watch(source, (value) => {
const newValue = {} as Record<string, number>;
for (const key in value) {
newValue[key] = alpha * value[key] + (1 - alpha) * ema.value[key];
}
ema.value = newValue as T;
});
return ema;
}
+76
View File
@@ -0,0 +1,76 @@
version: "3.4"
services:
custom_base:
container_name: custom_base
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- DOZZLE_FILTER=name=custom_base
- DOZZLE_BASE=/foobarbase
- DOZZLE_NO_ANALYTICS=1
ports:
- 8080:8080
build:
context: .
auth:
container_name: auth
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- DOZZLE_FILTER=name=auth
- DOZZLE_USERNAME=foo
- DOZZLE_PASSWORD=bar
- DOZZLE_NO_ANALYTICS=1
ports:
- 9090:8080
build:
context: .
dozzle:
container_name: dozzle
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- DOZZLE_FILTER=name=dozzle
- DOZZLE_NO_ANALYTICS=1
ports:
- 7070:8080
build:
context: .
remote:
container_name: remote
environment:
- DOZZLE_REMOTE_HOST=tcp://proxy:2375
- DOZZLE_FILTER=name=dozzle
- DOZZLE_NO_ANALYTICS=1
ports:
- 5050:8080
build:
context: .
depends_on:
- proxy
proxy:
container_name: proxy
image: tecnativa/docker-socket-proxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- CONTAINERS=1
ports:
- 2375:2375
playwright:
container_name: playwright
image: mcr.microsoft.com/playwright:v1.36.2-jammy
working_dir: /app
volumes:
- .:/app
command: npx --yes playwright test
environment:
- PWTEST_SKIP_TEST_OUTPUT=1
- CI=1
depends_on:
- dozzle
- custom_base
- auth
- remote
+109 -46
View File
@@ -5,6 +5,8 @@ import (
"encoding/json"
"fmt"
"io"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
@@ -14,15 +16,33 @@ import (
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
log "github.com/sirupsen/logrus"
)
type dockerClient struct {
cli dockerProxy
filters filters.Args
type StdType int
const (
UNKNOWN StdType = 1 << iota
STDOUT
STDERR
)
const STDALL = STDOUT | STDERR
func (s StdType) String() string {
switch s {
case STDOUT:
return "stdout"
case STDERR:
return "stderr"
case STDALL:
return "all"
default:
return "unknown"
}
}
type dockerProxy interface {
type DockerCLI interface {
ContainerList(context.Context, types.ContainerListOptions) ([]types.Container, error)
ContainerLogs(context.Context, string, types.ContainerLogsOptions) (io.ReadCloser, error)
Events(context.Context, types.EventsOptions) (<-chan events.Message, <-chan error)
@@ -31,19 +51,18 @@ type dockerProxy interface {
Ping(ctx context.Context) (types.Ping, error)
}
// Client is a proxy around the docker client
type Client interface {
ListContainers() ([]Container, error)
FindContainer(string) (Container, error)
ContainerLogs(context.Context, string, int, string) (io.ReadCloser, error)
Events(context.Context) (<-chan ContainerEvent, <-chan error)
ContainerLogsBetweenDates(context.Context, string, time.Time, time.Time) (io.ReadCloser, error)
ContainerStats(context.Context, string, chan<- ContainerStat) error
Ping(context.Context) (types.Ping, error)
type Client struct {
cli DockerCLI
filters filters.Args
host *Host
}
func NewClient(cli DockerCLI, filters filters.Args, host *Host) *Client {
return &Client{cli, filters, host}
}
// NewClientWithFilters creates a new instance of Client with docker filters
func NewClientWithFilters(f map[string][]string) Client {
func NewClientWithFilters(f map[string][]string) (*Client, error) {
filterArgs := filters.NewArgs()
for key, values := range f {
for _, value := range values {
@@ -56,13 +75,49 @@ func NewClientWithFilters(f map[string][]string) Client {
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
log.Fatal(err)
return nil, err
}
return &dockerClient{cli, filterArgs}
return NewClient(cli, filterArgs, &Host{Name: "localhost", ID: "localhost"}), nil
}
func (d *dockerClient) FindContainer(id string) (Container, error) {
func NewClientWithTlsAndFilter(f map[string][]string, host Host) (*Client, error) {
filterArgs := filters.NewArgs()
for key, values := range f {
for _, value := range values {
filterArgs.Add(key, value)
}
}
log.Debugf("filterArgs = %v", filterArgs)
if host.URL.Scheme != "tcp" {
log.Fatal("Only tcp scheme is supported")
}
opts := []client.Opt{
client.WithHost(host.URL.String()),
}
if host.ValidCerts {
log.Debugf("Using TLS client config with certs at: %s", filepath.Dir(host.CertPath))
opts = append(opts, client.WithTLSClientConfig(host.CACertPath, host.CertPath, host.KeyPath))
} else {
log.Debugf("No valid certs found, using plain TCP")
}
opts = append(opts, client.WithAPIVersionNegotiation())
cli, err := client.NewClientWithOpts(opts...)
if err != nil {
return nil, err
}
return NewClient(cli, filterArgs, &host), nil
}
func (d *Client) FindContainer(id string) (Container, error) {
var container Container
containers, err := d.ListContainers()
if err != nil {
@@ -81,10 +136,16 @@ func (d *dockerClient) FindContainer(id string) (Container, error) {
return container, fmt.Errorf("unable to find container with id: %s", id)
}
if json, err := d.cli.ContainerInspect(context.Background(), container.ID); err == nil {
container.Tty = json.Config.Tty
} else {
return container, err
}
return container, nil
}
func (d *dockerClient) ListContainers() ([]Container, error) {
func (d *Client) ListContainers() ([]Container, error) {
containerListOptions := types.ContainerListOptions{
Filters: d.filters,
All: true,
@@ -106,6 +167,8 @@ func (d *dockerClient) ListContainers() ([]Container, error) {
Created: c.Created,
State: c.State,
Status: c.Status,
Host: d.host.ID,
Health: findBetweenParentheses(c.Status),
}
containers = append(containers, container)
}
@@ -117,7 +180,7 @@ func (d *dockerClient) ListContainers() ([]Container, error) {
return containers, nil
}
func (d *dockerClient) ContainerStats(ctx context.Context, id string, stats chan<- ContainerStat) error {
func (d *Client) ContainerStats(ctx context.Context, id string, stats chan<- ContainerStat) error {
response, err := d.cli.ContainerStats(ctx, id, true)
if err != nil {
@@ -169,8 +232,8 @@ func (d *dockerClient) ContainerStats(ctx context.Context, id string, stats chan
return nil
}
func (d *dockerClient) ContainerLogs(ctx context.Context, id string, tailSize int, since string) (io.ReadCloser, error) {
log.WithField("id", id).WithField("since", since).Debug("streaming logs for container")
func (d *Client) ContainerLogs(ctx context.Context, id string, since string, stdType StdType) (io.ReadCloser, error) {
log.WithField("id", id).WithField("since", since).WithField("stdType", stdType).Debug("streaming logs for container")
if since != "" {
if millis, err := strconv.ParseInt(since, 10, 64); err == nil {
@@ -181,34 +244,26 @@ func (d *dockerClient) ContainerLogs(ctx context.Context, id string, tailSize in
}
options := types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
ShowStdout: stdType&STDOUT != 0,
ShowStderr: stdType&STDERR != 0,
Follow: true,
Tail: strconv.Itoa(tailSize),
Tail: "300",
Timestamps: true,
Since: since,
}
log.Debugf("streaming logs from Docker with option: %+v", options)
reader, err := d.cli.ContainerLogs(ctx, id, options)
if err != nil {
return nil, err
}
containerJSON, err := d.cli.ContainerInspect(ctx, id)
if err != nil {
return nil, err
}
return newLogReader(reader, containerJSON.Config.Tty), nil
return reader, nil
}
func (d *dockerClient) Events(ctx context.Context) (<-chan ContainerEvent, <-chan error) {
func (d *Client) Events(ctx context.Context, messages chan<- ContainerEvent) <-chan error {
dockerMessages, errors := d.cli.Events(ctx, types.EventsOptions{})
messages := make(chan ContainerEvent)
go func() {
defer close(messages)
for {
select {
@@ -223,19 +278,20 @@ func (d *dockerClient) Events(ctx context.Context) (<-chan ContainerEvent, <-cha
messages <- ContainerEvent{
ActorID: message.Actor.ID[:12],
Name: message.Action,
Host: d.host.ID,
}
}
}
}
}()
return messages, errors
return errors
}
func (d *dockerClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time) (io.ReadCloser, error) {
func (d *Client) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time, stdType StdType) (io.ReadCloser, error) {
options := types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
ShowStdout: stdType&STDOUT != 0,
ShowStderr: stdType&STDERR != 0,
Timestamps: true,
Since: from.Format(time.RFC3339),
Until: to.Format(time.RFC3339),
@@ -244,19 +300,26 @@ func (d *dockerClient) ContainerLogsBetweenDates(ctx context.Context, id string,
log.Debugf("fetching logs from Docker with option: %+v", options)
reader, err := d.cli.ContainerLogs(ctx, id, options)
if err != nil {
return nil, err
}
containerJSON, err := d.cli.ContainerInspect(ctx, id)
if err != nil {
return nil, err
}
return newLogReader(reader, containerJSON.Config.Tty), nil
return reader, nil
}
func (d *dockerClient) Ping(ctx context.Context) (types.Ping, error) {
func (d *Client) Ping(ctx context.Context) (types.Ping, error) {
return d.cli.Ping(ctx)
}
func (d *Client) Host() *Host {
return d.host
}
var PARENTHESIS_RE = regexp.MustCompile(`\(([a-zA-Z]+)\)`)
func findBetweenParentheses(s string) string {
if results := PARENTHESIS_RE.FindStringSubmatch(s); results != nil {
return results[1]
}
return ""
}
+23 -46
View File
@@ -6,8 +6,7 @@ import (
"encoding/binary"
"errors"
"io"
"io/ioutil"
"strings"
"testing"
"github.com/docker/docker/api/types"
@@ -20,7 +19,7 @@ import (
type mockedProxy struct {
mock.Mock
dockerProxy
DockerCLI
}
func (m *mockedProxy) ContainerList(context.Context, types.ContainerListOptions) ([]types.Container, error) {
@@ -44,12 +43,7 @@ func (m *mockedProxy) ContainerLogs(ctx context.Context, id string, options type
func (m *mockedProxy) ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) {
args := m.Called(ctx, containerID)
json, ok := args.Get(0).(types.ContainerJSON)
if !ok && args.Get(0) != nil {
panic("proxies return value is not of type types.ContainerJSON")
}
return json, args.Error(1)
return args.Get(0).(types.ContainerJSON), args.Error(1)
}
func (m *mockedProxy) ContainerStats(ctx context.Context, containerID string, stream bool) (types.ContainerStats, error) {
@@ -59,7 +53,7 @@ func (m *mockedProxy) ContainerStats(ctx context.Context, containerID string, st
func Test_dockerClient_ListContainers_null(t *testing.T) {
proxy := new(mockedProxy)
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil)
client := &dockerClient{proxy, filters.NewArgs()}
client := &Client{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
list, err := client.ListContainers()
assert.Empty(t, list, "list should be empty")
@@ -71,7 +65,7 @@ func Test_dockerClient_ListContainers_null(t *testing.T) {
func Test_dockerClient_ListContainers_error(t *testing.T) {
proxy := new(mockedProxy)
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, errors.New("test"))
client := &dockerClient{proxy, filters.NewArgs()}
client := &Client{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
list, err := client.ListContainers()
assert.Nil(t, list, "list should be nil")
@@ -94,7 +88,7 @@ func Test_dockerClient_ListContainers_happy(t *testing.T) {
proxy := new(mockedProxy)
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
client := &dockerClient{proxy, filters.NewArgs()}
client := &Client{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
list, err := client.ListContainers()
require.NoError(t, err, "error should not return an error.")
@@ -104,11 +98,13 @@ func Test_dockerClient_ListContainers_happy(t *testing.T) {
ID: "1234567890_a",
Name: "a_test_container",
Names: []string{"/a_test_container"},
Host: "localhost",
},
{
ID: "abcdefghijkl",
Name: "z_test_container",
Names: []string{"/z_test_container"},
Host: "localhost",
},
})
@@ -125,40 +121,15 @@ func Test_dockerClient_ContainerLogs_happy(t *testing.T) {
binary.BigEndian.PutUint32(b[4:], uint32(len(expected)))
b = append(b, []byte(expected)...)
reader := ioutil.NopCloser(bytes.NewReader(b))
reader := io.NopCloser(bytes.NewReader(b))
options := types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: true, Tail: "300", Timestamps: true, Since: "since"}
proxy.On("ContainerLogs", mock.Anything, id, options).Return(reader, nil)
json := types.ContainerJSON{Config: &container.Config{Tty: false}}
proxy.On("ContainerInspect", mock.Anything, id).Return(json, nil)
client := &dockerClient{proxy, filters.NewArgs()}
logReader, _ := client.ContainerLogs(context.Background(), id, 300, "since")
actual, _ := ioutil.ReadAll(logReader)
assert.Equal(t, expected, string(actual), "message doesn't match expected")
proxy.AssertExpectations(t)
}
func Test_dockerClient_ContainerLogs_happy_with_tty(t *testing.T) {
id := "123456"
proxy := new(mockedProxy)
expected := "INFO Testing logs..."
reader := ioutil.NopCloser(strings.NewReader(expected))
options := types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: true, Tail: "300", Timestamps: true}
proxy.On("ContainerLogs", mock.Anything, id, options).Return(reader, nil)
json := types.ContainerJSON{Config: &container.Config{Tty: true}}
proxy.On("ContainerInspect", mock.Anything, id).Return(json, nil)
client := &dockerClient{proxy, filters.NewArgs()}
logReader, _ := client.ContainerLogs(context.Background(), id, 300, "")
actual, _ := ioutil.ReadAll(logReader)
assert.Equal(t, expected, string(actual), "message doesn't match expected")
client := &Client{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
logReader, _ := client.ContainerLogs(context.Background(), id, "since", STDALL)
actual, _ := io.ReadAll(logReader)
assert.Equal(t, string(b), string(actual), "message doesn't match expected")
proxy.AssertExpectations(t)
}
@@ -168,9 +139,9 @@ func Test_dockerClient_ContainerLogs_error(t *testing.T) {
proxy.On("ContainerLogs", mock.Anything, id, mock.Anything).Return(nil, errors.New("test"))
client := &dockerClient{proxy, filters.NewArgs()}
client := &Client{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
reader, err := client.ContainerLogs(context.Background(), id, 300, "")
reader, err := client.ContainerLogs(context.Background(), id, "", STDALL)
assert.Nil(t, reader, "reader should be nil")
assert.Error(t, err, "error should have been returned")
@@ -191,7 +162,11 @@ func Test_dockerClient_FindContainer_happy(t *testing.T) {
proxy := new(mockedProxy)
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
client := &dockerClient{proxy, filters.NewArgs()}
json := types.ContainerJSON{Config: &container.Config{Tty: false}}
proxy.On("ContainerInspect", mock.Anything, "abcdefghijkl").Return(json, nil)
client := &Client{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
container, err := client.FindContainer("abcdefghijkl")
require.NoError(t, err, "error should not be thrown")
@@ -200,6 +175,8 @@ func Test_dockerClient_FindContainer_happy(t *testing.T) {
ID: "abcdefghijkl",
Name: "z_test_container",
Names: []string{"/z_test_container"},
Host: "localhost",
Tty: false,
})
proxy.AssertExpectations(t)
@@ -218,7 +195,7 @@ func Test_dockerClient_FindContainer_error(t *testing.T) {
proxy := new(mockedProxy)
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
client := &dockerClient{proxy, filters.NewArgs()}
client := &Client{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
_, err := client.FindContainer("not_valid")
require.Error(t, err, "error should be thrown")
+198
View File
@@ -0,0 +1,198 @@
package docker
import (
"bufio"
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
"hash/fnv"
"io"
"strings"
"sync"
"time"
log "github.com/sirupsen/logrus"
)
type EventGenerator struct {
Events chan *LogEvent
Errors chan error
reader *bufio.Reader
next *LogEvent
buffer chan *LogEvent
tty bool
wg sync.WaitGroup
}
var bufPool = sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
}
var BadHeaderErr = fmt.Errorf("dozzle/docker: unable to read header")
func NewEventGenerator(reader io.Reader, tty bool) *EventGenerator {
generator := &EventGenerator{
reader: bufio.NewReader(reader),
buffer: make(chan *LogEvent, 100),
Errors: make(chan error, 1),
Events: make(chan *LogEvent),
tty: tty,
}
generator.wg.Add(2)
go generator.consumeReader()
go generator.processBuffer()
return generator
}
func (g *EventGenerator) processBuffer() {
var current, next *LogEvent
for {
if g.next != nil {
current = g.next
g.next = nil
next = g.peek()
} else {
event, ok := <-g.buffer
if !ok {
close(g.Events)
break
}
current = event
next = g.peek()
}
checkPosition(current, next)
g.Events <- current
}
g.wg.Done()
}
func (g *EventGenerator) consumeReader() {
for {
message, streamType, readerError := readEvent(g.reader, g.tty)
if message != "" {
logEvent := createEvent(message, streamType)
logEvent.Level = guessLogLevel(logEvent)
g.buffer <- logEvent
}
if readerError != nil {
if readerError != BadHeaderErr {
g.Errors <- readerError
}
close(g.buffer)
break
}
}
g.wg.Done()
}
func (g *EventGenerator) peek() *LogEvent {
if g.next != nil {
return g.next
}
select {
case event := <-g.buffer:
g.next = event
return g.next
case <-time.After(50 * time.Millisecond):
return nil
}
}
func readEvent(reader *bufio.Reader, tty bool) (string, StdType, error) {
header := []byte{0, 0, 0, 0, 0, 0, 0, 0}
buffer := bufPool.Get().(*bytes.Buffer)
buffer.Reset()
defer bufPool.Put(buffer)
var streamType StdType = STDOUT
if tty {
message, err := reader.ReadString('\n')
if err != nil {
return message, streamType, err
}
return message, streamType, nil
} else {
n, err := reader.Read(header)
if err != nil {
return "", streamType, err
}
if n != 8 {
return "", streamType, BadHeaderErr
}
switch header[0] {
case 1:
streamType = STDOUT
case 2:
streamType = STDERR
default:
log.Warnf("unknown stream type %d", header[0])
}
count := binary.BigEndian.Uint32(header[4:])
if count == 0 {
return "", streamType, nil
}
_, err = io.CopyN(buffer, reader, int64(count))
if err != nil {
return "", streamType, err
}
return buffer.String(), streamType, nil
}
}
func createEvent(message string, streamType StdType) *LogEvent {
h := fnv.New32a()
h.Write([]byte(message))
logEvent := &LogEvent{Id: h.Sum32(), Message: message, Stream: streamType.String()}
if index := strings.IndexAny(message, " "); index != -1 {
logId := message[:index]
if timestamp, err := time.Parse(time.RFC3339Nano, logId); err == nil {
logEvent.Timestamp = timestamp.UnixMilli()
message = strings.TrimSuffix(message[index+1:], "\n")
logEvent.Message = message
if json.Valid([]byte(message)) {
var data map[string]interface{}
if err := json.Unmarshal([]byte(message), &data); err != nil {
log.Warnf("unable to parse json logs - error was \"%v\" while trying unmarshal \"%v\"", err.Error(), message)
} else {
logEvent.Message = data
}
}
}
}
return logEvent
}
func checkPosition(currentEvent *LogEvent, nextEvent *LogEvent) {
currentLevel := guessLogLevel(currentEvent)
if nextEvent != nil {
if currentEvent.IsCloseToTime(nextEvent) && currentLevel != "" && !nextEvent.HasLevel() {
currentEvent.Position = START
nextEvent.Position = MIDDLE
}
// If next item is not close to current item or has level, set current item position to end
if currentEvent.Position == MIDDLE && (nextEvent.HasLevel() || !currentEvent.IsCloseToTime(nextEvent)) {
currentEvent.Position = END
}
// If next item is close to current item and has no level, set next item position to middle
if currentEvent.Position == MIDDLE && !nextEvent.HasLevel() && currentEvent.IsCloseToTime(nextEvent) {
nextEvent.Position = MIDDLE
}
// Set next item level to current item level
if currentEvent.Position == START || currentEvent.Position == MIDDLE {
nextEvent.Level = currentEvent.Level
}
} else if currentEvent.Position == MIDDLE {
currentEvent.Position = END
}
}
+149
View File
@@ -0,0 +1,149 @@
package docker
import (
"bufio"
"bytes"
"encoding/binary"
"reflect"
"strings"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestEventGenerator_Events_tty(t *testing.T) {
input := "example input"
reader := bufio.NewReader(strings.NewReader(input))
g := NewEventGenerator(reader, true)
event := <-g.Events
require.NotNil(t, event, "Expected event to not be nil, but got nil")
assert.Equal(t, input, event.Message)
}
func TestEventGenerator_Events_non_tty(t *testing.T) {
input := "example input"
reader := bytes.NewReader(makeMessage(input, STDOUT))
g := NewEventGenerator(reader, false)
event := <-g.Events
require.NotNil(t, event, "Expected event to not be nil, but got nil")
assert.Equal(t, input, event.Message)
}
func TestEventGenerator_Events_non_tty_close_channel(t *testing.T) {
input := "example input"
reader := bytes.NewReader(makeMessage(input, STDOUT))
g := NewEventGenerator(reader, false)
<-g.Events
_, ok := <-g.Events
assert.False(t, ok, "Expected channel to be closed")
}
func TestEventGenerator_Events_routines_done(t *testing.T) {
input := "example input"
reader := bytes.NewReader(makeMessage(input, STDOUT))
g := NewEventGenerator(reader, false)
<-g.Events
assert.False(t, waitTimeout(&g.wg, 1*time.Second), "Expected routines to be done")
}
func makeMessage(message string, stream StdType) []byte {
data := make([]byte, 8)
binary.BigEndian.PutUint32(data[4:], uint32(len(message)))
data[0] = byte(stream / 2)
data = append(data, []byte(message)...)
return data
}
func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
c := make(chan struct{})
go func() {
defer close(c)
wg.Wait()
}()
select {
case <-c:
return false // completed normally
case <-time.After(timeout):
return true // timed out
}
}
func Test_createEvent(t *testing.T) {
type args struct {
message string
streamType StdType
}
tests := []struct {
name string
args args
want *LogEvent
}{
{
name: "empty message",
args: args{
message: "",
},
want: &LogEvent{
Message: "",
},
}, {
name: "simple json message",
args: args{
message: "2020-05-13T18:55:37.772853839Z {\"key\": \"value\"}",
},
want: &LogEvent{
Message: map[string]interface{}{
"key": "value",
},
},
},
{
name: "invalid json message",
args: args{
message: "2020-05-13T18:55:37.772853839Z {\"key\"}",
},
want: &LogEvent{
Message: "{\"key\"}",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := createEvent(tt.args.message, STDOUT); !reflect.DeepEqual(got.Message, tt.want.Message) {
t.Errorf("createEvent() = %v, want %v", got.Message, tt.want.Message)
}
})
}
}
type mockReadCloser struct {
bytes []byte
}
func (m mockReadCloser) Read(p []byte) (int, error) {
return copy(p, m.bytes), nil
}
func Benchmark_readEvent(b *testing.B) {
b.ReportAllocs()
data := makeMessage("2020-05-13T18:55:37.772853839Z {\"key\": \"value\"}\n", STDOUT)
reader := bufio.NewReader(mockReadCloser{bytes: data})
for i := 0; i < b.N; i++ {
readEvent(reader, true)
// println(message, stream)
}
}
+68
View File
@@ -0,0 +1,68 @@
package docker
import (
"fmt"
"log"
"net/url"
"os"
"path/filepath"
"strings"
)
type Host struct {
Name string `json:"name"`
ID string `json:"id"`
URL *url.URL `json:"-"`
CertPath string `json:"-"`
CACertPath string `json:"-"`
KeyPath string `json:"-"`
ValidCerts bool `json:"-"`
}
func ParseConnection(connection string) (Host, error) {
parts := strings.Split(connection, "|")
if len(parts) > 2 {
return Host{}, fmt.Errorf("invalid connection string: %s", connection)
}
remoteUrl, err := url.Parse(parts[0])
if err != nil {
return Host{}, err
}
name := remoteUrl.Hostname()
if len(parts) == 2 {
name = parts[1]
}
basePath, err := filepath.Abs("./certs")
if err != nil {
log.Fatalf("error converting certs path to absolute: %s", err)
}
host := remoteUrl.Hostname()
if _, err := os.Stat(filepath.Join(basePath, host)); !os.IsNotExist(err) {
basePath = filepath.Join(basePath, host)
}
cacertPath := filepath.Join(basePath, "ca.pem")
certPath := filepath.Join(basePath, "cert.pem")
keyPath := filepath.Join(basePath, "key.pem")
hasCerts := true
if _, err := os.Stat(cacertPath); os.IsNotExist(err) {
cacertPath = ""
hasCerts = false
}
return Host{
ID: strings.ReplaceAll(remoteUrl.String(), "/", ""),
Name: name,
URL: remoteUrl,
CertPath: certPath,
CACertPath: cacertPath,
KeyPath: keyPath,
ValidCerts: hasCerts,
}, nil
}
+41
View File
@@ -0,0 +1,41 @@
package docker
import (
"regexp"
"strings"
)
var KEY_VALUE_REGEX = regexp.MustCompile(`level=(\w+)`)
var ANSI_COLOR_REGEX = regexp.MustCompile(`\x1b\[[0-9;]*m`)
func guessLogLevel(logEvent *LogEvent) string {
switch value := logEvent.Message.(type) {
case string:
levels := []string{"error", "warn", "warning", "info", "debug", "trace", "fatal"}
stripped := ANSI_COLOR_REGEX.ReplaceAllString(value, "") // remove ansi color codes
for _, level := range levels {
if match, _ := regexp.MatchString("(?i)^"+level+"[^a-z]", stripped); match {
return level
}
if strings.Contains(value, "["+strings.ToUpper(level)+"]") {
return level
}
if strings.Contains(value, " "+strings.ToUpper(level)+" ") {
return level
}
}
if matches := KEY_VALUE_REGEX.FindStringSubmatch(value); matches != nil {
return matches[1]
}
case map[string]interface{}:
if level, ok := value["level"].(string); ok {
return level
}
}
return ""
}
-49
View File
@@ -1,49 +0,0 @@
package docker
import (
"bytes"
"encoding/binary"
"io"
)
type logReader struct {
readerCloser io.ReadCloser
tty bool
lastHeader []byte
buffer bytes.Buffer
}
func newLogReader(reader io.ReadCloser, tty bool) io.ReadCloser {
return &logReader{
reader,
tty,
make([]byte, 8),
bytes.Buffer{},
}
}
func (r *logReader) Read(p []byte) (n int, err error) {
if r.tty {
return r.readerCloser.Read(p)
} else {
if r.buffer.Len() > 0 {
return r.buffer.Read(p)
} else {
r.buffer.Reset()
_, err := r.readerCloser.Read(r.lastHeader)
if err != nil {
return 0, err
}
count := binary.BigEndian.Uint32(r.lastHeader[4:])
_, err = io.CopyN(&r.buffer, r.readerCloser, int64(count))
if err != nil {
return 0, err
}
return r.buffer.Read(p)
}
}
}
func (r *logReader) Close() error {
return r.readerCloser.Close()
}
+30 -3
View File
@@ -1,5 +1,9 @@
package docker
import (
"math"
)
// Container represents an internal representation of docker containers
type Container struct {
ID string `json:"id"`
@@ -11,6 +15,9 @@ type Container struct {
Created int64 `json:"created"`
State string `json:"state"`
Status string `json:"status"`
Health string `json:"health,omitempty"`
Host string `json:"host,omitempty"`
Tty bool `json:"-"`
}
// ContainerStat represent stats instant for a container
@@ -25,10 +32,30 @@ type ContainerStat struct {
type ContainerEvent struct {
ActorID string `json:"actorId"`
Name string `json:"name"`
Host string `json:"host"`
}
type LogPosition string
const (
START LogPosition = "start"
MIDDLE LogPosition = "middle"
END LogPosition = "end"
)
type LogEvent struct {
Message any `json:"m,omitempty"`
Timestamp int64 `json:"ts"`
Id uint32 `json:"id,omitempty"`
Message any `json:"m,omitempty"`
Timestamp int64 `json:"ts"`
Id uint32 `json:"id,omitempty"`
Level string `json:"l,omitempty"`
Position LogPosition `json:"p,omitempty"`
Stream string `json:"s,omitempty"`
}
func (l *LogEvent) HasLevel() bool {
return l.Level != ""
}
func (l *LogEvent) IsCloseToTime(other *LogEvent) bool {
return math.Abs(float64(l.Timestamp-other.Timestamp)) < 10
}
+10
View File
@@ -0,0 +1,10 @@
.DS_Store
.vite-ssg-dist
.vite-ssg-temp
*.local
dist
dist-ssr
node_modules
.idea/
*.log
!.vscode
+2
View File
@@ -0,0 +1,2 @@
dist
cache
+119
View File
@@ -0,0 +1,119 @@
import { createRequire } from "module";
import { defineConfig } from "vitepress";
import { createWriteStream } from "node:fs";
import { resolve } from "node:path";
import { SitemapStream, streamToPromise } from "sitemap";
const require = createRequire(import.meta.url);
const pkg = require("dozzle/package.json");
const links = [] as { url: string; lastmod?: number }[];
export default defineConfig({
lang: "en-US",
title: "Dozzle",
description: "A lightweight, open-source, and secure log viewer for Docker.",
lastUpdated: true,
cleanUrls: true,
head: [
[
"script",
{
async: "",
src: "https://www.googletagmanager.com/gtag/js?id=G-X3Z4496XFK",
},
],
[
"script",
{},
`function gtag(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],gtag("js",new Date),gtag("config","G-X3Z4496XFK")`,
],
],
themeConfig: {
editLink: {
pattern: "https://github.com/amir20/dozzle/edit/master/docs/:path",
},
nav: [
{ text: "Home", link: "/" },
{ text: "Guide", link: "/guide/what-is-dozzle", activeMatch: "/guide/" },
{
text: `v${pkg.version}`,
items: [
{
text: "Releases",
link: "https://github.com/amir20/dozzle/releases",
},
{
text: "New Issue",
link: "https://github.com/amir20/dozzle/issues/new/choose",
},
],
},
],
sidebar: [
{
text: "Introduction",
items: [
{ text: "What is Dozzle?", link: "/guide/what-is-dozzle" },
{ text: "Getting Started", link: "/guide/getting-started" },
],
},
{
text: "Advanced Configuration",
items: [
{ text: "Authentication", link: "/guide/authentication" },
{ text: "Healthcheck", link: "/guide/healthcheck" },
{ text: "Remote Hosts", link: "/guide/remote-hosts" },
{ text: "Supported Env Vars", link: "/guide/supported-env-vars" },
{ text: "Logging Files on Disk", link: "/guide/log-files-on-disk" },
{ text: "Analytics", link: "/guide/analytics" },
],
},
{
text: "Troubleshooting",
items: [
{ text: "FAQ", link: "/guide/faq" },
{ text: "Debugging", link: "/guide/debugging" },
],
},
],
footer: {
message: "Released under the MIT License. Open sourced and sponsored by Docker OSS.",
copyright: "Copyright © 2019-present <a href='https://amirraminfar.me'>Amir Raminfar</a>",
},
socialLinks: [
{ icon: "github", link: "https://github.com/amir20/dozzle" },
{
icon: {
svg: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M21.81 10.25c-.06-.04-.56-.43-1.64-.43c-.28 0-.56.03-.84.08c-.21-1.4-1.38-2.11-1.43-2.14l-.29-.17l-.18.27c-.24.36-.43.77-.51 1.19c-.2.8-.08 1.56.33 2.21c-.49.28-1.29.35-1.46.35H2.62c-.34 0-.62.28-.62.63c0 1.15.18 2.3.58 3.38c.45 1.19 1.13 2.07 2 2.61c.98.6 2.59.94 4.42.94c.79 0 1.61-.07 2.42-.22c1.12-.2 2.2-.59 3.19-1.16A8.3 8.3 0 0 0 16.78 16c1.05-1.17 1.67-2.5 2.12-3.65h.19c1.14 0 1.85-.46 2.24-.85c.26-.24.45-.53.59-.87l.08-.24l-.19-.14m-17.96.99h1.76c.08 0 .16-.07.16-.16V9.5c0-.08-.07-.16-.16-.16H3.85c-.09 0-.16.07-.16.16v1.58c.01.09.07.16.16.16m2.43 0h1.76c.08 0 .16-.07.16-.16V9.5c0-.08-.07-.16-.16-.16H6.28c-.09 0-.16.07-.16.16v1.58c.01.09.07.16.16.16m2.47 0h1.75c.1 0 .17-.07.17-.16V9.5c0-.08-.06-.16-.17-.16H8.75c-.08 0-.15.07-.15.16v1.58c0 .09.06.16.15.16m2.44 0h1.77c.08 0 .15-.07.15-.16V9.5c0-.08-.06-.16-.15-.16h-1.77c-.08 0-.15.07-.15.16v1.58c0 .09.07.16.15.16M6.28 9h1.76c.08 0 .16-.09.16-.18V7.25c0-.09-.07-.16-.16-.16H6.28c-.09 0-.16.06-.16.16v1.57c.01.09.07.18.16.18m2.47 0h1.75c.1 0 .17-.09.17-.18V7.25c0-.09-.06-.16-.17-.16H8.75c-.08 0-.15.06-.15.16v1.57c0 .09.06.18.15.18m2.44 0h1.77c.08 0 .15-.09.15-.18V7.25c0-.09-.07-.16-.15-.16h-1.77c-.08 0-.15.06-.15.16v1.57c0 .09.07.18.15.18m0-2.28h1.77c.08 0 .15-.07.15-.16V5c0-.1-.07-.17-.15-.17h-1.77c-.08 0-.15.06-.15.17v1.56c0 .08.07.16.15.16m2.46 4.52h1.76c.09 0 .16-.07.16-.16V9.5c0-.08-.07-.16-.16-.16h-1.76c-.08 0-.15.07-.15.16v1.58c0 .09.07.16.15.16"/></svg>`,
},
link: "https://hub.docker.com/r/amir20/dozzle",
},
],
},
transformHtml: (_, id, { pageData }) => {
if (!/[\\/]404\.html$/.test(id))
links.push({
// you might need to change this if not using clean urls mode
url: pageData.relativePath.replace(/((^|\/)index)?\.md$/, "$2"),
lastmod: pageData.lastUpdated,
});
},
buildEnd: async ({ outDir }) => {
const sitemap = new SitemapStream({
hostname: "https://dozzle.dev/",
});
const writeStream = createWriteStream(resolve(outDir, "sitemap.xml"));
sitemap.pipe(writeStream);
links.forEach((link) => sitemap.write(link));
const promise = streamToPromise(sitemap);
sitemap.end();
await promise;
},
});
@@ -0,0 +1,14 @@
<script setup></script>
<template>
<div border="~ solid rounded light-100 dark:dark-50" bg="[#eee] dark:[#222]">
<div flex gap="2" p-2>
<div circle-red />
<div circle-yellow />
<div circle-green />
</div>
<div class="content">
<slot />
</div>
</div>
</template>

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