Compare commits
	
		
			62 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					72839fea10 | ||
| 
						 | 
					262ba5e9f4 | ||
| 
						 | 
					d0e1614e08 | ||
| 
						 | 
					da62f2543f | ||
| 
						 | 
					9462adaae2 | ||
| 
						 | 
					952c38e7b3 | ||
| 
						 | 
					00615f5ffa | ||
| 
						 | 
					38f90f396f | ||
| 
						 | 
					4a2f757754 | ||
| 
						 | 
					2016a4c59c | ||
| 
						 | 
					09a8ace624 | ||
| 
						 | 
					8c2ad64439 | ||
| 
						 | 
					e0c6244d54 | ||
| 
						 | 
					2a837762ed | ||
| 
						 | 
					19ce377ee9 | ||
| 
						 | 
					b4532bf9f7 | ||
| 
						 | 
					033636dd46 | ||
| 
						 | 
					769de61657 | ||
| 
						 | 
					9d59278035 | ||
| 
						 | 
					1f510cdd4e | ||
| 
						 | 
					52bfda2ab2 | ||
| 
						 | 
					b92de8a508 | ||
| 
						 | 
					957a5104b8 | ||
| 
						 | 
					a4981f1b2c | ||
| 
						 | 
					beaeecd457 | ||
| 
						 | 
					dcc9088e31 | ||
| 
						 | 
					27e8129caa | ||
| 
						 | 
					7d801379db | ||
| 
						 | 
					420da8c363 | ||
| 
						 | 
					917384a2d9 | ||
| 
						 | 
					e97e69a0e1 | ||
| 
						 | 
					87d51409ff | ||
| 
						 | 
					551b1f04c7 | ||
| 
						 | 
					a35f4ef32e | ||
| 
						 | 
					441f234398 | ||
| 
						 | 
					dc452e2847 | ||
| 
						 | 
					b9ee28ca8d | ||
| 
						 | 
					474ce714db | ||
| 
						 | 
					988afbe1c0 | ||
| 
						 | 
					f9a0d4e881 | ||
| 
						 | 
					78dd5b1d8b | ||
| 
						 | 
					ede15194a1 | ||
| 
						 | 
					88838ec63d | ||
| 
						 | 
					66b902a5e0 | ||
| 
						 | 
					e4d4d5251b | ||
| 
						 | 
					0c50ff7c91 | ||
| 
						 | 
					427edaa1ef | ||
| 
						 | 
					b8c82af838 | ||
| 
						 | 
					57ad1b98ff | ||
| 
						 | 
					9d2bdb6a53 | ||
| 
						 | 
					d97f7c5c6f | ||
| 
						 | 
					abd334f3b8 | ||
| 
						 | 
					33de8a4f07 | ||
| 
						 | 
					93cfd0e597 | ||
| 
						 | 
					537f7c0a01 | ||
| 
						 | 
					e114f877c1 | ||
| 
						 | 
					55bc51c9c2 | ||
| 
						 | 
					d93b662907 | ||
| 
						 | 
					3eec6cdd14 | ||
| 
						 | 
					3b9adf8260 | ||
| 
						 | 
					dde707a97a | ||
| 
						 | 
					50ccf6311b | 
@@ -10,10 +10,8 @@ trim_trailing_whitespace = true
 | 
			
		||||
max_line_length = 120
 | 
			
		||||
 | 
			
		||||
[*.go]
 | 
			
		||||
indent_size = 4
 | 
			
		||||
 | 
			
		||||
[Makefile]
 | 
			
		||||
indent_style = tab
 | 
			
		||||
indent_size = 4
 | 
			
		||||
 | 
			
		||||
[package.json]
 | 
			
		||||
indent_size = 1
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
*.snapshot binary
 | 
			
		||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -4,4 +4,5 @@ node_modules
 | 
			
		||||
.cache
 | 
			
		||||
static
 | 
			
		||||
a_main-packr.go
 | 
			
		||||
dozzle
 | 
			
		||||
dozzle
 | 
			
		||||
gin-bin
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.reflex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.reflex
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
-r '\.go$' -R '^node_modules/' -R '^static/' -R '^.cache/' -G '*_test.go' -s -- go run main.go --level debug
 | 
			
		||||
@@ -3,6 +3,10 @@ language: go
 | 
			
		||||
go:
 | 
			
		||||
  - "1.11"
 | 
			
		||||
 | 
			
		||||
env:
 | 
			
		||||
  global:
 | 
			
		||||
    - GO111MODULE=on
 | 
			
		||||
 | 
			
		||||
services:
 | 
			
		||||
  - docker
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
[](https://goreportcard.com/report/github.com/amir20/dozzle)
 | 
			
		||||
 | 
			
		||||
# dozzle
 | 
			
		||||
 | 
			
		||||
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?
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										77
									
								
								__snapshots__/dozzle.snapshot
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								__snapshots__/dozzle.snapshot
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
			
		||||
/* snapshot: Test_createRoutes_foobar */
 | 
			
		||||
HTTP/1.1 200 OK
 | 
			
		||||
Connection: close
 | 
			
		||||
Content-Type: text/plain; charset=utf-8
 | 
			
		||||
 | 
			
		||||
foo page
 | 
			
		||||
 | 
			
		||||
/* snapshot: Test_createRoutes_index */
 | 
			
		||||
HTTP/1.1 200 OK
 | 
			
		||||
Connection: close
 | 
			
		||||
Content-Type: text/plain; charset=utf-8
 | 
			
		||||
 | 
			
		||||
index page
 | 
			
		||||
 | 
			
		||||
/* snapshot: Test_createRoutes_redirect */
 | 
			
		||||
HTTP/1.1 301 Moved Permanently
 | 
			
		||||
Connection: close
 | 
			
		||||
Content-Type: text/html; charset=utf-8
 | 
			
		||||
Location: /foobar/
 | 
			
		||||
 | 
			
		||||
<a href="/foobar/">Moved Permanently</a>.
 | 
			
		||||
 | 
			
		||||
/* snapshot: Test_createRoutes_version */
 | 
			
		||||
HTTP/1.1 200 OK
 | 
			
		||||
Connection: close
 | 
			
		||||
Content-Type: text/plain; charset=utf-8
 | 
			
		||||
 | 
			
		||||
dev
 | 
			
		||||
none
 | 
			
		||||
unknown
 | 
			
		||||
 | 
			
		||||
/* snapshot: Test_handler_listContainers_happy */
 | 
			
		||||
HTTP/1.1 200 OK
 | 
			
		||||
Connection: close
 | 
			
		||||
Content-Type: text/plain; charset=utf-8
 | 
			
		||||
 | 
			
		||||
[{"id":"1234567890","names":null,"name":"test","image":"image","imageId":"image_id","command":"command","created":0,"state":"state","status":"status"}]
 | 
			
		||||
 | 
			
		||||
/* snapshot: Test_handler_streamEvents_error */
 | 
			
		||||
HTTP/1.1 200 OK
 | 
			
		||||
Connection: close
 | 
			
		||||
Cache-Control: no-cache
 | 
			
		||||
Connection: keep-alive
 | 
			
		||||
Content-Type: text/event-stream
 | 
			
		||||
 | 
			
		||||
/* snapshot: Test_handler_streamEvents_error_request */
 | 
			
		||||
HTTP/1.1 200 OK
 | 
			
		||||
Connection: close
 | 
			
		||||
Cache-Control: no-cache
 | 
			
		||||
Connection: keep-alive
 | 
			
		||||
Content-Type: text/event-stream
 | 
			
		||||
 | 
			
		||||
/* snapshot: Test_handler_streamEvents_happy */
 | 
			
		||||
HTTP/1.1 200 OK
 | 
			
		||||
Connection: close
 | 
			
		||||
Cache-Control: no-cache
 | 
			
		||||
Connection: keep-alive
 | 
			
		||||
Content-Type: text/event-stream
 | 
			
		||||
 | 
			
		||||
event: containers-changed
 | 
			
		||||
data: start
 | 
			
		||||
 | 
			
		||||
/* snapshot: Test_handler_streamLogs_error_reading */
 | 
			
		||||
HTTP/1.1 200 OK
 | 
			
		||||
Connection: close
 | 
			
		||||
Cache-Control: no-cache
 | 
			
		||||
Connection: keep-alive
 | 
			
		||||
Content-Type: text/event-stream
 | 
			
		||||
 | 
			
		||||
/* snapshot: Test_handler_streamLogs_happy */
 | 
			
		||||
HTTP/1.1 200 OK
 | 
			
		||||
Connection: close
 | 
			
		||||
Cache-Control: no-cache
 | 
			
		||||
Connection: keep-alive
 | 
			
		||||
Content-Type: text/event-stream
 | 
			
		||||
 | 
			
		||||
data: INFO Testing logs...
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<template lang="html">
 | 
			
		||||
  <div class="columns is-marginless">
 | 
			
		||||
    <aside class="column menu is-2">
 | 
			
		||||
    <aside class="column menu is-2-desktop is-3-tablet">
 | 
			
		||||
      <a
 | 
			
		||||
        role="button"
 | 
			
		||||
        class="navbar-burger burger is-white is-hidden-tablet is-pulled-right"
 | 
			
		||||
@@ -13,18 +13,13 @@
 | 
			
		||||
      <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"
 | 
			
		||||
            class="tooltip is-tooltip-right is-tooltip-info"
 | 
			
		||||
            :data-tooltip="item.name"
 | 
			
		||||
          >
 | 
			
		||||
          <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-2"><router-view></router-view></div>
 | 
			
		||||
    <div class="column is-offset-2-desktop is-offset-3-tablet"><router-view></router-view></div>
 | 
			
		||||
    <vue-headful :title="title" />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -74,6 +69,12 @@ aside {
 | 
			
		||||
  z-index: 2;
 | 
			
		||||
  padding: 1em;
 | 
			
		||||
 | 
			
		||||
  @media screen and (min-width: 769px) {
 | 
			
		||||
    & {
 | 
			
		||||
      height: 100vh;
 | 
			
		||||
      overflow: auto;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  @media screen and (max-width: 768px) {
 | 
			
		||||
    & {
 | 
			
		||||
      position: sticky;
 | 
			
		||||
@@ -83,11 +84,6 @@ aside {
 | 
			
		||||
      background: #222;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .tooltip::after,
 | 
			
		||||
    .tooltip::before {
 | 
			
		||||
      display: none !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .menu-label {
 | 
			
		||||
      margin-top: 1em;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1" />
 | 
			
		||||
    <title>Dozzle</title>
 | 
			
		||||
    <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" />
 | 
			
		||||
    <script>
 | 
			
		||||
      window["BASE_PATH"] = "{{ .Base }}";
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								assets/manifest.webmanifest
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								assets/manifest.webmanifest
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "Dozzle Log Viewer",
 | 
			
		||||
  "short_name": "Dozzle",
 | 
			
		||||
  "theme_color": "#111111",
 | 
			
		||||
  "background_color": "#111111",
 | 
			
		||||
  "display": "standalone",
 | 
			
		||||
  "scope": "/",
 | 
			
		||||
  "start_url": "/"
 | 
			
		||||
}
 | 
			
		||||
@@ -4,7 +4,6 @@ $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/bulma-tooltip/src/sass";
 | 
			
		||||
 | 
			
		||||
.is-dark {
 | 
			
		||||
    color: #ddd;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										148
									
								
								docker/client.go
									
									
									
									
									
								
							
							
						
						
									
										148
									
								
								docker/client.go
									
									
									
									
									
								
							@@ -1,69 +1,125 @@
 | 
			
		||||
package docker
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
    "context"
 | 
			
		||||
    "github.com/docker/docker/api/types"
 | 
			
		||||
    "github.com/docker/docker/api/types/events"
 | 
			
		||||
    "github.com/docker/docker/client"
 | 
			
		||||
    "io"
 | 
			
		||||
    "log"
 | 
			
		||||
    "sort"
 | 
			
		||||
    "strings"
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/binary"
 | 
			
		||||
	"github.com/docker/docker/api/types"
 | 
			
		||||
	"github.com/docker/docker/api/types/events"
 | 
			
		||||
	"github.com/docker/docker/client"
 | 
			
		||||
	"io"
 | 
			
		||||
	"log"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type dockerClient struct {
 | 
			
		||||
    cli *client.Client
 | 
			
		||||
	cli dockerProxy
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DockerClient interface {
 | 
			
		||||
    ListContainers() ([]Container, error)
 | 
			
		||||
    ContainerLogs(ctx context.Context, id string) (io.ReadCloser, error)
 | 
			
		||||
    Events(ctx context.Context) (<-chan events.Message, <-chan error)
 | 
			
		||||
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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewDockerClient() DockerClient {
 | 
			
		||||
    cli, err := client.NewClientWithOpts(client.FromEnv)
 | 
			
		||||
    if err != nil {
 | 
			
		||||
        log.Fatal(err)
 | 
			
		||||
    }
 | 
			
		||||
    return &dockerClient{cli}
 | 
			
		||||
// Client is a proxy around the docker client
 | 
			
		||||
type Client interface {
 | 
			
		||||
	ListContainers() ([]Container, error)
 | 
			
		||||
	ContainerLogs(ctx context.Context, id string) (<-chan string, <-chan error)
 | 
			
		||||
	Events(ctx context.Context) (<-chan events.Message, <-chan error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewClient creates a new instance of Client
 | 
			
		||||
func NewClient() Client {
 | 
			
		||||
	cli, err := client.NewClientWithOpts(client.FromEnv)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	return &dockerClient{cli}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *dockerClient) ListContainers() ([]Container, error) {
 | 
			
		||||
    list, err := d.cli.ContainerList(context.Background(), types.ContainerListOptions{})
 | 
			
		||||
    if err != nil {
 | 
			
		||||
        return nil, err
 | 
			
		||||
    }
 | 
			
		||||
	list, err := d.cli.ContainerList(context.Background(), types.ContainerListOptions{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    var containers []Container
 | 
			
		||||
    for _, c := range list {
 | 
			
		||||
	var containers []Container
 | 
			
		||||
	for _, c := range list {
 | 
			
		||||
 | 
			
		||||
        container := Container{
 | 
			
		||||
            ID:      c.ID[:12],
 | 
			
		||||
            Names:   c.Names,
 | 
			
		||||
            Name:    strings.TrimPrefix(c.Names[0], "/"),
 | 
			
		||||
            Image:   c.Image,
 | 
			
		||||
            ImageID: c.ImageID,
 | 
			
		||||
            Command: c.Command,
 | 
			
		||||
            Created: c.Created,
 | 
			
		||||
            State:   c.State,
 | 
			
		||||
            Status:  c.Status,
 | 
			
		||||
        }
 | 
			
		||||
        containers = append(containers, container)
 | 
			
		||||
    }
 | 
			
		||||
		container := Container{
 | 
			
		||||
			ID:      c.ID[:12],
 | 
			
		||||
			Names:   c.Names,
 | 
			
		||||
			Name:    strings.TrimPrefix(c.Names[0], "/"),
 | 
			
		||||
			Image:   c.Image,
 | 
			
		||||
			ImageID: c.ImageID,
 | 
			
		||||
			Command: c.Command,
 | 
			
		||||
			Created: c.Created,
 | 
			
		||||
			State:   c.State,
 | 
			
		||||
			Status:  c.Status,
 | 
			
		||||
		}
 | 
			
		||||
		containers = append(containers, container)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    sort.Slice(containers, func(i, j int) bool {
 | 
			
		||||
        return containers[i].Name < containers[j].Name
 | 
			
		||||
    })
 | 
			
		||||
	sort.Slice(containers, func(i, j int) bool {
 | 
			
		||||
		return containers[i].Name < containers[j].Name
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
    return containers, nil
 | 
			
		||||
	if containers == nil {
 | 
			
		||||
		containers = []Container{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return containers, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *dockerClient) ContainerLogs(ctx context.Context, id string) (io.ReadCloser, error) {
 | 
			
		||||
    options := types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: true, Tail: "300", Timestamps: true}
 | 
			
		||||
    return d.cli.ContainerLogs(ctx, id, options)
 | 
			
		||||
func (d *dockerClient) ContainerLogs(ctx context.Context, id string) (<-chan string, <-chan error) {
 | 
			
		||||
	options := types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: true, Tail: "300", Timestamps: true}
 | 
			
		||||
	reader, err := d.cli.ContainerLogs(ctx, id, options)
 | 
			
		||||
	errChannel := make(chan error, 1)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errChannel <- err
 | 
			
		||||
		close(errChannel)
 | 
			
		||||
		return nil, errChannel
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	messages := make(chan string)
 | 
			
		||||
	go func() {
 | 
			
		||||
		<-ctx.Done()
 | 
			
		||||
		reader.Close()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	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()
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	return messages, errChannel
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (d *dockerClient) Events(ctx context.Context) (<-chan events.Message, <-chan error) {
 | 
			
		||||
    return d.cli.Events(ctx, types.EventsOptions{})
 | 
			
		||||
	return d.cli.Events(ctx, types.EventsOptions{})
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										140
									
								
								docker/client_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								docker/client_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,140 @@
 | 
			
		||||
package docker
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type mockedProxy struct {
 | 
			
		||||
	mock.Mock
 | 
			
		||||
	dockerProxy
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *mockedProxy) ContainerList(context.Context, types.ContainerListOptions) ([]types.Container, error) {
 | 
			
		||||
	args := m.Called()
 | 
			
		||||
	containers, ok := args.Get(0).([]types.Container)
 | 
			
		||||
	if !ok && args.Get(0) != nil {
 | 
			
		||||
		panic("containers is not of type []types.Container")
 | 
			
		||||
	}
 | 
			
		||||
	return containers, args.Error(1)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *mockedProxy) ContainerLogs(ctx context.Context, id string, options types.ContainerLogsOptions) (io.ReadCloser, error) {
 | 
			
		||||
	args := m.Called(ctx, id)
 | 
			
		||||
	reader, ok := args.Get(0).(io.ReadCloser)
 | 
			
		||||
	if !ok && args.Get(0) != nil {
 | 
			
		||||
		panic("reader is not of type io.ReadCloser")
 | 
			
		||||
	}
 | 
			
		||||
	return reader, 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}
 | 
			
		||||
 | 
			
		||||
	list, err := client.ListContainers()
 | 
			
		||||
	assert.Empty(t, list, "list should be empty")
 | 
			
		||||
	require.NoError(t, err, "error should not return an error.")
 | 
			
		||||
 | 
			
		||||
	proxy.AssertExpectations(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}
 | 
			
		||||
 | 
			
		||||
	list, err := client.ListContainers()
 | 
			
		||||
	assert.Nil(t, list, "list should be nil")
 | 
			
		||||
	require.Error(t, err, "test.")
 | 
			
		||||
 | 
			
		||||
	proxy.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_dockerClient_ListContainers_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}
 | 
			
		||||
 | 
			
		||||
	list, err := client.ListContainers()
 | 
			
		||||
	require.NoError(t, err, "error should not return an error.")
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, list, []Container{
 | 
			
		||||
		{
 | 
			
		||||
			ID:    "1234567890_a",
 | 
			
		||||
			Name:  "a_test_container",
 | 
			
		||||
			Names: []string{"/a_test_container"},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			ID:    "abcdefghijkl",
 | 
			
		||||
			Name:  "z_test_container",
 | 
			
		||||
			Names: []string{"/z_test_container"},
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	proxy.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_dockerClient_ContainerLogs_happy(t *testing.T) {
 | 
			
		||||
	id := "123456"
 | 
			
		||||
 | 
			
		||||
	proxy := new(mockedProxy)
 | 
			
		||||
	expected := "INFO Testing logs..."
 | 
			
		||||
	b := make([]byte, 8)
 | 
			
		||||
 | 
			
		||||
	binary.BigEndian.PutUint32(b[4:], uint32(len(expected)))
 | 
			
		||||
	b = append(b, []byte(expected)...)
 | 
			
		||||
 | 
			
		||||
	var reader io.ReadCloser
 | 
			
		||||
	reader = ioutil.NopCloser(bytes.NewReader(b))
 | 
			
		||||
	proxy.On("ContainerLogs", mock.Anything, id, mock.Anything).Return(reader, nil)
 | 
			
		||||
 | 
			
		||||
	client := &dockerClient{proxy}
 | 
			
		||||
	messages, _ := client.ContainerLogs(context.Background(), id)
 | 
			
		||||
 | 
			
		||||
	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_error(t *testing.T) {
 | 
			
		||||
	id := "123456"
 | 
			
		||||
	proxy := new(mockedProxy)
 | 
			
		||||
	proxy.On("ContainerLogs", mock.Anything, id, mock.Anything).Return(nil, errors.New("test"))
 | 
			
		||||
 | 
			
		||||
	client := &dockerClient{proxy}
 | 
			
		||||
	messages, err := client.ContainerLogs(context.Background(), id)
 | 
			
		||||
 | 
			
		||||
	assert.Nil(t, messages, "messages should be nil")
 | 
			
		||||
 | 
			
		||||
	e, _ := <-err
 | 
			
		||||
	assert.Error(t, e, "error should have been returned")
 | 
			
		||||
	_, ok := <-err
 | 
			
		||||
	assert.False(t, ok, "error channel should have been closed")
 | 
			
		||||
	proxy.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,13 +1,14 @@
 | 
			
		||||
package docker
 | 
			
		||||
 | 
			
		||||
// Container represents an internal representation of docker containers
 | 
			
		||||
type Container struct {
 | 
			
		||||
    ID      string   `json:"id"`
 | 
			
		||||
    Names   []string `json:"names"`
 | 
			
		||||
    Name    string   `json:"name"`
 | 
			
		||||
    Image   string   `json:"image"`
 | 
			
		||||
    ImageID string   `json:"imageId"`
 | 
			
		||||
    Command string   `json:"command"`
 | 
			
		||||
    Created int64    `json:"created"`
 | 
			
		||||
    State   string   `json:"state"`
 | 
			
		||||
    Status  string   `json:"status"`
 | 
			
		||||
	ID      string   `json:"id"`
 | 
			
		||||
	Names   []string `json:"names"`
 | 
			
		||||
	Name    string   `json:"name"`
 | 
			
		||||
	Image   string   `json:"image"`
 | 
			
		||||
	ImageID string   `json:"imageId"`
 | 
			
		||||
	Command string   `json:"command"`
 | 
			
		||||
	Created int64    `json:"created"`
 | 
			
		||||
	State   string   `json:"state"`
 | 
			
		||||
	Status  string   `json:"status"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										38
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
module github.com/amir20/dozzle
 | 
			
		||||
 | 
			
		||||
replace github.com/docker/docker v0.0.0-20170601211448-f5ec1e2936dc => github.com/docker/engine v0.0.0-20180718150940-a3ef7e9a9bda
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
 | 
			
		||||
	github.com/Microsoft/go-winio v0.4.11 // indirect
 | 
			
		||||
	github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
 | 
			
		||||
	github.com/beme/abide v0.0.0-20180929160954-122a1e1cddf6
 | 
			
		||||
	github.com/docker/distribution v2.7.0+incompatible // indirect
 | 
			
		||||
	github.com/docker/docker v1.13.1
 | 
			
		||||
	github.com/docker/go-connections v0.4.0 // indirect
 | 
			
		||||
	github.com/docker/go-units v0.3.3 // indirect
 | 
			
		||||
	github.com/gobuffalo/packr v1.21.8
 | 
			
		||||
	github.com/gogo/protobuf v1.1.1 // indirect
 | 
			
		||||
	github.com/google/go-cmp v0.2.0 // indirect
 | 
			
		||||
	github.com/gorilla/context v1.1.1 // indirect
 | 
			
		||||
	github.com/gorilla/mux v1.6.2
 | 
			
		||||
	github.com/magiconair/properties v1.8.0
 | 
			
		||||
	github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
 | 
			
		||||
	github.com/opencontainers/image-spec v1.0.1 // indirect
 | 
			
		||||
	github.com/sergi/go-diff v1.0.0 // indirect
 | 
			
		||||
	github.com/sirupsen/logrus v1.2.0
 | 
			
		||||
	github.com/spf13/pflag v1.0.3
 | 
			
		||||
	github.com/stretchr/testify v1.2.2
 | 
			
		||||
	golang.org/x/time v0.0.0-20181108054448-85acf8d2951c // indirect
 | 
			
		||||
	google.golang.org/appengine v1.2.0 // indirect
 | 
			
		||||
	google.golang.org/grpc v1.17.0 // indirect
 | 
			
		||||
	gotest.tools v2.2.0+incompatible // indirect
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 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/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
 | 
			
		||||
							
								
								
									
										394
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										394
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,394 @@
 | 
			
		||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 | 
			
		||||
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/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 | 
			
		||||
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
 | 
			
		||||
github.com/Microsoft/go-winio v0.4.11/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/ajg/form v0.0.0-20160822230020-523a5da1a92f/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
 | 
			
		||||
github.com/beme/abide v0.0.0-20180929160954-122a1e1cddf6 h1:rcGcvdWoCgSPJCqpEXMMpSksG807sIHfWHyt36scFzQ=
 | 
			
		||||
github.com/beme/abide v0.0.0-20180929160954-122a1e1cddf6/go.mod h1:6+8gCKsZnxzhGTmKRh4BSkLos9CbWRJNcrp55We4SqQ=
 | 
			
		||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 | 
			
		||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
 | 
			
		||||
github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk=
 | 
			
		||||
github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0=
 | 
			
		||||
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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 | 
			
		||||
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/distribution v2.7.0+incompatible h1:neUDAlf3wX6Ml4HdqTrbcOHXtfRN0TFIwt6YFL7N9RU=
 | 
			
		||||
github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
 | 
			
		||||
github.com/docker/docker v1.13.1 h1:5VBhsO6ckUxB0A8CE5LlUJdXzik9cbEbBTQ/ggeml7M=
 | 
			
		||||
github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 | 
			
		||||
github.com/docker/engine v0.0.0-20180816081446-320063a2ad06 h1:7dNp3Uy9WPejW7wy26j3UCz9VP0Hk46uH97DTU0+iX0=
 | 
			
		||||
github.com/docker/engine v0.0.0-20180816081446-320063a2ad06/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-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
 | 
			
		||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 | 
			
		||||
github.com/dustin/go-humanize v0.0.0-20180713052910-9f541cc9db5d/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 | 
			
		||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 | 
			
		||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 | 
			
		||||
github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
 | 
			
		||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 | 
			
		||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 | 
			
		||||
github.com/gobuffalo/buffalo v0.12.8-0.20181004233540-fac9bb505aa8/go.mod h1:sLyT7/dceRXJUxSsE813JTQtA3Eb1vjxWfo/N//vXIY=
 | 
			
		||||
github.com/gobuffalo/buffalo v0.13.0/go.mod h1:Mjn1Ba9wpIbpbrD+lIDMy99pQ0H0LiddMIIDGse7qT4=
 | 
			
		||||
github.com/gobuffalo/buffalo-plugins v1.0.2/go.mod h1:pOp/uF7X3IShFHyobahTkTLZaeUXwb0GrUTb9ngJWTs=
 | 
			
		||||
github.com/gobuffalo/buffalo-plugins v1.0.4/go.mod h1:pWS1vjtQ6uD17MVFWf7i3zfThrEKWlI5+PYLw/NaDB4=
 | 
			
		||||
github.com/gobuffalo/buffalo-plugins v1.4.3/go.mod h1:uCzTY0woez4nDMdQjkcOYKanngeUVRO2HZi7ezmAjWY=
 | 
			
		||||
github.com/gobuffalo/buffalo-plugins v1.5.1/go.mod h1:jbmwSZK5+PiAP9cC09VQOrGMZFCa/P0UMlIS3O12r5w=
 | 
			
		||||
github.com/gobuffalo/buffalo-plugins v1.6.4/go.mod h1:/+N1aophkA2jZ1ifB2O3Y9yGwu6gKOVMtUmJnbg+OZI=
 | 
			
		||||
github.com/gobuffalo/buffalo-plugins v1.6.5/go.mod h1:0HVkbgrVs/MnPZ/FOseDMVanCTm2RNcdM0PuXcL1NNI=
 | 
			
		||||
github.com/gobuffalo/buffalo-plugins v1.6.7/go.mod h1:ZGZRkzz2PiKWHs0z7QsPBOTo2EpcGRArMEym6ghKYgk=
 | 
			
		||||
github.com/gobuffalo/buffalo-plugins v1.6.9/go.mod h1:yYlYTrPdMCz+6/+UaXg5Jm4gN3xhsvsQ2ygVatZV5vw=
 | 
			
		||||
github.com/gobuffalo/buffalo-plugins v1.6.11/go.mod h1:eAA6xJIL8OuynJZ8amXjRmHND6YiusVAaJdHDN1Lu8Q=
 | 
			
		||||
github.com/gobuffalo/buffalo-plugins v1.8.2/go.mod h1:9te6/VjEQ7pKp7lXlDIMqzxgGpjlKoAcAANdCgoR960=
 | 
			
		||||
github.com/gobuffalo/buffalo-pop v1.0.5/go.mod h1:Fw/LfFDnSmB/vvQXPvcXEjzP98Tc+AudyNWUBWKCwQ8=
 | 
			
		||||
github.com/gobuffalo/envy v1.6.4/go.mod h1:Abh+Jfw475/NWtYMEt+hnJWRiC8INKWibIMyNt1w2Mc=
 | 
			
		||||
github.com/gobuffalo/envy v1.6.5/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ=
 | 
			
		||||
github.com/gobuffalo/envy v1.6.6/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ=
 | 
			
		||||
github.com/gobuffalo/envy v1.6.7/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ=
 | 
			
		||||
github.com/gobuffalo/envy v1.6.8/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ=
 | 
			
		||||
github.com/gobuffalo/envy v1.6.9/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ=
 | 
			
		||||
github.com/gobuffalo/envy v1.6.10/go.mod h1:X0CFllQjTV5ogsnUrg+Oks2yTI+PU2dGYBJOEI2D1Uo=
 | 
			
		||||
github.com/gobuffalo/envy v1.6.11 h1:dCKSFypLRvqaaUtyzkfKKF2j35ce5agsqfyIrRmm02E=
 | 
			
		||||
github.com/gobuffalo/envy v1.6.11/go.mod h1:Fiq52W7nrHGDggFPhn2ZCcHw4u/rqXkqo+i7FB6EAcg=
 | 
			
		||||
github.com/gobuffalo/events v1.0.3/go.mod h1:Txo8WmqScapa7zimEQIwgiJBvMECMe9gJjsKNPN3uZw=
 | 
			
		||||
github.com/gobuffalo/events v1.0.7/go.mod h1:z8txf6H9jWhQ5Scr7YPLWg/cgXBRj8Q4uYI+rsVCCSQ=
 | 
			
		||||
github.com/gobuffalo/events v1.0.8/go.mod h1:A5KyqT1sA+3GJiBE4QKZibse9mtOcI9nw8gGrDdqYGs=
 | 
			
		||||
github.com/gobuffalo/events v1.1.3/go.mod h1:9yPGWYv11GENtzrIRApwQRMYSbUgCsZ1w6R503fCfrk=
 | 
			
		||||
github.com/gobuffalo/events v1.1.4/go.mod h1:09/YRRgZHEOts5Isov+g9X2xajxdvOAcUuAHIX/O//A=
 | 
			
		||||
github.com/gobuffalo/events v1.1.5/go.mod h1:3YUSzgHfYctSjEjLCWbkXP6djH2M+MLaVRzb4ymbAK0=
 | 
			
		||||
github.com/gobuffalo/events v1.1.7/go.mod h1:6fGqxH2ing5XMb3EYRq9LEkVlyPGs4oO/eLzh+S8CxY=
 | 
			
		||||
github.com/gobuffalo/events v1.1.8/go.mod h1:UFy+W6X6VbCWS8k2iT81HYX65dMtiuVycMy04cplt/8=
 | 
			
		||||
github.com/gobuffalo/fizz v1.0.12/go.mod h1:C0sltPxpYK8Ftvf64kbsQa2yiCZY4RZviurNxXdAKwc=
 | 
			
		||||
github.com/gobuffalo/flect v0.0.0-20180907193754-dc14d8acaf9f/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA=
 | 
			
		||||
github.com/gobuffalo/flect v0.0.0-20181002182613-4571df4b1daf/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA=
 | 
			
		||||
github.com/gobuffalo/flect v0.0.0-20181007231023-ae7ed6bfe683/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA=
 | 
			
		||||
github.com/gobuffalo/flect v0.0.0-20181018182602-fd24a256709f/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA=
 | 
			
		||||
github.com/gobuffalo/flect v0.0.0-20181019110701-3d6f0b585514/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA=
 | 
			
		||||
github.com/gobuffalo/flect v0.0.0-20181024204909-8f6be1a8c6c2/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA=
 | 
			
		||||
github.com/gobuffalo/flect v0.0.0-20181104133451-1f6e9779237a/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA=
 | 
			
		||||
github.com/gobuffalo/flect v0.0.0-20181114183036-47375f6d8328/go.mod h1:0HvNbHdfh+WOvDSIASqJOSxTOWSxCCUF++k/Y53v9rI=
 | 
			
		||||
github.com/gobuffalo/genny v0.0.0-20180924032338-7af3a40f2252/go.mod h1:tUTQOogrr7tAQnhajMSH6rv1BVev34H2sa1xNHMy94g=
 | 
			
		||||
github.com/gobuffalo/genny v0.0.0-20181003150629-3786a0744c5d/go.mod h1:WAd8HmjMVrnkAZbmfgH5dLBUchsZfqzp/WS5sQz+uTM=
 | 
			
		||||
github.com/gobuffalo/genny v0.0.0-20181005145118-318a41a134cc/go.mod h1:WAd8HmjMVrnkAZbmfgH5dLBUchsZfqzp/WS5sQz+uTM=
 | 
			
		||||
github.com/gobuffalo/genny v0.0.0-20181007153042-b8de7d566757/go.mod h1:+oG5Ljrw04czAHbPXREwaFojJbpUvcIy4DiOnbEJFTA=
 | 
			
		||||
github.com/gobuffalo/genny v0.0.0-20181012161047-33e5f43d83a6/go.mod h1:+oG5Ljrw04czAHbPXREwaFojJbpUvcIy4DiOnbEJFTA=
 | 
			
		||||
github.com/gobuffalo/genny v0.0.0-20181017160347-90a774534246/go.mod h1:+oG5Ljrw04czAHbPXREwaFojJbpUvcIy4DiOnbEJFTA=
 | 
			
		||||
github.com/gobuffalo/genny v0.0.0-20181024195656-51392254bf53/go.mod h1:o9GEH5gn5sCKLVB5rHFC4tq40rQ3VRUzmx6WwmaqISE=
 | 
			
		||||
github.com/gobuffalo/genny v0.0.0-20181025145300-af3f81d526b8/go.mod h1:uZ1fFYvdcP8mu0B/Ynarf6dsGvp7QFIpk/QACUuFUVI=
 | 
			
		||||
github.com/gobuffalo/genny v0.0.0-20181027191429-94d6cfb5c7fc/go.mod h1:x7SkrQQBx204Y+O9EwRXeszLJDTaWN0GnEasxgLrQTA=
 | 
			
		||||
github.com/gobuffalo/genny v0.0.0-20181027195209-3887b7171c4f/go.mod h1:JbKx8HSWICu5zyqWOa0dVV1pbbXOHusrSzQUprW6g+w=
 | 
			
		||||
github.com/gobuffalo/genny v0.0.0-20181106193839-7dcb0924caf1/go.mod h1:x61yHxvbDCgQ/7cOAbJCacZQuHgB0KMSzoYcw5debjU=
 | 
			
		||||
github.com/gobuffalo/genny v0.0.0-20181107223128-f18346459dbe/go.mod h1:utQD3aKKEsdb03oR+Vi/6ztQb1j7pO10N3OBoowRcSU=
 | 
			
		||||
github.com/gobuffalo/genny v0.0.0-20181114215459-0a4decd77f5d/go.mod h1:kN2KZ8VgXF9VIIOj/GM0Eo7YK+un4Q3tTreKOf0q1ng=
 | 
			
		||||
github.com/gobuffalo/genny v0.0.0-20181119162812-e8ff4adce8bb/go.mod h1:BA9htSe4bZwBDJLe8CUkoqkypq3hn3+CkoHqVOW718E=
 | 
			
		||||
github.com/gobuffalo/genny v0.0.0-20181127225641-2d959acc795b/go.mod h1:l54xLXNkteX/PdZ+HlgPk1qtcrgeOr3XUBBPDbH+7CQ=
 | 
			
		||||
github.com/gobuffalo/genny v0.0.0-20181128191930-77e34f71ba2a/go.mod h1:FW/D9p7cEEOqxYA71/hnrkOWm62JZ5ZNxcNIVJEaWBU=
 | 
			
		||||
github.com/gobuffalo/genny v0.0.0-20181203165245-fda8bcce96b1/go.mod h1:wpNSANu9UErftfiaAlz1pDZclrYzLtO5lALifODyjuM=
 | 
			
		||||
github.com/gobuffalo/genny v0.0.0-20181203201232-849d2c9534ea/go.mod h1:wpNSANu9UErftfiaAlz1pDZclrYzLtO5lALifODyjuM=
 | 
			
		||||
github.com/gobuffalo/genny v0.0.0-20181206121324-d6fb8a0dbe36/go.mod h1:wpNSANu9UErftfiaAlz1pDZclrYzLtO5lALifODyjuM=
 | 
			
		||||
github.com/gobuffalo/genny v0.0.0-20181207164119-84844398a37d/go.mod h1:y0ysCHGGQf2T3vOhCrGHheYN54Y/REj0ayd0Suf4C/8=
 | 
			
		||||
github.com/gobuffalo/github_flavored_markdown v1.0.4/go.mod h1:uRowCdK+q8d/RF0Kt3/DSalaIXbb0De/dmTqMQdkQ4I=
 | 
			
		||||
github.com/gobuffalo/github_flavored_markdown v1.0.5/go.mod h1:U0643QShPF+OF2tJvYNiYDLDGDuQmJZXsf/bHOJPsMY=
 | 
			
		||||
github.com/gobuffalo/github_flavored_markdown v1.0.7/go.mod h1:w93Pd9Lz6LvyQXEG6DktTPHkOtCbr+arAD5mkwMzXLI=
 | 
			
		||||
github.com/gobuffalo/httptest v1.0.2/go.mod h1:7T1IbSrg60ankme0aDLVnEY0h056g9M1/ZvpVThtB7E=
 | 
			
		||||
github.com/gobuffalo/licenser v0.0.0-20180924033006-eae28e638a42/go.mod h1:Ubo90Np8gpsSZqNScZZkVXXAo5DGhTb+WYFIjlnog8w=
 | 
			
		||||
github.com/gobuffalo/licenser v0.0.0-20181025145548-437d89de4f75/go.mod h1:x3lEpYxkRG/XtGCUNkio+6RZ/dlOvLzTI9M1auIwFcw=
 | 
			
		||||
github.com/gobuffalo/licenser v0.0.0-20181027200154-58051a75da95/go.mod h1:BzhaaxGd1tq1+OLKObzgdCV9kqVhbTulxOpYbvMQWS0=
 | 
			
		||||
github.com/gobuffalo/licenser v0.0.0-20181109171355-91a2a7aac9a7/go.mod h1:m+Ygox92pi9bdg+gVaycvqE8RVSjZp7mWw75+K5NPHk=
 | 
			
		||||
github.com/gobuffalo/licenser v0.0.0-20181128165715-cc7305f8abed/go.mod h1:oU9F9UCE+AzI/MueCKZamsezGOOHfSirltllOVeRTAE=
 | 
			
		||||
github.com/gobuffalo/licenser v0.0.0-20181203160806-fe900bbede07/go.mod h1:ph6VDNvOzt1CdfaWC+9XwcBnlSTBz2j49PBwum6RFaU=
 | 
			
		||||
github.com/gobuffalo/logger v0.0.0-20181022175615-46cfb361fc27/go.mod h1:8sQkgyhWipz1mIctHF4jTxmJh1Vxhp7mP8IqbljgJZo=
 | 
			
		||||
github.com/gobuffalo/logger v0.0.0-20181027144941-73d08d2bb969/go.mod h1:7uGg2duHKpWnN4+YmyKBdLXfhopkAdVM6H3nKbyFbz8=
 | 
			
		||||
github.com/gobuffalo/logger v0.0.0-20181027193913-9cf4dd0efe46/go.mod h1:7uGg2duHKpWnN4+YmyKBdLXfhopkAdVM6H3nKbyFbz8=
 | 
			
		||||
github.com/gobuffalo/logger v0.0.0-20181109185836-3feeab578c17/go.mod h1:oNErH0xLe+utO+OW8ptXMSA5DkiSEDW1u3zGIt8F9Ew=
 | 
			
		||||
github.com/gobuffalo/logger v0.0.0-20181117211126-8e9b89b7c264/go.mod h1:5etB91IE0uBlw9k756fVKZJdS+7M7ejVhmpXXiSFj0I=
 | 
			
		||||
github.com/gobuffalo/logger v0.0.0-20181127160119-5b956e21995c/go.mod h1:+HxKANrR9VGw9yN3aOAppJKvhO05ctDi63w4mDnKv2U=
 | 
			
		||||
github.com/gobuffalo/makr v1.1.5/go.mod h1:Y+o0btAH1kYAMDJW/TX3+oAXEu0bmSLLoC9mIFxtzOw=
 | 
			
		||||
github.com/gobuffalo/mapi v1.0.0/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
 | 
			
		||||
github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
 | 
			
		||||
github.com/gobuffalo/meta v0.0.0-20181018155829-df62557efcd3/go.mod h1:XTTOhwMNryif3x9LkTTBO/Llrveezd71u3quLd0u7CM=
 | 
			
		||||
github.com/gobuffalo/meta v0.0.0-20181018192820-8c6cef77dab3/go.mod h1:E94EPzx9NERGCY69UWlcj6Hipf2uK/vnfrF4QD0plVE=
 | 
			
		||||
github.com/gobuffalo/meta v0.0.0-20181025145500-3a985a084b0a/go.mod h1:YDAKBud2FP7NZdruCSlmTmDOZbVSa6bpK7LJ/A/nlKg=
 | 
			
		||||
github.com/gobuffalo/meta v0.0.0-20181114191255-b130ebedd2f7/go.mod h1:K6cRZ29ozr4Btvsqkjvg5nDFTLOgTqf03KA70Ks0ypE=
 | 
			
		||||
github.com/gobuffalo/meta v0.0.0-20181127070345-0d7e59dd540b/go.mod h1:RLO7tMvE0IAKAM8wny1aN12pvEKn7EtkBLkUZR00Qf8=
 | 
			
		||||
github.com/gobuffalo/mw-basicauth v1.0.3/go.mod h1:dg7+ilMZOKnQFHDefUzUHufNyTswVUviCBgF244C1+0=
 | 
			
		||||
github.com/gobuffalo/mw-contenttype v0.0.0-20180802152300-74f5a47f4d56/go.mod h1:7EvcmzBbeCvFtQm5GqF9ys6QnCxz2UM1x0moiWLq1No=
 | 
			
		||||
github.com/gobuffalo/mw-csrf v0.0.0-20180802151833-446ff26e108b/go.mod h1:sbGtb8DmDZuDUQoxjr8hG1ZbLtZboD9xsn6p77ppcHo=
 | 
			
		||||
github.com/gobuffalo/mw-forcessl v0.0.0-20180802152810-73921ae7a130/go.mod h1:JvNHRj7bYNAMUr/5XMkZaDcw3jZhUZpsmzhd//FFWmQ=
 | 
			
		||||
github.com/gobuffalo/mw-i18n v0.0.0-20180802152014-e3060b7e13d6/go.mod h1:91AQfukc52A6hdfIfkxzyr+kpVYDodgAeT5cjX1UIj4=
 | 
			
		||||
github.com/gobuffalo/mw-paramlogger v0.0.0-20181005191442-d6ee392ec72e/go.mod h1:6OJr6VwSzgJMqWMj7TYmRUqzNe2LXu/W1rRW4MAz/ME=
 | 
			
		||||
github.com/gobuffalo/mw-tokenauth v0.0.0-20181001105134-8545f626c189/go.mod h1:UqBF00IfKvd39ni5+yI5MLMjAf4gX7cDKN/26zDOD6c=
 | 
			
		||||
github.com/gobuffalo/packd v0.0.0-20181027182251-01ad393492c8/go.mod h1:SmdBdhj6uhOsg1Ui4SFAyrhuc7U4VCildosO5IDJ3lc=
 | 
			
		||||
github.com/gobuffalo/packd v0.0.0-20181027190505-aafc0d02c411/go.mod h1:SmdBdhj6uhOsg1Ui4SFAyrhuc7U4VCildosO5IDJ3lc=
 | 
			
		||||
github.com/gobuffalo/packd v0.0.0-20181027194105-7ae579e6d213/go.mod h1:SmdBdhj6uhOsg1Ui4SFAyrhuc7U4VCildosO5IDJ3lc=
 | 
			
		||||
github.com/gobuffalo/packd v0.0.0-20181031195726-c82734870264/go.mod h1:Yf2toFaISlyQrr5TfO3h6DB9pl9mZRmyvBGQb/aQ/pI=
 | 
			
		||||
github.com/gobuffalo/packd v0.0.0-20181104210303-d376b15f8e96/go.mod h1:Yf2toFaISlyQrr5TfO3h6DB9pl9mZRmyvBGQb/aQ/pI=
 | 
			
		||||
github.com/gobuffalo/packd v0.0.0-20181111195323-b2e760a5f0ff/go.mod h1:Yf2toFaISlyQrr5TfO3h6DB9pl9mZRmyvBGQb/aQ/pI=
 | 
			
		||||
github.com/gobuffalo/packd v0.0.0-20181114190715-f25c5d2471d7/go.mod h1:Yf2toFaISlyQrr5TfO3h6DB9pl9mZRmyvBGQb/aQ/pI=
 | 
			
		||||
github.com/gobuffalo/packd v0.0.0-20181124090624-311c6248e5fb/go.mod h1:Foenia9ZvITEvG05ab6XpiD5EfBHPL8A6hush8SJ0o8=
 | 
			
		||||
github.com/gobuffalo/packd v0.0.0-20181207120301-c49825f8f6f4 h1:bxxuyBF+Wl5me1hFJoSaXlvvtTXhIBhbKK0J7ZbcG8Y=
 | 
			
		||||
github.com/gobuffalo/packd v0.0.0-20181207120301-c49825f8f6f4/go.mod h1:LYc0TGKFBBFTRC9dg2pcRcMqGCTMD7T2BIMP7OBuQAA=
 | 
			
		||||
github.com/gobuffalo/packr v1.13.7/go.mod h1:KkinLIn/n6+3tVXMwg6KkNvWwVsrRAz4ph+jgpk3Z24=
 | 
			
		||||
github.com/gobuffalo/packr v1.15.0/go.mod h1:t5gXzEhIviQwVlNx/+3SfS07GS+cZ2hn76WLzPp6MGI=
 | 
			
		||||
github.com/gobuffalo/packr v1.15.1/go.mod h1:IeqicJ7jm8182yrVmNbM6PR4g79SjN9tZLH8KduZZwE=
 | 
			
		||||
github.com/gobuffalo/packr v1.19.0/go.mod h1:MstrNkfCQhd5o+Ct4IJ0skWlxN8emOq8DsoT1G98VIU=
 | 
			
		||||
github.com/gobuffalo/packr v1.20.0/go.mod h1:JDytk1t2gP+my1ig7iI4NcVaXr886+N0ecUga6884zw=
 | 
			
		||||
github.com/gobuffalo/packr v1.21.0/go.mod h1:H00jGfj1qFKxscFJSw8wcL4hpQtPe1PfU2wa6sg/SR0=
 | 
			
		||||
github.com/gobuffalo/packr v1.21.8 h1:qvCD8cQzb0MEDTM1xI6l+9R+HNzQ3hNcllUQdCXzcC0=
 | 
			
		||||
github.com/gobuffalo/packr v1.21.8/go.mod h1:aRZXyERYmMgohDp5wDWnbgn5KiWuCKG19WnWZcAqeII=
 | 
			
		||||
github.com/gobuffalo/packr/v2 v2.0.0-rc.8/go.mod h1:y60QCdzwuMwO2R49fdQhsjCPv7tLQFR0ayzxxla9zes=
 | 
			
		||||
github.com/gobuffalo/packr/v2 v2.0.0-rc.10/go.mod h1:4CWWn4I5T3v4c1OsJ55HbHlUEKNWMITG5iIkdr4Px4w=
 | 
			
		||||
github.com/gobuffalo/packr/v2 v2.0.0-rc.11/go.mod h1:JoieH/3h3U4UmatmV93QmqyPUdf4wVM9HELaHEu+3fk=
 | 
			
		||||
github.com/gobuffalo/packr/v2 v2.0.0-rc.12/go.mod h1:FV1zZTsVFi1DSCboO36Xgs4pzCZBjB/tDV9Cz/lSaR8=
 | 
			
		||||
github.com/gobuffalo/plush v3.7.16+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI=
 | 
			
		||||
github.com/gobuffalo/plush v3.7.20+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI=
 | 
			
		||||
github.com/gobuffalo/plush v3.7.21+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI=
 | 
			
		||||
github.com/gobuffalo/plush v3.7.22+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI=
 | 
			
		||||
github.com/gobuffalo/plush v3.7.23+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI=
 | 
			
		||||
github.com/gobuffalo/plush v3.7.30+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI=
 | 
			
		||||
github.com/gobuffalo/plush v3.7.32+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI=
 | 
			
		||||
github.com/gobuffalo/plushgen v0.0.0-20181128164830-d29dcb966cb2/go.mod h1:r9QwptTFnuvSaSRjpSp4S2/4e2D3tJhARYbvEBcKSb4=
 | 
			
		||||
github.com/gobuffalo/plushgen v0.0.0-20181203163832-9fc4964505c2/go.mod h1:opEdT33AA2HdrIwK1aibqnTJDVVKXC02Bar/GT1YRVs=
 | 
			
		||||
github.com/gobuffalo/plushgen v0.0.0-20181207152837-eedb135bd51b/go.mod h1:Lcw7HQbEVm09sAQrCLzIxuhFbB3nAgp4c55E+UlynR0=
 | 
			
		||||
github.com/gobuffalo/pop v4.8.2+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg=
 | 
			
		||||
github.com/gobuffalo/pop v4.8.3+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg=
 | 
			
		||||
github.com/gobuffalo/pop v4.8.4+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg=
 | 
			
		||||
github.com/gobuffalo/release v1.0.35/go.mod h1:VtHFAKs61vO3wboCec5xr9JPTjYyWYcvaM3lclkc4x4=
 | 
			
		||||
github.com/gobuffalo/release v1.0.38/go.mod h1:VtHFAKs61vO3wboCec5xr9JPTjYyWYcvaM3lclkc4x4=
 | 
			
		||||
github.com/gobuffalo/release v1.0.42/go.mod h1:RPs7EtafH4oylgetOJpGP0yCZZUiO4vqHfTHJjSdpug=
 | 
			
		||||
github.com/gobuffalo/release v1.0.52/go.mod h1:RPs7EtafH4oylgetOJpGP0yCZZUiO4vqHfTHJjSdpug=
 | 
			
		||||
github.com/gobuffalo/release v1.0.53/go.mod h1:FdF257nd8rqhNaqtDWFGhxdJ/Ig4J7VcS3KL7n/a+aA=
 | 
			
		||||
github.com/gobuffalo/release v1.0.54/go.mod h1:Pe5/RxRa/BE8whDpGfRqSI7D1a0evGK1T4JDm339tJc=
 | 
			
		||||
github.com/gobuffalo/release v1.0.61/go.mod h1:mfIO38ujUNVDlBziIYqXquYfBF+8FDHUjKZgYC1Hj24=
 | 
			
		||||
github.com/gobuffalo/release v1.0.72/go.mod h1:NP5NXgg/IX3M5XmHmWR99D687/3Dt9qZtTK/Lbwc1hU=
 | 
			
		||||
github.com/gobuffalo/release v1.1.1/go.mod h1:Sluak1Xd6kcp6snkluR1jeXAogdJZpFFRzTYRs/2uwg=
 | 
			
		||||
github.com/gobuffalo/shoulders v1.0.1/go.mod h1:V33CcVmaQ4gRUmHKwq1fiTXuf8Gp/qjQBUL5tHPmvbA=
 | 
			
		||||
github.com/gobuffalo/syncx v0.0.0-20181120191700-98333ab04150/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
 | 
			
		||||
github.com/gobuffalo/syncx v0.0.0-20181120194010-558ac7de985f h1:S5EeH1reN93KR0L6TQvkRpu9YggCYXrUqFh1iEgvdC0=
 | 
			
		||||
github.com/gobuffalo/syncx v0.0.0-20181120194010-558ac7de985f/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
 | 
			
		||||
github.com/gobuffalo/tags v2.0.11+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY=
 | 
			
		||||
github.com/gobuffalo/tags v2.0.14+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY=
 | 
			
		||||
github.com/gobuffalo/uuid v2.0.3+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw4cVi1RtSpnwYrxuvkfE=
 | 
			
		||||
github.com/gobuffalo/uuid v2.0.4+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw4cVi1RtSpnwYrxuvkfE=
 | 
			
		||||
github.com/gobuffalo/uuid v2.0.5+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw4cVi1RtSpnwYrxuvkfE=
 | 
			
		||||
github.com/gobuffalo/validate v2.0.3+incompatible/go.mod h1:N+EtDe0J8252BgfzQUChBgfd6L93m9weay53EWFVsMM=
 | 
			
		||||
github.com/gobuffalo/x v0.0.0-20181003152136-452098b06085/go.mod h1:WevpGD+5YOreDJznWevcn8NTmQEW5STSBgIkpkjzqXc=
 | 
			
		||||
github.com/gobuffalo/x v0.0.0-20181007152206-913e47c59ca7/go.mod h1:9rDPXaB3kXdKWzMc4odGQQdG2e2DIEmANy5aSJ9yesY=
 | 
			
		||||
github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 | 
			
		||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 | 
			
		||||
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/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 | 
			
		||||
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 | 
			
		||||
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/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
 | 
			
		||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 | 
			
		||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
 | 
			
		||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
 | 
			
		||||
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
 | 
			
		||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 | 
			
		||||
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY=
 | 
			
		||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
 | 
			
		||||
github.com/gorilla/sessions v1.1.2/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
 | 
			
		||||
github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
 | 
			
		||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 | 
			
		||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 | 
			
		||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 | 
			
		||||
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
 | 
			
		||||
github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
 | 
			
		||||
github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU=
 | 
			
		||||
github.com/joho/godotenv v1.2.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
 | 
			
		||||
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/karrick/godirwalk v1.7.5/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34=
 | 
			
		||||
github.com/karrick/godirwalk v1.7.7/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34=
 | 
			
		||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
 | 
			
		||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 | 
			
		||||
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 | 
			
		||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
 | 
			
		||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 | 
			
		||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 | 
			
		||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 | 
			
		||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 | 
			
		||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 | 
			
		||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 | 
			
		||||
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
 | 
			
		||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 | 
			
		||||
github.com/markbates/deplist v1.0.4/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM=
 | 
			
		||||
github.com/markbates/deplist v1.0.5/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM=
 | 
			
		||||
github.com/markbates/going v1.0.2/go.mod h1:UWCk3zm0UKefHZ7l8BNqi26UyiEMniznk8naLdTcy6c=
 | 
			
		||||
github.com/markbates/grift v1.0.4/go.mod h1:wbmtW74veyx+cgfwFhlnnMWqhoz55rnHR47oMXzsyVs=
 | 
			
		||||
github.com/markbates/hmax v1.0.0/go.mod h1:cOkR9dktiESxIMu+65oc/r/bdY4bE8zZw3OLhLx0X2c=
 | 
			
		||||
github.com/markbates/inflect v1.0.0/go.mod h1:oTeZL2KHA7CUX6X+fovmK9OvIOFuqu0TwdQrZjLTh88=
 | 
			
		||||
github.com/markbates/inflect v1.0.1/go.mod h1:uv3UVNBe5qBIfCm8O8Q+DW+S1EopeyINj+Ikhc7rnCk=
 | 
			
		||||
github.com/markbates/inflect v1.0.3/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs=
 | 
			
		||||
github.com/markbates/inflect v1.0.4/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs=
 | 
			
		||||
github.com/markbates/oncer v0.0.0-20180924031910-e862a676800b/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
 | 
			
		||||
github.com/markbates/oncer v0.0.0-20180924034138-723ad0170a46/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
 | 
			
		||||
github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
 | 
			
		||||
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2 h1:JgVTCPf0uBVcUSWpyXmGpgOc62nK5HWUBKAGc3Qqa5k=
 | 
			
		||||
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
 | 
			
		||||
github.com/markbates/refresh v1.4.10/go.mod h1:NDPHvotuZmTmesXxr95C9bjlw1/0frJwtME2dzcVKhc=
 | 
			
		||||
github.com/markbates/safe v1.0.0/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
 | 
			
		||||
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
 | 
			
		||||
github.com/markbates/sigtx v1.0.0/go.mod h1:QF1Hv6Ic6Ca6W+T+DL0Y/ypborFKyvUY9HmuCD4VeTc=
 | 
			
		||||
github.com/markbates/willie v1.0.9/go.mod h1:fsrFVWl91+gXpx/6dv715j7i11fYPfZ9ZGfH0DQzY7w=
 | 
			
		||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 | 
			
		||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 | 
			
		||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
 | 
			
		||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 | 
			
		||||
github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 | 
			
		||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 | 
			
		||||
github.com/monoculum/formam v0.0.0-20180901015400-4e68be1d79ba/go.mod h1:RKgILGEJq24YyJ2ban8EO0RUVSJlF1pGsEvoLEACr/Q=
 | 
			
		||||
github.com/nicksnyder/go-i18n v1.10.0/go.mod h1:HrK7VCrbOvQoUAQ7Vpy7i87N7JZZZ7R2xBGjv0j365Q=
 | 
			
		||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 | 
			
		||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 | 
			
		||||
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 | 
			
		||||
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 | 
			
		||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 | 
			
		||||
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.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
 | 
			
		||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 | 
			
		||||
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.0.0 h1:o4VLZ5jqHE+HahLT6drNtSGTrrUA3wPBmtpgqtdbClo=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.0.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 | 
			
		||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 | 
			
		||||
github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs=
 | 
			
		||||
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/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
 | 
			
		||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
 | 
			
		||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
 | 
			
		||||
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
 | 
			
		||||
github.com/shurcooL/highlight_go v0.0.0-20170515013102-78fb10f4a5f8/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
 | 
			
		||||
github.com/shurcooL/octicon v0.0.0-20180602230221-c42b0e3b24d9/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
 | 
			
		||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 | 
			
		||||
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
 | 
			
		||||
github.com/sirupsen/logrus v1.1.0/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A=
 | 
			
		||||
github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A=
 | 
			
		||||
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
 | 
			
		||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 | 
			
		||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
 | 
			
		||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
 | 
			
		||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 | 
			
		||||
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
 | 
			
		||||
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/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 | 
			
		||||
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 | 
			
		||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
 | 
			
		||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 | 
			
		||||
github.com/spf13/viper v1.2.1/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI=
 | 
			
		||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
 | 
			
		||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
 | 
			
		||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 | 
			
		||||
github.com/unrolled/secure v0.0.0-20180918153822-f340ee86eb8b/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA=
 | 
			
		||||
github.com/unrolled/secure v0.0.0-20181005190816-ff9db2ff917f/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20181024171144-74cb1d3d52f4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20181025113841-85e1b3f9139a/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20181106171534-e4dc69e5b2fd/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 | 
			
		||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20180816102801-aaf60122140d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20180921000356-2f5d2388922f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20181017193950-04a2e542c03f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/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-20181207154023-610586996380 h1:zPQexyRtNYBc7bcHmehl1dH6TB3qn8zytv8cBGLDNY0=
 | 
			
		||||
golang.org/x/net v0.0.0-20181207154023-610586996380/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 | 
			
		||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
 | 
			
		||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180921163948-d47a0f339242/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180927150500-dad3d9fb7b6e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20181022134430-8a28ead16f52/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20181024145615-5cd93ef61a7c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20181025063200-d989b31c8746/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20181026064943-731415f00dce/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20181106135930-3a76605856fd/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e h1:njOxP/wVblhCLIUhjHXf6X+dzTt5OQ3vMQo9mkOIKIo=
 | 
			
		||||
golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 | 
			
		||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
 | 
			
		||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/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-20181003024731-2f84ea8ef872/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20181006002542-f60d9635b16a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20181008205924-a2b3f7f249e9/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20181013182035-5e66757b835f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20181017214349-06f26fdaaa28/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20181024171208-a2dc47679d30/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20181026183834-f60e5f99f081/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20181105230042-78dc5bac0cac/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20181107215632-34b416bd17b3/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20181114190951-94339b83286c/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20181119130350-139d099f6620/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20181127195227-b4e97c0ed882/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20181127232545-e782529d0ddd/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20181205224935-3576414c54a4/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20181206194817-bcd4e47d0288/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 | 
			
		||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 | 
			
		||||
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/grpc v1.17.0 h1:TRJYBgMclJvGYn2rIMjj+h9KtMt5r1Ij7ODVRIZkwhk=
 | 
			
		||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 | 
			
		||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
 | 
			
		||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 | 
			
		||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
 | 
			
		||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
 | 
			
		||||
gopkg.in/mail.v2 v2.0.0-20180731213649-a0242b2233b4/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
 | 
			
		||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gotest.tools v2.2.0+incompatible h1:y0IMTfclpMdsdIbr6uwmJn5/WZ7vFuObxDMdrylFM3A=
 | 
			
		||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
 | 
			
		||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 | 
			
		||||
							
								
								
									
										348
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										348
									
								
								main.go
									
									
									
									
									
								
							@@ -1,190 +1,224 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
    "context"
 | 
			
		||||
    "encoding/binary"
 | 
			
		||||
    "encoding/json"
 | 
			
		||||
    "fmt"
 | 
			
		||||
    "github.com/amir20/dozzle/docker"
 | 
			
		||||
    "github.com/gobuffalo/packr"
 | 
			
		||||
    "github.com/gorilla/mux"
 | 
			
		||||
    flag "github.com/spf13/pflag"
 | 
			
		||||
    "html/template"
 | 
			
		||||
    "log"
 | 
			
		||||
    "net/http"
 | 
			
		||||
    "strings"
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/amir20/dozzle/docker"
 | 
			
		||||
	"github.com/gobuffalo/packr"
 | 
			
		||||
	"github.com/gorilla/mux"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
	flag "github.com/spf13/pflag"
 | 
			
		||||
	"html/template"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/signal"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
    dockerClient docker.DockerClient
 | 
			
		||||
    addr         = ""
 | 
			
		||||
    base         = "/"
 | 
			
		||||
    version      = "dev"
 | 
			
		||||
    commit       = "none"
 | 
			
		||||
    date         = "unknown"
 | 
			
		||||
	addr    = ""
 | 
			
		||||
	base    = ""
 | 
			
		||||
	level   = ""
 | 
			
		||||
	version = "dev"
 | 
			
		||||
	commit  = "none"
 | 
			
		||||
	date    = "unknown"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
    flag.StringVar(&addr, "addr", ":8080", "http service address")
 | 
			
		||||
    flag.StringVar(&base, "base", "/", "base address of the application to mount")
 | 
			
		||||
type handler struct {
 | 
			
		||||
	client docker.Client
 | 
			
		||||
	box    packr.Box
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    dockerClient = docker.NewDockerClient()
 | 
			
		||||
    flag.Parse()
 | 
			
		||||
func init() {
 | 
			
		||||
	flag.StringVar(&addr, "addr", ":8080", "http service address")
 | 
			
		||||
	flag.StringVar(&base, "base", "/", "base address of the application to mount")
 | 
			
		||||
	flag.StringVar(&level, "level", "info", "logging level")
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
 | 
			
		||||
	l, _ := log.ParseLevel(level)
 | 
			
		||||
	log.SetLevel(l)
 | 
			
		||||
 | 
			
		||||
	log.SetFormatter(&log.TextFormatter{
 | 
			
		||||
		DisableTimestamp:       true,
 | 
			
		||||
		DisableLevelTruncation: true,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createRoutes(base string, h *handler) *mux.Router {
 | 
			
		||||
	r := mux.NewRouter()
 | 
			
		||||
	if base != "/" {
 | 
			
		||||
		r.HandleFunc(base, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
			http.Redirect(w, req, base+"/", http.StatusMovedPermanently)
 | 
			
		||||
		}))
 | 
			
		||||
	}
 | 
			
		||||
	s := r.PathPrefix(base).Subrouter()
 | 
			
		||||
	s.HandleFunc("/api/containers.json", h.listContainers)
 | 
			
		||||
	s.HandleFunc("/api/logs/stream", h.streamLogs)
 | 
			
		||||
	s.HandleFunc("/api/events/stream", h.streamEvents)
 | 
			
		||||
	s.HandleFunc("/version", h.version)
 | 
			
		||||
	s.PathPrefix("/").Handler(http.StripPrefix(base, http.HandlerFunc(h.index)))
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
    r := mux.NewRouter()
 | 
			
		||||
	dockerClient := docker.NewClient()
 | 
			
		||||
	_, err := dockerClient.ListContainers()
 | 
			
		||||
 | 
			
		||||
    if base != "/" {
 | 
			
		||||
        r.HandleFunc(base, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
            http.Redirect(w, req, base+"/", http.StatusMovedPermanently)
 | 
			
		||||
        }))
 | 
			
		||||
    }
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatalf("Could not connect to Docker Engine: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    s := r.PathPrefix(base).Subrouter()
 | 
			
		||||
    box := packr.NewBox("./static")
 | 
			
		||||
	box := packr.NewBox("./static")
 | 
			
		||||
	r := createRoutes(base, &handler{dockerClient, box})
 | 
			
		||||
	srv := &http.Server{Addr: addr, Handler: r}
 | 
			
		||||
 | 
			
		||||
    s.HandleFunc("/api/containers.json", listContainers)
 | 
			
		||||
    s.HandleFunc("/api/logs/stream", streamLogs)
 | 
			
		||||
    s.HandleFunc("/api/events/stream", streamEvents)
 | 
			
		||||
    s.HandleFunc("/version", versionHandler)
 | 
			
		||||
    s.PathPrefix("/").Handler(http.StripPrefix(base, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
        fileServer := http.FileServer(box)
 | 
			
		||||
        if box.Has(req.URL.Path) && req.URL.Path != "" && req.URL.Path != "/" {
 | 
			
		||||
            fileServer.ServeHTTP(w, req)
 | 
			
		||||
        } else {
 | 
			
		||||
            handleIndex(box, w)
 | 
			
		||||
        }
 | 
			
		||||
    })))
 | 
			
		||||
	go func() {
 | 
			
		||||
		log.Infof("Accepting connections on %s", srv.Addr)
 | 
			
		||||
		if err := srv.ListenAndServe(); err != nil {
 | 
			
		||||
			log.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
    log.Printf("Accepting connections on %s", addr)
 | 
			
		||||
    log.Fatal(http.ListenAndServe(addr, r))
 | 
			
		||||
	c := make(chan os.Signal, 1)
 | 
			
		||||
	signal.Notify(c, os.Interrupt)
 | 
			
		||||
	signal.Notify(c, os.Kill)
 | 
			
		||||
	<-c
 | 
			
		||||
	log.Infof("Shutting down...")
 | 
			
		||||
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
	srv.Shutdown(ctx)
 | 
			
		||||
	os.Exit(0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func versionHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
    fmt.Fprintln(w, version)
 | 
			
		||||
    fmt.Fprintln(w, commit)
 | 
			
		||||
    fmt.Fprintln(w, date)
 | 
			
		||||
func (h *handler) index(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
	fileServer := http.FileServer(h.box)
 | 
			
		||||
	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 = strings.Replace(text, "__BASE__", "{{ .Base }}", -1)
 | 
			
		||||
		tmpl, err := template.New("index.html").Parse(text)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			panic(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		path := ""
 | 
			
		||||
		if base != "/" {
 | 
			
		||||
			path = base
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		data := struct{ Base string }{path}
 | 
			
		||||
		err = tmpl.Execute(w, data)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func listContainers(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
    containers, err := dockerClient.ListContainers()
 | 
			
		||||
    if err != nil {
 | 
			
		||||
        http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
    err = json.NewEncoder(w).Encode(containers)
 | 
			
		||||
    if err != nil {
 | 
			
		||||
        http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
func (h *handler) listContainers(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	containers, err := h.client.ListContainers()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	err = json.NewEncoder(w).Encode(containers)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func handleIndex(box packr.Box, w http.ResponseWriter) {
 | 
			
		||||
    text, _ := box.FindString("index.html")
 | 
			
		||||
    text = strings.Replace(text, "__BASE__", "{{ .Base }}", -1)
 | 
			
		||||
    tmpl, err := template.New("index.html").Parse(text)
 | 
			
		||||
    if err != nil {
 | 
			
		||||
        panic(err)
 | 
			
		||||
    }
 | 
			
		||||
func (h *handler) streamLogs(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	id := r.URL.Query().Get("id")
 | 
			
		||||
	if id == "" {
 | 
			
		||||
		http.Error(w, "id is required", http.StatusBadRequest)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    path := ""
 | 
			
		||||
    if base != "/" {
 | 
			
		||||
        path = base
 | 
			
		||||
    }
 | 
			
		||||
	f, ok := w.(http.Flusher)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    data := struct{ Base string }{path}
 | 
			
		||||
    err = tmpl.Execute(w, data)
 | 
			
		||||
    if err != nil {
 | 
			
		||||
        panic(err)
 | 
			
		||||
    }
 | 
			
		||||
	messages, err := h.client.ContainerLogs(r.Context(), id)
 | 
			
		||||
 | 
			
		||||
	w.Header().Set("Content-Type", "text/event-stream")
 | 
			
		||||
	w.Header().Set("Cache-Control", "no-cache")
 | 
			
		||||
	w.Header().Set("Connection", "keep-alive")
 | 
			
		||||
	w.Header().Set("Transfer-Encoding", "chunked")
 | 
			
		||||
 | 
			
		||||
	log.Debugf("Starting to stream logs for %s", id)
 | 
			
		||||
Loop:
 | 
			
		||||
	for {
 | 
			
		||||
		select {
 | 
			
		||||
		case message, ok := <-messages:
 | 
			
		||||
			if !ok {
 | 
			
		||||
				break Loop
 | 
			
		||||
			}
 | 
			
		||||
			_, e := fmt.Fprintf(w, "data: %s\n\n", message)
 | 
			
		||||
			if e != nil {
 | 
			
		||||
				log.Debugf("Error while writing to log stream: %v", e)
 | 
			
		||||
				break Loop
 | 
			
		||||
			}
 | 
			
		||||
			f.Flush()
 | 
			
		||||
		case e := <-err:
 | 
			
		||||
			log.Debugf("Error while reading from log stream: %v", e)
 | 
			
		||||
			break Loop
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.WithField("NumGoroutine", runtime.NumGoroutine()).Debug("runtime stats")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func streamLogs(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
    id := r.URL.Query().Get("id")
 | 
			
		||||
    if id == "" {
 | 
			
		||||
        http.Error(w, "id is required", http.StatusBadRequest)
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	f, ok := w.(http.Flusher)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    f, ok := w.(http.Flusher)
 | 
			
		||||
    if !ok {
 | 
			
		||||
        http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
	w.Header().Set("Content-Type", "text/event-stream")
 | 
			
		||||
	w.Header().Set("Cache-Control", "no-cache")
 | 
			
		||||
	w.Header().Set("Connection", "keep-alive")
 | 
			
		||||
	w.Header().Set("Transfer-Encoding", "chunked")
 | 
			
		||||
 | 
			
		||||
    ctx, cancel := context.WithCancel(context.Background())
 | 
			
		||||
    defer cancel()
 | 
			
		||||
    reader, err := dockerClient.ContainerLogs(ctx, id)
 | 
			
		||||
    if err != nil {
 | 
			
		||||
        log.Println(err)
 | 
			
		||||
        http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
    defer reader.Close()
 | 
			
		||||
	ctx := r.Context()
 | 
			
		||||
	messages, err := h.client.Events(ctx)
 | 
			
		||||
 | 
			
		||||
    w.Header().Set("Content-Type", "text/event-stream")
 | 
			
		||||
    w.Header().Set("Cache-Control", "no-cache")
 | 
			
		||||
    w.Header().Set("Connection", "keep-alive")
 | 
			
		||||
    w.Header().Set("Transfer-Encoding", "chunked")
 | 
			
		||||
Loop:
 | 
			
		||||
	for {
 | 
			
		||||
		select {
 | 
			
		||||
		case message, ok := <-messages:
 | 
			
		||||
			if !ok {
 | 
			
		||||
				break Loop
 | 
			
		||||
			}
 | 
			
		||||
			switch message.Action {
 | 
			
		||||
			case "connect", "disconnect", "create", "destroy", "start", "stop":
 | 
			
		||||
				log.Debugf("Triggering docker event: %v", message.Action)
 | 
			
		||||
				_, err := fmt.Fprintf(w, "event: containers-changed\ndata: %s\n\n", message.Action)
 | 
			
		||||
 | 
			
		||||
    hdr := make([]byte, 8)
 | 
			
		||||
    content := make([]byte, 1024, 1024*1024)
 | 
			
		||||
    for {
 | 
			
		||||
        _, err := reader.Read(hdr)
 | 
			
		||||
        if err != nil {
 | 
			
		||||
            log.Panicln(err)
 | 
			
		||||
        }
 | 
			
		||||
        count := binary.BigEndian.Uint32(hdr[4:])
 | 
			
		||||
        n, err := reader.Read(content[:count])
 | 
			
		||||
        if err != nil {
 | 
			
		||||
            log.Println(err)
 | 
			
		||||
            break
 | 
			
		||||
        }
 | 
			
		||||
        _, err = fmt.Fprintf(w, "data: %s\n\n", content[:n])
 | 
			
		||||
        if err != nil {
 | 
			
		||||
            log.Println(err)
 | 
			
		||||
            break
 | 
			
		||||
        }
 | 
			
		||||
        f.Flush()
 | 
			
		||||
    }
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					log.Debugf("Error while writing to event stream: %v", err)
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
				f.Flush()
 | 
			
		||||
			default:
 | 
			
		||||
				log.Debugf("Ignoring docker event: %v", message.Action)
 | 
			
		||||
			}
 | 
			
		||||
		case <-ctx.Done():
 | 
			
		||||
			break Loop
 | 
			
		||||
		case <-err:
 | 
			
		||||
			break Loop
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func streamEvents(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
    f, ok := w.(http.Flusher)
 | 
			
		||||
    if !ok {
 | 
			
		||||
        http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
 | 
			
		||||
        return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    w.Header().Set("Content-Type", "text/event-stream")
 | 
			
		||||
    w.Header().Set("Cache-Control", "no-cache")
 | 
			
		||||
    w.Header().Set("Connection", "keep-alive")
 | 
			
		||||
    w.Header().Set("Transfer-Encoding", "chunked")
 | 
			
		||||
 | 
			
		||||
    ctx, cancel := context.WithCancel(context.Background())
 | 
			
		||||
    defer cancel()
 | 
			
		||||
    messages, _ := dockerClient.Events(ctx)
 | 
			
		||||
 | 
			
		||||
    for message := range messages {
 | 
			
		||||
        switch message.Action {
 | 
			
		||||
        case "connect":
 | 
			
		||||
            fallthrough
 | 
			
		||||
        case "disconnect":
 | 
			
		||||
            fallthrough
 | 
			
		||||
        case "create":
 | 
			
		||||
            fallthrough
 | 
			
		||||
        case "destroy":
 | 
			
		||||
            _, err := fmt.Fprintf(w, "event: containers-changed\n")
 | 
			
		||||
            _, err = fmt.Fprintf(w, "data: %s\n\n", message.Action)
 | 
			
		||||
 | 
			
		||||
            if err != nil {
 | 
			
		||||
                log.Println(err)
 | 
			
		||||
                break
 | 
			
		||||
            }
 | 
			
		||||
            f.Flush()
 | 
			
		||||
        default:
 | 
			
		||||
            // Do nothing
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
func (h *handler) version(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	fmt.Fprintln(w, version)
 | 
			
		||||
	fmt.Fprintln(w, commit)
 | 
			
		||||
	fmt.Fprintln(w, date)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										287
									
								
								main_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								main_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,287 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"github.com/magiconair/properties/assert"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/http/httptest"
 | 
			
		||||
	"os"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/amir20/dozzle/docker"
 | 
			
		||||
	"github.com/beme/abide"
 | 
			
		||||
	"github.com/docker/docker/api/types/events"
 | 
			
		||||
	"github.com/gobuffalo/packr"
 | 
			
		||||
	"github.com/stretchr/testify/mock"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type MockedClient struct {
 | 
			
		||||
	mock.Mock
 | 
			
		||||
	docker.Client
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockedClient) ListContainers() ([]docker.Container, error) {
 | 
			
		||||
	args := m.Called()
 | 
			
		||||
	containers, ok := args.Get(0).([]docker.Container)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic("containers is not of type []docker.Container")
 | 
			
		||||
	}
 | 
			
		||||
	return containers, args.Error(1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockedClient) ContainerLogs(ctx context.Context, id string) (<-chan string, <-chan error) {
 | 
			
		||||
	args := m.Called(ctx, id)
 | 
			
		||||
	channel, ok := args.Get(0).(chan string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic("channel is not of type chan string")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err, ok := args.Get(1).(chan error)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic("error is not of type chan error")
 | 
			
		||||
	}
 | 
			
		||||
	return channel, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *MockedClient) Events(ctx context.Context) (<-chan events.Message, <-chan error) {
 | 
			
		||||
	args := m.Called(ctx)
 | 
			
		||||
	channel, ok := args.Get(0).(chan events.Message)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic("channel is not of type chan events.Message")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err, ok := args.Get(1).(chan error)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		panic("error is not of type chan error")
 | 
			
		||||
	}
 | 
			
		||||
	return channel, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_handler_listContainers_happy(t *testing.T) {
 | 
			
		||||
	req, err := http.NewRequest("GET", "/api/containers.json", nil)
 | 
			
		||||
	require.NoError(t, err, "NewRequest should not return an error.")
 | 
			
		||||
 | 
			
		||||
	rr := httptest.NewRecorder()
 | 
			
		||||
 | 
			
		||||
	mockedClient := new(MockedClient)
 | 
			
		||||
	containers := []docker.Container{
 | 
			
		||||
		{
 | 
			
		||||
			ID:      "1234567890",
 | 
			
		||||
			Status:  "status",
 | 
			
		||||
			State:   "state",
 | 
			
		||||
			Name:    "test",
 | 
			
		||||
			Created: 0,
 | 
			
		||||
			Command: "command",
 | 
			
		||||
			ImageID: "image_id",
 | 
			
		||||
			Image:   "image",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	mockedClient.On("ListContainers", mock.Anything).Return(containers, nil)
 | 
			
		||||
 | 
			
		||||
	h := handler{client: mockedClient}
 | 
			
		||||
	handler := http.HandlerFunc(h.listContainers)
 | 
			
		||||
	handler.ServeHTTP(rr, req)
 | 
			
		||||
	abide.AssertHTTPResponse(t, t.Name(), rr.Result())
 | 
			
		||||
	mockedClient.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_handler_streamLogs_happy(t *testing.T) {
 | 
			
		||||
	id := "123456"
 | 
			
		||||
	req, err := http.NewRequest("GET", "/api/logs/stream", nil)
 | 
			
		||||
	q := req.URL.Query()
 | 
			
		||||
	q.Add("id", "123456")
 | 
			
		||||
	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).Return(messages, errChannel)
 | 
			
		||||
	go func() {
 | 
			
		||||
		messages <- "INFO Testing logs..."
 | 
			
		||||
		close(messages)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	h := handler{client: mockedClient}
 | 
			
		||||
	handler := http.HandlerFunc(h.streamLogs)
 | 
			
		||||
	handler.ServeHTTP(rr, req)
 | 
			
		||||
	abide.AssertHTTPResponse(t, t.Name(), rr.Result())
 | 
			
		||||
	mockedClient.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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")
 | 
			
		||||
	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).Return(messages, errChannel)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		errChannel <- errors.New("test error")
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	h := handler{client: mockedClient}
 | 
			
		||||
	handler := http.HandlerFunc(h.streamLogs)
 | 
			
		||||
	handler.ServeHTTP(rr, req)
 | 
			
		||||
	abide.AssertHTTPResponse(t, t.Name(), rr.Result())
 | 
			
		||||
	mockedClient.AssertExpectations(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)
 | 
			
		||||
	mockedClient.On("Events", mock.Anything).Return(messages, errChannel)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		messages <- events.Message{
 | 
			
		||||
			Action: "start",
 | 
			
		||||
		}
 | 
			
		||||
		messages <- events.Message{
 | 
			
		||||
			Action: "something-random",
 | 
			
		||||
		}
 | 
			
		||||
		close(messages)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	h := handler{client: mockedClient}
 | 
			
		||||
	handler := http.HandlerFunc(h.streamEvents)
 | 
			
		||||
	handler.ServeHTTP(rr, req)
 | 
			
		||||
	abide.AssertHTTPResponse(t, t.Name(), rr.Result())
 | 
			
		||||
	mockedClient.AssertExpectations(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)
 | 
			
		||||
	mockedClient.On("Events", mock.Anything).Return(messages, errChannel)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		errChannel <- errors.New("fake error")
 | 
			
		||||
		close(messages)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	h := handler{client: mockedClient}
 | 
			
		||||
	handler := http.HandlerFunc(h.streamEvents)
 | 
			
		||||
	handler.ServeHTTP(rr, req)
 | 
			
		||||
	abide.AssertHTTPResponse(t, t.Name(), rr.Result())
 | 
			
		||||
	mockedClient.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
	errChannel := make(chan error)
 | 
			
		||||
	mockedClient.On("Events", mock.Anything).Return(messages, errChannel)
 | 
			
		||||
 | 
			
		||||
	ctx, cancel := context.WithCancel(context.Background())
 | 
			
		||||
	req = req.WithContext(ctx)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		cancel()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	h := handler{client: mockedClient}
 | 
			
		||||
	handler := http.HandlerFunc(h.streamEvents)
 | 
			
		||||
	handler.ServeHTTP(rr, req)
 | 
			
		||||
	abide.AssertHTTPResponse(t, t.Name(), rr.Result())
 | 
			
		||||
	mockedClient.AssertExpectations(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_createRoutes_index(t *testing.T) {
 | 
			
		||||
	mockedClient := new(MockedClient)
 | 
			
		||||
	box := packr.NewBox("./virtual")
 | 
			
		||||
	require.NoError(t, box.AddString("index.html", "index page"), "AddString should have no error.")
 | 
			
		||||
 | 
			
		||||
	handler := createRoutes("/", &handler{mockedClient, box})
 | 
			
		||||
	req, err := http.NewRequest("GET", "/", nil)
 | 
			
		||||
	require.NoError(t, err, "NewRequest should not return an error.")
 | 
			
		||||
	rr := httptest.NewRecorder()
 | 
			
		||||
 | 
			
		||||
	handler.ServeHTTP(rr, req)
 | 
			
		||||
	abide.AssertHTTPResponse(t, t.Name(), rr.Result())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_createRoutes_redirect(t *testing.T) {
 | 
			
		||||
	mockedClient := new(MockedClient)
 | 
			
		||||
	box := packr.NewBox("./virtual")
 | 
			
		||||
 | 
			
		||||
	handler := createRoutes("/foobar", &handler{mockedClient, box})
 | 
			
		||||
	req, err := http.NewRequest("GET", "/foobar", nil)
 | 
			
		||||
	require.NoError(t, err, "NewRequest should not return an error.")
 | 
			
		||||
	rr := httptest.NewRecorder()
 | 
			
		||||
 | 
			
		||||
	handler.ServeHTTP(rr, req)
 | 
			
		||||
	abide.AssertHTTPResponse(t, t.Name(), rr.Result())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_createRoutes_foobar(t *testing.T) {
 | 
			
		||||
	mockedClient := new(MockedClient)
 | 
			
		||||
	box := packr.NewBox("./virtual")
 | 
			
		||||
	require.NoError(t, box.AddString("index.html", "foo page"), "AddString should have no error.")
 | 
			
		||||
 | 
			
		||||
	handler := createRoutes("/foobar", &handler{mockedClient, box})
 | 
			
		||||
	req, err := http.NewRequest("GET", "/foobar/", nil)
 | 
			
		||||
	require.NoError(t, err, "NewRequest should not return an error.")
 | 
			
		||||
	rr := httptest.NewRecorder()
 | 
			
		||||
 | 
			
		||||
	handler.ServeHTTP(rr, req)
 | 
			
		||||
	abide.AssertHTTPResponse(t, t.Name(), rr.Result())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_createRoutes_foobar_file(t *testing.T) {
 | 
			
		||||
	mockedClient := new(MockedClient)
 | 
			
		||||
	box := packr.NewBox("./virtual")
 | 
			
		||||
	require.NoError(t, box.AddString("/test", "test page"), "AddString should have no error.")
 | 
			
		||||
 | 
			
		||||
	handler := createRoutes("/foobar", &handler{mockedClient, box})
 | 
			
		||||
	req, err := http.NewRequest("GET", "/foobar/test", nil)
 | 
			
		||||
	require.NoError(t, err, "NewRequest should not return an error.")
 | 
			
		||||
	rr := httptest.NewRecorder()
 | 
			
		||||
 | 
			
		||||
	handler.ServeHTTP(rr, req)
 | 
			
		||||
	assert.Equal(t, rr.Body.String(), "test page", "page doesn't match")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_createRoutes_version(t *testing.T) {
 | 
			
		||||
	mockedClient := new(MockedClient)
 | 
			
		||||
	box := packr.NewBox("./virtual")
 | 
			
		||||
 | 
			
		||||
	handler := createRoutes("/", &handler{mockedClient, box})
 | 
			
		||||
	req, err := http.NewRequest("GET", "/version", nil)
 | 
			
		||||
	require.NoError(t, err, "NewRequest should not return an error.")
 | 
			
		||||
	rr := httptest.NewRecorder()
 | 
			
		||||
 | 
			
		||||
	handler.ServeHTTP(rr, req)
 | 
			
		||||
	abide.AssertHTTPResponse(t, t.Name(), rr.Result())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMain(m *testing.M) {
 | 
			
		||||
	exit := m.Run()
 | 
			
		||||
	abide.Cleanup()
 | 
			
		||||
	os.Exit(exit)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										113
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										113
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "dozzle",
 | 
			
		||||
  "version": "1.3.0",
 | 
			
		||||
  "version": "1.5.2",
 | 
			
		||||
  "lockfileVersion": 1,
 | 
			
		||||
  "requires": true,
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
@@ -14,18 +14,18 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@babel/core": {
 | 
			
		||||
      "version": "7.1.6",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.1.6.tgz",
 | 
			
		||||
      "integrity": "sha512-Hz6PJT6e44iUNpAn8AoyAs6B3bl60g7MJQaI0rZEar6ECzh6+srYO1xlIdssio34mPaUtAb1y+XlkkSJzok3yw==",
 | 
			
		||||
      "version": "7.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-7pvAdC4B+iKjFFp9Ztj0QgBndJ++qaMeonT185wAqUnhipw8idm9Rv1UMyBuKtYjfl6ORNkgEgcsYLfHX/GpLw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@babel/code-frame": "^7.0.0",
 | 
			
		||||
        "@babel/generator": "^7.1.6",
 | 
			
		||||
        "@babel/helpers": "^7.1.5",
 | 
			
		||||
        "@babel/parser": "^7.1.6",
 | 
			
		||||
        "@babel/generator": "^7.2.0",
 | 
			
		||||
        "@babel/helpers": "^7.2.0",
 | 
			
		||||
        "@babel/parser": "^7.2.0",
 | 
			
		||||
        "@babel/template": "^7.1.2",
 | 
			
		||||
        "@babel/traverse": "^7.1.6",
 | 
			
		||||
        "@babel/types": "^7.1.6",
 | 
			
		||||
        "@babel/types": "^7.2.0",
 | 
			
		||||
        "convert-source-map": "^1.1.0",
 | 
			
		||||
        "debug": "^4.1.0",
 | 
			
		||||
        "json5": "^2.1.0",
 | 
			
		||||
@@ -36,12 +36,12 @@
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@babel/generator": {
 | 
			
		||||
          "version": "7.1.6",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.1.6.tgz",
 | 
			
		||||
          "integrity": "sha512-brwPBtVvdYdGxtenbQgfCdDPmtkmUBZPjUoK5SXJEBuHaA5BCubh9ly65fzXz7R6o5rA76Rs22ES8Z+HCc0YIQ==",
 | 
			
		||||
          "version": "7.2.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.0.tgz",
 | 
			
		||||
          "integrity": "sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg==",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "@babel/types": "^7.1.6",
 | 
			
		||||
            "@babel/types": "^7.2.0",
 | 
			
		||||
            "jsesc": "^2.5.1",
 | 
			
		||||
            "lodash": "^4.17.10",
 | 
			
		||||
            "source-map": "^0.5.0",
 | 
			
		||||
@@ -49,9 +49,9 @@
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "@babel/parser": {
 | 
			
		||||
          "version": "7.1.6",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.1.6.tgz",
 | 
			
		||||
          "integrity": "sha512-dWP6LJm9nKT6ALaa+bnL247GHHMWir3vSlZ2+IHgHgktZQx0L3Uvq2uAWcuzIe+fujRsYWBW2q622C5UvGK9iQ==",
 | 
			
		||||
          "version": "7.2.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.2.0.tgz",
 | 
			
		||||
          "integrity": "sha512-M74+GvK4hn1eejD9lZ7967qAwvqTZayQa3g10ag4s9uewgR7TKjeaT0YMyoq+gVfKYABiWZ4MQD701/t5e1Jhg==",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "@babel/traverse": {
 | 
			
		||||
@@ -72,9 +72,9 @@
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "@babel/types": {
 | 
			
		||||
          "version": "7.1.6",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.1.6.tgz",
 | 
			
		||||
          "integrity": "sha512-DMiUzlY9DSjVsOylJssxLHSgj6tWM9PRFJOGW/RaOglVOK9nzTxoOMfTfRQXGUCUQ/HmlG2efwC+XqUEJ5ay4w==",
 | 
			
		||||
          "version": "7.2.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz",
 | 
			
		||||
          "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "esutils": "^2.0.2",
 | 
			
		||||
@@ -350,23 +350,23 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@babel/helpers": {
 | 
			
		||||
      "version": "7.1.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.1.5.tgz",
 | 
			
		||||
      "integrity": "sha512-2jkcdL02ywNBry1YNFAH/fViq4fXG0vdckHqeJk+75fpQ2OH+Az6076tX/M0835zA45E0Cqa6pV5Kiv9YOqjEg==",
 | 
			
		||||
      "version": "7.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-Fr07N+ea0dMcMN8nFpuK6dUIT7/ivt9yKQdEEnjVS83tG2pHwPi03gYmk/tyuwONnZ+sY+GFFPlWGgCtW1hF9A==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@babel/template": "^7.1.2",
 | 
			
		||||
        "@babel/traverse": "^7.1.5",
 | 
			
		||||
        "@babel/types": "^7.1.5"
 | 
			
		||||
        "@babel/types": "^7.2.0"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@babel/generator": {
 | 
			
		||||
          "version": "7.1.6",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.1.6.tgz",
 | 
			
		||||
          "integrity": "sha512-brwPBtVvdYdGxtenbQgfCdDPmtkmUBZPjUoK5SXJEBuHaA5BCubh9ly65fzXz7R6o5rA76Rs22ES8Z+HCc0YIQ==",
 | 
			
		||||
          "version": "7.2.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.0.tgz",
 | 
			
		||||
          "integrity": "sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg==",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "@babel/types": "^7.1.6",
 | 
			
		||||
            "@babel/types": "^7.2.0",
 | 
			
		||||
            "jsesc": "^2.5.1",
 | 
			
		||||
            "lodash": "^4.17.10",
 | 
			
		||||
            "source-map": "^0.5.0",
 | 
			
		||||
@@ -374,9 +374,9 @@
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "@babel/parser": {
 | 
			
		||||
          "version": "7.1.6",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.1.6.tgz",
 | 
			
		||||
          "integrity": "sha512-dWP6LJm9nKT6ALaa+bnL247GHHMWir3vSlZ2+IHgHgktZQx0L3Uvq2uAWcuzIe+fujRsYWBW2q622C5UvGK9iQ==",
 | 
			
		||||
          "version": "7.2.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.2.0.tgz",
 | 
			
		||||
          "integrity": "sha512-M74+GvK4hn1eejD9lZ7967qAwvqTZayQa3g10ag4s9uewgR7TKjeaT0YMyoq+gVfKYABiWZ4MQD701/t5e1Jhg==",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "@babel/traverse": {
 | 
			
		||||
@@ -397,9 +397,9 @@
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "@babel/types": {
 | 
			
		||||
          "version": "7.1.6",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.1.6.tgz",
 | 
			
		||||
          "integrity": "sha512-DMiUzlY9DSjVsOylJssxLHSgj6tWM9PRFJOGW/RaOglVOK9nzTxoOMfTfRQXGUCUQ/HmlG2efwC+XqUEJ5ay4w==",
 | 
			
		||||
          "version": "7.2.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz",
 | 
			
		||||
          "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "esutils": "^2.0.2",
 | 
			
		||||
@@ -881,9 +881,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@babel/plugin-transform-runtime": {
 | 
			
		||||
      "version": "7.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-WFLMgzu5DLQEah0lKTJzYb14vd6UiES7PTnXcvrPZ1VrwFeJ+mTbvr65fFAsXYMt2bIoOoC0jk76zY1S7HZjUg==",
 | 
			
		||||
      "version": "7.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-jIgkljDdq4RYDnJyQsiWbdvGeei/0MOTtSHKO/rfbd/mXBxNpdlulMx49L0HQ4pug1fXannxoqCI+fYSle9eSw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@babel/helper-module-imports": "^7.0.0",
 | 
			
		||||
@@ -1823,11 +1823,6 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/bulma/-/bulma-0.7.2.tgz",
 | 
			
		||||
      "integrity": "sha512-6JHEu8U/1xsyOst/El5ImLcZIiE2JFXgvrz8GGWbnDLwTNRPJzdAM0aoUM1Ns0avALcVb6KZz9NhzmU53dGDcQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "bulma-tooltip": {
 | 
			
		||||
      "version": "2.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/bulma-tooltip/-/bulma-tooltip-2.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-xsqWeWV7tsUn3uH04SqJeP7/CyC1RaDVIyVzr4/sIO3friIIOi7L6jc5g7qUwDxuBQl72yH/yRPuefpXoQ4hWg=="
 | 
			
		||||
    },
 | 
			
		||||
    "cache-base": {
 | 
			
		||||
      "version": "1.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
 | 
			
		||||
@@ -7685,9 +7680,9 @@
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "prettier": {
 | 
			
		||||
      "version": "1.15.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.15.2.tgz",
 | 
			
		||||
      "integrity": "sha512-YgPLFFA0CdKL4Eg2IHtUSjzj/BWgszDHiNQAe0VAIBse34148whfdzLagRL+QiKS+YfK5ftB6X4v/MBw8yCoug==",
 | 
			
		||||
      "version": "1.15.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.15.3.tgz",
 | 
			
		||||
      "integrity": "sha512-gAU9AGAPMaKb3NNSUUuhhFAS7SCO4ALTN4nRIn6PJ075Qd28Yn2Ig2ahEJWdJwJmlEBTUfC7mMUSFy8MwsOCfg==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "pretty-format": {
 | 
			
		||||
@@ -8060,9 +8055,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "sass": {
 | 
			
		||||
      "version": "1.15.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/sass/-/sass-1.15.1.tgz",
 | 
			
		||||
      "integrity": "sha512-WiDa5BsePB/rQEbh/Fv2pVDUCasxuRYjW7GsWx8Ld23LY61vx1VV5Mzf/7mu5kLWKMryMqo65fzYL34HgaM47w==",
 | 
			
		||||
      "version": "1.15.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/sass/-/sass-1.15.2.tgz",
 | 
			
		||||
      "integrity": "sha512-YFncPpx3ewKEhMg9sWdCxKUpPN/jwVLa0Q9iO2tcV5Y5Z/YAlFV6k6JaQwq3tmbN6FXKjUYElXRHcG0g4D1zkQ==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "chokidar": "^2.0.0"
 | 
			
		||||
@@ -9127,9 +9122,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "vue": {
 | 
			
		||||
      "version": "2.5.17",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/vue/-/vue-2.5.17.tgz",
 | 
			
		||||
      "integrity": "sha512-mFbcWoDIJi0w0Za4emyLiW72Jae0yjANHbCVquMKijcavBGypqlF7zHRgMa5k4sesdv7hv2rB4JPdZfR+TPfhQ=="
 | 
			
		||||
      "version": "2.5.19",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/vue/-/vue-2.5.19.tgz",
 | 
			
		||||
      "integrity": "sha512-zs7w/dy/yMTCcUunUl05UHhYEI2bt67R7G2jD42HnJ28JxCtC1FY8zZ9GU4PF2SJP8Ff/B9HyojWk/NztqKfFA=="
 | 
			
		||||
    },
 | 
			
		||||
    "vue-headful": {
 | 
			
		||||
      "version": "2.0.1",
 | 
			
		||||
@@ -9151,9 +9146,9 @@
 | 
			
		||||
      "integrity": "sha512-opKtsxjp9eOcFWdp6xLQPLmRGgfM932Tl56U9chYTnoWqKxQ8M20N7AkdEbM5beUh6wICoFGYugAX9vQjyJLFg=="
 | 
			
		||||
    },
 | 
			
		||||
    "vue-template-compiler": {
 | 
			
		||||
      "version": "2.5.17",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.5.17.tgz",
 | 
			
		||||
      "integrity": "sha512-63uI4syCwtGR5IJvZM0LN5tVsahrelomHtCxvRkZPJ/Tf3ADm1U1wG6KWycK3qCfqR+ygM5vewUvmJ0REAYksg==",
 | 
			
		||||
      "version": "2.5.19",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.5.19.tgz",
 | 
			
		||||
      "integrity": "sha512-SIoE8gvl0UjZ/wccwHhj0HUqL+kLkJNy/8v7TXElz6rqxqKOKLoAynVV4L78qHZOHhLoyRl5BOmUFQPPdcD5qw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "de-indent": "^1.0.2",
 | 
			
		||||
@@ -9268,9 +9263,9 @@
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "yargs": {
 | 
			
		||||
      "version": "12.0.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.4.tgz",
 | 
			
		||||
      "integrity": "sha512-f5esswlPO351AnejaO2A1ZZr0zesz19RehQKwiRDqWtrraWrJy16tsUIKgDXFMVytvNOHPVmTiaTh3wO67I0fQ==",
 | 
			
		||||
      "version": "12.0.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz",
 | 
			
		||||
      "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "cliui": "^4.0.0",
 | 
			
		||||
@@ -9284,13 +9279,13 @@
 | 
			
		||||
        "string-width": "^2.0.0",
 | 
			
		||||
        "which-module": "^2.0.0",
 | 
			
		||||
        "y18n": "^3.2.1 || ^4.0.0",
 | 
			
		||||
        "yargs-parser": "^11.1.0"
 | 
			
		||||
        "yargs-parser": "^11.1.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "yargs-parser": {
 | 
			
		||||
      "version": "11.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-lGA5HsbjkpCfekDBHAhgE5OE8xEoqiUDylowr+BvhRCwG1xVYTsd8hx2CYC0NY4k9RIgJeybFTG2EZW4P2aN1w==",
 | 
			
		||||
      "version": "11.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "camelcase": "^5.0.0",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										21
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								package.json
									
									
									
									
									
								
							@@ -1,14 +1,16 @@
 | 
			
		||||
{
 | 
			
		||||
 "name": "dozzle",
 | 
			
		||||
 "version": "1.3.0",
 | 
			
		||||
 "version": "1.5.2",
 | 
			
		||||
 "description": "",
 | 
			
		||||
 "main": "index.js",
 | 
			
		||||
 "scripts": {
 | 
			
		||||
  "start": "concurrently 'go run main.go' 'npm run watch-assets'",
 | 
			
		||||
  "prestart": "npm run clean",
 | 
			
		||||
  "start": "DOCKER_API_VERSION=1.38 concurrently 'npm run watch-server' 'npm run watch-assets'",
 | 
			
		||||
  "watch-assets": "parcel watch --public-url '__BASE__' assets/index.html -d static",
 | 
			
		||||
  "watch-server": "reflex -c .reflex",
 | 
			
		||||
  "prebuild": "npm run clean",
 | 
			
		||||
  "build": "parcel build --no-source-maps --public-url '__BASE__' assets/index.html -d static",
 | 
			
		||||
  "clean": "rm -rf static",
 | 
			
		||||
  "clean": "rm -rf static/ a_main-packr.go",
 | 
			
		||||
  "release": "goreleaser --rm-dist"
 | 
			
		||||
 },
 | 
			
		||||
 "repository": {
 | 
			
		||||
@@ -23,24 +25,23 @@
 | 
			
		||||
 "homepage": "https://github.com/amir20/dozzle#readme",
 | 
			
		||||
 "dependencies": {
 | 
			
		||||
  "bulma": "^0.7.2",
 | 
			
		||||
  "bulma-tooltip": "^2.0.2",
 | 
			
		||||
  "date-fns": "^2.0.0-alpha.25",
 | 
			
		||||
  "vue": "^2.5.17",
 | 
			
		||||
  "vue": "^2.5.19",
 | 
			
		||||
  "vue-headful": "^2.0.1",
 | 
			
		||||
  "vue-router": "^3.0.2"
 | 
			
		||||
 },
 | 
			
		||||
 "devDependencies": {
 | 
			
		||||
  "@babel/core": "^7.1.6",
 | 
			
		||||
  "@babel/plugin-transform-runtime": "^7.1.0",
 | 
			
		||||
  "@babel/core": "^7.2.0",
 | 
			
		||||
  "@babel/plugin-transform-runtime": "^7.2.0",
 | 
			
		||||
  "@vue/component-compiler-utils": "^2.3.0",
 | 
			
		||||
  "concurrently": "^4.1.0",
 | 
			
		||||
  "husky": "^1.2.0",
 | 
			
		||||
  "lint-staged": "^8.1.0",
 | 
			
		||||
  "parcel-bundler": "^1.10.3",
 | 
			
		||||
  "prettier": "^1.15.2",
 | 
			
		||||
  "sass": "^1.15.1",
 | 
			
		||||
  "prettier": "^1.15.3",
 | 
			
		||||
  "sass": "^1.15.2",
 | 
			
		||||
  "vue-hot-reload-api": "^2.3.1",
 | 
			
		||||
  "vue-template-compiler": "^2.5.17"
 | 
			
		||||
  "vue-template-compiler": "^2.5.19"
 | 
			
		||||
 },
 | 
			
		||||
 "husky": {
 | 
			
		||||
  "hooks": {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user