Compare commits

...

112 Commits

Author SHA1 Message Date
Amir Raminfar
553cc9f337 1.21.1 2020-03-18 14:43:27 -07:00
Amir Raminfar
354d328ff8 Uses dozzle instead of test 2020-03-18 14:32:35 -07:00
Amir Raminfar
4fc1a0efc1 Updates more names 2020-03-18 14:22:05 -07:00
Amir Raminfar
9a3be58542 Adds name 2020-03-18 14:21:15 -07:00
Amir Raminfar
34f7a98035 1.21.0 2020-03-18 14:19:16 -07:00
Amir Raminfar
3a24c6e665 Adds support for multi-arch (#306)
* Uses experimental docker multi-arch

* Fixes dockerfile to build node and golang images

* Adds ldflags

* Adds buildx to push.yml

* Adds docker login

* Adds login in again

* Login using run

* Login using run

* Adds Makefile

* Updates actions

* Fixes push
2020-03-18 14:13:35 -07:00
Amir Raminfar
4bde14bd6c Fixes security 2020-03-15 14:34:45 -07:00
dependabot-preview[bot]
b99a227953 Bump date-fns from 2.10.0 to 2.11.0 (#304)
Bumps [date-fns](https://github.com/date-fns/date-fns) from 2.10.0 to 2.11.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.10.0...v2.11.0)

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

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-13 16:12:07 -07:00
dependabot-preview[bot]
09a700b36e Bump sass from 1.26.2 to 1.26.3 (#302)
Bumps [sass](https://github.com/sass/dart-sass) from 1.26.2 to 1.26.3.
- [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.26.2...1.26.3)

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

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-13 16:11:57 -07:00
dependabot-preview[bot]
eab577607f [Security] Bump acorn from 5.7.3 to 5.7.4 (#303)
Bumps [acorn](https://github.com/acornjs/acorn) from 5.7.3 to 5.7.4. **This update includes a security fix.**
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/5.7.3...5.7.4)

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

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-13 16:11:46 -07:00
dependabot-preview[bot]
ea6e3e0725 Bump vuex from 3.1.2 to 3.1.3 (#300)
Bumps [vuex](https://github.com/vuejs/vuex) from 3.1.2 to 3.1.3.
- [Release notes](https://github.com/vuejs/vuex/releases)
- [Commits](https://github.com/vuejs/vuex/compare/v3.1.2...v3.1.3)

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

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-11 15:10:42 -07:00
dependabot-preview[bot]
187138d7b1 Bump buefy from 0.8.12 to 0.8.13 (#301)
Bumps [buefy](https://github.com/buefy/buefy) from 0.8.12 to 0.8.13.
- [Release notes](https://github.com/buefy/buefy/releases)
- [Changelog](https://github.com/buefy/buefy/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/buefy/buefy/compare/v0.8.12...v0.8.13)

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

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-11 15:10:29 -07:00
Amir Raminfar
dba0ddd344 Bump sass from 1.26.1 to 1.26.2 (#296)
Bumps [sass](https://github.com/sass/dart-sass) from 1.26.1 to 1.26.2.
- [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.26.1...1.26.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-05 10:28:27 -08:00
Amir Raminfar
4b9aece222 Bump @babel/core from 7.8.4 to 7.8.7 (#298)
Bumps [@babel/core](https://github.com/babel/babel) from 7.8.4 to 7.8.7.
- [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.8.4...v7.8.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-05 10:28:16 -08:00
Amir Raminfar
e756b609e8 1.20.21 2020-02-26 13:08:06 -08:00
Amir Raminfar
ba99a113cb Updates to go 1.14 2020-02-26 12:47:58 -08:00
Amir Raminfar
8be56bc673 Update README.md 2020-02-26 12:37:15 -08:00
Amir Raminfar
9bbd97a3a6 Updates node modules 2020-02-26 11:53:30 -08:00
Daniel M. Drucker
02de518971 fix filter syntax (#294) 2020-02-26 11:45:02 -08:00
Amir Raminfar
a627f2ffd5 Updates node modules 2020-02-25 11:10:29 -08:00
Amir Raminfar
0448416819 Updates buefy 2020-02-22 14:59:54 -08:00
dependabot-preview[bot]
3b2dfdc2f4 Bump github.com/stretchr/testify from 1.5.0 to 1.5.1 (#283)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.5.0 to 1.5.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.5.0...v1.5.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-20 08:23:00 -08:00
Amir Raminfar
efa5777766 Updates buefy 2020-02-19 15:10:50 -08:00
dependabot-preview[bot]
38713d1f48 Bump husky from 4.2.2 to 4.2.3 (#279)
Bumps [husky](https://github.com/typicode/husky) from 4.2.2 to 4.2.3.
- [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/v4.2.2...v4.2.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-19 12:08:23 -08:00
dependabot-preview[bot]
1259438a3b Bump github.com/stretchr/testify from 1.4.0 to 1.5.0 (#281)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.4.0 to 1.5.0.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.4.0...v1.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-19 12:08:11 -08:00
Amir Raminfar
ba494ddd57 1.20.20 2020-02-14 10:48:29 -08:00
Amir Raminfar
c5b84959c0 Collapse menu (#274)
* Tries to implement collapse menu

* Fixes using hiding

* Fixes tests

* Updates styles

* Adds better styles for collapse

* Fixes tests
2020-02-14 10:48:03 -08:00
dependabot-preview[bot]
2333084085 Bump semver from 7.1.2 to 7.1.3 (#276)
Bumps [semver](https://github.com/npm/node-semver) from 7.1.2 to 7.1.3.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/master/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v7.1.2...v7.1.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-12 10:42:57 -08:00
dependabot-preview[bot]
caa8f9964a Bump husky from 4.2.1 to 4.2.2 (#277)
Bumps [husky](https://github.com/typicode/husky) from 4.2.1 to 4.2.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/v4.2.1...v4.2.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-12 10:42:45 -08:00
dependabot-preview[bot]
2c756e8b46 Bump github.com/gorilla/mux from 1.7.3 to 1.7.4 (#278)
Bumps [github.com/gorilla/mux](https://github.com/gorilla/mux) from 1.7.3 to 1.7.4.
- [Release notes](https://github.com/gorilla/mux/releases)
- [Commits](https://github.com/gorilla/mux/compare/v1.7.3...v1.7.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-12 10:42:31 -08:00
Amir
1e1e956397 Fixes with go fmt 2020-02-07 15:51:09 -08:00
dependabot-preview[bot]
2594df9882 Bump ansi-to-html from 0.6.13 to 0.6.14 (#272)
Bumps [ansi-to-html](https://github.com/rburns/ansi-to-html) from 0.6.13 to 0.6.14.
- [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>
2020-02-07 12:05:59 -08:00
Amir Raminfar
8da054d3fb #270, tries to fix scrollbars. Thanks @mnpenner 2020-02-02 14:38:28 -08:00
Amir Raminfar
3e7ef846ad Adds new setting option for smaller scrollbars 2020-02-02 14:37:09 -08:00
Amir Raminfar
f069c65496 Fixes double scrollbar 2020-02-02 14:15:02 -08:00
Amir
cca37c0559 Updates npm modules 2020-01-31 11:41:01 -08:00
Amir
a9da3163eb Updates npm modules 2020-01-30 13:52:55 -08:00
Amir
7cff89ae26 1.20.18 2020-01-29 13:02:46 -08:00
Amir
0a3a273aac Removes unused pacakges and updates others 2020-01-29 10:31:14 -08:00
Amir Raminfar
905ee32256 Fixes lint-staged 2020-01-22 16:25:59 -08:00
Amir Raminfar
b0c7ce13e8 Updates modules 2020-01-22 16:22:54 -08:00
dependabot-preview[bot]
6a439840f0 Bump github.com/spf13/viper from 1.6.1 to 1.6.2 (#247)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.6.1 to 1.6.2.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.6.1...v1.6.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-22 16:04:00 -08:00
Amir Raminfar
35e7e08a07 Updates vue router 2020-01-15 19:59:36 -08:00
Amir Raminfar
2cb02f76a1 Updates vue router 2020-01-14 20:16:23 -08:00
dependabot-preview[bot]
16d286ba8f Bump @babel/plugin-transform-runtime from 7.8.0 to 7.8.3 (#239)
Bumps [@babel/plugin-transform-runtime](https://github.com/babel/babel) from 7.8.0 to 7.8.3.
- [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.8.0...v7.8.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-13 15:02:41 -08:00
dependabot-preview[bot]
94999dc95b Bump @babel/core from 7.8.0 to 7.8.3 (#240)
Bumps [@babel/core](https://github.com/babel/babel) from 7.8.0 to 7.8.3.
- [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.8.0...v7.8.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-13 14:57:56 -08:00
Amir
08fcfd8ec4 Fixes Firefox bug with scroll 2020-01-13 12:31:44 -08:00
Amir
4a253cab1a Uses actions/checkout@v1 instead 2020-01-13 12:29:04 -08:00
Amir Raminfar
911a25e0f0 Fixes firefox error 2020-01-12 20:09:28 -08:00
Amir Raminfar
f110a4c2f3 1.20.16 2020-01-12 08:25:21 -08:00
Amir Raminfar
ee18405f54 Updates modules 2020-01-12 08:15:16 -08:00
Amir Raminfar
be7012a860 1.20.15 2020-01-10 21:24:09 -08:00
Amir Raminfar
4892dcd892 Fixes tests 2020-01-10 21:24:03 -08:00
Amir Raminfar
8538fb2f55 1.20.14 2020-01-10 21:20:14 -08:00
Amir Raminfar
2592d62ed9 Removes size for second pane 2020-01-10 21:20:04 -08:00
Amir Raminfar
f91c7ccb21 1.20.13 2020-01-10 08:18:43 -08:00
Amir Raminfar
24ade2f856 Updates libs 2020-01-10 08:18:38 -08:00
Amir
756a8e4643 1.20.12 2020-01-09 11:51:53 -08:00
Amir
34533cd830 Updates npm modules 2020-01-09 11:51:49 -08:00
Amir
21e88b645e 1.20.11 2020-01-07 11:32:40 -08:00
Amir
775715a17c Fixes #185 2020-01-07 11:32:35 -08:00
Amir
a59f7caafc Updates husky 2020-01-07 10:50:14 -08:00
Amir
6903299523 1.20.10 2020-01-07 10:39:56 -08:00
Amir
1f34ebfdc1 Fixes search placeholder 2020-01-07 10:38:56 -08:00
Amir
98ee491865 Fixes title 2020-01-07 10:37:57 -08:00
Amir
d408cfca1d 1.20.9 2020-01-06 17:03:00 -08:00
Amir
a8366174e9 Fixes ui bug with menu 2020-01-06 17:02:55 -08:00
Amir
1b97d18ef0 1.20.8 2020-01-06 16:29:27 -08:00
Amir Raminfar
678b197d6a Fixes mobile to use document as container for scrolling (#223)
* Uses intersectionObserver instead

* Use intersectionObserver

* Updates mods

* Adds title when more than one container is active

* Updates logic to use native scrolling when only one logger view is open

* Fixes broken test

* Uses close instead of closed

* Fixes scrollingParent
2020-01-06 16:28:45 -08:00
Amir Raminfar
86bb4e12b3 Fixes tty bug with #200 2020-01-04 12:15:54 -08:00
Amir Raminfar
32dd847f4f Refactors tty code 2020-01-04 12:02:47 -08:00
Amir Raminfar
35a5093f8e Cleans up settings page 2019-12-30 17:06:45 -08:00
Amir Raminfar
6b5f5aeae3 Updates buefy 2019-12-30 17:02:47 -08:00
Amir Raminfar
b41f315a25 1.20.6 2019-12-30 09:50:49 -08:00
Amir Raminfar
376ee2d730 Adds compression 2019-12-30 09:50:37 -08:00
Amir Raminfar
79a42bf9fb 1.20.5 2019-12-30 09:46:19 -08:00
Amir Raminfar
2eff0dbeee Removes standard-version 2019-12-30 09:46:09 -08:00
Amir Raminfar
da9cddb691 perf: fixes a performance issues with store using strict mode 2019-12-30 09:23:13 -08:00
Amir Raminfar
184e742b1b 1.20.3 2019-12-29 15:16:08 -08:00
Amir Raminfar
42287f8848 chore(release): 1.20.4 2019-12-29 15:16:07 -08:00
Amir Raminfar
6495531d45 1.20.2 2019-12-29 15:15:53 -08:00
Amir Raminfar
3045d6011f chore(release): 1.20.3 2019-12-29 15:15:52 -08:00
Amir Raminfar
8a78db30c6 1.20.1 2019-12-29 15:15:42 -08:00
Amir Raminfar
cbe8aede9c chore(release): 1.20.2 2019-12-29 15:15:41 -08:00
Amir Raminfar
a0019b1019 Fixes scripts 2019-12-29 15:14:34 -08:00
Amir Raminfar
4e6d9c4c40 chore(release): 1.20.1 2019-12-29 15:06:13 -08:00
Amir Raminfar
54a636163c Adds standversion 2019-12-29 15:05:29 -08:00
Amir Raminfar
cc99eaa819 fix: tries to fix a scroll bug in mobile 2019-12-29 15:05:02 -08:00
Amir Raminfar
541227494f 1.20.0 2019-12-29 11:12:59 -08:00
Amir Raminfar
f4987ff9c3 Settings (#216)
* Settings in WIP

* Updates some styles

* Removes unused import

* Adds version and switcher

* Adds ionicons instead of fontawesome

* Fixes ionicon for vuejs

* Updates modules

* Adds buefy

* Adds search filter as settings

* Adds localstorage

* Fixes tests

* Adds settings for menu width

* Changes copy
2019-12-29 11:12:46 -08:00
Amir
c7ce201050 1.19.4 2019-12-20 11:27:51 -08:00
Amir
283dd90b46 Fixes z-index for mobile menu 2019-12-20 11:27:45 -08:00
Amir
5a7768f988 1.19.3 2019-12-20 10:53:40 -08:00
Amir
a84c1ce964 Fixes scroll bar bug 2019-12-20 10:52:54 -08:00
Amir
cd51084efe Updates babel 2019-12-18 17:30:24 -08:00
Amir
eb3fa00089 1.19.2 2019-12-18 17:24:27 -08:00
Amir
04b219236c Uses from and to find a good time range 2019-12-18 17:24:21 -08:00
Amir
6f3917edb8 1.19.1 2019-12-18 15:58:44 -08:00
Amir
e06954a92c Fixes UI bugs 2019-12-18 15:58:32 -08:00
Amir
9ebefc3698 Adds scrollback loading 2019-12-17 16:03:14 -08:00
Amir Raminfar
c938b2ea1b Fetch more (#209)
* Adds code to fetch more

* Adds working in progress

* Adds debugging test

* Cleans up and creates a new component

* Adds debug logs

* Adds debounce for messages

* Fixes scrolling

* Fixes go code to handle length

* Fixes tests

* Adds loader

* Fixes tests
2019-12-17 14:58:29 -08:00
Amir
9668b2cccd Adds support for split panes for the left menu 2019-12-11 13:38:14 -08:00
dependabot-preview[bot]
5b5716b51b [Security] Bump serialize-to-js from 3.0.0 to 3.0.2 (#201)
Bumps [serialize-to-js](https://github.com/commenthol/serialize-to-js) from 3.0.0 to 3.0.2. **This update includes a security fix.**
- [Release notes](https://github.com/commenthol/serialize-to-js/releases)
- [Commits](https://github.com/commenthol/serialize-to-js/compare/v3.0.0...v3.0.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-11 13:31:17 -08:00
Amir
93a7f9dc7d Updates libs 2019-12-11 13:25:48 -08:00
dependabot-preview[bot]
d7a52b569c Bump github.com/spf13/viper from 1.5.0 to 1.6.1 (#203)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.5.0 to 1.6.1.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.5.0...v1.6.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-11 13:24:37 -08:00
Amir Raminfar
1b4a4e626a Menu resizable (#206)
* Makes side menu resizable

* Fixes hiding of side menu

* Adds mobile menu

* Adds missing file
2019-12-11 13:23:24 -08:00
Amir
05b26bea9c 1.17.3 2019-12-11 11:52:52 -08:00
Amir
08cd8a21d4 Fixes broken regex searches 2019-12-11 11:52:37 -08:00
Amir
4a7c0f55a7 1.17.2 2019-12-03 12:35:16 -08:00
Amir
64b39d6d2b 1.17.1 2019-12-03 12:35:04 -08:00
Amir
cbca6a8413 Adds html title tag 2019-12-03 12:35:03 -08:00
TUNER88
9cfc20815a Add filter usage hints (#194)
* Add reference to supported filters

* fix spacing
2019-12-03 08:06:18 -08:00
38 changed files with 4406 additions and 2074 deletions

6
.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
node_modules
.cache
.idea
dist
.git
static

View File

@@ -1,6 +0,0 @@
FROM golang:1.13
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD [""]

View File

@@ -1,4 +0,0 @@
#!/bin/sh
set -e
go test -cover ./...

View File

@@ -1,13 +0,0 @@
FROM golang:1.13-alpine
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
ENTRYPOINT ["/entrypoint.sh"]
CMD [""]

View File

@@ -1,18 +0,0 @@
#!/usr/bin/env bash
if [ -n "$DOCKER_USERNAME" ] && [ -n "$DOCKER_PASSWORD" ]; then
echo "Login to the docker..."
docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD $DOCKER_REGISTRY
fi
# Workaround for github actions when access to different repositories is needed.
# Github actions provides a GITHUB_TOKEN secret that can only access the current
# repository and you cannot configure it's value.
# Access to different repositories is needed by brew for example.
if [ -n "$GORELEASER_GITHUB_TOKEN" ] ; then
export GITHUB_TOKEN=$GORELEASER_GITHUB_TOKEN
fi
npm ci
goreleaser $@

46
.github/workflows/deploy.yml vendored Normal file
View File

@@ -0,0 +1,46 @@
on:
push:
tags:
- "v*"
name: Test and Release
jobs:
npm-test:
name: npm test
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: npm test
uses: actions/setup-node@v1
- name: npm it
run: npm it
go-test:
name: go test
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v1
with:
go-version: "1.14.x"
- name: Checkout code
uses: actions/checkout@v2
- name: Test
run: go test -cover ./...
buildx:
needs: [go-test, npm-test]
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Docker Buildx
id: buildx
uses: crazy-max/ghaction-docker-buildx@v1
with:
version: latest
- name: Available platforms
run: echo ${{ steps.buildx.outputs.platforms }}
- name: Docker Login
run: docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
- name: Run Buildx
run: make publish

View File

@@ -1,23 +1,25 @@
on: push
name: Build, Test and Release
name: Test
jobs:
build:
npm-test:
name: npm test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Checkout code
uses: actions/checkout@v2
- 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 }}
- name: npm it
run: npm it
go-test:
name: go test
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v1
with:
args: release
if: contains(github.ref, 'tags')
go-version: "1.14.x"
- name: Checkout code
uses: actions/checkout@v2
- name: Test
run: go test -cover ./...

View File

@@ -2,7 +2,7 @@ before:
hooks:
- npm run clean
- npm run build
- packr
- packr -z
builds:
- env:
- CGO_ENABLED=0

View File

@@ -1,9 +1,56 @@
FROM alpine:latest as certs
RUN apk --update add ca-certificates
# Build assets
FROM node:13-alpine as node
RUN apk add --no-cache git openssh python make g++ util-linux
WORKDIR /build
# Install dependencies
COPY package*.json ./
RUN npm install
# Copy assets to build
COPY assets ./assets
# Do the build
RUN npm run build
FROM golang:alpine AS builder
RUN apk add --no-cache git ca-certificates
RUN mkdir /dozzle
WORKDIR /dozzle
# Needed for assets
RUN go get -u github.com/gobuffalo/packr/packr
# Copy go mod files
COPY go.* ./
RUN go mod download
# Copy assets built with node
COPY --from=node /build/static ./static
# Copy all other files
COPY . .
# Compile static files
RUN packr -z
# Args
ARG TAG=dev
# Build binary
RUN CGO_ENABLED=0 go build -ldflags "-s -w -X main.version=$TAG" -o dozzle
FROM scratch
ENV PATH=/bin
ENV DOCKER_API_VERSION 1.38
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY dozzle /
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=builder /dozzle/dozzle /dozzle
ENTRYPOINT ["/dozzle"]

6
Makefile Normal file
View File

@@ -0,0 +1,6 @@
TAG := $(shell git describe --tags)
PLATFROMS := linux/amd64,linux/arm64,linux/arm/v7
.PHONY: publish
publish:
docker buildx build --build-arg TAG=$(TAG) --platform $(PLATFROMS) -t amir20/dozzle:latest -t amir20/dozzle:$(TAG) --push .

View File

@@ -5,11 +5,11 @@
# Dozzle - [dozzle.dev](https://dozzle.dev/)
Dozzle is a real-time log viewer for Docker. It's free. It's small. And it's right in your browser.
Dozzle is a real-time log viewer for Docker. It's free. It's small. And it's 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. Dozzle is not a database. It does not store or save any logs. You can only see live logs while using Dozzle.
But if you don't want to pay for these services, then Dozzle can help! 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)
@@ -45,6 +45,7 @@ dozzle will be available at [http://localhost:8888/](http://localhost:8888/). Yo
image: amir20/dozzle:latest
environment:
- DOZZLE_TAILSIZE=100
- DOZZLE_FILTER=status=running
volumes:
- /var/run/docker.sock:/var/run/docker.sock
ports:
@@ -62,7 +63,7 @@ If you wish to restrict the containers shown you can pass the `--filter` paramet
$ 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"
this would then only allow you to view containers with a name starting with "foo". You can use other filters like `status` as well, please check the official docker [command line docs](https://docs.docker.com/engine/reference/commandline/ps/#filtering) for available filters.
#### Changing base URL

View File

@@ -8,7 +8,7 @@ const localVue = createLocalVue();
localVue.use(Vuex);
describe("<App />", () => {
const stubs = { RouterLink: RouterLinkStub, "router-view": true };
const stubs = { RouterLink: RouterLinkStub, "router-view": true, "ion-icon": true };
let store;
beforeEach(() => {
@@ -18,7 +18,8 @@ describe("<App />", () => {
containers: [
{ id: "abc", name: "Test 1" },
{ id: "xyz", name: "Test 2" }
]
],
settings: { menuWidth: 15 }
};
const actions = {

View File

@@ -1,27 +1,40 @@
<template lang="html">
<div class="columns is-marginless">
<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>
<main>
<mobile-menu v-if="isMobile"></mobile-menu>
<splitpanes @resized="onResized($event)">
<pane min-size="10" :size="settings.menuWidth" v-if="!isMobile" v-show="!collapseNav">
<side-menu></side-menu>
</pane>
<pane min-size="10">
<splitpanes>
<pane class="has-min-height">
<search></search>
<router-view></router-view>
</pane>
<pane v-for="other in activeContainers" :key="other.id" v-if="!isMobile">
<scrollable-view>
<template v-slot:header>
<container-title :value="other.name" closable @close="removeActiveContainer(other)"></container-title>
</template>
<log-viewer-with-source :id="other.id"></log-viewer-with-source>
</scrollable-view>
</pane>
</splitpanes>
</pane>
</splitpanes>
<button
@click="collapseNav = !collapseNav"
class="button is-small is-rounded is-settings-control"
:class="{ collapsed: collapseNav }"
id="hide-nav"
v-if="!isMobile"
>
<span class="icon">
<ion-icon :name="collapseNav ? 'arrow-dropright' : 'arrow-dropleft'" size="large"></ion-icon>
</span>
</button>
</main>
</template>
<script>
@@ -31,20 +44,26 @@ import { Splitpanes, Pane } from "splitpanes";
import LogViewerWithSource from "./components/LogViewerWithSource";
import ScrollableView from "./components/ScrollableView";
import SideMenu from "./components/SideMenu";
import MobileMenu from "./components/MobileMenu";
import Search from "./components/Search";
import ContainerTitle from "./components/ContainerTitle";
export default {
name: "App",
components: {
LogViewerWithSource,
SideMenu,
MobileMenu,
ScrollableView,
Splitpanes,
Pane
Pane,
Search,
ContainerTitle
},
data() {
return {
title: "",
showNav: false
collapseNav: false
};
},
metaInfo() {
@@ -57,33 +76,73 @@ export default {
await this.fetchContainerList();
this.title = `${this.containers.length} containers`;
},
mounted() {
if (this.hasSmallerScrollbars) {
document.documentElement.classList.add("has-custom-scrollbars");
}
this.menuWidth = this.settings.menuWidth;
},
watch: {
hasSmallerScrollbars(newValue, oldValue) {
if (newValue) {
document.documentElement.classList.add("has-custom-scrollbars");
} else {
document.documentElement.classList.remove("has-custom-scrollbars");
}
}
},
computed: {
...mapState(["containers", "activeContainers"])
...mapState(["containers", "activeContainers", "isMobile", "settings"]),
hasSmallerScrollbars() {
return this.settings.smallerScrollbars;
}
},
methods: {
...mapActions({
fetchContainerList: "FETCH_CONTAINERS",
removeActiveContainer: "REMOVE_ACTIVE_CONTAINER"
})
},
watch: {
$route(to, from) {
this.showNav = false;
removeActiveContainer: "REMOVE_ACTIVE_CONTAINER",
updateSetting: "UPDATE_SETTING"
}),
onResized(e) {
if (e.length == 2) {
const menuWidth = e[0].size;
this.updateSetting({ menuWidth });
}
}
}
};
</script>
<style scoped lang="scss">
.name {
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
background: rgba(0, 0, 0, 0.1);
font-weight: bold;
font-family: monospace;
}
::v-deep .splitpanes__splitter {
min-width: 4px;
background: #666;
&:hover {
background: rgb(255, 221, 87);
}
}
.button.has-no-border {
border-color: transparent !important;
}
.has-min-height {
min-height: 100vh;
}
#hide-nav {
position: fixed;
left: 10px;
bottom: 10px;
&.collapsed {
left: -40px;
width: 60px;
padding-left: 40px;
background: rgba(0, 0, 0, 0.95);
&:hover {
left: -25px;
}
}
}
</style>

View File

@@ -1,26 +1,55 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<App /> renders correctly 1`] = `
<div
class="columns is-marginless"
>
<side-menu-stub />
<main>
<!---->
<div
class="column is-offset-3-tablet is-offset-2-widescreen is-9-tablet is-10-widescreen is-paddingless"
<splitpanes-stub
dblclicksplitter="true"
pushotherpanes="true"
>
<splitpanes-stub
dblclicksplitter="true"
pushotherpanes="true"
<pane-stub
maxsize="100"
minsize="10"
size="15"
>
<pane-stub
maxsize="100"
minsize="0"
<side-menu-stub />
</pane-stub>
<pane-stub
maxsize="100"
minsize="10"
>
<splitpanes-stub
dblclicksplitter="true"
pushotherpanes="true"
>
<router-view-stub />
</pane-stub>
</splitpanes-stub>
</div>
</div>
<pane-stub
class="has-min-height"
maxsize="100"
minsize="0"
>
<search-stub />
<router-view-stub />
</pane-stub>
</splitpanes-stub>
</pane-stub>
</splitpanes-stub>
<button
class="button is-small is-rounded is-settings-control"
id="hide-nav"
>
<span
class="icon"
>
<ion-icon-stub
name="arrow-dropleft"
size="large"
/>
</span>
</button>
</main>
`;

View File

@@ -0,0 +1,30 @@
<template lang="html">
<div class="name columns is-marginless">
<span class="column">{{ value }}</span>
<span class="column is-narrow" v-if="closable">
<button class="delete is-medium" @click="$emit('close')"></button>
</span>
</div>
</template>
<script>
export default {
props: {
value: String,
closable: {
type: Boolean,
default: false
}
},
name: "ContainerTitle"
};
</script>
<style lang="scss" scoped>
.name {
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
background: rgba(0, 0, 0, 0.1);
font-weight: bold;
font-family: monospace;
}
</style>

View File

@@ -0,0 +1,37 @@
<template lang="html">
<div ref="observer" class="control" :class="{ 'is-loading': isLoading }"></div>
</template>
<script>
export default {
name: "InfiniteLoader",
data() {
return {
isLoading: false
};
},
props: {
onLoadMore: Function,
enabled: Boolean
},
mounted() {
const intersectionObserver = new IntersectionObserver(
async entries => {
if (entries[0].intersectionRatio <= 0) return;
if (this.onLoadMore && this.enabled) {
const scrollingParent = this.$el.closest("[data-scrolling]") || document.documentElement;
const previousHeight = scrollingParent.scrollHeight;
this.isLoading = true;
await this.onLoadMore();
this.isLoading = false;
this.$nextTick(() => (scrollingParent.scrollTop += scrollingParent.scrollHeight - previousHeight));
}
},
{ threshholds: 1 }
);
intersectionObserver.observe(this.$refs.observer);
}
};
</script>
<style scoped lang="scss"></style>

View File

@@ -3,15 +3,26 @@ import { sources } from "eventsourcemock";
import { shallowMount, mount, createLocalVue } from "@vue/test-utils";
import Vuex from "vuex";
import MockDate from "mockdate";
import debounce from "lodash.debounce";
import LogEventSource from "./LogEventSource.vue";
import LogViewer from "./LogViewer.vue";
jest.mock("lodash.debounce", () => jest.fn(fn => fn));
describe("<LogEventSource />", () => {
beforeEach(() => {
global.BASE_PATH = "";
global.EventSource = EventSource;
MockDate.set("6/12/2019", 0);
window.scrollTo = jest.fn();
const observe = jest.fn();
const unobserve = jest.fn();
global.IntersectionObserver = jest.fn(() => ({
observe,
unobserve
}));
debounce.mockClear();
});
afterEach(() => MockDate.reset());
@@ -23,7 +34,7 @@ describe("<LogEventSource />", () => {
localVue.component("log-event-source", LogEventSource);
localVue.component("log-viewer", LogViewer);
const state = { searchFilter };
const state = { searchFilter, settings: { size: "medium" } };
const store = new Vuex.Store({
state
@@ -48,7 +59,17 @@ describe("<LogEventSource />", () => {
test("renders correctly", async () => {
const wrapper = createLogEventSource();
expect(wrapper.element).toMatchSnapshot();
expect(wrapper.element).toMatchInlineSnapshot(`
<div>
<div
class="control"
/>
<ul
class="events medium"
/>
</div>
`);
});
test("should connect to EventSource", async () => {
@@ -68,15 +89,15 @@ describe("<LogEventSource />", () => {
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(key).toBe("2019-06-12T10:55:42.459034602Z");
expect(messageWithoutKey).toMatchInlineSnapshot(`
Object {
"date": 2019-06-12T10:55:42.459Z,
"message": " \\"This is a message.\\"",
"message": "\\"This is a message.\\"",
}
`);
});
@@ -89,12 +110,12 @@ describe("<LogEventSource />", () => {
const { key, ...messageWithoutKey } = message;
expect(key).toBeGreaterThanOrEqual(0);
expect(key).toBe("2019-06-12T10:55:42.459034602Z");
expect(messageWithoutKey).toMatchInlineSnapshot(`
Object {
"date": 2019-06-12T10:55:42.459Z,
"message": " \\"This is a message.\\"",
"message": "\\"This is a message.\\"",
}
`);
});
@@ -105,8 +126,8 @@ describe("<LogEventSource />", () => {
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 class="events medium">
<li><span class="date">today at 10:55 AM</span> <span class="text">"This is a message."</span></li>
</ul>
`);
});
@@ -119,8 +140,8 @@ describe("<LogEventSource />", () => {
});
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 class="events medium">
<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>
`);
});
@@ -133,8 +154,8 @@ describe("<LogEventSource />", () => {
});
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 class="events medium">
<li><span class="date">today at 10:55 AM</span> <span class="text">&lt;test&gt;foo bar&lt;/test&gt;</span></li>
</ul>
`);
});
@@ -150,8 +171,8 @@ describe("<LogEventSource />", () => {
});
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 class="events medium">
<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

@@ -1,15 +1,18 @@
<template lang="html">
<div>
<slot v-bind:messages="messages"></slot>
<infinite-loader :onLoadMore="loadOlderLogs" :enabled="messages.length > 100"></infinite-loader>
<slot :messages="messages"></slot>
</div>
</template>
<script>
let nextId = 0;
import debounce from "lodash.debounce";
import InfiniteLoader from "./InfiniteLoader";
function parseMessage(data) {
const date = new Date(data.substring(0, 30));
const message = data.substring(30);
const key = nextId++;
const key = data.substring(0, 30);
const message = data.substring(30).trim();
return {
key,
date,
@@ -20,9 +23,13 @@ function parseMessage(data) {
export default {
props: ["id"],
name: "LogEventSource",
components: {
InfiniteLoader
},
data() {
return {
messages: []
messages: [],
buffer: []
};
},
created() {
@@ -37,11 +44,38 @@ export default {
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);
const flushBuffer = debounce(
() => {
this.messages.push(...this.buffer);
this.buffer = [];
},
250,
{ maxWait: 1000 }
);
this.es.onmessage = e => {
this.buffer.push(parseMessage(e.data));
flushBuffer();
};
this.es.onerror = e => console.log("EventSource failed." + e);
this.$once("hook:beforeDestroy", () => this.es.close());
},
async loadOlderLogs() {
if (this.messages.length < 300) return;
const to = this.messages[0].date;
const last = this.messages[299].date;
const delta = to - last;
const from = new Date(to.getTime() + delta);
const logs = await (
await fetch(`/api/logs?id=${this.id}&from=${from.toISOString()}&to=${to.toISOString()}`)
).text();
if (logs) {
const newMessages = logs
.trim()
.split("\n")
.map(line => parseMessage(line));
this.messages.unshift(...newMessages);
}
}
},
watch: {

View File

@@ -1,5 +1,5 @@
<template lang="html">
<ul class="events">
<ul class="events" :class="settings.size">
<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>
@@ -32,18 +32,26 @@ export default {
}
},
computed: {
...mapState(["searchFilter"]),
...mapState(["searchFilter", "settings"]),
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>")
}));
try {
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>")
}));
} catch (e) {
if (e instanceof SyntaxError) {
console.info(`Ignoring SytaxError from search.`, e);
return messages;
}
throw e;
}
}
return messages;
}
@@ -61,9 +69,20 @@ export default {
font-family: "Roboto Mono", monaco, monospace;
& > li {
font-size: 13px;
line-height: 16px;
word-wrap: break-word;
line-height: 130%;
}
&.small {
font-size: 60%;
}
&.medium {
font-size: 80%;
}
&.large {
font-size: 120%;
}
}
@@ -76,7 +95,7 @@ export default {
white-space: pre-wrap;
}
>>> mark {
::v-deep mark {
border-radius: 2px;
background-color: #ffdd57;
animation: pops 0.2s ease-out;

View File

@@ -0,0 +1,88 @@
<template lang="html">
<aside>
<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">
{{ item.name }}
</div>
</router-link>
</li>
</ul>
</aside>
</template>
<script>
import { mapActions, mapGetters, mapState } from "vuex";
export default {
props: [],
name: "MobileMenu",
data() {
return {
showNav: false
};
},
computed: {
...mapState(["containers"]),
...mapGetters(["activeContainersById"])
},
methods: {
...mapActions({})
},
watch: {
$route(to, from) {
this.showNav = false;
}
}
};
</script>
<style scoped lang="scss">
aside {
padding: 1em;
position: fixed;
top: 0;
left: 0;
right: 0;
background: #222;
z-index: 2;
max-height: 100vh;
overflow: auto;
.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;
}
}
</style>

View File

@@ -1,20 +1,21 @@
<template lang="html">
<section>
<section :class="{ 'is-full-height-scrollable': scrollable }">
<header v-if="$slots.header">
<slot name="header"></slot>
</header>
<main ref="content" @scroll.passive="onScroll">
<main ref="content" :data-scrolling="scrollable">
<slot></slot>
<div ref="scrollObserver"></div>
</main>
<div class="scroll-bar-notification">
<transition name="fade">
<button
class="button"
:class="hasMore ? 'is-warning' : 'is-primary'"
@click="scrollToBottom('smooth')"
@click="scrollToBottom('instant')"
v-show="paused"
>
<span class="icon large"> <i class="fas fa-chevron-down"></i> </span>
<ion-icon name="download"></ion-icon>
</button>
</transition>
</div>
@@ -23,6 +24,12 @@
<script>
export default {
props: {
scrollable: {
type: Boolean,
default: true
}
},
name: "ScrollableView",
data() {
return {
@@ -39,35 +46,37 @@ export default {
this.hasMore = true;
}
}).observe(content, { childList: true, subtree: true });
const intersectionObserver = new IntersectionObserver(
entries => (this.paused = entries[0].intersectionRatio == 0),
{ threshholds: [0, 1], rootMargin: "80px 0px" }
);
intersectionObserver.observe(this.$refs.scrollObserver);
},
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.$refs.scrollObserver.scrollIntoView({ behavior });
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;
&.is-full-height-scrollable {
height: 100vh;
}
main {
flex: 1;
overflow: auto;
}
.scroll-bar-notification {
text-align: right;
margin-right: 65px;

View File

@@ -1,9 +1,18 @@
<template lang="html">
<div class="search columns is-gapless is-vcentered" v-show="showSearch">
<div class="search columns is-gapless is-vcentered" v-show="showSearch" v-if="settings.search">
<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>
<input
class="input"
type="text"
placeholder="Find / RegEx"
ref="filter"
v-model="filter"
@keyup.esc="resetSearch()"
/>
<span class="icon is-small is-left">
<ion-icon name="search"></ion-icon>
</span>
</p>
</div>
<div class="column is-1 has-text-centered">
@@ -14,6 +23,8 @@
<script>
import { mapActions, mapState } from "vuex";
import hotkeys from "hotkeys-js";
export default {
props: [],
name: "Search",
@@ -23,31 +34,26 @@ export default {
};
},
mounted() {
window.addEventListener("keydown", this.onKeyDown);
},
destroyed() {
window.removeEventListener("keydown", this.onKeyDown);
hotkeys("command+f, ctrl+f", (event, handler) => {
this.showSearch = true;
this.$nextTick(() => this.$refs.filter.focus() || this.$refs.filter.select());
event.preventDefault();
});
hotkeys("esc", (event, handler) => {
this.resetSearch();
});
},
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"]),
...mapState(["searchFilter", "settings"]),
filter: {
get() {
return this.searchFilter;

View File

@@ -1,25 +1,34 @@
<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 }">
<aside>
<div class="columns is-marginless">
<div class="column">
<h1 class="title has-text-warning is-marginless">Dozzle</h1>
</div>
<div class="column is-narrow has-text-right is-hidden-mobile">
<router-link
:to="{ name: 'settings' }"
active-class="is-active"
class="button is-small is-rounded is-settings-control"
>
<span class="icon"><ion-icon name="settings" size="large"></ion-icon></span>
</router-link>
</div>
</div>
<p class="menu-label is-hidden-mobile">Containers</p>
<ul class="menu-list is-hidden-mobile">
<li v-for="item in containers">
<router-link :to="{ name: 'container', params: { id: item.id, name: item.name } }" active-class="is-active">
<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>
<ion-icon name="ios-add-circle"></ion-icon>
</span>
{{ item.name }}
</div>
@@ -36,25 +45,11 @@ 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>")
return {};
},
computed: {
...mapState(["containers", "activeContainers"]),
activeContainersById() {
return this.activeContainers.reduce((map, obj) => {
map[obj.id] = obj;
return map;
}, {});
}
...mapGetters(["activeContainersById"])
},
methods: {
...mapActions({
@@ -65,30 +60,12 @@ export default {
</script>
<style scoped lang="scss">
aside {
position: fixed;
z-index: 2;
padding: 1em;
height: 100vh;
overflow: auto;
position: fixed;
width: inherit;
@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;
@@ -102,16 +79,11 @@ aside {
.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;
}

View File

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

View File

@@ -7,14 +7,16 @@
<link href="https://fonts.googleapis.com/css?family=Roboto|Roboto+Mono|Gafata" rel="stylesheet" />
<link rel="manifest" href="manifest.webmanifest" />
<link href="styles.scss" rel="stylesheet" />
<link rel="icon" href="favicon.ico">
<link rel="icon" href="favicon.ico" />
<script>
window["BASE_PATH"] = "{{ .Base }}";
window["VERSION"] = "{{ .Version }}";
</script>
<script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
<script type="module" src="https://unpkg.com/ionicons@4.5.10-0/dist/ionicons/ionicons.esm.js"></script>
<script nomodule="" src="https://unpkg.com/ionicons@4.5.10-0/dist/ionicons/ionicons.js"></script>
</head>
<body class="is-dark">
<body>
<div id="app"></div>
<script src="main.js"></script>
</body>

View File

@@ -1,14 +1,19 @@
import Vue from "vue";
import VueRouter from "vue-router";
import Meta from "vue-meta";
import Vuex from "vuex";
import { Dropdown, Switch } from "buefy";
import store from "./store";
import App from "./App.vue";
import Container from "./pages/Container.vue";
import Settings from "./pages/Settings.vue";
import Index from "./pages/Index.vue";
Vue.use(VueRouter);
Vue.use(Meta);
Vue.use(Dropdown);
Vue.use(Switch);
Vue.config.ignoredElements = [/^ion-/];
const routes = [
{
@@ -21,6 +26,11 @@ const routes = [
component: Container,
name: "container",
props: true
},
{
path: "/settings",
component: Settings,
name: "settings"
}
];

View File

@@ -1,16 +1,18 @@
<template lang="html">
<div>
<search></search>
<scrollable-view>
<log-viewer-with-source :id="id"></log-viewer-with-source>
</scrollable-view>
</div>
<scrollable-view :scrollable="activeContainers.length > 0">
<template v-slot:header v-if="activeContainers.length > 0">
<container-title :value="allContainersById[id].name"></container-title>
</template>
<log-viewer-with-source :id="id"></log-viewer-with-source>
</scrollable-view>
</template>
<script>
import { mapActions, mapGetters, mapState } from "vuex";
import LogViewerWithSource from "../components/LogViewerWithSource";
import Search from "../components/Search";
import ScrollableView from "../components/ScrollableView";
import ContainerTitle from "../components/ContainerTitle";
export default {
props: ["id", "name"],
@@ -18,14 +20,34 @@ export default {
components: {
LogViewerWithSource,
ScrollableView,
Search
ContainerTitle
},
data() {
return {
title: "loading"
};
},
metaInfo() {
return {
title: this.name,
titleTemplate: "%s - Dozzle"
title: this.title
};
},
mounted() {
if (this.allContainersById[this.id]) {
this.title = this.allContainersById[this.id].name;
}
},
computed: {
...mapState(["activeContainers"]),
...mapGetters(["allContainersById"])
},
watch: {
id() {
this.title = this.allContainersById[this.id].name;
},
allContainersById() {
this.title = this.allContainersById[this.id].name;
}
}
};
</script>
<style lang="scss" scoped></style>

138
assets/pages/Settings.vue Normal file
View File

@@ -0,0 +1,138 @@
<template lang="html">
<div>
<section class="section">
<div class="has-underline">
<h2 class="title is-4">About</h2>
</div>
<div>
You are using Dozzle <i>{{ currentVersion }}</i
>.
<span v-if="hasUpdate">
New version is available! Update to
<a :href="nextRelease.html_url" class="next-release">{{ nextRelease.name }}</a
>.
</span>
</div>
</section>
<section class="section">
<div class="has-underline">
<h2 class="title is-4">Display</h2>
</div>
<div class="item">
<b-switch v-model="search">
Enable searching with Dozzle using <code>command+f</code> or <code>ctrl+f</code>
</b-switch>
</div>
<div class="item">
<b-switch v-model="smallerScrollbars">
Use smaller scrollbars
</b-switch>
</div>
<div class="item">
<h2 class="title is-6 is-marginless">Font size</h2>
Modify the font size when viewing logs.
<b-dropdown v-model="size" aria-role="list" style="margin:-8px 10px 0">
<button class="button is-primary" type="button" slot="trigger">
<span class="is-capitalized">{{ size }}</span>
<span class="icon"><ion-icon name="ios-arrow-down"></ion-icon></span>
</button>
<b-dropdown-item :value="value" aria-role="listitem" v-for="value in ['small', 'medium', 'large']">
<div class="media">
<span class="icon">
<ion-icon name="checkmark" v-if="value == size"></ion-icon>
</span>
<div class="media-content">
<h3 class="is-capitalized">{{ value }}</h3>
</div>
</div>
</b-dropdown-item>
</b-dropdown>
</div>
</section>
</div>
</template>
<script>
import gt from "semver/functions/gt";
import valid from "semver/functions/valid";
import { mapActions, mapState } from "vuex";
export default {
props: [],
name: "Settings",
components: {},
data() {
return {
currentVersion: VERSION,
nextRelease: null,
hasUpdate: false
};
},
async created() {
const releases = await (await fetch("https://api.github.com/repos/amir20/dozzle/releases")).json();
this.hasUpdate = gt(releases[0].tag_name, this.currentVersion);
this.nextRelease = releases[0];
},
metaInfo() {
return {
title: "Settings"
};
},
methods: {
...mapActions({
updateSetting: "UPDATE_SETTING"
})
},
computed: {
...mapState(["settings"]),
...["search", "size", "smallerScrollbars"].reduce((map, name) => {
map[name] = {
get() {
return this.settings[name];
},
set(value) {
this.updateSetting({ [name]: value });
}
};
return map;
}, {})
}
};
</script>
<style lang="scss">
.title {
color: #eee;
}
a.next-release {
text-decoration: underline;
color: #00d1b2;
&:hover {
text-decoration: none;
}
}
.section {
padding: 1rem 1.5rem;
}
.has-underline {
border-bottom: 1px solid #fff;
padding: 1em 0px;
margin-bottom: 1em;
}
.item {
padding: 1em 0;
}
code {
border-radius: 4px;
background-color: #444;
}
</style>

View File

@@ -1,13 +1,20 @@
import Vue from "vue";
import Vuex from "vuex";
// import storage from "store/dist/store.modern";
import storage from "store/dist/store.modern";
import { DEFAULT_SETTINGS, DOZZLE_SETTINGS_KEY } from "./settings";
Vue.use(Vuex);
const mql = window.matchMedia("(max-width: 770px)");
storage.set(DOZZLE_SETTINGS_KEY, { ...DEFAULT_SETTINGS, ...storage.get(DOZZLE_SETTINGS_KEY) });
const state = {
containers: [],
activeContainers: [],
searchFilter: null
searchFilter: null,
isMobile: mql.matches,
settings: storage.get(DOZZLE_SETTINGS_KEY)
};
const mutations = {
@@ -22,6 +29,13 @@ const mutations = {
},
SET_SEARCH(state, filter) {
state.searchFilter = filter;
},
SET_MOBILE_WIDTH(state, value) {
state.isMobile = value;
},
UPDATE_SETTINGS(state, newValues) {
state.settings = { ...state.settings, ...newValues };
storage.set(DOZZLE_SETTINGS_KEY, state.settings);
}
};
@@ -38,15 +52,31 @@ const actions = {
async FETCH_CONTAINERS({ commit }) {
const containers = await (await fetch(`${BASE_PATH}/api/containers.json`)).json();
commit("SET_CONTAINERS", containers);
},
UPDATE_SETTING({ commit }, setting) {
commit("UPDATE_SETTINGS", setting);
}
};
const getters = {
activeContainersById(state) {
return state.activeContainers.reduce((map, obj) => {
map[obj.id] = obj;
return map;
}, {});
},
allContainersById(state) {
return state.containers.reduce((map, obj) => {
map[obj.id] = obj;
return map;
}, {});
}
};
const getters = {};
const es = new EventSource(`${BASE_PATH}/api/events/stream`);
es.addEventListener("containers-changed", e => setTimeout(() => store.dispatch("FETCH_CONTAINERS"), 1000), false);
mql.addListener(e => store.commit("SET_MOBILE_WIDTH", e.matches));
const store = new Vuex.Store({
strict: true,
state,
getters,
actions,

7
assets/store/settings.js Normal file
View File

@@ -0,0 +1,7 @@
export const DOZZLE_SETTINGS_KEY = "DOZZLE_SETTINGS";
export const DEFAULT_SETTINGS = {
search: true,
size: "medium",
menuWidth: 15,
smallerScrollbars: false
};

View File

@@ -3,22 +3,72 @@
$menu-item-active-background-color: hsl(171, 100%, 41%);
$menu-item-color: hsl(0, 6%, 87%);
@import "../node_modules/bulma/bulma.sass";
@import "~bulma";
@import "../node_modules/splitpanes/dist/splitpanes.css";
.is-dark {
color: #ddd;
background-color: #111;
}
@import "~buefy/src/scss/utils/_all";
@import "~buefy/src/scss/components/_dropdown";
@import "~buefy/src/scss/components/_switch";
body {
font-family: "Roboto", sans-serif;
color: #ddd;
background-color: #111;
}
h1.title {
font-family: "Gafata", sans-serif;
}
.column {
min-width: 0;
html {
overflow-x: unset;
overflow-y: unset;
}
html.has-custom-scrollbars {
::-webkit-scrollbar {
width: 8px;
display: content;
}
::-webkit-scrollbar-thumb {
background-color: rgba(128, 128, 128, 0.33);
outline: 1px solid slategrey;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:active {
background-color: #777;
}
::-webkit-scrollbar-track {
background-color: transparent;
}
::-webkit-scrollbar-track:hover {
background-color: rgba(64, 64, 64, 0.33);
}
section main {
scrollbar-color: #353535 transparent;
scrollbar-width: thin;
}
}
.is-settings-control {
background: rgba(0, 0, 0, 0.4);
color: #fff;
border-color: transparent;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
border-color: rgb(255, 221, 87) !important;
background: rgba(0, 0, 0, 0.8) !important;
color: #fff !important;
}
&:focus {
box-shadow: none !important;
color: unset;
border-color: transparent;
}
}

View File

@@ -10,6 +10,7 @@ import (
"sort"
"strconv"
"strings"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/events"
@@ -36,6 +37,7 @@ type Client interface {
FindContainer(string) (Container, error)
ContainerLogs(context.Context, string, int) (<-chan string, <-chan error)
Events(context.Context) (<-chan events.Message, <-chan error)
ContainerLogsBetweenDates(context.Context, string, time.Time, time.Time) ([]string, error)
}
// NewClient creates a new instance of Client
@@ -121,6 +123,34 @@ func (d *dockerClient) ListContainers(showAll bool) ([]Container, error) {
return containers, nil
}
func logReader(reader io.ReadCloser, tty bool) func() (string, error) {
if tty {
scanner := bufio.NewScanner(reader)
return func() (string, error) {
if scanner.Scan() {
return scanner.Text(), nil
}
return "", io.EOF
}
}
hdr := make([]byte, 8)
var buffer bytes.Buffer
return func() (string, error) {
buffer.Reset()
_, err := reader.Read(hdr)
if err != nil {
return "", err
}
count := binary.BigEndian.Uint32(hdr[4:])
_, err = io.CopyN(&buffer, reader, int64(count))
if err != nil {
return "", err
}
return strings.TrimSpace(buffer.String()), nil
}
}
func (d *dockerClient) ContainerLogs(ctx context.Context, id string, tailSize int) (<-chan string, <-chan error) {
options := types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: true, Tail: strconv.Itoa(tailSize), Timestamps: true}
reader, err := d.cli.ContainerLogs(ctx, id, options)
@@ -140,49 +170,23 @@ func (d *dockerClient) ContainerLogs(ctx context.Context, id string, tailSize in
containerJSON, _ := d.cli.ContainerInspect(ctx, id)
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():
}
go func() {
defer close(messages)
defer close(errChannel)
defer reader.Close()
nextEntry := logReader(reader, containerJSON.Config.Tty)
for {
line, err := nextEntry()
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 <- line:
case <-ctx.Done():
}
}()
}
}
}()
return messages, errChannel
}
@@ -190,3 +194,34 @@ func (d *dockerClient) ContainerLogs(ctx context.Context, id string, tailSize in
func (d *dockerClient) Events(ctx context.Context) (<-chan events.Message, <-chan error) {
return d.cli.Events(ctx, types.EventsOptions{})
}
func (d *dockerClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time) ([]string, error) {
options := types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Timestamps: true,
Since: strconv.FormatInt(from.Unix(), 10),
Until: strconv.FormatInt(to.Unix(), 10),
}
reader, _ := d.cli.ContainerLogs(ctx, id, options)
defer reader.Close()
containerJSON, _ := d.cli.ContainerInspect(ctx, id)
nextEntry := logReader(reader, containerJSON.Config.Tty)
var messages []string
for {
line, err := nextEntry()
if err != nil {
if err == io.EOF {
break
} else {
return nil, err
}
}
messages = append(messages, line)
}
return messages, nil
}

31
go.mod
View File

@@ -14,35 +14,42 @@ require (
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
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/containerd/containerd v1.3.3 // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect
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/envy v1.9.0 // indirect
github.com/gobuffalo/packd v1.0.0 // indirect
github.com/gobuffalo/packr v1.30.1
github.com/gogo/protobuf v1.3.0 // indirect
github.com/gogo/protobuf v1.3.1 // indirect
github.com/golang/protobuf v1.3.4 // indirect
github.com/google/go-cmp v0.3.1 // indirect
github.com/gorilla/mux v1.7.3
github.com/gorilla/mux v1.7.4
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/pelletier/go-toml v1.6.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rogpeppe/go-internal v1.5.2 // 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/cast v1.3.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.5.0
github.com/spf13/viper v1.6.2
github.com/stretchr/objx v0.2.0 // 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
github.com/stretchr/testify v1.5.1
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b // indirect
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae // indirect
golang.org/x/text v0.3.2 // indirect
google.golang.org/genproto v0.0.0-20190916214212-f660b8655731 // indirect
google.golang.org/grpc v1.23.1 // indirect
google.golang.org/genproto v0.0.0-20200226201735-46b91f19d98c // indirect
google.golang.org/grpc v1.27.1 // indirect
gopkg.in/ini.v1 v1.52.0 // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect
gotest.tools v2.2.0+incompatible // indirect
)
go 1.13
go 1.14

73
go.sum
View File

@@ -25,10 +25,11 @@ github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR
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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
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/containerd/containerd v1.3.3 h1:LoIzb5y9x5l8VKAlyrbusNPXqBY0+kviRloxFUMFwKc=
github.com/containerd/containerd v1.3.3/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=
@@ -54,6 +55,8 @@ github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw
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/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
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=
@@ -65,19 +68,21 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
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/envy v1.9.0 h1:eZR0DuEgVLfeIb1zIKt3bT4YovIMf9O9LXQeCZLXpqE=
github.com/gobuffalo/envy v1.9.0/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=
github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM=
github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI=
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
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/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/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=
@@ -88,15 +93,19 @@ 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/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
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.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 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
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=
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
@@ -109,6 +118,7 @@ github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht
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 h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
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=
@@ -146,12 +156,14 @@ github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVo
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg=
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.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=
@@ -160,6 +172,7 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf
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/client_model v0.0.0-20190812154241-14fe0d1b01d4/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=
@@ -173,6 +186,8 @@ github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhg
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/rogpeppe/go-internal v1.5.2 h1:qLvObTrvO/XRCqmkKxUlOBc48bI3efyDuAZe25QiF0w=
github.com/rogpeppe/go-internal v1.5.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=
@@ -181,8 +196,11 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
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 h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
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/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/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=
@@ -191,6 +209,8 @@ 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/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/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=
@@ -203,10 +223,8 @@ 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/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E=
github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
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=
@@ -216,6 +234,8 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
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/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
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=
@@ -250,8 +270,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
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-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/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/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=
@@ -272,8 +292,8 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
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-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/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/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=
@@ -294,20 +314,27 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
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-20190916214212-f660b8655731 h1:Phvl0+G5t5k/EUFUi0wPdUUeTL2HydMQUXHnunWgSb0=
google.golang.org/genproto v0.0.0-20190916214212-f660b8655731/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200226201735-46b91f19d98c h1:xFOdgVPpeowWAH0MJ5i0XMp+3yWiWamMtN/kx9xThIQ=
google.golang.org/genproto v0.0.0-20200226201735-46b91f19d98c/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
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.23.1 h1:q4XQuHFC6I28BKZpo6IYyb3mNO+l7lSOxRuYTCiDfXk=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
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=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.52.0 h1:j+Lt/M1oPPejkniCg1TkWE2J3Eh1oZTsHSXzMTzUXn4=
gopkg.in/ini.v1 v1.52.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -315,6 +342,8 @@ 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=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/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=

33
main.go
View File

@@ -34,9 +34,9 @@ var (
)
type handler struct {
client docker.Client
client docker.Client
showAll bool
box packr.Box
box packr.Box
}
func init() {
@@ -91,6 +91,7 @@ func createRoutes(base string, h *handler) *mux.Router {
s := r.PathPrefix(base).Subrouter()
s.HandleFunc("/api/containers.json", h.listContainers)
s.HandleFunc("/api/logs/stream", h.streamLogs)
s.HandleFunc("/api/logs", h.fetchLogsBetweenDates)
s.HandleFunc("/api/events/stream", h.streamEvents)
s.HandleFunc("/version", h.version)
s.PathPrefix("/").Handler(http.StripPrefix(base, http.HandlerFunc(h.index)))
@@ -108,9 +109,9 @@ func main() {
box := packr.NewBox("./static")
r := createRoutes(base, &handler{
client: dockerClient,
client: dockerClient,
showAll: showAll,
box: box,
box: box,
})
srv := &http.Server{Addr: addr, Handler: r}
@@ -137,7 +138,10 @@ func (h *handler) index(w http.ResponseWriter, req *http.Request) {
if h.box.Has(req.URL.Path) && req.URL.Path != "" && req.URL.Path != "/" {
fileServer.ServeHTTP(w, req)
} else {
text, _ := h.box.FindString("index.html")
text, err := h.box.FindString("index.html")
if err != nil {
panic(err)
}
text = strings.Replace(text, "__BASE__", "{{ .Base }}", -1)
tmpl, err := template.New("index.html").Parse(text)
if err != nil {
@@ -149,7 +153,10 @@ func (h *handler) index(w http.ResponseWriter, req *http.Request) {
path = base
}
data := struct{ Base string }{path}
data := struct {
Base string
Version string
}{path, version}
err = tmpl.Execute(w, data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -170,6 +177,20 @@ func (h *handler) listContainers(w http.ResponseWriter, r *http.Request) {
}
}
func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
from, _ := time.Parse(time.RFC3339, r.URL.Query().Get("from"))
to, _ := time.Parse(time.RFC3339, r.URL.Query().Get("to"))
id := r.URL.Query().Get("id")
messages, _ := h.client.ContainerLogsBetweenDates(r.Context(), id, from, to)
for _, m := range messages {
fmt.Fprintln(w, m)
}
}
func (h *handler) streamLogs(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
if id == "" {

View File

@@ -259,7 +259,7 @@ func Test_createRoutes_redirect(t *testing.T) {
mockedClient := new(MockedClient)
box := packr.NewBox("./virtual")
handler := createRoutes("/foobar", &handler{mockedClient, true,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()

4982
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
{
"name": "dozzle",
"version": "1.17.0",
"version": "1.21.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,37 +24,41 @@
},
"homepage": "https://github.com/amir20/dozzle#readme",
"dependencies": {
"ansi-to-html": "^0.6.13",
"ansi-to-html": "^0.6.14",
"buefy": "^0.8.13",
"bulma": "^0.8.0",
"date-fns": "^2.8.1",
"splitpanes": "^2.1.1",
"date-fns": "^2.11.0",
"hotkeys-js": "^3.7.3",
"lodash.debounce": "^4.0.8",
"semver": "^7.1.3",
"splitpanes": "^2.2.1",
"store": "^2.0.12",
"vue": "^2.6.10",
"vue-meta": "^2.3.1",
"vue-router": "^3.1.3",
"vuex": "^3.1.2"
"vue": "^2.6.11",
"vue-meta": "^2.3.3",
"vue-router": "^3.1.6",
"vuex": "^3.1.3"
},
"devDependencies": {
"@babel/core": "^7.7.4",
"@babel/plugin-transform-runtime": "^7.7.4",
"@vue/component-compiler-utils": "^3.0.2",
"@babel/core": "^7.8.7",
"@babel/plugin-transform-runtime": "^7.8.3",
"@vue/component-compiler-utils": "^3.1.1",
"@vue/test-utils": "^1.0.0-beta.29",
"babel-core": "^7.0.0-bridge.0",
"babel-jest": "^24.9.0",
"concurrently": "^5.0.0",
"babel-jest": "^25.1.0",
"concurrently": "^5.1.0",
"eventsourcemock": "^2.0.0",
"husky": "^3.1.0",
"jest": "^24.9.0",
"husky": "^4.2.3",
"jest": "^25.1.0",
"jest-serializer-vue": "^2.0.2",
"lint-staged": "^9.4.3",
"lint-staged": "^10.0.8",
"mockdate": "^2.0.5",
"node-fetch": "^2.6.0",
"parcel-bundler": "^1.12.4",
"prettier": "^1.19.1",
"sass": "^1.23.7",
"sass": "^1.26.3",
"vue-hot-reload-api": "^2.3.4",
"vue-jest": "^3.0.5",
"vue-template-compiler": "^2.6.10"
"vue-template-compiler": "^2.6.11"
},
"husky": {
"hooks": {
@@ -63,8 +67,7 @@
},
"lint-staged": {
"*.{js,vue,css}": [
"prettier --write",
"git add"
"prettier --write"
]
},
"browserslist": [
@@ -100,5 +103,10 @@
".*\\.vue$": "vue-jest",
".+\\.js$": "babel-jest"
}
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
}