Compare commits
	
		
			41 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					2325881bd8 | ||
| 
						 | 
					067fea2b7a | ||
| 
						 | 
					8ac689ca57 | ||
| 
						 | 
					5e9ffe7fcf | ||
| 
						 | 
					3b3ba92d27 | ||
| 
						 | 
					df2834fd81 | ||
| 
						 | 
					2ecfefb35f | ||
| 
						 | 
					d18d3f800b | ||
| 
						 | 
					9d7fd4eaf0 | ||
| 
						 | 
					032ebfd307 | ||
| 
						 | 
					7f74a0f551 | ||
| 
						 | 
					dc42180339 | ||
| 
						 | 
					972cbb8b2e | ||
| 
						 | 
					5ee895357d | ||
| 
						 | 
					d7cfe64273 | ||
| 
						 | 
					f06354f909 | ||
| 
						 | 
					0416fd541c | ||
| 
						 | 
					98701b1c7c | ||
| 
						 | 
					17e08c02bb | ||
| 
						 | 
					22108a2782 | ||
| 
						 | 
					742056bbef | ||
| 
						 | 
					2556dd07b3 | ||
| 
						 | 
					e43879b69c | ||
| 
						 | 
					239bd874b2 | ||
| 
						 | 
					c9b8b3f95a | ||
| 
						 | 
					3454e907d3 | ||
| 
						 | 
					d33376e03b | ||
| 
						 | 
					f045f8bc95 | ||
| 
						 | 
					3e5f174f6e | ||
| 
						 | 
					34a21463b5 | ||
| 
						 | 
					02fc893d4b | ||
| 
						 | 
					7c342e17a1 | ||
| 
						 | 
					8e23f61220 | ||
| 
						 | 
					4a65020cbf | ||
| 
						 | 
					f59aa6bfaa | ||
| 
						 | 
					ef78f94cb5 | ||
| 
						 | 
					1ed4dde60e | ||
| 
						 | 
					959966d3eb | ||
| 
						 | 
					07ffc18770 | ||
| 
						 | 
					9773d39655 | ||
| 
						 | 
					b41f755c08 | 
							
								
								
									
										12
									
								
								.babelrc
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								.babelrc
									
									
									
									
									
								
							@@ -1,10 +1,16 @@
 | 
			
		||||
{
 | 
			
		||||
  "presets": ["env"],
 | 
			
		||||
  "presets": [
 | 
			
		||||
    [
 | 
			
		||||
      "@babel/preset-env",
 | 
			
		||||
      {
 | 
			
		||||
        "modules": false
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  ],
 | 
			
		||||
  "plugins": [
 | 
			
		||||
    [
 | 
			
		||||
      "transform-runtime",
 | 
			
		||||
      "@babel/plugin-transform-runtime",
 | 
			
		||||
      {
 | 
			
		||||
        "polyfill": false,
 | 
			
		||||
        "regenerator": true
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										25
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								.editorconfig
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
root = true
 | 
			
		||||
 | 
			
		||||
[*]
 | 
			
		||||
charset = utf-8
 | 
			
		||||
end_of_line = lf
 | 
			
		||||
indent_style = space
 | 
			
		||||
indent_size = 4
 | 
			
		||||
insert_final_newline = true
 | 
			
		||||
trim_trailing_whitespace = true
 | 
			
		||||
max_line_length = 120
 | 
			
		||||
 | 
			
		||||
[*.sh]
 | 
			
		||||
indent_size = 2
 | 
			
		||||
 | 
			
		||||
[*.js]
 | 
			
		||||
indent_size = 2
 | 
			
		||||
 | 
			
		||||
[*.vue]
 | 
			
		||||
indent_size = 2
 | 
			
		||||
 | 
			
		||||
[Makefile]
 | 
			
		||||
indent_style = tab
 | 
			
		||||
 | 
			
		||||
[package.json]
 | 
			
		||||
indent_size = 1
 | 
			
		||||
@@ -30,25 +30,6 @@ changelog:
 | 
			
		||||
    exclude:
 | 
			
		||||
      - "^docs:"
 | 
			
		||||
      - "^test:"
 | 
			
		||||
 | 
			
		||||
nfpm:
 | 
			
		||||
  name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
 | 
			
		||||
  replacements:
 | 
			
		||||
    amd64: 64-bit
 | 
			
		||||
    386: 32-bit
 | 
			
		||||
    arm64: ARM_64-bit
 | 
			
		||||
    arm: ARM_32-bit
 | 
			
		||||
    linux: Linux
 | 
			
		||||
    darwin: macOS
 | 
			
		||||
 | 
			
		||||
  vendor: Amir Raminfar
 | 
			
		||||
  homepage: https://github.com/amir20/dozzle/
 | 
			
		||||
  maintainer: Amir Raminfar <findamir@gmail.com>
 | 
			
		||||
  license: MIT
 | 
			
		||||
  formats:
 | 
			
		||||
    - deb
 | 
			
		||||
    - rpm
 | 
			
		||||
 | 
			
		||||
dockers:
 | 
			
		||||
  - image_templates:
 | 
			
		||||
      - "amir20/dozzle:{{ .Tag }}"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								.prettierrc.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.prettierrc.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
{
 | 
			
		||||
  "printWidth": 120
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										26
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								.travis.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
language: go
 | 
			
		||||
 | 
			
		||||
go:
 | 
			
		||||
  - "1.11"
 | 
			
		||||
 | 
			
		||||
services:
 | 
			
		||||
  - docker
 | 
			
		||||
 | 
			
		||||
before_install:
 | 
			
		||||
  - nvm install --lts
 | 
			
		||||
  - 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.
 | 
			
		||||
  # DOCKER_PASSWORD should be a secret in your .travis.yml configuration.
 | 
			
		||||
  # - test -n "$TRAVIS_TAG" && docker login -u=myuser -p="$DOCKER_PASSWORD"
 | 
			
		||||
 | 
			
		||||
deploy:
 | 
			
		||||
  - provider: script
 | 
			
		||||
    skip_cleanup: true
 | 
			
		||||
    script: curl -sL https://git.io/goreleaser | bash
 | 
			
		||||
    on:
 | 
			
		||||
      tags: true
 | 
			
		||||
      condition: $TRAVIS_OS_NAME = linux
 | 
			
		||||
							
								
								
									
										47
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
# 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.
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
## Getting dozzle
 | 
			
		||||
 | 
			
		||||
Dozzle is a very small Docker container (13.3MB virtual). Pull the latest release from the index:
 | 
			
		||||
 | 
			
		||||
    $ docker pull amir20/dozzle:latest
 | 
			
		||||
 | 
			
		||||
## Using dozzle
 | 
			
		||||
 | 
			
		||||
The simplest way to use dozzle is to run the docker container. Also, mount the Docker Unix socket with `-volume` to `/var/run/docker.sock`:
 | 
			
		||||
 | 
			
		||||
    $ docker run --name dozzle -d --volume=/var/run/docker.sock:/var/run/docker.sock -p 8888:8080 amir20/dozzle:latest
 | 
			
		||||
 | 
			
		||||
dozzle will be available at [http://localhost:8888/](http://localhost:8888/). You can change `-p 8888:8080` to any port. For example, if you want to view dozzle over port 4040 then you would do `-p 4040:8080`.
 | 
			
		||||
 | 
			
		||||
#### Security
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
will bind to `localhost` on port `1224`. You can then use use reverse proxy to control who can see dozzle.
 | 
			
		||||
 | 
			
		||||
#### Environment variable, DOCKER_API_VERSION
 | 
			
		||||
 | 
			
		||||
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`
 | 
			
		||||
 | 
			
		||||
    $ 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.
 | 
			
		||||
 | 
			
		||||
## License
 | 
			
		||||
 | 
			
		||||
[MIT](LICENSE)
 | 
			
		||||
@@ -1,15 +1,48 @@
 | 
			
		||||
<template lang="html">
 | 
			
		||||
   <router-view></router-view>
 | 
			
		||||
  <div class="columns is-marginless">
 | 
			
		||||
    <aside class="column menu is-2 section">
 | 
			
		||||
      <h1 class="title has-text-warning">Dozzle</h1>
 | 
			
		||||
      <p class="menu-label">Containers</p>
 | 
			
		||||
      <ul class="menu-list">
 | 
			
		||||
        <li v-for="item in containers">
 | 
			
		||||
          <router-link
 | 
			
		||||
            :to="{ name: 'container', params: { id: item.Id } }"
 | 
			
		||||
            active-class="is-active"
 | 
			
		||||
            class="tooltip is-tooltip-right is-tooltip-info"
 | 
			
		||||
            :data-tooltip="item.Names[0]"
 | 
			
		||||
          >
 | 
			
		||||
            <div class="hide-overflow">{{ item.Names[0] }}</div>
 | 
			
		||||
          </router-link>
 | 
			
		||||
        </li>
 | 
			
		||||
      </ul>
 | 
			
		||||
    </aside>
 | 
			
		||||
    <div class="column is-offset-2"><router-view></router-view></div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  name: "App"
 | 
			
		||||
  name: "App",
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      containers: []
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  async created() {
 | 
			
		||||
    this.containers = await (await fetch(`/api/containers.json`)).json();
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="css">
 | 
			
		||||
.section.is-fullwidth {
 | 
			
		||||
  padding: 0 !important;
 | 
			
		||||
<style scoped>
 | 
			
		||||
aside {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  padding-right: 0;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
.hide-overflow {
 | 
			
		||||
  text-overflow: ellipsis;
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
</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,16 +1,16 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
  <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">
 | 
			
		||||
    <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
 | 
			
		||||
  </head>
 | 
			
		||||
  <body>
 | 
			
		||||
  <section class="section is-fullwidth">
 | 
			
		||||
    <div id="app"></div>
 | 
			
		||||
  </section>
 | 
			
		||||
  <script src="/main.js"></script>
 | 
			
		||||
  </body>
 | 
			
		||||
</html>
 | 
			
		||||
    <head>
 | 
			
		||||
        <meta charset="utf-8" />
 | 
			
		||||
        <meta name="viewport" content="width=device-width, initial-scale=1" />
 | 
			
		||||
        <title>Dozzle</title>
 | 
			
		||||
        <link href="https://fonts.googleapis.com/css?family=Roboto|Roboto+Mono|Gafata" rel="stylesheet">
 | 
			
		||||
        <link href="./styles.scss" rel="stylesheet">
 | 
			
		||||
        <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
 | 
			
		||||
    </head>
 | 
			
		||||
 | 
			
		||||
    <body class="is-dark">
 | 
			
		||||
        <div id="app"></div>
 | 
			
		||||
        <script src="/main.js"></script>
 | 
			
		||||
    </body>
 | 
			
		||||
</html>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,17 @@
 | 
			
		||||
import Vue from "vue";
 | 
			
		||||
import VueRouter from "vue-router";
 | 
			
		||||
import App from "./App.vue";
 | 
			
		||||
import Container from "./pages/Container.vue";
 | 
			
		||||
import Index from "./pages/Index.vue";
 | 
			
		||||
 | 
			
		||||
Vue.use(VueRouter);
 | 
			
		||||
 | 
			
		||||
import App from "./App.vue";
 | 
			
		||||
import Index from "./pages/Index.vue";
 | 
			
		||||
import Container from "./pages/Container.vue";
 | 
			
		||||
 | 
			
		||||
const routes = [
 | 
			
		||||
  { path: "/", component: Index },
 | 
			
		||||
  {
 | 
			
		||||
    path: "/",
 | 
			
		||||
    component: Index,
 | 
			
		||||
    name: "default"
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: "/container/:id",
 | 
			
		||||
    component: Container,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,41 +1,95 @@
 | 
			
		||||
<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>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
let ws;
 | 
			
		||||
import { formatRelative } from "date-fns";
 | 
			
		||||
import ScrollbarNotification from "../components/ScrollbarNotification";
 | 
			
		||||
 | 
			
		||||
let ws = 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
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  props: ["id"],
 | 
			
		||||
  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 parent = this.$refs.events;
 | 
			
		||||
      const item = document.createElement("li");
 | 
			
		||||
      item.classList.add("event");
 | 
			
		||||
      item.innerHTML = e.data;
 | 
			
		||||
      parent.appendChild(item);
 | 
			
		||||
      item.scrollIntoView();
 | 
			
		||||
  components: {
 | 
			
		||||
    ScrollbarNotification
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      messages: []
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  created() {
 | 
			
		||||
    this.loadLogs(this.id);
 | 
			
		||||
  },
 | 
			
		||||
  beforeDestroy() {
 | 
			
		||||
    ws.close();
 | 
			
		||||
    ws = null;
 | 
			
		||||
  },
 | 
			
		||||
  watch: {
 | 
			
		||||
    id(newValue, oldValue) {
 | 
			
		||||
      if (oldValue !== newValue) {
 | 
			
		||||
        this.loadLogs(newValue);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    loadLogs(id) {
 | 
			
		||||
      if (ws) {
 | 
			
		||||
        ws.close();
 | 
			
		||||
        ws = null;
 | 
			
		||||
        this.messages = [];
 | 
			
		||||
      }
 | 
			
		||||
      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 message = parseMessage(e.data);
 | 
			
		||||
        this.messages.push(message);
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
</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;  
 | 
			
		||||
  word-wrap: break-word;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
.date {
 | 
			
		||||
  background-color: #262626;
 | 
			
		||||
  color: #258ccd;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.is-fullheight {
 | 
			
		||||
  min-height: 100vh;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,44 +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="columns unstyled box">
 | 
			
		||||
        <div class="column is-4">
 | 
			
		||||
          <router-link :to="{name: 'container', params: {id: item.Id}}"><h2 class="is-2">{{ item.Names[0] }}</h2></router-link>
 | 
			
		||||
          <span class="subtitle is-6">{{ item.Command}}</span>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="column is-4 image-name">
 | 
			
		||||
          <code>{{ item.Image }} {{ item.Image }}</code>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="column is-4">
 | 
			
		||||
          <span class="subtitle is-7">{{ item.Status}}</span>
 | 
			
		||||
        </div>
 | 
			
		||||
      </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>
 | 
			
		||||
</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">
 | 
			
		||||
.image-name {
 | 
			
		||||
  text-overflow: ellipsis;
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
<style scoped>
 | 
			
		||||
.hero.is-dark {
 | 
			
		||||
  color: #ddd;
 | 
			
		||||
  background-color: #111;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								assets/styles.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								assets/styles.scss
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
@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";
 | 
			
		||||
@import "../node_modules/bulma-tooltip/src/sass";
 | 
			
		||||
 | 
			
		||||
.is-dark {
 | 
			
		||||
    color: #ddd;
 | 
			
		||||
    background-color: #111;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
    font-family: "Roboto", sans-serif;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h1.title {
 | 
			
		||||
    font-family: "Gafata", sans-serif;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								main.go
									
									
									
									
									
								
							@@ -5,6 +5,7 @@ import (
 | 
			
		||||
	"encoding/binary"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
@@ -36,6 +37,7 @@ func main() {
 | 
			
		||||
	box := packr.NewBox("./static")
 | 
			
		||||
	http.HandleFunc("/api/containers.json", listContainers)
 | 
			
		||||
	http.HandleFunc("/api/logs", logs)
 | 
			
		||||
	http.HandleFunc("/version", versionHandler)
 | 
			
		||||
	http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
		fileServer := http.FileServer(box)
 | 
			
		||||
		if box.Has(req.URL.Path) {
 | 
			
		||||
@@ -49,6 +51,12 @@ func main() {
 | 
			
		||||
	log.Fatal(http.ListenAndServe(*addr, nil))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func versionHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	fmt.Fprintln(w, version)
 | 
			
		||||
	fmt.Fprintln(w, commit)
 | 
			
		||||
	fmt.Fprintln(w, date)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func listContainers(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -66,7 +74,7 @@ func logs(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	}
 | 
			
		||||
	defer c.Close()
 | 
			
		||||
 | 
			
		||||
	options := types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: true, Tail: "40"}
 | 
			
		||||
	options := types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: true, Tail: "300", Timestamps: true}
 | 
			
		||||
	reader, err := cli.ContainerLogs(context.Background(), id, options)
 | 
			
		||||
	defer reader.Close()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -78,7 +86,7 @@ func logs(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	for {
 | 
			
		||||
		_, err := reader.Read(hdr)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			panic(err)
 | 
			
		||||
            log.Panicln(err)
 | 
			
		||||
		}
 | 
			
		||||
		count := binary.BigEndian.Uint32(hdr[4:])
 | 
			
		||||
		n, err := reader.Read(content[:count])
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2035
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2035
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										91
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										91
									
								
								package.json
									
									
									
									
									
								
							@@ -1,40 +1,57 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "dozzle",
 | 
			
		||||
  "version": "1.0.3",
 | 
			
		||||
  "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": {
 | 
			
		||||
    "@vue/component-compiler-utils": "^2.3.0",
 | 
			
		||||
    "babel-core": "^6.26.3",
 | 
			
		||||
    "babel-plugin-transform-runtime": "^6.23.0",
 | 
			
		||||
    "babel-preset-env": "^1.7.0",
 | 
			
		||||
    "babel-runtime": "^6.26.0",
 | 
			
		||||
    "parcel-bundler": "^1.10.3",
 | 
			
		||||
    "concurrently": "^4.0.1",
 | 
			
		||||
    "vue-hot-reload-api": "^2.3.1",
 | 
			
		||||
    "vue-template-compiler": "^2.5.17"
 | 
			
		||||
  },
 | 
			
		||||
  "browserslist": [
 | 
			
		||||
    ">5%"
 | 
			
		||||
 "name": "dozzle",
 | 
			
		||||
 "version": "1.0.16",
 | 
			
		||||
 "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": {
 | 
			
		||||
  "bulma": "^0.7.2",
 | 
			
		||||
  "bulma-tooltip": "^2.0.2",
 | 
			
		||||
  "date-fns": "^2.0.0-alpha.25",
 | 
			
		||||
  "vue": "^2.5.17",
 | 
			
		||||
  "vue-router": "^3.0.1"
 | 
			
		||||
 },
 | 
			
		||||
 "devDependencies": {
 | 
			
		||||
  "@babel/core": "^7.1.6",
 | 
			
		||||
  "@babel/plugin-transform-runtime": "^7.1.0",
 | 
			
		||||
  "@vue/component-compiler-utils": "^2.3.0",
 | 
			
		||||
  "concurrently": "^4.1.0",
 | 
			
		||||
  "husky": "^1.1.4",
 | 
			
		||||
  "lint-staged": "^8.0.5",
 | 
			
		||||
  "parcel-bundler": "^1.10.3",
 | 
			
		||||
  "prettier": "^1.15.2",
 | 
			
		||||
  "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