Compare commits
	
		
			114 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					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 | ||
| 
						 | 
					705a339e49 | ||
| 
						 | 
					f81c240a47 | ||
| 
						 | 
					262095e5bb | ||
| 
						 | 
					ba900c4374 | ||
| 
						 | 
					9dc6b3790d | ||
| 
						 | 
					b96785f2be | ||
| 
						 | 
					dd6f4b1e31 | ||
| 
						 | 
					651291ecad | ||
| 
						 | 
					a5bcec68cb | ||
| 
						 | 
					77d6a22122 | ||
| 
						 | 
					40f97073e8 | ||
| 
						 | 
					75339ffba1 | ||
| 
						 | 
					2fdfba5a42 | ||
| 
						 | 
					5987330cdc | ||
| 
						 | 
					14c7c21f9f | ||
| 
						 | 
					fc9fdaf8b6 | ||
| 
						 | 
					5979a6d0e5 | ||
| 
						 | 
					ca2c46ffce | ||
| 
						 | 
					1cc7e92466 | ||
| 
						 | 
					cfc3e81820 | ||
| 
						 | 
					67ab2ab170 | ||
| 
						 | 
					e1ce378421 | ||
| 
						 | 
					f083ea028d | ||
| 
						 | 
					063a82198c | ||
| 
						 | 
					d03c3440de | ||
| 
						 | 
					f7b28ad1e0 | ||
| 
						 | 
					52a95757ce | ||
| 
						 | 
					efc725dadc | ||
| 
						 | 
					ff8c539829 | ||
| 
						 | 
					875e17717e | ||
| 
						 | 
					929f8c19f8 | ||
| 
						 | 
					bdcc856071 | ||
| 
						 | 
					2325881bd8 | ||
| 
						 | 
					067fea2b7a | ||
| 
						 | 
					8ac689ca57 | ||
| 
						 | 
					5e9ffe7fcf | ||
| 
						 | 
					3b3ba92d27 | ||
| 
						 | 
					df2834fd81 | ||
| 
						 | 
					2ecfefb35f | ||
| 
						 | 
					d18d3f800b | ||
| 
						 | 
					9d7fd4eaf0 | ||
| 
						 | 
					032ebfd307 | ||
| 
						 | 
					7f74a0f551 | ||
| 
						 | 
					dc42180339 | ||
| 
						 | 
					972cbb8b2e | ||
| 
						 | 
					5ee895357d | ||
| 
						 | 
					d7cfe64273 | ||
| 
						 | 
					f06354f909 | ||
| 
						 | 
					0416fd541c | ||
| 
						 | 
					98701b1c7c | ||
| 
						 | 
					17e08c02bb | ||
| 
						 | 
					22108a2782 | ||
| 
						 | 
					742056bbef | ||
| 
						 | 
					2556dd07b3 | ||
| 
						 | 
					e43879b69c | 
							
								
								
									
										17
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
root = true
 | 
			
		||||
 | 
			
		||||
[*]
 | 
			
		||||
charset = utf-8
 | 
			
		||||
end_of_line = lf
 | 
			
		||||
indent_style = space
 | 
			
		||||
indent_size = 2
 | 
			
		||||
insert_final_newline = true
 | 
			
		||||
trim_trailing_whitespace = true
 | 
			
		||||
max_line_length = 120
 | 
			
		||||
 | 
			
		||||
[*.go]
 | 
			
		||||
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,5 +1,6 @@
 | 
			
		||||
before:
 | 
			
		||||
  hooks:
 | 
			
		||||
    - npm run clean
 | 
			
		||||
    - npm run build
 | 
			
		||||
    - packr
 | 
			
		||||
builds:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								.prettierrc.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.prettierrc.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
{
 | 
			
		||||
  "printWidth": 120
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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,12 +3,18 @@ language: go
 | 
			
		||||
go:
 | 
			
		||||
  - "1.11"
 | 
			
		||||
 | 
			
		||||
env:
 | 
			
		||||
  global:
 | 
			
		||||
    - GO111MODULE=on
 | 
			
		||||
 | 
			
		||||
services:
 | 
			
		||||
  - docker
 | 
			
		||||
 | 
			
		||||
before_install:
 | 
			
		||||
  - nvm install --lts
 | 
			
		||||
  - npm i
 | 
			
		||||
  - npm i -g npm
 | 
			
		||||
  - npm ci
 | 
			
		||||
  - go get -u github.com/gobuffalo/packr/packr
 | 
			
		||||
 | 
			
		||||
after_success:
 | 
			
		||||
  # docker login is required if you want to push docker images.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										26
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								README.md
									
									
									
									
									
								
							@@ -1,16 +1,18 @@
 | 
			
		||||
[](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?
 | 
			
		||||
 | 
			
		||||
While dozzle should work for most, it is not meant to be a full logging solution. For enterprise use, I recommend you look at [Loggly](https://www.loggly.com), [Papertrail](https://papertrailapp.com) or [Kibana](https://www.elastic.co/products/kibana).
 | 
			
		||||
 | 
			
		||||
But if you don't want to pay for those service, then you are in luck! Dozzle will be able capture all logs from your containers and send them in real-time to your browser. Installation is also very easy.
 | 
			
		||||
But if you don't want to pay for those services, then you are in luck! Dozzle will be able to capture all logs from your containers and send them in real-time to your browser. Installation is also very easy.
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
## Getting dozzle
 | 
			
		||||
 | 
			
		||||
Dozzle is a very small Docker container (13.3MB virtual). Pull the latest release from the index:
 | 
			
		||||
Dozzle is a very small Docker container (4 MB compressed). Pull the latest release from the index:
 | 
			
		||||
 | 
			
		||||
    $ docker pull amir20/dozzle:latest
 | 
			
		||||
 | 
			
		||||
@@ -24,11 +26,21 @@ dozzle will be available at [http://localhost:8888/](http://localhost:8888/). Yo
 | 
			
		||||
 | 
			
		||||
#### Security
 | 
			
		||||
 | 
			
		||||
dozzle doesn't support authentication out of the box. You can control the device dozzle binds to by passing `-addr` parameter. For example,
 | 
			
		||||
dozzle doesn't support authentication out of the box. You can control the device dozzle binds to by passing `--addr` parameter. For example,
 | 
			
		||||
 | 
			
		||||
    $ docker run --volume=/var/run/docker.sock:/var/run/docker.sock -p 8888:1224 amir20/dozzle:latest -addr localhost:1224
 | 
			
		||||
    $ docker run --volume=/var/run/docker.sock:/var/run/docker.sock -p 8888:1224 amir20/dozzle:latest --addr localhost:1224
 | 
			
		||||
 | 
			
		||||
will bind to `localhost` on port `1224`. You can then use a reverse proxy to control who can see dozzle.
 | 
			
		||||
 | 
			
		||||
#### Changing base URL
 | 
			
		||||
 | 
			
		||||
dozzle by default mounts to "/". If you want to control the base path you can use the `--base` option. For example, if you want to mount at "/foobar",
 | 
			
		||||
then you can override by using `--base /foobar`.
 | 
			
		||||
 | 
			
		||||
    $ docker run --volume=/var/run/docker.sock:/var/run/docker.sock -p 8080:8080 amir20/dozzle:latest --base /foobar
 | 
			
		||||
 | 
			
		||||
dozzle will be available at [http://localhost:8080/foobar/](http://localhost:8080/foobar/).
 | 
			
		||||
 | 
			
		||||
will bind to `localhost` on port `1224`. You can then use use reverse proxy to control who can see the dozzle.
 | 
			
		||||
 | 
			
		||||
#### Environment variable, DOCKER_API_VERSION
 | 
			
		||||
 | 
			
		||||
@@ -36,9 +48,9 @@ If you see
 | 
			
		||||
 | 
			
		||||
    2018/10/31 08:53:17 Error response from daemon: client version 1.40 is too new. Maximum supported API version is 1.38
 | 
			
		||||
 | 
			
		||||
Then you need to modify `DOCKER_API_VERSION` to let dozzle know which version of the API is supported. By default, `DOCKER_API_VERSION=1.38` and you can change it to by passing `-e` flag. For example, this would change the `DOCKER_API_VERSION` to `1.20`
 | 
			
		||||
Then you need to modify `DOCKER_API_VERSION` to let dozzle know which version of the API is supported. By default, `DOCKER_API_VERSION=1.38` and you can change it by passing `-e` flag. For example, this would change the `DOCKER_API_VERSION` to `1.20`
 | 
			
		||||
 | 
			
		||||
$ docker run --volume=/var/run/docker.sock:/var/run/docker.sock -e DOCKER_API_VERSION=1.20 -p 8888:8080 amir20/dozzle:latest
 | 
			
		||||
    $ docker run --volume=/var/run/docker.sock:/var/run/docker.sock -e DOCKER_API_VERSION=1.20 -p 8888:8080 amir20/dozzle:latest
 | 
			
		||||
 | 
			
		||||
If you are not sure what to set `DOCKER_API_VERSION` then run `docker version` which will show supported API version.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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,15 +1,102 @@
 | 
			
		||||
<template lang="html">
 | 
			
		||||
    <router-view></router-view>
 | 
			
		||||
  <div class="columns is-marginless">
 | 
			
		||||
    <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"
 | 
			
		||||
        @click="showNav = !showNav;"
 | 
			
		||||
        :class="{ 'is-active': showNav }"
 | 
			
		||||
      >
 | 
			
		||||
        <span></span> <span></span> <span></span>
 | 
			
		||||
      </a>
 | 
			
		||||
      <h1 class="title has-text-warning is-marginless">Dozzle</h1>
 | 
			
		||||
      <p class="menu-label is-hidden-mobile" :class="{ 'is-active': showNav }">Containers</p>
 | 
			
		||||
      <ul class="menu-list is-hidden-mobile" :class="{ 'is-active': showNav }">
 | 
			
		||||
        <li v-for="item in containers">
 | 
			
		||||
          <router-link :to="{ name: 'container', params: { id: item.id, name: item.name } }" active-class="is-active">
 | 
			
		||||
            <div class="hide-overflow">{{ item.name }}</div>
 | 
			
		||||
          </router-link>
 | 
			
		||||
        </li>
 | 
			
		||||
      </ul>
 | 
			
		||||
    </aside>
 | 
			
		||||
    <div class="column is-offset-2-desktop is-offset-3-tablet"><router-view></router-view></div>
 | 
			
		||||
    <vue-headful :title="title" />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
let es;
 | 
			
		||||
export default {
 | 
			
		||||
  name: "App"
 | 
			
		||||
  name: "App",
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      title: "Dozzle",
 | 
			
		||||
      containers: [],
 | 
			
		||||
      showNav: false
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  async created() {
 | 
			
		||||
    await this.fetchContainerList();
 | 
			
		||||
    this.title = `${this.containers.length} containers - Dozzle`;
 | 
			
		||||
    es = new EventSource(`${BASE_PATH}/api/events/stream`);
 | 
			
		||||
    es.addEventListener("containers-changed", e => setTimeout(this.fetchContainerList, 1000), false);
 | 
			
		||||
  },
 | 
			
		||||
  beforeDestroy() {
 | 
			
		||||
    if (es) {
 | 
			
		||||
      es.close();
 | 
			
		||||
      es = null;
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    async fetchContainerList() {
 | 
			
		||||
      this.containers = await (await fetch(`${BASE_PATH}/api/containers.json`)).json();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="css">
 | 
			
		||||
.section.is-fullwidth {
 | 
			
		||||
  padding: 0 !important;
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
.is-hidden-mobile.is-active {
 | 
			
		||||
  display: block !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.navbar-burger {
 | 
			
		||||
  height: 2.35rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
aside {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  z-index: 2;
 | 
			
		||||
  padding: 1em;
 | 
			
		||||
 | 
			
		||||
  @media screen and (min-width: 769px) {
 | 
			
		||||
    & {
 | 
			
		||||
      height: 100vh;
 | 
			
		||||
      overflow: auto;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  @media screen and (max-width: 768px) {
 | 
			
		||||
    & {
 | 
			
		||||
      position: sticky;
 | 
			
		||||
      top: 0;
 | 
			
		||||
      left: 0;
 | 
			
		||||
      right: 0;
 | 
			
		||||
      background: #222;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .menu-label {
 | 
			
		||||
      margin-top: 1em;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.hide-overflow {
 | 
			
		||||
  text-overflow: ellipsis;
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.burger.is-white {
 | 
			
		||||
  color: #fff;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										71
									
								
								assets/components/ScrollbarNotification.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								assets/components/ScrollbarNotification.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
<template lang="html">
 | 
			
		||||
  <transition name="fade">
 | 
			
		||||
    <button
 | 
			
		||||
      class="button scroll-notification"
 | 
			
		||||
      :class="hasNew ? 'is-warning' : 'is-primary'"
 | 
			
		||||
      @click="scrollToBottom"
 | 
			
		||||
      v-show="visible"
 | 
			
		||||
    >
 | 
			
		||||
      <span class="icon large"> <i class="fas fa-chevron-down"></i> </span>
 | 
			
		||||
    </button>
 | 
			
		||||
  </transition>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  props: ["messages"],
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      visible: false,
 | 
			
		||||
      hasNew: false
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    document.addEventListener("scroll", this.onScroll, { passive: true });
 | 
			
		||||
    setTimeout(() => this.scrollToBottom(), 500);
 | 
			
		||||
  },
 | 
			
		||||
  beforeDestroy() {
 | 
			
		||||
    document.removeEventListener("scroll", this.onScroll);
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    scrollToBottom() {
 | 
			
		||||
      this.visible = false;
 | 
			
		||||
      window.scrollTo(0, document.documentElement.scrollHeight || document.body.scrollHeight);
 | 
			
		||||
    },
 | 
			
		||||
    onScroll() {
 | 
			
		||||
      const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
 | 
			
		||||
      const scrollBottom =
 | 
			
		||||
        (document.documentElement.scrollHeight || document.body.scrollHeight) - document.documentElement.clientHeight;
 | 
			
		||||
      const diff = Math.abs(scrollTop - scrollBottom);
 | 
			
		||||
      this.visible = diff > 50;
 | 
			
		||||
      if (!this.visible) {
 | 
			
		||||
        this.hasNew = false;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  watch: {
 | 
			
		||||
    messages(newValue, oldValue) {
 | 
			
		||||
      if (this.visible) {
 | 
			
		||||
        this.hasNew = true;
 | 
			
		||||
      } else {
 | 
			
		||||
        this.scrollToBottom();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style scoped>
 | 
			
		||||
.scroll-notification {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  right: 40px;
 | 
			
		||||
  bottom: 30px;
 | 
			
		||||
}
 | 
			
		||||
.fade-enter-active,
 | 
			
		||||
.fade-leave-active {
 | 
			
		||||
  transition: opacity 0.15s ease-in;
 | 
			
		||||
}
 | 
			
		||||
.fade-enter,
 | 
			
		||||
.fade-leave-to {
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,19 +1,20 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="utf-8">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1">
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1" />
 | 
			
		||||
    <title>Dozzle</title>
 | 
			
		||||
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css">
 | 
			
		||||
    <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 }}";
 | 
			
		||||
    </script>
 | 
			
		||||
    <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
 | 
			
		||||
</head>
 | 
			
		||||
  </head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
    <section class="section is-fullwidth">
 | 
			
		||||
        <div id="app"></div>
 | 
			
		||||
    </section>
 | 
			
		||||
    <script src="/main.js"></script>
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
</html>
 | 
			
		||||
  <body class="is-dark">
 | 
			
		||||
    <div id="app"></div>
 | 
			
		||||
    <script src="main.js"></script>
 | 
			
		||||
  </body>
 | 
			
		||||
</html>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,19 @@
 | 
			
		||||
import Vue from "vue";
 | 
			
		||||
import VueRouter from "vue-router";
 | 
			
		||||
import vueHeadful from "vue-headful";
 | 
			
		||||
import App from "./App.vue";
 | 
			
		||||
import Index from "./pages/Index.vue";
 | 
			
		||||
import Container from "./pages/Container.vue";
 | 
			
		||||
import Index from "./pages/Index.vue";
 | 
			
		||||
 | 
			
		||||
Vue.use(VueRouter);
 | 
			
		||||
Vue.component("vue-headful", vueHeadful);
 | 
			
		||||
 | 
			
		||||
const routes = [
 | 
			
		||||
  { path: "/", component: Index },
 | 
			
		||||
  {
 | 
			
		||||
    path: "/",
 | 
			
		||||
    component: Index,
 | 
			
		||||
    name: "default"
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: "/container/:id",
 | 
			
		||||
    component: Container,
 | 
			
		||||
@@ -18,6 +24,7 @@ const routes = [
 | 
			
		||||
 | 
			
		||||
const router = new VueRouter({
 | 
			
		||||
  mode: "history",
 | 
			
		||||
  base: BASE_PATH + "/",
 | 
			
		||||
  routes
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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": "/"
 | 
			
		||||
}
 | 
			
		||||
@@ -1,16 +1,28 @@
 | 
			
		||||
<template lang="html">
 | 
			
		||||
    <ul ref="events" class="events"></ul>
 | 
			
		||||
  <div class="is-fullheight">
 | 
			
		||||
    <ul ref="events" class="events">
 | 
			
		||||
      <li v-for="item in messages" class="event" :key="item.key">
 | 
			
		||||
        <span class="date">{{ item.dateRelative }}</span> <span class="text">{{ item.message }}</span>
 | 
			
		||||
      </li>
 | 
			
		||||
    </ul>
 | 
			
		||||
    <scrollbar-notification :messages="messages"></scrollbar-notification>
 | 
			
		||||
    <vue-headful :title="title" />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import { formatRelative } from "date-fns";
 | 
			
		||||
let ws;
 | 
			
		||||
import ScrollbarNotification from "../components/ScrollbarNotification";
 | 
			
		||||
 | 
			
		||||
let es = null;
 | 
			
		||||
let nextId = 0;
 | 
			
		||||
const parseMessage = data => {
 | 
			
		||||
  const date = new Date(data.substring(0, 30));
 | 
			
		||||
  const dateRelative = formatRelative(date, new Date());
 | 
			
		||||
  const message = data.substring(30);
 | 
			
		||||
  const key = nextId++;
 | 
			
		||||
  return {
 | 
			
		||||
    key,
 | 
			
		||||
    date,
 | 
			
		||||
    dateRelative,
 | 
			
		||||
    message
 | 
			
		||||
@@ -18,48 +30,56 @@ const parseMessage = data => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  props: ["id"],
 | 
			
		||||
  props: ["id", "name"],
 | 
			
		||||
  name: "Container",
 | 
			
		||||
  mounted() {
 | 
			
		||||
    ws = new WebSocket(`ws://${window.location.host}/api/logs?id=${this.id}`);
 | 
			
		||||
    ws.onopen = e => console.log("Connection opened.");
 | 
			
		||||
    ws.onclose = e => console.log("Connection closed.");
 | 
			
		||||
    ws.onerror = e => console.error("Connection error: " + e.data);
 | 
			
		||||
    ws.onmessage = e => {
 | 
			
		||||
      const data = parseMessage(e.data);
 | 
			
		||||
      const parent = this.$refs.events;
 | 
			
		||||
      const item = document.createElement("li");
 | 
			
		||||
      item.className = "event";
 | 
			
		||||
 | 
			
		||||
      const date = document.createElement("span");
 | 
			
		||||
      date.className = "date";
 | 
			
		||||
      date.innerHTML = data.dateRelative;
 | 
			
		||||
      item.appendChild(date);
 | 
			
		||||
 | 
			
		||||
      const message = document.createElement("span");
 | 
			
		||||
      message.className = "text";
 | 
			
		||||
      message.innerHTML = data.message;
 | 
			
		||||
      item.appendChild(message);
 | 
			
		||||
 | 
			
		||||
      parent.appendChild(item);
 | 
			
		||||
 | 
			
		||||
      this.$nextTick(() => item.scrollIntoView());
 | 
			
		||||
  components: {
 | 
			
		||||
    ScrollbarNotification
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      messages: [],
 | 
			
		||||
      title: ""
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  created() {
 | 
			
		||||
    this.loadLogs(this.id);
 | 
			
		||||
  },
 | 
			
		||||
  beforeDestroy() {
 | 
			
		||||
    if (es) {
 | 
			
		||||
      es.close();
 | 
			
		||||
      es = null;
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  watch: {
 | 
			
		||||
    id(newValue, oldValue) {
 | 
			
		||||
      if (oldValue !== newValue) {
 | 
			
		||||
        this.loadLogs(newValue);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    loadLogs(id) {
 | 
			
		||||
      if (es) {
 | 
			
		||||
        es.close();
 | 
			
		||||
        es = null;
 | 
			
		||||
        this.messages = [];
 | 
			
		||||
      }
 | 
			
		||||
      es = new EventSource(`${BASE_PATH}/api/logs/stream?id=${id}`);
 | 
			
		||||
      es.onmessage = e => this.messages.push(parseMessage(e.data));
 | 
			
		||||
      this.title = `${this.name} - Dozzle`;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style>
 | 
			
		||||
<style scoped>
 | 
			
		||||
.events {
 | 
			
		||||
  color: #ddd;
 | 
			
		||||
  background-color: #111;
 | 
			
		||||
  padding: 10px;
 | 
			
		||||
  font-family: "Roboto Mono", monaco, monospace;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.event {
 | 
			
		||||
  font-family: monaco, monospace;
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
  font-size: 13px;
 | 
			
		||||
  line-height: 16px;
 | 
			
		||||
  padding: 0 15px 0 30px;
 | 
			
		||||
  word-wrap: break-word;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -67,4 +87,8 @@ export default {
 | 
			
		||||
  background-color: #262626;
 | 
			
		||||
  color: #258ccd;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.is-fullheight {
 | 
			
		||||
  min-height: 100vh;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,55 +1,22 @@
 | 
			
		||||
<template lang="html">
 | 
			
		||||
<div class="container">
 | 
			
		||||
    <div class="content">
 | 
			
		||||
        <section class="section">
 | 
			
		||||
            <ul class="is-marginless is-paddless">
 | 
			
		||||
                <li v-for="item in containers" class=" unstyled box">
 | 
			
		||||
                    <router-link :to="{name: 'container', params: {id: item.Id}}" class="columns">
 | 
			
		||||
                        <div class="column is-6">
 | 
			
		||||
                            <h2 class="is-2 hide-overflow">{{ item.Names[0] }}</h2>
 | 
			
		||||
                            <span class="subtitle is-6 code hide-overflow">{{ item.Command}}</span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="column is-4">
 | 
			
		||||
                            <span class="code hide-overflow">{{ item.Image }}</span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="column is-narrow">
 | 
			
		||||
                            <span class="subtitle is-7">{{ item.Status}}</span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </router-link>
 | 
			
		||||
                </li>
 | 
			
		||||
            </ul>
 | 
			
		||||
        </section>
 | 
			
		||||
  <div class="hero is-fullheight is-dark">
 | 
			
		||||
    <div class="hero-body">
 | 
			
		||||
      <div class="container has-text-centered">
 | 
			
		||||
        <h1 class="title">Please choose a container from the list to view the logs</h1>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  name: "Index",
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      containers: []
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  async created() {
 | 
			
		||||
    this.containers = await (await fetch(`/api/containers.json`)).json();
 | 
			
		||||
  }
 | 
			
		||||
  props: [],
 | 
			
		||||
  name: "Default"
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="css">
 | 
			
		||||
.hide-overflow {
 | 
			
		||||
  text-overflow: ellipsis;
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
.code {
 | 
			
		||||
  background-color: #f5f5f5;
 | 
			
		||||
  color: #ff3860;
 | 
			
		||||
  font-size: 0.875em;
 | 
			
		||||
  font-weight: 400;
 | 
			
		||||
  padding: 0.25em 0.5em 0.25em;
 | 
			
		||||
  display: block;
 | 
			
		||||
  border-radius: 2px;
 | 
			
		||||
<style scoped>
 | 
			
		||||
.hero.is-dark {
 | 
			
		||||
  color: #ddd;
 | 
			
		||||
  background-color: #111;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								assets/styles.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								assets/styles.scss
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
@charset "utf-8";
 | 
			
		||||
 | 
			
		||||
$menu-item-active-background-color: hsl(171, 100%, 41%);
 | 
			
		||||
$menu-item-color: hsl(0, 6%, 87%);
 | 
			
		||||
 | 
			
		||||
@import "../node_modules/bulma/bulma.sass";
 | 
			
		||||
 | 
			
		||||
.is-dark {
 | 
			
		||||
    color: #ddd;
 | 
			
		||||
    background-color: #111;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
    font-family: "Roboto", sans-serif;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1.title {
 | 
			
		||||
    font-family: "Gafata", sans-serif;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								demo.gif
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								demo.gif
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 41 MiB After Width: | Height: | Size: 24 MiB  | 
							
								
								
									
										125
									
								
								docker/client.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								docker/client.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,125 @@
 | 
			
		||||
package docker
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"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 dockerProxy
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sort.Slice(containers, func(i, j int) bool {
 | 
			
		||||
		return containers[i].Name < containers[j].Name
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	if containers == nil {
 | 
			
		||||
		containers = []Container{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return containers, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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{})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								docker/types.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								docker/types.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +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"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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=
 | 
			
		||||
							
								
								
									
										261
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										261
									
								
								main.go
									
									
									
									
									
								
							@@ -2,94 +2,223 @@ package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/binary"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/types"
 | 
			
		||||
	"github.com/docker/docker/client"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/amir20/dozzle/docker"
 | 
			
		||||
	"github.com/gobuffalo/packr"
 | 
			
		||||
	"github.com/gorilla/websocket"
 | 
			
		||||
	"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 (
 | 
			
		||||
	cli      *client.Client
 | 
			
		||||
	addr     = flag.String("addr", ":8080", "http service address")
 | 
			
		||||
	upgrader = websocket.Upgrader{}
 | 
			
		||||
	version  = "dev"
 | 
			
		||||
	commit   = "none"
 | 
			
		||||
	date     = "unknown"
 | 
			
		||||
	addr    = ""
 | 
			
		||||
	base    = ""
 | 
			
		||||
	level   = ""
 | 
			
		||||
	version = "dev"
 | 
			
		||||
	commit  = "none"
 | 
			
		||||
	date    = "unknown"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type handler struct {
 | 
			
		||||
	client docker.Client
 | 
			
		||||
	box    packr.Box
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	var err error
 | 
			
		||||
	cli, err = client.NewClientWithOpts(client.FromEnv)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	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() {
 | 
			
		||||
	dockerClient := docker.NewClient()
 | 
			
		||||
	_, err := dockerClient.ListContainers()
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatalf("Could not connect to Docker Engine: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	box := packr.NewBox("./static")
 | 
			
		||||
	http.HandleFunc("/api/containers.json", listContainers)
 | 
			
		||||
	http.HandleFunc("/api/logs", logs)
 | 
			
		||||
	http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
		fileServer := http.FileServer(box)
 | 
			
		||||
		if box.Has(req.URL.Path) {
 | 
			
		||||
			fileServer.ServeHTTP(w, req)
 | 
			
		||||
		} else {
 | 
			
		||||
			bytes, _ := box.Find("index.html")
 | 
			
		||||
			w.Write(bytes)
 | 
			
		||||
	r := createRoutes(base, &handler{dockerClient, box})
 | 
			
		||||
	srv := &http.Server{Addr: addr, Handler: r}
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		log.Infof("Accepting connections on %s", srv.Addr)
 | 
			
		||||
		if err := srv.ListenAndServe(); err != nil {
 | 
			
		||||
			log.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	}))
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	log.Fatal(http.ListenAndServe(*addr, nil))
 | 
			
		||||
	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 listContainers(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	json.NewEncoder(w).Encode(containers)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func logs(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	id := r.URL.Query().Get("id")
 | 
			
		||||
	c, err := upgrader.Upgrade(w, r, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer c.Close()
 | 
			
		||||
 | 
			
		||||
	options := types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: true, Tail: "500", Timestamps: true}
 | 
			
		||||
	reader, err := cli.ContainerLogs(context.Background(), id, options)
 | 
			
		||||
	defer reader.Close()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hdr := make([]byte, 8)
 | 
			
		||||
	content := make([]byte, 1024, 1024*1024)
 | 
			
		||||
	for {
 | 
			
		||||
		_, err := reader.Read(hdr)
 | 
			
		||||
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)
 | 
			
		||||
		}
 | 
			
		||||
		count := binary.BigEndian.Uint32(hdr[4:])
 | 
			
		||||
		n, err := reader.Read(content[:count])
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Println(err)
 | 
			
		||||
			break
 | 
			
		||||
 | 
			
		||||
		path := ""
 | 
			
		||||
		if base != "/" {
 | 
			
		||||
			path = base
 | 
			
		||||
		}
 | 
			
		||||
		err = c.WriteMessage(websocket.TextMessage, content[:n])
 | 
			
		||||
 | 
			
		||||
		data := struct{ Base string }{path}
 | 
			
		||||
		err = tmpl.Execute(w, data)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Println(err)
 | 
			
		||||
			break
 | 
			
		||||
			http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 (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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	f, ok := w.(http.Flusher)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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 (h *handler) 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 := r.Context()
 | 
			
		||||
	messages, err := h.client.Events(ctx)
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
 | 
			
		||||
				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 (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)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										522
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										522
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "dozzle",
 | 
			
		||||
  "version": "1.0.8",
 | 
			
		||||
  "version": "1.5.1",
 | 
			
		||||
  "lockfileVersion": 1,
 | 
			
		||||
  "requires": true,
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
@@ -14,40 +14,102 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@babel/core": {
 | 
			
		||||
      "version": "7.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.1.2.tgz",
 | 
			
		||||
      "integrity": "sha512-IFeSSnjXdhDaoysIlev//UzHZbdEmm7D0EIH2qtse9xK7mXEZQpYjs2P00XlP1qYsYvid79p+Zgg6tz1mp6iVw==",
 | 
			
		||||
      "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.2",
 | 
			
		||||
        "@babel/helpers": "^7.1.2",
 | 
			
		||||
        "@babel/parser": "^7.1.2",
 | 
			
		||||
        "@babel/generator": "^7.2.0",
 | 
			
		||||
        "@babel/helpers": "^7.2.0",
 | 
			
		||||
        "@babel/parser": "^7.2.0",
 | 
			
		||||
        "@babel/template": "^7.1.2",
 | 
			
		||||
        "@babel/traverse": "^7.1.0",
 | 
			
		||||
        "@babel/types": "^7.1.2",
 | 
			
		||||
        "@babel/traverse": "^7.1.6",
 | 
			
		||||
        "@babel/types": "^7.2.0",
 | 
			
		||||
        "convert-source-map": "^1.1.0",
 | 
			
		||||
        "debug": "^3.1.0",
 | 
			
		||||
        "json5": "^0.5.0",
 | 
			
		||||
        "debug": "^4.1.0",
 | 
			
		||||
        "json5": "^2.1.0",
 | 
			
		||||
        "lodash": "^4.17.10",
 | 
			
		||||
        "resolve": "^1.3.2",
 | 
			
		||||
        "semver": "^5.4.1",
 | 
			
		||||
        "source-map": "^0.5.0"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@babel/generator": {
 | 
			
		||||
          "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.2.0",
 | 
			
		||||
            "jsesc": "^2.5.1",
 | 
			
		||||
            "lodash": "^4.17.10",
 | 
			
		||||
            "source-map": "^0.5.0",
 | 
			
		||||
            "trim-right": "^1.0.1"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "@babel/parser": {
 | 
			
		||||
          "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": {
 | 
			
		||||
          "version": "7.1.6",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.1.6.tgz",
 | 
			
		||||
          "integrity": "sha512-CXedit6GpISz3sC2k2FsGCUpOhUqKdyL0lqNrImQojagnUMXf8hex4AxYFRuMkNGcvJX5QAFGzB5WJQmSv8SiQ==",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "@babel/code-frame": "^7.0.0",
 | 
			
		||||
            "@babel/generator": "^7.1.6",
 | 
			
		||||
            "@babel/helper-function-name": "^7.1.0",
 | 
			
		||||
            "@babel/helper-split-export-declaration": "^7.0.0",
 | 
			
		||||
            "@babel/parser": "^7.1.6",
 | 
			
		||||
            "@babel/types": "^7.1.6",
 | 
			
		||||
            "debug": "^4.1.0",
 | 
			
		||||
            "globals": "^11.1.0",
 | 
			
		||||
            "lodash": "^4.17.10"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "@babel/types": {
 | 
			
		||||
          "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",
 | 
			
		||||
            "lodash": "^4.17.10",
 | 
			
		||||
            "to-fast-properties": "^2.0.0"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "debug": {
 | 
			
		||||
          "version": "3.2.6",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
 | 
			
		||||
          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
 | 
			
		||||
          "version": "4.1.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz",
 | 
			
		||||
          "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "ms": "^2.1.1"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "jsesc": {
 | 
			
		||||
          "version": "2.5.2",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
 | 
			
		||||
          "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "json5": {
 | 
			
		||||
          "version": "0.5.1",
 | 
			
		||||
          "resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
 | 
			
		||||
          "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
 | 
			
		||||
          "version": "2.1.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz",
 | 
			
		||||
          "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "minimist": "^1.2.0"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "minimist": {
 | 
			
		||||
          "version": "1.2.0",
 | 
			
		||||
          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
 | 
			
		||||
          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "ms": {
 | 
			
		||||
@@ -55,6 +117,12 @@
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
 | 
			
		||||
          "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "to-fast-properties": {
 | 
			
		||||
          "version": "2.0.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
 | 
			
		||||
          "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
@@ -282,14 +350,90 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@babel/helpers": {
 | 
			
		||||
      "version": "7.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.1.2.tgz",
 | 
			
		||||
      "integrity": "sha512-Myc3pUE8eswD73aWcartxB16K6CGmHDv9KxOmD2CeOs/FaEAQodr3VYGmlvOmog60vNQ2w8QbatuahepZwrHiA==",
 | 
			
		||||
      "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.0",
 | 
			
		||||
        "@babel/types": "^7.1.2"
 | 
			
		||||
        "@babel/traverse": "^7.1.5",
 | 
			
		||||
        "@babel/types": "^7.2.0"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@babel/generator": {
 | 
			
		||||
          "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.2.0",
 | 
			
		||||
            "jsesc": "^2.5.1",
 | 
			
		||||
            "lodash": "^4.17.10",
 | 
			
		||||
            "source-map": "^0.5.0",
 | 
			
		||||
            "trim-right": "^1.0.1"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "@babel/parser": {
 | 
			
		||||
          "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": {
 | 
			
		||||
          "version": "7.1.6",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.1.6.tgz",
 | 
			
		||||
          "integrity": "sha512-CXedit6GpISz3sC2k2FsGCUpOhUqKdyL0lqNrImQojagnUMXf8hex4AxYFRuMkNGcvJX5QAFGzB5WJQmSv8SiQ==",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "@babel/code-frame": "^7.0.0",
 | 
			
		||||
            "@babel/generator": "^7.1.6",
 | 
			
		||||
            "@babel/helper-function-name": "^7.1.0",
 | 
			
		||||
            "@babel/helper-split-export-declaration": "^7.0.0",
 | 
			
		||||
            "@babel/parser": "^7.1.6",
 | 
			
		||||
            "@babel/types": "^7.1.6",
 | 
			
		||||
            "debug": "^4.1.0",
 | 
			
		||||
            "globals": "^11.1.0",
 | 
			
		||||
            "lodash": "^4.17.10"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "@babel/types": {
 | 
			
		||||
          "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",
 | 
			
		||||
            "lodash": "^4.17.10",
 | 
			
		||||
            "to-fast-properties": "^2.0.0"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "debug": {
 | 
			
		||||
          "version": "4.1.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz",
 | 
			
		||||
          "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "ms": "^2.1.1"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "jsesc": {
 | 
			
		||||
          "version": "2.5.2",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
 | 
			
		||||
          "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "ms": {
 | 
			
		||||
          "version": "2.1.1",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
 | 
			
		||||
          "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "to-fast-properties": {
 | 
			
		||||
          "version": "2.0.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
 | 
			
		||||
          "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@babel/highlight": {
 | 
			
		||||
@@ -737,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",
 | 
			
		||||
@@ -986,6 +1130,58 @@
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@iamstarkov/listr-update-renderer": {
 | 
			
		||||
      "version": "0.4.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@iamstarkov/listr-update-renderer/-/listr-update-renderer-0.4.1.tgz",
 | 
			
		||||
      "integrity": "sha512-IJyxQWsYDEkf8C8QthBn5N8tIUR9V9je6j3sMIpAkonaadjbvxmRC6RAhpa3RKxndhNnU2M6iNbtJwd7usQYIA==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "chalk": "^1.1.3",
 | 
			
		||||
        "cli-truncate": "^0.2.1",
 | 
			
		||||
        "elegant-spinner": "^1.0.1",
 | 
			
		||||
        "figures": "^1.7.0",
 | 
			
		||||
        "indent-string": "^3.0.0",
 | 
			
		||||
        "log-symbols": "^1.0.2",
 | 
			
		||||
        "log-update": "^2.3.0",
 | 
			
		||||
        "strip-ansi": "^3.0.1"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "ansi-styles": {
 | 
			
		||||
          "version": "2.2.1",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
 | 
			
		||||
          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "chalk": {
 | 
			
		||||
          "version": "1.1.3",
 | 
			
		||||
          "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
 | 
			
		||||
          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "ansi-styles": "^2.2.1",
 | 
			
		||||
            "escape-string-regexp": "^1.0.2",
 | 
			
		||||
            "has-ansi": "^2.0.0",
 | 
			
		||||
            "strip-ansi": "^3.0.0",
 | 
			
		||||
            "supports-color": "^2.0.0"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "log-symbols": {
 | 
			
		||||
          "version": "1.0.2",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz",
 | 
			
		||||
          "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "chalk": "^1.0.0"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "supports-color": {
 | 
			
		||||
          "version": "2.0.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
 | 
			
		||||
          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@mrmlnc/readdir-enhanced": {
 | 
			
		||||
      "version": "2.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
 | 
			
		||||
@@ -1622,6 +1818,11 @@
 | 
			
		||||
      "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "bulma": {
 | 
			
		||||
      "version": "0.7.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/bulma/-/bulma-0.7.2.tgz",
 | 
			
		||||
      "integrity": "sha512-6JHEu8U/1xsyOst/El5ImLcZIiE2JFXgvrz8GGWbnDLwTNRPJzdAM0aoUM1Ns0avALcVb6KZz9NhzmU53dGDcQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "cache-base": {
 | 
			
		||||
      "version": "1.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
 | 
			
		||||
@@ -1646,9 +1847,9 @@
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "camelcase": {
 | 
			
		||||
      "version": "4.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
 | 
			
		||||
      "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
 | 
			
		||||
      "version": "5.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "caniuse-api": {
 | 
			
		||||
@@ -2024,16 +2225,16 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "concurrently": {
 | 
			
		||||
      "version": "4.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-4.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-D8UI+mlI/bfvrA57SeKOht6sEpb01dKk+8Yee4fbnkk1Ue8r3S+JXoEdFZIpzQlXJGtnxo47Wvvg/kG4ba3U6Q==",
 | 
			
		||||
      "version": "4.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-4.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-pwzXCE7qtOB346LyO9eFWpkFJVO3JQZ/qU/feGeaAHiX1M3Rw3zgXKc5cZ8vSH5DGygkjzLFDzA/pwoQDkRNGg==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "chalk": "^2.4.1",
 | 
			
		||||
        "date-fns": "^1.23.0",
 | 
			
		||||
        "lodash": "^4.17.10",
 | 
			
		||||
        "read-pkg": "^4.0.1",
 | 
			
		||||
        "rxjs": "6.2.2",
 | 
			
		||||
        "rxjs": "^6.3.3",
 | 
			
		||||
        "spawn-command": "^0.0.2-1",
 | 
			
		||||
        "supports-color": "^4.5.0",
 | 
			
		||||
        "tree-kill": "^1.1.0",
 | 
			
		||||
@@ -2449,8 +2650,7 @@
 | 
			
		||||
    "date-fns": {
 | 
			
		||||
      "version": "2.0.0-alpha.25",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.0.0-alpha.25.tgz",
 | 
			
		||||
      "integrity": "sha512-iQzJkHF0L4wah9Ae9PkvwemwFz6qmRLuNZcghmvf2t+ptLs1qXzONLiGtjmPQzL6+JpC01JjlTopY2AEy4NFAg==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
      "integrity": "sha512-iQzJkHF0L4wah9Ae9PkvwemwFz6qmRLuNZcghmvf2t+ptLs1qXzONLiGtjmPQzL6+JpC01JjlTopY2AEy4NFAg=="
 | 
			
		||||
    },
 | 
			
		||||
    "date-now": {
 | 
			
		||||
      "version": "0.1.4",
 | 
			
		||||
@@ -2902,12 +3102,6 @@
 | 
			
		||||
        "strip-eof": "^1.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "exit-hook": {
 | 
			
		||||
      "version": "1.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz",
 | 
			
		||||
      "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "expand-brackets": {
 | 
			
		||||
      "version": "2.1.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
 | 
			
		||||
@@ -3794,6 +3988,12 @@
 | 
			
		||||
      "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "globals": {
 | 
			
		||||
      "version": "11.9.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/globals/-/globals-11.9.0.tgz",
 | 
			
		||||
      "integrity": "sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "globby": {
 | 
			
		||||
      "version": "6.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
 | 
			
		||||
@@ -3925,6 +4125,11 @@
 | 
			
		||||
      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "headful": {
 | 
			
		||||
      "version": "1.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/headful/-/headful-1.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-vF9Vfddn1QWmziliht2mji6ayI78+hUuSC+Kt0GEqLw/51zWgi1KF7oLtIQf3nlkg8sQQOlznkkIaF4W9lIt9w=="
 | 
			
		||||
    },
 | 
			
		||||
    "hex-color-regex": {
 | 
			
		||||
      "version": "1.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz",
 | 
			
		||||
@@ -4443,13 +4648,13 @@
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "husky": {
 | 
			
		||||
      "version": "1.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/husky/-/husky-1.1.2.tgz",
 | 
			
		||||
      "integrity": "sha512-9TdkUpBeEOjz0AnFdUN4i3w8kEbOsVs9/WSeJqWLq2OO6bcKQhVW64Zi+pVd/AMRLpN3QTINb6ZXiELczvdmqQ==",
 | 
			
		||||
      "version": "1.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/husky/-/husky-1.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-/ib3+iycykXC0tYIxsyqierikVa9DA2DrT32UEirqNEFVqOj1bFMTgP3jAz8HM7FgC/C8pc/BTUa9MV2GEkZaA==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "cosmiconfig": "^5.0.6",
 | 
			
		||||
        "execa": "^0.9.0",
 | 
			
		||||
        "execa": "^1.0.0",
 | 
			
		||||
        "find-up": "^3.0.0",
 | 
			
		||||
        "get-stdin": "^6.0.0",
 | 
			
		||||
        "is-ci": "^1.2.1",
 | 
			
		||||
@@ -4460,25 +4665,14 @@
 | 
			
		||||
        "slash": "^2.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "cross-spawn": {
 | 
			
		||||
          "version": "5.1.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
 | 
			
		||||
          "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "lru-cache": "^4.0.1",
 | 
			
		||||
            "shebang-command": "^1.2.0",
 | 
			
		||||
            "which": "^1.2.9"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "execa": {
 | 
			
		||||
          "version": "0.9.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/execa/-/execa-0.9.0.tgz",
 | 
			
		||||
          "integrity": "sha512-BbUMBiX4hqiHZUA5+JujIjNb6TyAlp2D5KLheMjMluwOuzcnylDL4AxZYLLn1n2AGB49eSWwyKvvEQoRpnAtmA==",
 | 
			
		||||
          "version": "1.0.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
 | 
			
		||||
          "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "cross-spawn": "^5.0.1",
 | 
			
		||||
            "get-stream": "^3.0.0",
 | 
			
		||||
            "cross-spawn": "^6.0.0",
 | 
			
		||||
            "get-stream": "^4.0.0",
 | 
			
		||||
            "is-stream": "^1.1.0",
 | 
			
		||||
            "npm-run-path": "^2.0.0",
 | 
			
		||||
            "p-finally": "^1.0.0",
 | 
			
		||||
@@ -4486,11 +4680,14 @@
 | 
			
		||||
            "strip-eof": "^1.0.0"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "slash": {
 | 
			
		||||
          "version": "2.0.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
 | 
			
		||||
          "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        "get-stream": {
 | 
			
		||||
          "version": "4.1.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
 | 
			
		||||
          "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "pump": "^3.0.0"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
@@ -4998,14 +5195,15 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "lint-staged": {
 | 
			
		||||
      "version": "8.0.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-8.0.4.tgz",
 | 
			
		||||
      "integrity": "sha512-Rs0VxXoyFqHMrPQgKAMy+O907+m5Po71UVPhBi7BUBwU7ZZ2aoc+mZmpOX3DVPCoTcy6+hqJa9yIZfacNpJHdg==",
 | 
			
		||||
      "version": "8.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-8.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-yfSkyJy7EuVsaoxtUSEhrD81spdJOe/gMTGea3XaV7HyoRhTb9Gdlp6/JppRZERvKSEYXP9bjcmq6CA5oL2lYQ==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@iamstarkov/listr-update-renderer": "0.4.1",
 | 
			
		||||
        "chalk": "^2.3.1",
 | 
			
		||||
        "commander": "^2.14.1",
 | 
			
		||||
        "cosmiconfig": "^5.0.2",
 | 
			
		||||
        "cosmiconfig": "5.0.6",
 | 
			
		||||
        "debug": "^3.1.0",
 | 
			
		||||
        "dedent": "^0.7.0",
 | 
			
		||||
        "del": "^3.0.0",
 | 
			
		||||
@@ -5016,7 +5214,6 @@
 | 
			
		||||
        "is-windows": "^1.0.2",
 | 
			
		||||
        "jest-validate": "^23.5.0",
 | 
			
		||||
        "listr": "^0.14.2",
 | 
			
		||||
        "listr-update-renderer": "https://github.com/okonet/listr-update-renderer/tarball/upgrade-log-update",
 | 
			
		||||
        "lodash": "^4.17.5",
 | 
			
		||||
        "log-symbols": "^2.2.0",
 | 
			
		||||
        "micromatch": "^3.1.8",
 | 
			
		||||
@@ -5072,9 +5269,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "listr": {
 | 
			
		||||
      "version": "0.14.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.2.tgz",
 | 
			
		||||
      "integrity": "sha512-vmaNJ1KlGuGWShHI35X/F8r9xxS0VTHh9GejVXwSN20fG5xpq3Jh4bJbnumoT6q5EDM/8/YP1z3YMtQbFmhuXw==",
 | 
			
		||||
      "version": "0.14.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz",
 | 
			
		||||
      "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@samverschueren/stream-to-observable": "^0.3.0",
 | 
			
		||||
@@ -5082,10 +5279,18 @@
 | 
			
		||||
        "is-promise": "^2.1.0",
 | 
			
		||||
        "is-stream": "^1.1.0",
 | 
			
		||||
        "listr-silent-renderer": "^1.1.1",
 | 
			
		||||
        "listr-update-renderer": "^0.4.0",
 | 
			
		||||
        "listr-verbose-renderer": "^0.4.0",
 | 
			
		||||
        "p-map": "^1.1.1",
 | 
			
		||||
        "rxjs": "^6.1.0"
 | 
			
		||||
        "listr-update-renderer": "^0.5.0",
 | 
			
		||||
        "listr-verbose-renderer": "^0.5.0",
 | 
			
		||||
        "p-map": "^2.0.0",
 | 
			
		||||
        "rxjs": "^6.3.3"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "p-map": {
 | 
			
		||||
          "version": "2.0.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.0.0.tgz",
 | 
			
		||||
          "integrity": "sha512-GO107XdrSUmtHxVoi60qc9tUl/KkNKm+X2CF4P9amalpGxv5YqVPJNfSb0wcA+syCopkZvYYIzW8OVTQW59x/w==",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "listr-silent-renderer": {
 | 
			
		||||
@@ -5095,8 +5300,9 @@
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "listr-update-renderer": {
 | 
			
		||||
      "version": "https://github.com/okonet/listr-update-renderer/tarball/upgrade-log-update",
 | 
			
		||||
      "integrity": "sha512-YF5bCQPbpiVDh/Ali3O5gmBYnvmNIcNZKBq0hueOqYum8T/+VR1gCLgLXmRs2OpPsVAzdsENQO0BJCyFt9FjKA==",
 | 
			
		||||
      "version": "0.5.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz",
 | 
			
		||||
      "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "chalk": "^1.1.3",
 | 
			
		||||
@@ -5146,72 +5352,31 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "listr-verbose-renderer": {
 | 
			
		||||
      "version": "0.4.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz",
 | 
			
		||||
      "integrity": "sha1-ggb0z21S3cWCfl/RSYng6WWTOjU=",
 | 
			
		||||
      "version": "0.5.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz",
 | 
			
		||||
      "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "chalk": "^1.1.3",
 | 
			
		||||
        "cli-cursor": "^1.0.2",
 | 
			
		||||
        "chalk": "^2.4.1",
 | 
			
		||||
        "cli-cursor": "^2.1.0",
 | 
			
		||||
        "date-fns": "^1.27.2",
 | 
			
		||||
        "figures": "^1.7.0"
 | 
			
		||||
        "figures": "^2.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "ansi-styles": {
 | 
			
		||||
          "version": "2.2.1",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
 | 
			
		||||
          "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "chalk": {
 | 
			
		||||
          "version": "1.1.3",
 | 
			
		||||
          "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
 | 
			
		||||
          "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "ansi-styles": "^2.2.1",
 | 
			
		||||
            "escape-string-regexp": "^1.0.2",
 | 
			
		||||
            "has-ansi": "^2.0.0",
 | 
			
		||||
            "strip-ansi": "^3.0.0",
 | 
			
		||||
            "supports-color": "^2.0.0"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "cli-cursor": {
 | 
			
		||||
          "version": "1.0.2",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
 | 
			
		||||
          "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "restore-cursor": "^1.0.1"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "date-fns": {
 | 
			
		||||
          "version": "1.29.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.29.0.tgz",
 | 
			
		||||
          "integrity": "sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw==",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "onetime": {
 | 
			
		||||
          "version": "1.1.0",
 | 
			
		||||
          "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
 | 
			
		||||
          "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "restore-cursor": {
 | 
			
		||||
          "version": "1.0.1",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz",
 | 
			
		||||
          "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=",
 | 
			
		||||
        "figures": {
 | 
			
		||||
          "version": "2.0.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
 | 
			
		||||
          "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "exit-hook": "^1.0.0",
 | 
			
		||||
            "onetime": "^1.0.0"
 | 
			
		||||
            "escape-string-regexp": "^1.0.5"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "supports-color": {
 | 
			
		||||
          "version": "2.0.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
 | 
			
		||||
          "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
@@ -5331,9 +5496,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "map-age-cleaner": {
 | 
			
		||||
      "version": "0.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.2.tgz",
 | 
			
		||||
      "integrity": "sha512-UN1dNocxQq44IhJyMI4TU8phc2m9BddacHRPRjKGLYaF0jqd3xLz0jS0skpAU9WgYyoR4gHtUpzytNBS385FWQ==",
 | 
			
		||||
      "version": "0.1.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
 | 
			
		||||
      "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "p-defer": "^1.0.0"
 | 
			
		||||
@@ -7515,9 +7680,9 @@
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "prettier": {
 | 
			
		||||
      "version": "1.14.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.14.3.tgz",
 | 
			
		||||
      "integrity": "sha512-qZDVnCrnpsRJJq5nSsiHCE3BYMED2OtsI+cmzIzF1QIfqm5ALf8tEJcO27zV1gKNKRPdhjO0dNWnrzssDQ1tFg==",
 | 
			
		||||
      "version": "1.15.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.15.3.tgz",
 | 
			
		||||
      "integrity": "sha512-gAU9AGAPMaKb3NNSUUuhhFAS7SCO4ALTN4nRIn6PJ075Qd28Yn2Ig2ahEJWdJwJmlEBTUfC7mMUSFy8MwsOCfg==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "pretty-format": {
 | 
			
		||||
@@ -7857,9 +8022,9 @@
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "rxjs": {
 | 
			
		||||
      "version": "6.2.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.2.tgz",
 | 
			
		||||
      "integrity": "sha512-0MI8+mkKAXZUF9vMrEoPnaoHkfzBPP4IGwUYRJhIRJF6/w3uByO1e91bEHn8zd43RdkTMKiooYKmwz7RH6zfOQ==",
 | 
			
		||||
      "version": "6.3.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz",
 | 
			
		||||
      "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "tslib": "^1.9.0"
 | 
			
		||||
@@ -7889,6 +8054,15 @@
 | 
			
		||||
        "clones": "^1.1.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "sass": {
 | 
			
		||||
      "version": "1.15.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/sass/-/sass-1.15.1.tgz",
 | 
			
		||||
      "integrity": "sha512-WiDa5BsePB/rQEbh/Fv2pVDUCasxuRYjW7GsWx8Ld23LY61vx1VV5Mzf/7mu5kLWKMryMqo65fzYL34HgaM47w==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "chokidar": "^2.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "sax": {
 | 
			
		||||
      "version": "1.2.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
 | 
			
		||||
@@ -8035,9 +8209,9 @@
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "simple-git": {
 | 
			
		||||
      "version": "1.106.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-1.106.0.tgz",
 | 
			
		||||
      "integrity": "sha512-LaxKq4X9Om7bb16Cpinc36hT1YLHMM9KDQMSWJVv4Y1TGDEUuZbs+0lAk2JSKkCEO3xFjcMSx5OjvZo+i4eJvQ==",
 | 
			
		||||
      "version": "1.107.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-1.107.0.tgz",
 | 
			
		||||
      "integrity": "sha512-t4OK1JRlp4ayKRfcW6owrWcRVLyHRUlhGd0uN6ZZTqfDq8a5XpcUdOKiGRNobHEuMtNqzp0vcJNvhYWwh5PsQA==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "debug": "^4.0.1"
 | 
			
		||||
@@ -8077,6 +8251,12 @@
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "slash": {
 | 
			
		||||
      "version": "2.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "slice-ansi": {
 | 
			
		||||
      "version": "0.0.4",
 | 
			
		||||
      "resolved": "http://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz",
 | 
			
		||||
@@ -8674,9 +8854,9 @@
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "tree-kill": {
 | 
			
		||||
      "version": "1.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-DlX6dR0lOIRDFxI0mjL9IYg6OTncLm/Zt+JiBhE5OlFcAR8yc9S7FFXU9so0oda47frdM/JFsk7UjNt9vscKcg==",
 | 
			
		||||
      "version": "1.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz",
 | 
			
		||||
      "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "trim-right": {
 | 
			
		||||
@@ -8946,6 +9126,14 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/vue/-/vue-2.5.17.tgz",
 | 
			
		||||
      "integrity": "sha512-mFbcWoDIJi0w0Za4emyLiW72Jae0yjANHbCVquMKijcavBGypqlF7zHRgMa5k4sesdv7hv2rB4JPdZfR+TPfhQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "vue-headful": {
 | 
			
		||||
      "version": "2.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/vue-headful/-/vue-headful-2.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-h2G/jXCi2hAx6O3gwWN8uTj1eQlSKNHgvkCVZcokZneGczWCRghAUCFYrOvZQM+F+SyFB3YXqoI62rE0Sc8QsA==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "headful": "^1.0.3"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "vue-hot-reload-api": {
 | 
			
		||||
      "version": "2.3.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.1.tgz",
 | 
			
		||||
@@ -8953,9 +9141,9 @@
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "vue-router": {
 | 
			
		||||
      "version": "3.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-vLLoY452L+JBpALMP5UHum9+7nzR9PeIBCghU9ZtJ1eWm6ieUI8Zb/DI3MYxH32bxkjzYV1LRjNv4qr8d+uX/w=="
 | 
			
		||||
      "version": "3.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-opKtsxjp9eOcFWdp6xLQPLmRGgfM932Tl56U9chYTnoWqKxQ8M20N7AkdEbM5beUh6wICoFGYugAX9vQjyJLFg=="
 | 
			
		||||
    },
 | 
			
		||||
    "vue-template-compiler": {
 | 
			
		||||
      "version": "2.5.17",
 | 
			
		||||
@@ -9056,12 +9244,6 @@
 | 
			
		||||
        "async-limiter": "~1.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "xregexp": {
 | 
			
		||||
      "version": "4.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "xtend": {
 | 
			
		||||
      "version": "4.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
 | 
			
		||||
@@ -9081,13 +9263,13 @@
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "yargs": {
 | 
			
		||||
      "version": "12.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==",
 | 
			
		||||
      "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",
 | 
			
		||||
        "decamelize": "^2.0.0",
 | 
			
		||||
        "decamelize": "^1.2.0",
 | 
			
		||||
        "find-up": "^3.0.0",
 | 
			
		||||
        "get-caller-file": "^1.0.1",
 | 
			
		||||
        "os-locale": "^3.0.0",
 | 
			
		||||
@@ -9097,27 +9279,17 @@
 | 
			
		||||
        "string-width": "^2.0.0",
 | 
			
		||||
        "which-module": "^2.0.0",
 | 
			
		||||
        "y18n": "^3.2.1 || ^4.0.0",
 | 
			
		||||
        "yargs-parser": "^10.1.0"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "decamelize": {
 | 
			
		||||
          "version": "2.0.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz",
 | 
			
		||||
          "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "xregexp": "4.0.0"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        "yargs-parser": "^11.1.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "yargs-parser": {
 | 
			
		||||
      "version": "10.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==",
 | 
			
		||||
      "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": "^4.1.0"
 | 
			
		||||
        "camelcase": "^5.0.0",
 | 
			
		||||
        "decamelize": "^1.2.0"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										109
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										109
									
								
								package.json
									
									
									
									
									
								
							@@ -1,54 +1,61 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "dozzle",
 | 
			
		||||
  "version": "1.0.8",
 | 
			
		||||
  "description": "",
 | 
			
		||||
  "main": "index.js",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "start": "concurrently 'go run main.go' 'npm run watch-assets'",
 | 
			
		||||
    "watch-assets": "parcel watch assets/index.html -d static",
 | 
			
		||||
    "build": "parcel build assets/index.html -d static",
 | 
			
		||||
    "clean": "rm -rf static"
 | 
			
		||||
  },
 | 
			
		||||
  "repository": {
 | 
			
		||||
    "type": "git",
 | 
			
		||||
    "url": "git+https://github.com/amir20/dozzle.git"
 | 
			
		||||
  },
 | 
			
		||||
  "author": "",
 | 
			
		||||
  "license": "ISC",
 | 
			
		||||
  "bugs": {
 | 
			
		||||
    "url": "https://github.com/amir20/dozzle/issues"
 | 
			
		||||
  },
 | 
			
		||||
  "homepage": "https://github.com/amir20/dozzle#readme",
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "vue": "^2.5.17",
 | 
			
		||||
    "vue-router": "^3.0.1"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@babel/core": "^7.1.2",
 | 
			
		||||
    "@babel/plugin-transform-runtime": "^7.1.0",
 | 
			
		||||
    "@vue/component-compiler-utils": "^2.3.0",
 | 
			
		||||
    "concurrently": "^4.0.1",
 | 
			
		||||
    "date-fns": "^2.0.0-alpha.25",
 | 
			
		||||
    "husky": "^1.1.2",
 | 
			
		||||
    "lint-staged": "^8.0.4",
 | 
			
		||||
    "parcel-bundler": "^1.10.3",
 | 
			
		||||
    "prettier": "^1.14.3",
 | 
			
		||||
    "vue-hot-reload-api": "^2.3.1",
 | 
			
		||||
    "vue-template-compiler": "^2.5.17"
 | 
			
		||||
  },
 | 
			
		||||
  "husky": {
 | 
			
		||||
    "hooks": {
 | 
			
		||||
      "pre-commit": "lint-staged"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "lint-staged": {
 | 
			
		||||
    "*.{js,vue,css}": [
 | 
			
		||||
      "prettier --write",
 | 
			
		||||
      "git add"
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  "browserslist": [
 | 
			
		||||
    ">5%",
 | 
			
		||||
    "not ie <= 8"
 | 
			
		||||
 "name": "dozzle",
 | 
			
		||||
 "version": "1.5.1",
 | 
			
		||||
 "description": "",
 | 
			
		||||
 "main": "index.js",
 | 
			
		||||
 "scripts": {
 | 
			
		||||
  "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/ a_main-packr.go",
 | 
			
		||||
  "release": "goreleaser --rm-dist"
 | 
			
		||||
 },
 | 
			
		||||
 "repository": {
 | 
			
		||||
  "type": "git",
 | 
			
		||||
  "url": "git+https://github.com/amir20/dozzle.git"
 | 
			
		||||
 },
 | 
			
		||||
 "author": "",
 | 
			
		||||
 "license": "ISC",
 | 
			
		||||
 "bugs": {
 | 
			
		||||
  "url": "https://github.com/amir20/dozzle/issues"
 | 
			
		||||
 },
 | 
			
		||||
 "homepage": "https://github.com/amir20/dozzle#readme",
 | 
			
		||||
 "dependencies": {
 | 
			
		||||
  "bulma": "^0.7.2",
 | 
			
		||||
  "date-fns": "^2.0.0-alpha.25",
 | 
			
		||||
  "vue": "^2.5.17",
 | 
			
		||||
  "vue-headful": "^2.0.1",
 | 
			
		||||
  "vue-router": "^3.0.2"
 | 
			
		||||
 },
 | 
			
		||||
 "devDependencies": {
 | 
			
		||||
  "@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.3",
 | 
			
		||||
  "sass": "^1.15.1",
 | 
			
		||||
  "vue-hot-reload-api": "^2.3.1",
 | 
			
		||||
  "vue-template-compiler": "^2.5.17"
 | 
			
		||||
 },
 | 
			
		||||
 "husky": {
 | 
			
		||||
  "hooks": {
 | 
			
		||||
   "pre-commit": "lint-staged"
 | 
			
		||||
  }
 | 
			
		||||
 },
 | 
			
		||||
 "lint-staged": {
 | 
			
		||||
  "*.{js,vue,css}": [
 | 
			
		||||
   "prettier --write",
 | 
			
		||||
   "git add"
 | 
			
		||||
  ]
 | 
			
		||||
 },
 | 
			
		||||
 "browserslist": [
 | 
			
		||||
  ">5%",
 | 
			
		||||
  "not ie <= 8"
 | 
			
		||||
 ]
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user