Compare commits

...

108 Commits

Author SHA1 Message Date
Amir
2a94f035a2 1.17.1 2019-12-03 12:31:10 -08:00
Amir
e8e2390776 Adds html title tag 2019-12-03 12:31:04 -08:00
Amir
71f214e20d Adds support for split panes 2019-11-25 15:28:04 -08:00
Amir Raminfar
63f132c820 Adds the ability to split panes and view multiple logs (#186)
* Fixes css to have full containers

* Adds split panes

* Fixes background color

* Fixes splitter

* Fixes tests

* Adds more tests

* Updates tests

* Adds vuex

* Moves splitpane to app

* Moves the panes to app

* Fixes columns with min-width

* Update packages

* Updates html to have better components

* Updates npm packages

* Fixes scrollar

* Creates a scrollable view

* Fixes App specs

* Adds vuex for component

* Updates to use splitpanes

* Styles splitter

* Removes fetch-mock
2019-11-25 15:26:42 -08:00
dependabot-preview[bot]
ffd964fe82 Bump prettier from 1.18.2 to 1.19.1 (#176)
Bumps [prettier](https://github.com/prettier/prettier) from 1.18.2 to 1.19.1.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/1.18.2...1.19.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-09 18:10:22 -08:00
Amir Raminfar
63dd413296 Refactors code to decouple view with event source (#177)
* Decouples provider and viewer

* Fixes title

* Clean up

* Fixes tests
2019-11-09 18:10:02 -08:00
Amir
88bb3f296a 1.16.2 2019-11-07 12:49:05 -08:00
Amir
f6e0e4ed08 Fixes search again 2019-11-07 12:43:39 -08:00
Amir
707ab974a1 Adds a test for filter 2019-11-07 12:05:18 -08:00
Amir
fa0f743cb4 1.16.1 2019-11-06 17:13:39 -08:00
Amir
bde434851c Fixes broken search 2019-11-06 17:13:20 -08:00
Amir
dc8f6f722e Adds more tests for tty 2019-11-06 11:39:07 -08:00
dependabot-preview[bot]
649e577483 Bump @vue/component-compiler-utils from 3.0.1 to 3.0.2 (#171)
Bumps [@vue/component-compiler-utils](https://github.com/vuejs/component-compiler-utils) from 3.0.1 to 3.0.2.
- [Release notes](https://github.com/vuejs/component-compiler-utils/releases)
- [Changelog](https://github.com/vuejs/component-compiler-utils/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vuejs/component-compiler-utils/compare/v3.0.1...v3.0.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-06 10:58:34 -08:00
dependabot-preview[bot]
4480587ae8 Bump fetch-mock from 7.7.2 to 7.7.3 (#169)
Bumps [fetch-mock](https://github.com/wheresrhys/fetch-mock) from 7.7.2 to 7.7.3.
- [Release notes](https://github.com/wheresrhys/fetch-mock/releases)
- [Commits](https://github.com/wheresrhys/fetch-mock/compare/v7.7.2...v7.7.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-06 10:57:45 -08:00
Amir
a4db64300c Adds support for TTY 2019-11-05 10:35:00 -08:00
Amir
8925a6ed14 Fixes security 2019-11-05 10:34:48 -08:00
Amir Raminfar
c5bd1fc735 Adds support for tty (#168) 2019-11-05 10:33:45 -08:00
dependabot-preview[bot]
61a35663dc Bump @babel/core from 7.6.4 to 7.7.0 (#167)
Bumps [@babel/core](https://github.com/babel/babel) from 7.6.4 to 7.7.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.6.4...v7.7.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-05 10:33:14 -08:00
dependabot-preview[bot]
2a52d7b6a1 Bump ansi-to-html from 0.6.12 to 0.6.13 (#166)
Bumps [ansi-to-html](https://github.com/rburns/ansi-to-html) from 0.6.12 to 0.6.13.
- [Release notes](https://github.com/rburns/ansi-to-html/releases)
- [Commits](https://github.com/rburns/ansi-to-html/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-05 10:26:03 -08:00
Amir
6adec499e1 Updates npm modules 2019-11-04 11:10:55 -08:00
Amir Raminfar
5990f126f4 Fixes npm node (#162) 2019-11-01 12:22:29 -07:00
Amir
e1d66b9c78 Adds a new test 2019-11-01 12:16:44 -07:00
dependabot-preview[bot]
6014e10f62 Bump github.com/spf13/viper from 1.4.0 to 1.5.0 (#161)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.4.0 to 1.5.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.4.0...v1.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-01 12:15:38 -07:00
Amir Raminfar
457b760da5 1.15.9 2019-10-29 09:07:15 -07:00
Amir Raminfar
e8ab871efb Adds escapeXML: true, 2019-10-29 09:07:05 -07:00
dependabot-preview[bot]
1651025969 Bump date-fns from 2.5.1 to 2.6.0 (#153)
Bumps [date-fns](https://github.com/date-fns/date-fns) from 2.5.1 to 2.6.0.
- [Release notes](https://github.com/date-fns/date-fns/releases)
- [Changelog](https://github.com/date-fns/date-fns/blob/master/CHANGELOG.md)
- [Commits](https://github.com/date-fns/date-fns/compare/v2.5.1...v2.6.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-23 12:15:33 -07:00
dependabot-preview[bot]
b81d718b7e Bump sass from 1.23.0 to 1.23.1 (#154)
Bumps [sass](https://github.com/sass/dart-sass) from 1.23.0 to 1.23.1.
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.23.0...1.23.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-23 12:15:24 -07:00
Amir
2600703625 Updates bulma 2019-10-18 15:39:10 -07:00
Amir
9a613e0b85 Fixes security 2019-10-15 12:56:21 -07:00
Amir Raminfar
8e63a5742a Updates modules 2019-10-14 08:22:41 -07:00
Amir Raminfar
c9475e10af Updates modules 2019-10-08 11:39:11 -07:00
Amir Raminfar
b10c99ae3e 1.15.8 2019-09-18 19:13:24 -07:00
Amir Raminfar
b93346b673 Updates modules 2019-09-18 19:09:59 -07:00
Amir Raminfar
4753dbd847 Updates node modules 2019-09-18 19:09:59 -07:00
dependabot-preview[bot]
44471e9d3a Bump sass from 1.22.10 to 1.22.12 (#123)
Bumps [sass](https://github.com/sass/dart-sass) from 1.22.10 to 1.22.12.
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.22.10...1.22.12)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-18 11:20:31 -07:00
dependabot-preview[bot]
0f5a89aae4 Bump date-fns from 2.1.0 to 2.2.1 (#124)
Bumps [date-fns](https://github.com/date-fns/date-fns) from 2.1.0 to 2.2.1.
- [Release notes](https://github.com/date-fns/date-fns/releases)
- [Changelog](https://github.com/date-fns/date-fns/blob/master/CHANGELOG.md)
- [Commits](https://github.com/date-fns/date-fns/compare/v2.1.0...v2.2.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-18 11:20:11 -07:00
Amir Raminfar
d5bdaa172b Removes showAll from reflex 2019-09-10 10:35:46 -07:00
Amir Raminfar
714975ca95 Removes github actions from readme 2019-09-09 15:51:37 -07:00
dependabot-preview[bot]
68f364c7ff Bump vue-jest from 3.0.4 to 3.0.5 (#121)
Bumps [vue-jest](https://github.com/vuejs/vue-jest) from 3.0.4 to 3.0.5.
- [Release notes](https://github.com/vuejs/vue-jest/releases)
- [Commits](https://github.com/vuejs/vue-jest/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-09 15:50:58 -07:00
Amir Raminfar
f34144e7fc 1.15.7 2019-09-09 15:34:13 -07:00
Amir Raminfar
e485f0fe9a Fixes bad dockerfile 2019-09-09 15:34:06 -07:00
Amir Raminfar
115e7de7bf 1.15.6 2019-09-09 15:28:42 -07:00
Amir Raminfar
080b0b29a7 Uses goreleaser_Linux_x86_64.tar.gz 2019-09-09 15:28:34 -07:00
Amir Raminfar
ebc9158a45 1.15.5 2019-09-08 15:12:27 -07:00
Amir Raminfar
38a3167cbd Reverts 2019-09-08 15:12:05 -07:00
Amir Raminfar
b48a75f30d 1.15.4 2019-09-08 14:56:28 -07:00
Amir Raminfar
5b10a39ad5 Adds g++ 2019-09-08 14:56:24 -07:00
Amir Raminfar
1d7048115e 1.15.3 2019-09-08 14:35:30 -07:00
Amir Raminfar
1eb373da5e Adds make 2019-09-08 14:34:55 -07:00
Amir Raminfar
9cf522f7df 1.15.2 2019-09-08 14:25:44 -07:00
Amir Raminfar
153a620676 Adds docker to entrypoint 2019-09-08 14:25:40 -07:00
Amir Raminfar
b1feba8314 1.15.1 2019-09-08 14:18:43 -07:00
Amir Raminfar
277701bc56 Tries to fix bash error 2019-09-08 14:18:39 -07:00
Amir Raminfar
39c250993e 1.15.0 2019-09-08 14:01:31 -07:00
Amir Raminfar
d8b82c8200 Updates goreleaser to alpine and uses 1.13 2019-09-08 14:00:47 -07:00
Amir Raminfar
93faeaaedc Updates to 1.13 2019-09-08 13:38:52 -07:00
Amir Raminfar
35d08a0a97 Updates tests to 1.13 2019-09-08 13:36:29 -07:00
Amir Raminfar
921082e32f Updates packages (#120) 2019-09-07 08:23:48 -07:00
Amir Raminfar
0dd119efae 1.14.0 2019-08-27 19:18:22 -07:00
Amir Raminfar
2019d29161 Merge branch 'updates-docker' 2019-08-27 19:17:04 -07:00
Amir Raminfar
c22afb19f3 Updates docker (#113)
* Updates go mods

* Updates docker
2019-08-27 19:14:38 -07:00
Amir Raminfar
50de4b7f11 Removes indirect 2019-08-27 19:11:16 -07:00
Amir Raminfar
a9c119297f Updates docker 2019-08-27 19:10:09 -07:00
Amir Raminfar
3c954a0840 Adds appengine 2019-08-27 19:10:03 -07:00
Amir Raminfar
494d95e9cd Updates go mods 2019-08-27 18:44:15 -07:00
dependabot-preview[bot]
489460042d Bump lint-staged from 9.2.3 to 9.2.5 (#111)
Bumps [lint-staged](https://github.com/okonet/lint-staged) from 9.2.3 to 9.2.5.
- [Release notes](https://github.com/okonet/lint-staged/releases)
- [Commits](https://github.com/okonet/lint-staged/compare/v9.2.3...v9.2.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-27 18:42:24 -07:00
Amir Raminfar
2315965eb2 Adds if for release (#112)
* Adds if for release

* Fixes if

* Fixes env
2019-08-27 15:51:42 -07:00
Amir Raminfar
0a5e6da3ae 1.13.1 2019-08-23 13:47:23 -07:00
Amir Raminfar
d9aedd78ba Renames jobs 2019-08-23 13:47:17 -07:00
Amir Raminfar
37fdeb6d18 Updates readme 2019-08-23 13:40:21 -07:00
Amir Raminfar
3bd3dd2d04 converted main.workflow to Actions V2 yml files 2019-08-23 13:35:07 -07:00
Amir Raminfar
af42d980d0 1.13.0 2019-08-23 13:29:31 -07:00
Amir Raminfar
2070b9eee4 Updates golang 2019-08-23 13:29:16 -07:00
Amir Raminfar
8651b37723 Updates libs 2019-08-23 13:27:21 -07:00
dependabot-preview[bot]
22c350aed5 Bump sass from 1.22.9 to 1.22.10 (#104)
Bumps [sass](https://github.com/sass/dart-sass) from 1.22.9 to 1.22.10.
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.22.9...1.22.10)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-23 13:24:54 -07:00
dependabot-preview[bot]
80686bc279 Bump concurrently from 4.1.1 to 4.1.2 (#105)
Bumps [concurrently](https://github.com/kimmobrunfeldt/concurrently) from 4.1.1 to 4.1.2.
- [Release notes](https://github.com/kimmobrunfeldt/concurrently/releases)
- [Commits](https://github.com/kimmobrunfeldt/concurrently/compare/v4.1.1...v4.1.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-23 13:24:43 -07:00
dependabot-preview[bot]
2b3a60b950 Bump husky from 3.0.3 to 3.0.4 (#106)
Bumps [husky](https://github.com/typicode/husky) from 3.0.3 to 3.0.4.
- [Release notes](https://github.com/typicode/husky/releases)
- [Changelog](https://github.com/typicode/husky/blob/master/CHANGELOG.md)
- [Commits](https://github.com/typicode/husky/compare/v3.0.3...v3.0.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-23 13:24:34 -07:00
dependabot-preview[bot]
08b52106fb Bump lint-staged from 9.2.1 to 9.2.3 (#107)
Bumps [lint-staged](https://github.com/okonet/lint-staged) from 9.2.1 to 9.2.3.
- [Release notes](https://github.com/okonet/lint-staged/releases)
- [Commits](https://github.com/okonet/lint-staged/compare/v9.2.1...v9.2.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-23 13:24:25 -07:00
dependabot-preview[bot]
1ed7c4e513 Bump github.com/stretchr/testify from 1.3.0 to 1.4.0 (#100)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.3.0 to 1.4.0.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.3.0...v1.4.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-23 13:24:02 -07:00
dependabot-preview[bot]
3b2b46f4ac Bump date-fns from 2.0.0-beta.4 to 2.0.0 (#108)
Bumps [date-fns](https://github.com/date-fns/date-fns) from 2.0.0-beta.4 to 2.0.0.
- [Release notes](https://github.com/date-fns/date-fns/releases)
- [Changelog](https://github.com/date-fns/date-fns/blob/master/CHANGELOG.md)
- [Commits](https://github.com/date-fns/date-fns/compare/v2.0.0-beta.4...v2.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-23 13:23:40 -07:00
Pavel Makarenko
b356ffcd68 added --showAll flag that will show all containers (default: false) (#109) 2019-08-23 13:23:30 -07:00
Amir Raminfar
197c2cc87b 1.12.2 2019-08-15 16:07:44 -07:00
Amir Raminfar
ee7e919c12 Updates libs 2019-08-15 16:04:35 -07:00
dependabot-preview[bot]
e4275003b3 Bump sass from 1.22.7 to 1.22.9 (#92)
Bumps [sass](https://github.com/sass/dart-sass) from 1.22.7 to 1.22.9.
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.22.7...1.22.9)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-01 20:46:46 -07:00
dependabot-preview[bot]
0da82500f3 Bump husky from 3.0.1 to 3.0.2 (#91)
Bumps [husky](https://github.com/typicode/husky) from 3.0.1 to 3.0.2.
- [Release notes](https://github.com/typicode/husky/releases)
- [Changelog](https://github.com/typicode/husky/blob/master/CHANGELOG.md)
- [Commits](https://github.com/typicode/husky/compare/v3.0.1...v3.0.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-01 20:46:22 -07:00
dependabot-preview[bot]
c14e8bfd1a Bump mockdate from 2.0.3 to 2.0.4 (#93)
Bumps [mockdate](https://github.com/boblauer/MockDate) from 2.0.3 to 2.0.4.
- [Release notes](https://github.com/boblauer/MockDate/releases)
- [Commits](https://github.com/boblauer/MockDate/compare/v2.0.3...v2.0.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-01 20:46:05 -07:00
dependabot-preview[bot]
d8eda3384b Bump vue-meta from 2.1.0 to 2.2.0 (#90)
Bumps [vue-meta](https://github.com/nuxt/vue-meta) from 2.1.0 to 2.2.0.
- [Release notes](https://github.com/nuxt/vue-meta/releases)
- [Changelog](https://github.com/nuxt/vue-meta/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nuxt/vue-meta/compare/v2.1.0...v2.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-01 20:45:55 -07:00
Amir
fe14cd184a Fixes tests 2019-07-25 14:24:59 -07:00
Amir
5a367fa20a Uses filter for relatice time instead 2019-07-25 14:23:28 -07:00
Amir Raminfar
2db858a227 Update README.md 2019-07-25 12:27:59 -07:00
Amir Raminfar
3cc17c0802 Update README.md 2019-07-25 12:26:11 -07:00
dependabot-preview[bot]
72d20457b1 Bump date-fns from 2.0.0-beta.2 to 2.0.0-beta.3 (#86)
Bumps [date-fns](https://github.com/date-fns/date-fns) from 2.0.0-beta.2 to 2.0.0-beta.3.
- [Release notes](https://github.com/date-fns/date-fns/releases)
- [Changelog](https://github.com/date-fns/date-fns/blob/master/CHANGELOG.md)
- [Commits](https://github.com/date-fns/date-fns/compare/v2.0.0-beta.2...v2.0.0-beta.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-24 08:37:05 -07:00
Amir Raminfar
1efa49644e 1.12.1 2019-07-24 08:30:28 -07:00
Amir Raminfar
e733049876 Updates vue meta 2019-07-24 08:30:04 -07:00
Amir Raminfar
0562c9a625 Adds filters to be used with all supported filters (#87)
* Adds filter WIP

* Updates with map

* Updates readme
2019-07-24 08:28:59 -07:00
Amir Raminfar
40c3c2d23e Update README.md 2019-07-22 13:11:45 -07:00
Amir
366329f497 Adds more tests 2019-07-22 13:09:11 -07:00
Amir
7942ae66b7 Adds more tests 2019-07-22 13:09:11 -07:00
Amir Raminfar
a0d65b7fc5 Update README.md 2019-07-22 12:46:44 -07:00
Amir Raminfar
b4623a6d8b Update README.md 2019-07-22 12:45:57 -07:00
Amir
77a70faf04 Adds filter by name 2019-07-22 12:40:51 -07:00
Amir Raminfar
6a44ebaa5c Adds tests and uses filterName instead. (#85)
* Adding:
- ability to restrict view to container by regex match on container name
- instructions for building/running locally

* refactoring based on comments

* Updates tests

* Fixes readme
2019-07-22 12:38:52 -07:00
Amir Raminfar
1d411b490b Updates go modules 2019-07-21 16:29:10 -07:00
Amir Raminfar
1b42514642 Updates mods 2019-07-21 13:53:19 -07:00
Amir Raminfar
d371aa5111 Fixes security 2019-07-21 13:52:46 -07:00
dependabot-preview[bot]
4743c8be8f Bump sass from 1.22.4 to 1.22.5 (#76)
Bumps [sass](https://github.com/sass/dart-sass) from 1.22.4 to 1.22.5.
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.22.4...1.22.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-07-18 12:58:30 -07:00
Amir
ff0a774549 1.11.8 2019-07-15 15:37:43 -07:00
Amir
94ec9e49f0 Fixes unit tests 2019-07-15 15:37:32 -07:00
32 changed files with 3806 additions and 2107 deletions

View File

@@ -1,4 +1,4 @@
FROM golang:1.12
FROM golang:1.13
COPY entrypoint.sh /entrypoint.sh

View File

@@ -1,7 +1,11 @@
FROM goreleaser/goreleaser:latest
FROM golang:1.13-alpine
RUN go get -u github.com/gobuffalo/packr/packr
RUN apk --no-cache add nodejs-current nodejs-npm && npm i -g npm
RUN apk --no-cache add git nodejs-current nodejs-npm make g++ bash bzr curl docker rpm && \
npm i -g npm && \
wget https://github.com/goreleaser/goreleaser/releases/latest/download/goreleaser_Linux_x86_64.tar.gz && \
tar -xvzf *.tar.gz -C /usr/local/bin && \
rm *.gz && \
go get -u github.com/gobuffalo/packr/packr
COPY entrypoint.sh /entrypoint.sh

28
.github/main.workflow vendored
View File

@@ -1,28 +0,0 @@
workflow "Build, Test and Release" {
on = "push"
resolves = [
"Release",
]
}
action "go test" {
uses = "./.github/golang/"
}
action "npm test" {
uses = "actions/npm@master"
args = "it"
}
action "Tag" {
uses = "actions/bin/filter@master"
needs = ["go test", "npm test"]
args = "tag"
}
action "Release" {
uses = "./.github/goreleaser/"
needs = ["Tag"]
args = "release"
secrets = ["GITHUB_TOKEN", "DOCKER_USERNAME", "DOCKER_PASSWORD"]
}

23
.github/workflows/push.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
on: push
name: Build, Test and Release
jobs:
build:
name: npm test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: npm test
uses: actions/setup-node@v1
- run: npm it
- name: go test
uses: ./.github/golang/
- name: Release
uses: ./.github/goreleaser/
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REF: ${{ github. ref }}
with:
args: release
if: contains(github.ref, 'tags')

View File

@@ -1,16 +1,15 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/amir20/dozzle)](https://goreportcard.com/report/github.com/amir20/dozzle)
[![Build Status](https://wdp9fww0r9.execute-api.us-west-2.amazonaws.com/production/badge/amir20/dozzle)](https://wdp9fww0r9.execute-api.us-west-2.amazonaws.com/production/results/amir20/dozzle)
[![Docker Pulls](https://img.shields.io/docker/pulls/amir20/dozzle.svg)](https://hub.docker.com/r/amir20/dozzle/)
[![Docker Size](https://images.microbadger.com/badges/image/amir20/dozzle.svg)](https://hub.docker.com/r/amir20/dozzle/)
[![Docker Version](https://images.microbadger.com/badges/version/amir20/dozzle.svg)](https://hub.docker.com/r/amir20/dozzle/)
# Dozzle - [dozzle.dev](https://dozzle.dev/)
Dozzle is a log viewer for Docker. It's free. It's small. And it's right in your browser. Oh, did I mention it is also real-time?
Dozzle is a real-time log viewer for Docker. It's free. It's small. And it's right in your browser.
While dozzle should work for most, it is not meant to be a full logging solution. For enterprise use, I recommend you look at [Loggly](https://www.loggly.com), [Papertrail](https://papertrailapp.com) or [Kibana](https://www.elastic.co/products/kibana).
But if you don't want to pay for those services, then you are in luck! Dozzle will be able to capture all logs from your containers and send them in real-time to your browser. Installation is also very easy.
But if you don't want to pay for those services, then you are in luck! Dozzle will be able to capture all logs from your containers and send them in real-time to your browser. Installation is also very easy. Dozzle is not a database. It does not store or save any logs. You can only see live logs while using Dozzle.
![Image](demo.gif)
@@ -22,21 +21,35 @@ Dozzle is a very small Docker container (4 MB compressed). Pull the latest relea
## Using dozzle
The simplest way to use dozzle is to run the docker container. Also, mount the Docker Unix socket with `-volume` to `/var/run/docker.sock`:
The simplest way to use dozzle is to run the docker container. Also, mount the Docker Unix socket with `--volume` to `/var/run/docker.sock`:
$ docker run --name dozzle -d --volume=/var/run/docker.sock:/var/run/docker.sock -p 8888:8080 amir20/dozzle:latest
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`.
## Docker swarm deploy
### With Docker swarm
docker service create \
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
version: "3"
services:
dozzle:
container_name: dozzle
image: amir20/dozzle:latest
environment:
- DOZZLE_TAILSIZE=100
volumes:
- /var/run/docker.sock:/var/run/docker.sock
ports:
- 9999:8080
#### Security
dozzle doesn't support authentication out of the box. You can control the device dozzle binds to by passing `--addr` parameter. For example,
@@ -45,6 +58,12 @@ dozzle doesn't support authentication out of the box. You can control the device
will bind to `localhost` on port `1224`. You can then use a reverse proxy to control who can see dozzle.
If you wish to restrict the containers shown you can pass the `--filter` parameter. For example,
$ docker run --volume=/var/run/docker.sock:/var/run/docker.sock -p 8888:1224 amir20/dozzle:latest --filter name=foo
this would then only allow you to view containers with a name starting with "foo"
#### Changing base URL
dozzle by default mounts to "/". If you want to control the base path you can use the `--base` option. For example, if you want to mount at "/foobar",
@@ -54,19 +73,33 @@ then you can override by using `--base /foobar`. See env variables below for usi
dozzle will be available at [http://localhost:8080/foobar/](http://localhost:8080/foobar/).
#### Environment variables and configuration
Dozzle follows the [12-factor](https://12factor.net/) model. Configurations can use the CLI flags or enviroment 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` | `1.38` |
| `--tailSize` | `DOZZLE_TAILSIZE` | `300` |
| Flag | Env Variable | Default |
| ------------ | -------------------- | ------- |
| `--addr` | `DOZZLE_ADDR` | `:8080` |
| `--base` | `DOZZLE_BASE` | `/` |
| `--level` | `DOZZLE_LEVEL` | `info` |
| `--showAll` | `DOZZLE_SHOWALL` | `false` |
| n/a | `DOCKER_API_VERSION` | `1.38` |
| `--tailSize` | `DOZZLE_TAILSIZE` | `300` |
| `--filter` | `DOZZLE_FILTER` | `""` |
## License
[MIT](LICENSE)
## Building
To Build and test locally:
1. Install NodeJs.
2. Install Go.
3. Globally install [packr utility](https://github.com/gobuffalo/packr) with `go get -u github.com/gobuffalo/packr/packr` outside of dozzle directory.
4. Install [reflex](https://github.com/cespare/reflex) with `get -u github.com/cespare/reflex` outside of dozzle.
5. Install node modules with `npm install`.
6. Do `npm start`
Instructions for Github actions can be found [here](.github/goreleaser/Dockerfile) which build and tests Dozzle.

View File

@@ -60,6 +60,14 @@ Content-Type: text/event-stream
event: containers-changed
data: start
/* snapshot: Test_handler_streamLogs_error_finding_container */
HTTP/1.1 500 Internal Server Error
Connection: close
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
error finding container
/* snapshot: Test_handler_streamLogs_error_reading */
HTTP/1.1 200 OK
Connection: close

View File

@@ -1,45 +1,50 @@
import fetchMock from "fetch-mock";
import EventSource from "eventsourcemock";
import { shallowMount, RouterLinkStub } from "@vue/test-utils";
import { shallowMount, RouterLinkStub, createLocalVue } from "@vue/test-utils";
import Vuex from "vuex";
import App from "./App";
const localVue = createLocalVue();
localVue.use(Vuex);
describe("<App />", () => {
const stubs = { RouterLink: RouterLinkStub, "router-view": true };
let store;
beforeEach(() => {
global.BASE_PATH = "";
global.EventSource = EventSource;
fetchMock.getOnce("/api/containers.json", [{ id: "abc", name: "Test 1" }, { id: "xyz", name: "Test 2" }]);
const state = {
containers: [
{ id: "abc", name: "Test 1" },
{ id: "xyz", name: "Test 2" }
]
};
const actions = {
FETCH_CONTAINERS: () => Promise.resolve()
};
store = new Vuex.Store({
state,
actions
});
});
afterEach(() => fetchMock.reset());
test("is a Vue instance", async () => {
const wrapper = shallowMount(App, { stubs });
const wrapper = shallowMount(App, { stubs, store, localVue });
expect(wrapper.isVueInstance()).toBeTruthy();
});
test("has right title", async () => {
const wrapper = shallowMount(App, { stubs });
await fetchMock.flush();
const wrapper = shallowMount(App, { stubs, store, localVue });
await wrapper.vm.$nextTick();
expect(wrapper.vm.title).toContain("2 containers");
});
test("renders correctly", async () => {
const wrapper = shallowMount(App, { stubs });
await fetchMock.flush();
const wrapper = shallowMount(App, { stubs, store, localVue });
await wrapper.vm.$nextTick();
expect(wrapper.element).toMatchSnapshot();
});
test("renders router-link correctly", async () => {
const wrapper = shallowMount(App, { stubs });
await fetchMock.flush();
expect(wrapper.find(RouterLinkStub).props().to).toMatchInlineSnapshot(`
Object {
"name": "container",
"params": Object {
"id": "abc",
"name": "Test 1",
},
}
`);
});
});

View File

@@ -1,38 +1,49 @@
<template lang="html">
<div class="columns is-marginless">
<aside class="column menu is-3-tablet is-2-widescreen">
<a
role="button"
class="navbar-burger burger is-white is-hidden-tablet is-pulled-right"
@click="showNav = !showNav"
:class="{ 'is-active': showNav }"
>
<span></span> <span></span> <span></span>
</a>
<h1 class="title has-text-warning is-marginless">Dozzle</h1>
<p class="menu-label is-hidden-mobile" :class="{ 'is-active': showNav }">Containers</p>
<ul class="menu-list is-hidden-mobile" :class="{ 'is-active': showNav }">
<li v-for="item in containers">
<router-link :to="{ name: 'container', params: { id: item.id, name: item.name } }" active-class="is-active">
<div class="hide-overflow">{{ item.name }}</div>
</router-link>
</li>
</ul>
</aside>
<div class="column is-offset-3-tablet is-offset-2-widescreen is-9-tablet is-10-widescreen">
<router-view></router-view>
<side-menu></side-menu>
<div class="column is-offset-3-tablet is-offset-2-widescreen is-9-tablet is-10-widescreen is-paddingless">
<splitpanes>
<pane>
<router-view></router-view>
</pane>
<pane v-for="other in activeContainers" :key="other.id">
<scrollable-view>
<template v-slot:header>
<div class="name columns is-marginless">
<span class="column">{{ other.name }}</span>
<span class="column is-narrow">
<button class="delete is-medium" @click="removeActiveContainer(other)"></button>
</span>
</div>
</template>
<log-viewer-with-source :id="other.id"></log-viewer-with-source>
</scrollable-view>
</pane>
</splitpanes>
</div>
</div>
</template>
<script>
let es;
import { mapActions, mapGetters, mapState } from "vuex";
import { Splitpanes, Pane } from "splitpanes";
import LogViewerWithSource from "./components/LogViewerWithSource";
import ScrollableView from "./components/ScrollableView";
import SideMenu from "./components/SideMenu";
export default {
name: "App",
components: {
LogViewerWithSource,
SideMenu,
ScrollableView,
Splitpanes,
Pane
},
data() {
return {
title: "",
containers: [],
showNav: false
};
},
@@ -44,20 +55,16 @@ export default {
},
async created() {
await this.fetchContainerList();
es = new EventSource(`${BASE_PATH}/api/events/stream`);
es.addEventListener("containers-changed", e => setTimeout(this.fetchContainerList, 1000), false);
this.title = `${this.containers.length} containers`;
},
beforeDestroy() {
if (es) {
es.close();
es = null;
}
computed: {
...mapState(["containers", "activeContainers"])
},
methods: {
async fetchContainerList() {
this.containers = await (await fetch(`${BASE_PATH}/api/containers.json`)).json();
this.title = `${this.containers.length} containers`;
}
...mapActions({
fetchContainerList: "FETCH_CONTAINERS",
removeActiveContainer: "REMOVE_ACTIVE_CONTAINER"
})
},
watch: {
$route(to, from) {
@@ -68,48 +75,15 @@ export default {
</script>
<style scoped lang="scss">
.is-hidden-mobile.is-active {
display: block !important;
.name {
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
background: rgba(0, 0, 0, 0.1);
font-weight: bold;
font-family: monospace;
}
.navbar-burger {
height: 2.35rem;
}
aside {
position: fixed;
z-index: 2;
padding: 1em;
@media screen and (min-width: 769px) {
& {
height: 100vh;
overflow: auto;
}
}
@media screen and (max-width: 768px) {
& {
position: sticky;
top: 0;
left: 0;
right: 0;
background: #222;
}
.menu-label {
margin-top: 1em;
}
}
}
.hide-overflow {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.burger.is-white {
color: #fff;
::v-deep .splitpanes__splitter {
min-width: 4px;
background: #666;
}
</style>

View File

@@ -4,60 +4,23 @@ exports[`<App /> renders correctly 1`] = `
<div
class="columns is-marginless"
>
<aside
class="column menu is-3-tablet is-2-widescreen"
>
<a
class="navbar-burger burger is-white is-hidden-tablet is-pulled-right"
role="button"
>
<span />
<span />
<span />
</a>
<h1
class="title has-text-warning is-marginless"
>
Dozzle
</h1>
<p
class="menu-label is-hidden-mobile"
>
Containers
</p>
<ul
class="menu-list is-hidden-mobile"
>
<li>
<a>
<div
class="hide-overflow"
>
Test 1
</div>
</a>
</li>
<li>
<a>
<div
class="hide-overflow"
>
Test 2
</div>
</a>
</li>
</ul>
</aside>
<side-menu-stub />
<div
class="column is-offset-3-tablet is-offset-2-widescreen"
class="column is-offset-3-tablet is-offset-2-widescreen is-9-tablet is-10-widescreen is-paddingless"
>
<router-view-stub />
<splitpanes-stub
dblclicksplitter="true"
pushotherpanes="true"
>
<pane-stub
maxsize="100"
minsize="0"
>
<router-view-stub />
</pane-stub>
</splitpanes-stub>
</div>
</div>
`;

View File

@@ -0,0 +1,158 @@
import EventSource from "eventsourcemock";
import { sources } from "eventsourcemock";
import { shallowMount, mount, createLocalVue } from "@vue/test-utils";
import Vuex from "vuex";
import MockDate from "mockdate";
import LogEventSource from "./LogEventSource.vue";
import LogViewer from "./LogViewer.vue";
describe("<LogEventSource />", () => {
beforeEach(() => {
global.BASE_PATH = "";
global.EventSource = EventSource;
MockDate.set("6/12/2019", 0);
window.scrollTo = jest.fn();
});
afterEach(() => MockDate.reset());
function createLogEventSource(searchFilter = null) {
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.component("log-event-source", LogEventSource);
localVue.component("log-viewer", LogViewer);
const state = { searchFilter };
const store = new Vuex.Store({
state
});
return mount(LogEventSource, {
localVue,
store,
scopedSlots: {
default: `
<log-viewer :messages="props.messages"></log-viewer>
`
},
propsData: { id: "abc" }
});
}
test("is a Vue instance", async () => {
const wrapper = shallowMount(LogEventSource);
expect(wrapper.isVueInstance()).toBeTruthy();
});
test("renders correctly", async () => {
const wrapper = createLogEventSource();
expect(wrapper.element).toMatchSnapshot();
});
test("should connect to EventSource", async () => {
shallowMount(LogEventSource);
sources["/api/logs/stream?id=abc"].emitOpen();
expect(sources["/api/logs/stream?id=abc"].readyState).toBe(1);
});
test("should close EventSource", async () => {
const wrapper = createLogEventSource();
sources["/api/logs/stream?id=abc"].emitOpen();
wrapper.destroy();
expect(sources["/api/logs/stream?id=abc"].readyState).toBe(2);
});
test("should parse messages", async () => {
const wrapper = createLogEventSource();
sources["/api/logs/stream?id=abc"].emitOpen();
sources["/api/logs/stream?id=abc"].emitMessage({ data: `2019-06-12T10:55:42.459034602Z "This is a message."` });
const [message, _] = wrapper.vm.messages;
const { key, ...messageWithoutKey } = message;
expect(key).toBeGreaterThanOrEqual(0);
expect(messageWithoutKey).toMatchInlineSnapshot(`
Object {
"date": 2019-06-12T10:55:42.459Z,
"message": " \\"This is a message.\\"",
}
`);
});
test("should pass messages to slot", async () => {
const wrapper = createLogEventSource();
sources["/api/logs/stream?id=abc"].emitOpen();
sources["/api/logs/stream?id=abc"].emitMessage({ data: `2019-06-12T10:55:42.459034602Z "This is a message."` });
const [message, _] = wrapper.find(LogViewer).vm.messages;
const { key, ...messageWithoutKey } = message;
expect(key).toBeGreaterThanOrEqual(0);
expect(messageWithoutKey).toMatchInlineSnapshot(`
Object {
"date": 2019-06-12T10:55:42.459Z,
"message": " \\"This is a message.\\"",
}
`);
});
test("should render messages", async () => {
const wrapper = createLogEventSource();
sources["/api/logs/stream?id=abc"].emitOpen();
sources["/api/logs/stream?id=abc"].emitMessage({ data: `2019-06-12T10:55:42.459034602Z "This is a message."` });
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
<ul class="events">
<li><span class="date">today at 10:55 AM</span> <span class="text"> "This is a message."</span></li>
</ul>
`);
});
test("should render messages with color", async () => {
const wrapper = createLogEventSource();
sources["/api/logs/stream?id=abc"].emitOpen();
sources["/api/logs/stream?id=abc"].emitMessage({
data: `2019-06-12T10:55:42.459034602Z \x1b[30mblack\x1b[37mwhite`
});
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
<ul class="events">
<li><span class="date">today at 10:55 AM</span> <span class="text"> <span style="color:#000">black<span style="color:#AAA">white</span></span></span></li>
</ul>
`);
});
test("should render messages with html entities", async () => {
const wrapper = createLogEventSource();
sources["/api/logs/stream?id=abc"].emitOpen();
sources["/api/logs/stream?id=abc"].emitMessage({
data: `2019-06-12T10:55:42.459034602Z <test>foo bar</test>`
});
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
<ul class="events">
<li><span class="date">today at 10:55 AM</span> <span class="text"> &lt;test&gt;foo bar&lt;/test&gt;</span></li>
</ul>
`);
});
test("should render messages with filter", async () => {
const wrapper = createLogEventSource("test");
sources["/api/logs/stream?id=abc"].emitOpen();
sources["/api/logs/stream?id=abc"].emitMessage({
data: `2019-06-11T10:55:42.459034602Z Foo bar`
});
sources["/api/logs/stream?id=abc"].emitMessage({
data: `2019-06-12T10:55:42.459034602Z This is a test <hi></hi>`
});
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
<ul class="events">
<li><span class="date">today at 10:55 AM</span> <span class="text"> This is a <mark>test</mark> &lt;hi&gt;&lt;/hi&gt;</span></li>
</ul>
`);
});
});

View File

@@ -0,0 +1,55 @@
<template lang="html">
<div>
<slot v-bind:messages="messages"></slot>
</div>
</template>
<script>
let nextId = 0;
function parseMessage(data) {
const date = new Date(data.substring(0, 30));
const message = data.substring(30);
const key = nextId++;
return {
key,
date,
message
};
}
export default {
props: ["id"],
name: "LogEventSource",
data() {
return {
messages: []
};
},
created() {
this.es = null;
this.loadLogs(this.id);
},
methods: {
loadLogs(id) {
if (this.es) {
this.es.close();
this.messages = [];
this.es = null;
}
this.es = new EventSource(`${BASE_PATH}/api/logs/stream?id=${this.id}`);
this.es.onmessage = e => this.messages.push(parseMessage(e.data));
this.es.onerror = function(e) {
console.log("EventSource failed." + e);
};
this.$once("hook:beforeDestroy", () => this.es.close());
}
},
watch: {
id(newValue, oldValue) {
if (oldValue !== newValue) {
this.loadLogs(newValue);
}
}
}
};
</script>

View File

@@ -0,0 +1,94 @@
<template lang="html">
<ul class="events">
<li v-for="item in filtered" :key="item.key">
<span class="date">{{ item.date | relativeTime }}</span>
<span class="text" v-html="colorize(item.message)"></span>
</li>
</ul>
</template>
<script>
import { mapActions, mapGetters, mapState } from "vuex";
import { formatRelative } from "date-fns";
import AnsiConvertor from "ansi-to-html";
const ansiConvertor = new AnsiConvertor({ escapeXML: true });
export default {
props: ["messages"],
name: "LogViewer",
components: {},
data() {
return {
showSearch: false
};
},
methods: {
colorize: function(value) {
return ansiConvertor
.toHtml(value)
.replace("&lt;mark&gt;", "<mark>")
.replace("&lt;/mark&gt;", "</mark>");
}
},
computed: {
...mapState(["searchFilter"]),
filtered() {
const { searchFilter, messages } = this;
if (searchFilter) {
const isSmartCase = searchFilter === searchFilter.toLowerCase();
const regex = isSmartCase ? new RegExp(searchFilter, "i") : new RegExp(searchFilter);
return messages
.filter(d => d.message.match(regex))
.map(d => ({
...d,
message: d.message.replace(regex, "<mark>$&</mark>")
}));
}
return messages;
}
},
filters: {
relativeTime(date) {
return formatRelative(date, new Date());
}
}
};
</script>
<style scoped lang="scss">
.events {
padding: 10px;
font-family: "Roboto Mono", monaco, monospace;
& > li {
font-size: 13px;
line-height: 16px;
word-wrap: break-word;
}
}
.date {
background-color: #262626;
color: #258ccd;
}
.text {
white-space: pre-wrap;
}
>>> mark {
border-radius: 2px;
background-color: #ffdd57;
animation: pops 0.2s ease-out;
display: inline-block;
}
@keyframes pops {
0% {
transform: scale(1.5);
}
100% {
transform: scale(1.05);
}
}
</style>

View File

@@ -0,0 +1,19 @@
<template lang="html">
<log-event-source :id="id" v-slot="eventSource">
<log-viewer :messages="eventSource.messages"></log-viewer>
</log-event-source>
</template>
<script>
import LogEventSource from "./LogEventSource";
import LogViewer from "./LogViewer";
export default {
props: ["id"],
name: "LogViewerWithSource",
components: {
LogEventSource,
LogViewer
}
};
</script>

View File

@@ -0,0 +1,89 @@
<template lang="html">
<section>
<header v-if="$slots.header">
<slot name="header"></slot>
</header>
<main ref="content" @scroll.passive="onScroll">
<slot></slot>
</main>
<div class="scroll-bar-notification">
<transition name="fade">
<button
class="button"
:class="hasMore ? 'is-warning' : 'is-primary'"
@click="scrollToBottom('smooth')"
v-show="paused"
>
<span class="icon large"> <i class="fas fa-chevron-down"></i> </span>
</button>
</transition>
</div>
</section>
</template>
<script>
export default {
name: "ScrollableView",
data() {
return {
paused: false,
hasMore: false
};
},
mounted() {
const { content } = this.$refs;
new MutationObserver(e => {
if (!this.paused) {
this.scrollToBottom("instant");
} else {
this.hasMore = true;
}
}).observe(content, { childList: true, subtree: true });
},
methods: {
scrollToBottom(behavior = "instant") {
const { content } = this.$refs;
if (typeof content.scroll === "function") {
content.scroll({ top: content.scrollHeight, behavior });
} else {
content.scrollTop = content.scrollHeight;
}
this.hasMore = false;
},
onScroll(e) {
const { content } = this.$refs;
this.paused = content.scrollTop + content.clientHeight + 1 < content.scrollHeight;
}
},
watch: {}
};
</script>
<style scoped lang="scss">
section {
display: flex;
flex-direction: column;
height: 100vh;
main {
flex: 1;
overflow: auto;
}
.scroll-bar-notification {
text-align: right;
margin-right: 65px;
button {
position: fixed;
bottom: 30px;
}
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.15s ease-in;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
}
</style>

View File

@@ -1,71 +0,0 @@
<template lang="html">
<transition name="fade">
<button
class="button scroll-notification"
:class="hasNew ? 'is-warning' : 'is-primary'"
@click="scrollToBottom"
v-show="visible"
>
<span class="icon large"> <i class="fas fa-chevron-down"></i> </span>
</button>
</transition>
</template>
<script>
export default {
props: ["messages"],
data() {
return {
visible: false,
hasNew: false
};
},
mounted() {
document.addEventListener("scroll", this.onScroll, { passive: true });
setTimeout(() => this.scrollToBottom(), 500);
},
beforeDestroy() {
document.removeEventListener("scroll", this.onScroll);
},
methods: {
scrollToBottom() {
this.visible = false;
window.scrollTo(0, document.documentElement.scrollHeight || document.body.scrollHeight);
},
onScroll() {
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
const scrollBottom =
(document.documentElement.scrollHeight || document.body.scrollHeight) - document.documentElement.clientHeight;
const diff = Math.abs(scrollTop - scrollBottom);
this.visible = diff > 50;
if (!this.visible) {
this.hasNew = false;
}
}
},
watch: {
messages() {
if (this.visible) {
this.hasNew = true;
} else {
this.scrollToBottom();
}
}
}
};
</script>
<style scoped>
.scroll-notification {
position: fixed;
right: 40px;
bottom: 30px;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.15s ease-in;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>

View File

@@ -0,0 +1,77 @@
<template lang="html">
<div class="search columns is-gapless is-vcentered" v-show="showSearch">
<div class="column">
<p class="control has-icons-left">
<input class="input" type="text" placeholder="Filter" ref="filter" v-model="filter" />
<span class="icon is-small is-left"><i class="fas fa-search"></i></span>
</p>
</div>
<div class="column is-1 has-text-centered">
<button class="delete is-medium" @click="resetSearch()"></button>
</div>
</div>
</template>
<script>
import { mapActions, mapState } from "vuex";
export default {
props: [],
name: "Search",
data() {
return {
showSearch: false
};
},
mounted() {
window.addEventListener("keydown", this.onKeyDown);
},
destroyed() {
window.removeEventListener("keydown", this.onKeyDown);
},
methods: {
...mapActions({
updateSearchFilter: "SET_SEARCH"
}),
onKeyDown(e) {
if ((e.metaKey || e.ctrlKey) && e.key === "f") {
this.showSearch = true;
this.$nextTick(() => this.$refs.filter.focus());
e.preventDefault();
} else if (e.key === "Escape") {
this.resetSearch();
}
},
resetSearch() {
this.showSearch = false;
this.filter = "";
}
},
computed: {
...mapState(["searchFilter"]),
filter: {
get() {
return this.searchFilter;
},
set(value) {
this.updateSearchFilter(value);
}
}
}
};
</script>
<style lang="scss" scoped>
.search {
width: 350px;
position: fixed;
padding: 10px;
background: rgba(50, 50, 50, 0.9);
top: 0;
right: 0;
border-radius: 0 0 0 5px;
z-index: 10;
}
.delete {
margin-left: 1em;
}
</style>

View File

@@ -0,0 +1,126 @@
<template lang="html">
<aside class="column menu is-3-tablet is-2-widescreen">
<a
role="button"
class="navbar-burger burger is-white is-hidden-tablet is-pulled-right"
@click="showNav = !showNav"
:class="{ 'is-active': showNav }"
>
<span></span> <span></span> <span></span>
</a>
<h1 class="title has-text-warning is-marginless">Dozzle</h1>
<p class="menu-label is-hidden-mobile" :class="{ 'is-active': showNav }">Containers</p>
<ul class="menu-list is-hidden-mobile" :class="{ 'is-active': showNav }">
<li v-for="item in containers">
<router-link
:to="{ name: 'container', params: { id: item.id, name: item.name } }"
active-class="is-active"
:title="item.name"
>
<div class="hide-overflow">
<span
@click.stop.prevent="appendActiveContainer(item)"
class="icon is-small will-append-container"
:class="{ 'is-active': activeContainersById[item.id] }"
>
<i class="fas fa-thumbtack"></i>
</span>
{{ item.name }}
</div>
</router-link>
</li>
</ul>
</aside>
</template>
<script>
import { mapActions, mapGetters, mapState } from "vuex";
export default {
props: [],
name: "SideMenu",
data() {
return {
showNav: false
};
},
methods: {
colorize: value =>
ansiConvertor
.toHtml(value)
.replace("&lt;mark&gt;", "<mark>")
.replace("&lt;/mark&gt;", "</mark>")
},
computed: {
...mapState(["containers", "activeContainers"]),
activeContainersById() {
return this.activeContainers.reduce((map, obj) => {
map[obj.id] = obj;
return map;
}, {});
}
},
methods: {
...mapActions({
appendActiveContainer: "APPEND_ACTIVE_CONTAINER"
})
}
};
</script>
<style scoped lang="scss">
aside {
position: fixed;
z-index: 2;
padding: 1em;
@media screen and (min-width: 769px) {
& {
height: 100vh;
overflow: auto;
}
}
@media screen and (max-width: 768px) {
& {
position: sticky;
top: 0;
left: 0;
right: 0;
background: #222;
}
.menu-label {
margin-top: 1em;
}
}
.hide-overflow {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.burger.is-white {
color: #fff;
}
.is-hidden-mobile.is-active {
display: block !important;
}
.navbar-burger {
height: 2.35rem;
}
}
.will-append-container.icon {
transition: transform 0.2s ease-out;
&.is-active {
transform: rotate(25deg);
pointer-events: none;
color: #00d1b2;
}
.router-link-exact-active & {
visibility: hidden;
}
}
</style>

View File

@@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<LogEventSource /> renders correctly 1`] = `
<div>
<ul
class="events"
/>
</div>
`;

View File

@@ -1,6 +1,8 @@
import Vue from "vue";
import VueRouter from "vue-router";
import Meta from "vue-meta";
import Vuex from "vuex";
import store from "./store";
import App from "./App.vue";
import Container from "./pages/Container.vue";
import Index from "./pages/Index.vue";
@@ -30,5 +32,6 @@ const router = new VueRouter({
new Vue({
router,
store,
render: h => h(App)
}).$mount("#app");

View File

@@ -1,90 +0,0 @@
import EventSource from "eventsourcemock";
import { sources } from "eventsourcemock";
import { shallowMount } from "@vue/test-utils";
import MockDate from "mockdate";
import Container from "./Container";
describe("<Container />", () => {
beforeEach(() => {
global.BASE_PATH = "";
global.EventSource = EventSource;
MockDate.set("6/12/2019", 0);
});
afterEach(() => MockDate.reset());
test("is a Vue instance", async () => {
const wrapper = shallowMount(Container);
expect(wrapper.isVueInstance()).toBeTruthy();
});
test("renders correctly", async () => {
const wrapper = shallowMount(Container);
expect(wrapper.element).toMatchSnapshot();
});
test("should connect to EventSource", async () => {
shallowMount(Container, {
propsData: { id: "abc" }
});
sources["/api/logs/stream?id=abc"].emitOpen();
expect(sources["/api/logs/stream?id=abc"].readyState).toBe(1);
});
test("should close EventSource", async () => {
const wrapper = shallowMount(Container, {
propsData: { id: "abc" }
});
sources["/api/logs/stream?id=abc"].emitOpen();
wrapper.destroy();
expect(sources["/api/logs/stream?id=abc"].readyState).toBe(2);
});
test("should parse messages", async () => {
const wrapper = shallowMount(Container, {
propsData: { id: "abc" }
});
sources["/api/logs/stream?id=abc"].emitOpen();
sources["/api/logs/stream?id=abc"].emitMessage({ data: `2019-06-12T10:55:42.459034602Z "This is a message."` });
const [message, _] = wrapper.vm.messages;
expect(message).toMatchInlineSnapshot(`
Object {
"date": 2019-06-12T10:55:42.459Z,
"dateRelative": "today at 10:55 AM",
"key": 0,
"message": " \\"This is a message.\\"",
}
`);
});
test("should render messages", async () => {
const wrapper = shallowMount(Container, {
propsData: { id: "abc" }
});
sources["/api/logs/stream?id=abc"].emitOpen();
sources["/api/logs/stream?id=abc"].emitMessage({ data: `2019-06-12T10:55:42.459034602Z "This is a message."` });
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
<ul class="events">
<li class="event"><span class="date">today at 10:55 AM</span> <span class="text"> "This is a message."</span></li>
</ul>
`);
});
test("should render messages with color", async () => {
const wrapper = shallowMount(Container, {
propsData: { id: "abc" }
});
sources["/api/logs/stream?id=abc"].emitOpen();
sources["/api/logs/stream?id=abc"].emitMessage({
data: `2019-06-12T10:55:42.459034602Z \x1b[30mblack\x1b[37mwhite`
});
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
<ul class="events">
<li class="event"><span class="date">today at 10:55 AM</span> <span class="text"> <span style="color:#000">black<span style="color:#AAA">white</span></span></span></li>
</ul>
`);
});
});

View File

@@ -1,190 +1,31 @@
<template lang="html">
<div class="is-fullheight">
<div class="search columns is-gapless is-vcentered" v-show="showSearch">
<div class="column">
<p class="control has-icons-left">
<input class="input" type="text" placeholder="Filter" ref="filter" v-model="filter" />
<span class="icon is-small is-left"><i class="fas fa-search"></i></span>
</p>
</div>
<div class="column is-1 has-text-centered">
<button class="delete is-medium" @click="resetSearch()"></button>
</div>
</div>
<ul class="events">
<li v-for="item in filtered" class="event" :key="item.key">
<span class="date">{{ item.dateRelative }}</span> <span class="text" v-html="colorize(item.message)"></span>
</li>
</ul>
<scrollbar-notification :messages="messages"></scrollbar-notification>
<div>
<search></search>
<scrollable-view>
<log-viewer-with-source :id="id"></log-viewer-with-source>
</scrollable-view>
</div>
</template>
<script>
import { formatRelative } from "date-fns";
import AnsiConvertor from "ansi-to-html";
import ScrollbarNotification from "../components/ScrollbarNotification";
const ansiConvertor = new AnsiConvertor();
let es = null;
let nextId = 0;
function parseMessage(data) {
const date = new Date(data.substring(0, 30));
const dateRelative = formatRelative(date, new Date());
const message = data.substring(30);
const key = nextId++;
return {
key,
date,
dateRelative,
message
};
}
import LogViewerWithSource from "../components/LogViewerWithSource";
import Search from "../components/Search";
import ScrollableView from "../components/ScrollableView";
export default {
props: ["id", "name"],
name: "Container",
components: {
ScrollbarNotification
},
data() {
return {
messages: [],
showSearch: false,
title: "",
filter: ""
};
LogViewerWithSource,
ScrollableView,
Search
},
metaInfo() {
return {
title: this.title,
title: this.name,
titleTemplate: "%s - Dozzle"
};
},
mounted() {
window.addEventListener("keydown", this.onKeyDown);
},
destroyed() {
window.removeEventListener("keydown", this.onKeyDown);
},
created() {
this.loadLogs(this.id);
},
beforeDestroy() {
if (es) {
es.close();
es = null;
}
},
watch: {
id(newValue, oldValue) {
if (oldValue !== newValue) {
this.loadLogs(newValue);
}
}
},
methods: {
loadLogs(id) {
if (es) {
es.close();
es = null;
this.messages = [];
}
es = new EventSource(`${BASE_PATH}/api/logs/stream?id=${id}`);
es.onmessage = e => this.messages.push(parseMessage(e.data));
this.title = `${this.name}`;
},
onKeyDown(e) {
if ((e.metaKey || e.ctrlKey) && e.key === "f") {
this.showSearch = true;
this.$nextTick(() => this.$refs.filter.focus());
e.preventDefault();
} else if ((e.metaKey || e.ctrlKey) && e.key === "k") {
this.messages = [];
} else if (e.key === "Escape") {
this.resetSearch();
}
},
resetSearch() {
this.showSearch = false;
this.filter = "";
},
colorize: function(value) {
return ansiConvertor.toHtml(value);
}
},
computed: {
filtered() {
const { filter } = this;
if (filter) {
const isSmartCase = filter === filter.toLowerCase();
const regex = isSmartCase ? new RegExp(filter, "i") : new RegExp(filter);
return this.messages
.filter(d => d.message.match(regex))
.map(d => ({
...d,
message: d.message.replace(regex, "<mark>$&</mark>")
}));
}
return this.messages;
}
}
};
</script>
<style scoped>
.events {
padding: 10px;
font-family: "Roboto Mono", monaco, monospace;
}
.event {
font-size: 13px;
line-height: 16px;
word-wrap: break-word;
}
.date {
background-color: #262626;
color: #258ccd;
}
.is-fullheight {
min-height: 100vh;
}
.text {
white-space: pre-wrap;
}
.search {
width: 350px;
position: fixed;
padding: 10px;
background: rgba(50, 50, 50, 0.9);
top: 0;
right: 0;
border-radius: 0 0 0 5px;
}
.delete {
margin-left: 1em;
}
/deep/ mark {
border-radius: 2px;
background-color: #ffdd57;
animation: pops 0.2s ease-out;
display: inline-block;
}
@keyframes pops {
0% {
transform: scale(1.5);
}
100% {
transform: scale(1.05);
}
}
</style>
<style lang="scss" scoped></style>

View File

@@ -1,50 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Container /> renders correctly 1`] = `
<div
class="is-fullheight"
>
<div
class="search columns is-gapless is-vcentered"
style="display: none;"
>
<div
class="column"
>
<p
class="control has-icons-left"
>
<input
class="input"
placeholder="Filter"
type="text"
/>
<span
class="icon is-small is-left"
>
<i
class="fas fa-search"
/>
</span>
</p>
</div>
<div
class="column is-1 has-text-centered"
>
<button
class="delete is-medium"
/>
</div>
</div>
<ul
class="events"
/>
<scrollbar-notification-stub
messages=""
/>
</div>
`;

56
assets/store/index.js Normal file
View File

@@ -0,0 +1,56 @@
import Vue from "vue";
import Vuex from "vuex";
// import storage from "store/dist/store.modern";
Vue.use(Vuex);
const state = {
containers: [],
activeContainers: [],
searchFilter: null
};
const mutations = {
SET_CONTAINERS(state, containers) {
state.containers = containers;
},
ADD_ACTIVE_CONTAINERS(state, container) {
state.activeContainers.push(container);
},
REMOVE_ACTIVE_CONTAINER(state, container) {
state.activeContainers.splice(state.activeContainers.indexOf(container), 1);
},
SET_SEARCH(state, filter) {
state.searchFilter = filter;
}
};
const actions = {
APPEND_ACTIVE_CONTAINER({ commit }, container) {
commit("ADD_ACTIVE_CONTAINERS", container);
},
REMOVE_ACTIVE_CONTAINER({ commit }, container) {
commit("REMOVE_ACTIVE_CONTAINER", container);
},
SET_SEARCH({ commit }, filter) {
commit("SET_SEARCH", filter);
},
async FETCH_CONTAINERS({ commit }) {
const containers = await (await fetch(`${BASE_PATH}/api/containers.json`)).json();
commit("SET_CONTAINERS", containers);
}
};
const getters = {};
const es = new EventSource(`${BASE_PATH}/api/events/stream`);
es.addEventListener("containers-changed", e => setTimeout(() => store.dispatch("FETCH_CONTAINERS"), 1000), false);
const store = new Vuex.Store({
strict: true,
state,
getters,
actions,
mutations
});
export default store;

View File

@@ -4,6 +4,7 @@ $menu-item-active-background-color: hsl(171, 100%, 41%);
$menu-item-color: hsl(0, 6%, 87%);
@import "../node_modules/bulma/bulma.sass";
@import "../node_modules/splitpanes/dist/splitpanes.css";
.is-dark {
color: #ddd;
@@ -17,3 +18,7 @@ body {
h1.title {
font-family: "Gafata", sans-serif;
}
.column {
min-width: 0;
}

View File

@@ -1,47 +1,94 @@
package docker
import (
"strconv"
"bufio"
"bytes"
"context"
"encoding/binary"
"fmt"
"io"
"sort"
"strconv"
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"io"
"log"
"sort"
"strings"
log "github.com/sirupsen/logrus"
)
type dockerClient struct {
cli dockerProxy
cli dockerProxy
filters filters.Args
}
type dockerProxy 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)
ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error)
}
// Client is a proxy around the docker client
type Client interface {
ListContainers() ([]Container, error)
ListContainers(showAll bool) ([]Container, error)
FindContainer(string) (Container, error)
ContainerLogs(context.Context, string, int) (<-chan string, <-chan error)
Events(context.Context) (<-chan events.Message, <-chan error)
}
// NewClient creates a new instance of Client
func NewClient() Client {
return NewClientWithFilters(map[string]string{})
}
// NewClientWithFilters creates a new instance of Client with docker filters
func NewClientWithFilters(f map[string]string) Client {
filterArgs := filters.NewArgs()
for k, v := range f {
filterArgs.Add(k, v)
}
log.Debugf("filterArgs = %v", filterArgs)
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
log.Fatal(err)
}
return &dockerClient{cli}
return &dockerClient{cli, filterArgs}
}
func (d *dockerClient) ListContainers() ([]Container, error) {
list, err := d.cli.ContainerList(context.Background(), types.ContainerListOptions{})
func (d *dockerClient) FindContainer(id string) (Container, error) {
var container Container
containers, err := d.ListContainers(true)
if err != nil {
return container, err
}
found := false
for _, c := range containers {
if c.ID == id {
container = c
found = true
break
}
}
if found == false {
return container, fmt.Errorf("Unable to find container with id: %s", id)
}
return container, nil
}
func (d *dockerClient) ListContainers(showAll bool) ([]Container, error) {
containerListOptions := types.ContainerListOptions{
Filters: d.filters,
All: showAll,
}
list, err := d.cli.ContainerList(context.Background(), containerListOptions)
if err != nil {
return nil, err
}
@@ -91,32 +138,51 @@ func (d *dockerClient) ContainerLogs(ctx context.Context, id string, tailSize in
reader.Close()
}()
go func() {
defer close(messages)
defer close(errChannel)
defer reader.Close()
containerJSON, _ := d.cli.ContainerInspect(ctx, id)
hdr := make([]byte, 8)
var buffer bytes.Buffer
for {
_, err := reader.Read(hdr)
if err != nil {
errChannel <- err
break
if containerJSON.Config.Tty {
go func() {
defer close(messages)
defer close(errChannel)
defer reader.Close()
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
line := scanner.Text()
select {
case messages <- line:
case <-ctx.Done():
}
}
count := binary.BigEndian.Uint32(hdr[4:])
_, err = io.CopyN(&buffer, reader, int64(count))
if err != nil {
errChannel <- err
break
}()
} else {
go func() {
defer close(messages)
defer close(errChannel)
defer reader.Close()
hdr := make([]byte, 8)
var buffer bytes.Buffer
for {
_, err := reader.Read(hdr)
if err != nil {
errChannel <- err
break
}
count := binary.BigEndian.Uint32(hdr[4:])
_, err = io.CopyN(&buffer, reader, int64(count))
if err != nil {
errChannel <- err
break
}
select {
case messages <- buffer.String():
case <-ctx.Done():
}
buffer.Reset()
}
select {
case messages <- buffer.String():
case <-ctx.Done():
}
buffer.Reset()
}
}()
}()
}
return messages, errChannel
}

View File

@@ -5,13 +5,16 @@ import (
"context"
"encoding/binary"
"errors"
"github.com/docker/docker/api/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"io"
"io/ioutil"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
type mockedProxy struct {
@@ -37,13 +40,22 @@ func (m *mockedProxy) ContainerLogs(ctx context.Context, id string, options type
}
return reader, args.Error(1)
}
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)
}
func Test_dockerClient_ListContainers_null(t *testing.T) {
proxy := new(mockedProxy)
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil)
client := &dockerClient{proxy}
client := &dockerClient{proxy, filters.NewArgs()}
list, err := client.ListContainers()
list, err := client.ListContainers(true)
assert.Empty(t, list, "list should be empty")
require.NoError(t, err, "error should not return an error.")
@@ -53,9 +65,9 @@ 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}
client := &dockerClient{proxy, filters.NewArgs()}
list, err := client.ListContainers()
list, err := client.ListContainers(true)
assert.Nil(t, list, "list should be nil")
require.Error(t, err, "test.")
@@ -76,9 +88,9 @@ func Test_dockerClient_ListContainers_happy(t *testing.T) {
proxy := new(mockedProxy)
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
client := &dockerClient{proxy}
client := &dockerClient{proxy, filters.NewArgs()}
list, err := client.ListContainers()
list, err := client.ListContainers(true)
require.NoError(t, err, "error should not return an error.")
assert.Equal(t, list, []Container{
@@ -107,12 +119,38 @@ func Test_dockerClient_ContainerLogs_happy(t *testing.T) {
binary.BigEndian.PutUint32(b[4:], uint32(len(expected)))
b = append(b, []byte(expected)...)
var reader io.ReadCloser
reader = ioutil.NopCloser(bytes.NewReader(b))
reader := ioutil.NopCloser(bytes.NewReader(b))
options := types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: true, Tail: "300", Timestamps: true}
proxy.On("ContainerLogs", mock.Anything, id, options).Return(reader, nil)
client := &dockerClient{proxy}
json := types.ContainerJSON{Config: &container.Config{Tty: false}}
proxy.On("ContainerInspect", mock.Anything, id).Return(json, nil)
client := &dockerClient{proxy, filters.NewArgs()}
messages, _ := client.ContainerLogs(context.Background(), id, 300)
actual, _ := <-messages
assert.Equal(t, expected, actual, "message doesn't match expected")
_, ok := <-messages
assert.False(t, ok, "channel should have been closed")
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(bytes.NewReader([]byte(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()}
messages, _ := client.ContainerLogs(context.Background(), id, 300)
actual, _ := <-messages
@@ -129,7 +167,7 @@ func Test_dockerClient_ContainerLogs_error(t *testing.T) {
proxy.On("ContainerLogs", mock.Anything, id, mock.Anything).Return(nil, errors.New("test"))
client := &dockerClient{proxy}
client := &dockerClient{proxy, filters.NewArgs()}
messages, err := client.ContainerLogs(context.Background(), id, 300)
@@ -141,3 +179,52 @@ func Test_dockerClient_ContainerLogs_error(t *testing.T) {
assert.False(t, ok, "error channel should have been closed")
proxy.AssertExpectations(t)
}
func Test_dockerClient_FindContainer_happy(t *testing.T) {
containers := []types.Container{
{
ID: "abcdefghijklmnopqrst",
Names: []string{"/z_test_container"},
},
{
ID: "1234567890_abcxyzdef",
Names: []string{"/a_test_container"},
},
}
proxy := new(mockedProxy)
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
client := &dockerClient{proxy, filters.NewArgs()}
container, err := client.FindContainer("abcdefghijkl")
require.NoError(t, err, "error should not be thrown")
assert.Equal(t, container, Container{
ID: "abcdefghijkl",
Name: "z_test_container",
Names: []string{"/z_test_container"},
})
proxy.AssertExpectations(t)
}
func Test_dockerClient_FindContainer_error(t *testing.T) {
containers := []types.Container{
{
ID: "abcdefghijklmnopqrst",
Names: []string{"/z_test_container"},
},
{
ID: "1234567890_abcxyzdef",
Names: []string{"/a_test_container"},
},
}
proxy := new(mockedProxy)
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
client := &dockerClient{proxy, filters.NewArgs()}
_, err := client.FindContainer("not_valid")
require.Error(t, err, "error should be thrown")
proxy.AssertExpectations(t)
}

39
go.mod
View File

@@ -1,45 +1,48 @@
module github.com/amir20/dozzle
replace github.com/docker/docker v0.0.0-20170601211448-f5ec1e2936dc => github.com/docker/engine v0.0.0-20180718150940-a3ef7e9a9bda
replace github.com/docker/docker v0.0.0-20190827232753-32688a47f341 => github.com/docker/engine v0.0.0-20190827232753-32688a47f341
// github.com/docker/engine v18.06.1-ce
replace github.com/docker/docker => github.com/docker/engine v0.0.0-20180816081446-320063a2ad06
// github.com/docker/engine v19.06.1-ce
replace github.com/docker/docker => github.com/docker/engine v0.0.0-20190827232753-32688a47f341
// github.com/docker/distribution master
// a proper tagged release is expected in early fall(September 2018)
// see; https://github.com/docker/distribution/issues/2693
replace github.com/docker/distribution => github.com/docker/distribution v2.6.0-rc.1.0.20180820212402-02bf4a2887a4+incompatible
replace github.com/docker/distribution => github.com/docker/distribution v0.0.0-20190711223531-1fb7fffdb266
require (
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
github.com/Microsoft/go-winio v0.4.12 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/beme/abide v0.0.0-20190610182227-9ffb2fa31bc4
github.com/Microsoft/go-winio v0.4.14 // indirect
github.com/beme/abide v0.0.0-20190723115211-635a09831760
github.com/containerd/containerd v1.2.9 // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v0.0.0-20170601211448-f5ec1e2936dc
github.com/docker/docker v0.0.0-20190827232753-32688a47f341
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/gobuffalo/envy v1.7.1 // indirect
github.com/gobuffalo/packr v1.30.1
github.com/google/go-cmp v0.3.0 // indirect
github.com/gogo/protobuf v1.3.0 // indirect
github.com/google/go-cmp v0.3.1 // indirect
github.com/gorilla/mux v1.7.3
github.com/magiconair/properties v1.8.1
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/pelletier/go-toml v1.4.0 // indirect
github.com/pkg/errors v0.8.1 // indirect
github.com/sergi/go-diff v1.0.0 // indirect
github.com/sirupsen/logrus v1.4.2
github.com/spf13/afero v1.2.2 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.3
github.com/spf13/viper v1.4.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.5.0
github.com/stretchr/objx v0.2.0 // indirect
github.com/stretchr/testify v1.3.0
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 // indirect
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 // indirect
github.com/stretchr/testify v1.4.0
golang.org/x/net v0.0.0-20190918130420-a8b05e9114ab // indirect
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3 // indirect
golang.org/x/text v0.3.2 // indirect
google.golang.org/appengine v1.4.0 // indirect
google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601 // indirect
google.golang.org/grpc v1.21.1 // indirect
google.golang.org/genproto v0.0.0-20190916214212-f660b8655731 // indirect
google.golang.org/grpc v1.23.1 // indirect
gotest.tools v2.2.0+incompatible // indirect
)
go 1.13

116
go.sum
View File

@@ -1,22 +1,34 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Microsoft/go-winio v0.4.12 h1:xAfWHN1IrQ0NJ9TBC0KBZoqLjzDTr1ML+4MywiUOryc=
github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/beme/abide v0.0.0-20190610182227-9ffb2fa31bc4 h1:1DaJaKd5Iq9whe32lhcwOICEedmGZIWDovNIZi7ubBw=
github.com/beme/abide v0.0.0-20190610182227-9ffb2fa31bc4/go.mod h1:6+8gCKsZnxzhGTmKRh4BSkLos9CbWRJNcrp55We4SqQ=
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
github.com/beme/abide v0.0.0-20190723115211-635a09831760 h1:FvTM5NSN5HYvfKpgL+8x73U5v063vHsd7AX05eV1DnM=
github.com/beme/abide v0.0.0-20190723115211-635a09831760/go.mod h1:6+8gCKsZnxzhGTmKRh4BSkLos9CbWRJNcrp55We4SqQ=
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/containerd/containerd v1.2.9 h1:6tyNjBmAMG47QuFPIT9LgiiexoVxC6qpTGR+eD0R0Z8=
github.com/containerd/containerd v1.2.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
@@ -27,25 +39,34 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/docker/distribution v2.6.0-rc.1.0.20180820212402-02bf4a2887a4+incompatible h1:x3ZXVm6ovZmIA+s9MEdSXjdyd5Zbd5VPBcda2KrSuWk=
github.com/docker/distribution v2.6.0-rc.1.0.20180820212402-02bf4a2887a4+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/engine v0.0.0-20180816081446-320063a2ad06 h1:CcxlLWAS/9b46iqHDTBlALJZF9atXVNjeymdCNrUfnY=
github.com/docker/engine v0.0.0-20180816081446-320063a2ad06/go.mod h1:3CPr2caMgTHxxIAZgEMd3uLYPDlRvPqCpyeRf6ncPcY=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/distribution v0.0.0-20190711223531-1fb7fffdb266 h1:6BCth6L0iZKTU3F0OxqlkECwdmUDLbHdD9qz6HXlpb4=
github.com/docker/distribution v0.0.0-20190711223531-1fb7fffdb266/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
github.com/docker/engine v0.0.0-20190827232753-32688a47f341 h1:EZsx4y4IdfCZofMwt/ICb/8P5TgSR69Zrnw21vOHKc0=
github.com/docker/engine v0.0.0-20190827232753-32688a47f341/go.mod h1:3CPr2caMgTHxxIAZgEMd3uLYPDlRvPqCpyeRf6ncPcY=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU=
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/envy v1.7.1 h1:OQl5ys5MBea7OGCdvPbBJWRgnhC/fGona6QKfvFeau8=
github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4=
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
@@ -55,6 +76,8 @@ github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWS
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.0 h1:G8O7TerXerS4F6sx9OV7/nRfJdnXgHZu/S/7F2SN+UE=
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -63,11 +86,14 @@ github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I=
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@@ -78,12 +104,16 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
@@ -97,14 +127,21 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE=
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
@@ -117,12 +154,16 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
@@ -130,12 +171,18 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.2 h1:XU784Pr0wdahMY2bYcyK6N1KuaRAdLtqD4qd8D18Bfs=
github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
@@ -144,6 +191,7 @@ github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
@@ -151,10 +199,14 @@ github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmq
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.5.0 h1:GpsTwfsQ27oS/Aha/6d1oD7tpKIqWnOA6tgOX9HHkt4=
github.com/spf13/viper v1.5.0/go.mod h1:AkYRkVJF8TkSG/xet6PzXX+l39KhhXa2pdqVSxnTcn4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
@@ -162,11 +214,18 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
@@ -183,15 +242,18 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190918130420-a8b05e9114ab h1:h5tBRKZ1aY/bo6GNqe/4zWC8GkaLOFQ5wPKIOQ0i2sA=
golang.org/x/net v0.0.0-20190918130420-a8b05e9114ab/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -206,10 +268,12 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438 h1:khxRGsvPk4n2y8I/mLLjp7e5dMTJmH75wvqS6nMwUtY=
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3 h1:7TYNF4UdlohbFwpNH04CoPMp1cHUZgO1Ebq5r2hIjfo=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -217,23 +281,30 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZe
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601 h1:9VBRTdmgQxbs6HE0sUnMrSWNePppAJU07NYvX5dIB04=
google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190916214212-f660b8655731 h1:Phvl0+G5t5k/EUFUi0wPdUUeTL2HydMQUXHnunWgSb0=
google.golang.org/genproto v0.0.0-20190916214212-f660b8655731/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.1 h1:q4XQuHFC6I28BKZpo6IYyb3mNO+l7lSOxRuYTCiDfXk=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
@@ -242,6 +313,9 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

69
main.go
View File

@@ -4,41 +4,48 @@ import (
"context"
"encoding/json"
"fmt"
"html/template"
"net/http"
"net/url"
"os"
"os/signal"
"runtime"
"strings"
"time"
"github.com/amir20/dozzle/docker"
"github.com/gobuffalo/packr"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"html/template"
"net/http"
"os"
"os/signal"
"runtime"
"strings"
"time"
)
var (
addr = ""
base = ""
level = ""
addr = ""
base = ""
level = ""
showAll = false
tailSize = 300
version = "dev"
commit = "none"
date = "unknown"
filters map[string]string
version = "dev"
commit = "none"
date = "unknown"
)
type handler struct {
client docker.Client
showAll bool
box packr.Box
}
func init() {
pflag.String("addr", ":8080", "http service address")
pflag.String("base", "/", "base address of the application to mount")
pflag.Bool("showAll", false, "show all containers, even stopped")
pflag.String("level", "info", "logging level")
pflag.Int("tailSize", 300, "Tail size to use for initial container logs")
pflag.StringToStringVar(&filters, "filter", map[string]string{}, "Container filters to use for showing logs")
pflag.Parse()
viper.AutomaticEnv()
@@ -49,6 +56,21 @@ func init() {
base = viper.GetString("base")
level = viper.GetString("level")
tailSize = viper.GetInt("tailSize")
showAll = viper.GetBool("showAll")
// Until https://github.com/spf13/viper/issues/608 is fixed. We have to use this hacky way.
// filters = viper.GetStringSlice("filter")
if value, ok := os.LookupEnv("DOZZLE_FILTER"); ok {
log.Infof("Parsing %s", value)
urlValues, err := url.ParseQuery(strings.ReplaceAll(value, ",", "&"))
if err != nil {
log.Fatal(err)
}
filters = map[string]string{}
for k, v := range urlValues {
filters[k] = v[0]
}
}
l, _ := log.ParseLevel(level)
log.SetLevel(l)
@@ -77,15 +99,19 @@ func createRoutes(base string, h *handler) *mux.Router {
func main() {
log.Infof("Dozzle version %s", version)
dockerClient := docker.NewClient()
_, err := dockerClient.ListContainers()
dockerClient := docker.NewClientWithFilters(filters)
_, err := dockerClient.ListContainers(true)
if err != nil {
log.Fatalf("Could not connect to Docker Engine: %v", err)
}
box := packr.NewBox("./static")
r := createRoutes(base, &handler{dockerClient, box})
r := createRoutes(base, &handler{
client: dockerClient,
showAll: showAll,
box: box,
})
srv := &http.Server{Addr: addr, Handler: r}
go func() {
@@ -132,7 +158,7 @@ func (h *handler) index(w http.ResponseWriter, req *http.Request) {
}
func (h *handler) listContainers(w http.ResponseWriter, r *http.Request) {
containers, err := h.client.ListContainers()
containers, err := h.client.ListContainers(h.showAll)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -156,7 +182,14 @@ func (h *handler) streamLogs(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
return
}
messages, err := h.client.ContainerLogs(r.Context(), id, tailSize)
container, e := h.client.FindContainer(id)
if e != nil {
http.Error(w, e.Error(), http.StatusInternalServerError)
return
}
messages, err := h.client.ContainerLogs(r.Context(), container.ID, tailSize)
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")

View File

@@ -3,12 +3,13 @@ package main
import (
"context"
"errors"
"github.com/magiconair/properties/assert"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/magiconair/properties/assert"
"github.com/amir20/dozzle/docker"
"github.com/beme/abide"
"github.com/docker/docker/api/types/events"
@@ -22,7 +23,16 @@ type MockedClient struct {
docker.Client
}
func (m *MockedClient) ListContainers() ([]docker.Container, error) {
func (m *MockedClient) FindContainer(id string) (docker.Container, error) {
args := m.Called(id)
container, ok := args.Get(0).(docker.Container)
if !ok {
panic("containers is not of type docker.Container")
}
return container, args.Error(1)
}
func (m *MockedClient) ListContainers(showAll bool) ([]docker.Container, error) {
args := m.Called()
containers, ok := args.Get(0).([]docker.Container)
if !ok {
@@ -95,13 +105,12 @@ func Test_handler_streamLogs_happy(t *testing.T) {
req.URL.RawQuery = q.Encode()
require.NoError(t, err, "NewRequest should not return an error.")
rr := httptest.NewRecorder()
mockedClient := new(MockedClient)
messages := make(chan string)
errChannel := make(chan error)
mockedClient.On("ContainerLogs", mock.Anything, id, 300).Return(messages, errChannel)
mockedClient.On("FindContainer", id).Return(docker.Container{ID: id}, nil)
mockedClient.On("ContainerLogs", mock.Anything, mock.Anything, 300).Return(messages, errChannel)
go func() {
messages <- "INFO Testing logs..."
close(messages)
@@ -109,6 +118,26 @@ func Test_handler_streamLogs_happy(t *testing.T) {
h := handler{client: mockedClient}
handler := http.HandlerFunc(h.streamLogs)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
mockedClient.AssertExpectations(t)
}
func Test_handler_streamLogs_error_finding_container(t *testing.T) {
id := "123456"
req, err := http.NewRequest("GET", "/api/logs/stream", nil)
q := req.URL.Query()
q.Add("id", id)
req.URL.RawQuery = q.Encode()
require.NoError(t, err, "NewRequest should not return an error.")
mockedClient := new(MockedClient)
mockedClient.On("FindContainer", id).Return(docker.Container{}, errors.New("error finding container"))
h := handler{client: mockedClient}
handler := http.HandlerFunc(h.streamLogs)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
mockedClient.AssertExpectations(t)
@@ -118,14 +147,14 @@ func Test_handler_streamLogs_error_reading(t *testing.T) {
id := "123456"
req, err := http.NewRequest("GET", "/api/logs/stream", nil)
q := req.URL.Query()
q.Add("id", "123456")
q.Add("id", id)
req.URL.RawQuery = q.Encode()
require.NoError(t, err, "NewRequest should not return an error.")
rr := httptest.NewRecorder()
mockedClient := new(MockedClient)
messages := make(chan string)
errChannel := make(chan error)
mockedClient.On("FindContainer", id).Return(docker.Container{ID: id}, nil)
mockedClient.On("ContainerLogs", mock.Anything, id, 300).Return(messages, errChannel)
go func() {
@@ -134,6 +163,7 @@ func Test_handler_streamLogs_error_reading(t *testing.T) {
h := handler{client: mockedClient}
handler := http.HandlerFunc(h.streamLogs)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
mockedClient.AssertExpectations(t)
@@ -142,7 +172,6 @@ func Test_handler_streamLogs_error_reading(t *testing.T) {
func Test_handler_streamEvents_happy(t *testing.T) {
req, err := http.NewRequest("GET", "/api/events/stream", nil)
require.NoError(t, err, "NewRequest should not return an error.")
rr := httptest.NewRecorder()
mockedClient := new(MockedClient)
messages := make(chan events.Message)
errChannel := make(chan error)
@@ -160,6 +189,7 @@ func Test_handler_streamEvents_happy(t *testing.T) {
h := handler{client: mockedClient}
handler := http.HandlerFunc(h.streamEvents)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
mockedClient.AssertExpectations(t)
@@ -168,7 +198,6 @@ func Test_handler_streamEvents_happy(t *testing.T) {
func Test_handler_streamEvents_error(t *testing.T) {
req, err := http.NewRequest("GET", "/api/events/stream", nil)
require.NoError(t, err, "NewRequest should not return an error.")
rr := httptest.NewRecorder()
mockedClient := new(MockedClient)
messages := make(chan events.Message)
errChannel := make(chan error)
@@ -181,6 +210,7 @@ func Test_handler_streamEvents_error(t *testing.T) {
h := handler{client: mockedClient}
handler := http.HandlerFunc(h.streamEvents)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
mockedClient.AssertExpectations(t)
@@ -190,8 +220,6 @@ func Test_handler_streamEvents_error_request(t *testing.T) {
req, err := http.NewRequest("GET", "/api/events/stream", nil)
require.NoError(t, err, "NewRequest should not return an error.")
rr := httptest.NewRecorder()
mockedClient := new(MockedClient)
messages := make(chan events.Message)
@@ -207,6 +235,7 @@ func Test_handler_streamEvents_error_request(t *testing.T) {
h := handler{client: mockedClient}
handler := http.HandlerFunc(h.streamEvents)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
mockedClient.AssertExpectations(t)
@@ -217,7 +246,7 @@ func Test_createRoutes_index(t *testing.T) {
box := packr.NewBox("./virtual")
require.NoError(t, box.AddString("index.html", "index page"), "AddString should have no error.")
handler := createRoutes("/", &handler{mockedClient, box})
handler := createRoutes("/", &handler{mockedClient, true, box})
req, err := http.NewRequest("GET", "/", nil)
require.NoError(t, err, "NewRequest should not return an error.")
rr := httptest.NewRecorder()
@@ -230,7 +259,7 @@ func Test_createRoutes_redirect(t *testing.T) {
mockedClient := new(MockedClient)
box := packr.NewBox("./virtual")
handler := createRoutes("/foobar", &handler{mockedClient, box})
handler := createRoutes("/foobar", &handler{mockedClient, true,box})
req, err := http.NewRequest("GET", "/foobar", nil)
require.NoError(t, err, "NewRequest should not return an error.")
rr := httptest.NewRecorder()
@@ -244,7 +273,7 @@ func Test_createRoutes_foobar(t *testing.T) {
box := packr.NewBox("./virtual")
require.NoError(t, box.AddString("index.html", "foo page"), "AddString should have no error.")
handler := createRoutes("/foobar", &handler{mockedClient, box})
handler := createRoutes("/foobar", &handler{mockedClient, true, box})
req, err := http.NewRequest("GET", "/foobar/", nil)
require.NoError(t, err, "NewRequest should not return an error.")
rr := httptest.NewRecorder()
@@ -258,7 +287,7 @@ func Test_createRoutes_foobar_file(t *testing.T) {
box := packr.NewBox("./virtual")
require.NoError(t, box.AddString("/test", "test page"), "AddString should have no error.")
handler := createRoutes("/foobar", &handler{mockedClient, box})
handler := createRoutes("/foobar", &handler{mockedClient, true, box})
req, err := http.NewRequest("GET", "/foobar/test", nil)
require.NoError(t, err, "NewRequest should not return an error.")
rr := httptest.NewRecorder()
@@ -271,7 +300,7 @@ func Test_createRoutes_version(t *testing.T) {
mockedClient := new(MockedClient)
box := packr.NewBox("./virtual")
handler := createRoutes("/", &handler{mockedClient, box})
handler := createRoutes("/", &handler{mockedClient, true, box})
req, err := http.NewRequest("GET", "/version", nil)
require.NoError(t, err, "NewRequest should not return an error.")
rr := httptest.NewRecorder()

3875
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
{
"name": "dozzle",
"version": "1.11.7",
"version": "1.17.1",
"description": "Realtime log viewer for docker containers. ",
"scripts": {
"prestart": "npm run clean",
"start": "DOCKER_API_VERSION=1.38 concurrently 'npm run watch-server' 'npm run watch-assets'",
"watch-assets": "npx parcel watch --public-url '__BASE__' assets/index.html -d static",
"watch-assets": "npx parcel watch --no-source-maps --public-url '__BASE__' assets/index.html -d static",
"watch-server": "reflex -c .reflex",
"prebuild": "npm run clean",
"build": "npx parcel build --no-source-maps --public-url '__BASE__' assets/index.html -d static",
@@ -24,34 +24,36 @@
},
"homepage": "https://github.com/amir20/dozzle#readme",
"dependencies": {
"ansi-to-html": "^0.6.11",
"bulma": "^0.7.5",
"date-fns": "^2.0.0-beta.2",
"ansi-to-html": "^0.6.13",
"bulma": "^0.8.0",
"date-fns": "^2.8.1",
"splitpanes": "^2.1.1",
"store": "^2.0.12",
"vue": "^2.6.10",
"vue-meta": "^2.0.5",
"vue-router": "^3.0.7"
"vue-meta": "^2.3.1",
"vue-router": "^3.1.3",
"vuex": "^3.1.2"
},
"devDependencies": {
"@babel/core": "^7.5.4",
"@babel/plugin-transform-runtime": "^7.5.0",
"@vue/component-compiler-utils": "^3.0.0",
"@babel/core": "^7.7.4",
"@babel/plugin-transform-runtime": "^7.7.4",
"@vue/component-compiler-utils": "^3.0.2",
"@vue/test-utils": "^1.0.0-beta.29",
"babel-core": "^7.0.0-bridge.0",
"babel-jest": "^24.8.0",
"concurrently": "^4.1.1",
"babel-jest": "^24.9.0",
"concurrently": "^5.0.0",
"eventsourcemock": "^2.0.0",
"fetch-mock": "^7.3.6",
"husky": "^3.0.0",
"jest": "^24.8.0",
"husky": "^3.1.0",
"jest": "^24.9.0",
"jest-serializer-vue": "^2.0.2",
"lint-staged": "^9.2.0",
"mockdate": "^2.0.3",
"lint-staged": "^9.4.3",
"mockdate": "^2.0.5",
"node-fetch": "^2.6.0",
"parcel-bundler": "^1.12.3",
"prettier": "^1.18.2",
"sass": "^1.22.4",
"vue-hot-reload-api": "^2.3.3",
"vue-jest": "^3.0.4",
"parcel-bundler": "^1.12.4",
"prettier": "^1.19.1",
"sass": "^1.23.7",
"vue-hot-reload-api": "^2.3.4",
"vue-jest": "^3.0.5",
"vue-template-compiler": "^2.6.10"
},
"husky": {
@@ -73,6 +75,7 @@
"vue": "./node_modules/vue/dist/vue.esm.js"
},
"jest": {
"clearMocks": true,
"moduleFileExtensions": [
"js",
"json",