Compare commits

...

19 Commits

Author SHA1 Message Date
Amir Raminfar 705a339e49 Adds sorting and cleans up ids and names 2018-11-26 14:52:20 -08:00
Amir Raminfar f81c240a47 1.3.0 2018-11-26 11:15:53 -08:00
Amir Raminfar 262095e5bb Update README.md 2018-11-26 11:15:32 -08:00
Amir Raminfar ba900c4374 Uses EventSource instead of websockets (#8)
* Replaces websockets with event-stream

* Adds base_path

* Removes SSL

* Adds event listener for events

* Adds more logging

* Adds event listener to home page
2018-11-26 11:05:16 -08:00
Amir Raminfar 9dc6b3790d 1.2.8 2018-11-25 16:15:59 -08:00
Amir Raminfar b96785f2be Adds title for pages 2018-11-25 16:14:55 -08:00
Amir Raminfar dd6f4b1e31 1.2.7 2018-11-25 12:24:15 -08:00
Amir Raminfar 651291ecad Removes hostname from title 2018-11-25 12:23:29 -08:00
Amir Raminfar a5bcec68cb 1.2.6 2018-11-25 12:14:12 -08:00
Amir Raminfar 77d6a22122 Disables source maps 2018-11-25 12:14:00 -08:00
Amir Raminfar 40f97073e8 1.2.5 2018-11-25 12:10:55 -08:00
Amir Raminfar 75339ffba1 Docker hostname added 2018-11-25 12:10:46 -08:00
Amir Raminfar 2fdfba5a42 Updates packages in node 2018-11-25 12:06:49 -08:00
Amir Raminfar 5987330cdc 1.2.4 2018-11-24 17:48:24 -08:00
Amir Raminfar 14c7c21f9f Fixes bug on ipad 2018-11-24 17:48:18 -08:00
Amir Raminfar fc9fdaf8b6 1.2.3 2018-11-24 17:34:20 -08:00
Amir Raminfar 5979a6d0e5 Adds responsiveness for menu on left 2018-11-24 17:32:22 -08:00
Amir Raminfar ca2c46ffce 1.2.2 2018-11-20 09:20:42 -08:00
Amir Raminfar 1cc7e92466 Update README.md 2018-11-20 09:20:31 -08:00
10 changed files with 363 additions and 163 deletions
+5 -5
View File
@@ -4,13 +4,13 @@ Dozzle is a log viewer for Docker. It's free. It's small. And it's right in your
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.
![Image](demo.gif)
## Getting dozzle
Dozzle is a very small Docker container (8.6MB 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
@@ -28,14 +28,14 @@ dozzle doesn't support authentication out of the box. You can control the device
$ 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.
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 8888:8080 amir20/dozzle:latest --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/).
@@ -46,7 +46,7 @@ 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
+69 -11
View File
@@ -1,43 +1,97 @@
<template lang="html">
<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">
<aside class="column menu is-2">
<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 } }"
:to="{ name: 'container', params: { id: item.id, name: item.name } }"
active-class="is-active"
class="tooltip is-tooltip-right is-tooltip-info"
:data-tooltip="item.Names[0]"
:data-tooltip="item.name"
>
<div class="hide-overflow">{{ item.Names[0] }}</div>
<div class="hide-overflow">{{ item.name }}</div>
</router-link>
</li>
</ul>
</aside>
<div class="column is-offset-2"><router-view></router-view></div>
<vue-headful :title="title" />
</div>
</template>
<script>
let es;
export default {
name: "App",
data() {
return {
containers: []
title: "Dozzle",
containers: [],
showNav: false
};
},
async created() {
this.containers = await (await fetch(`${BASE_PATH}/api/containers.json`)).json();
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 scoped>
<style scoped lang="scss">
.is-hidden-mobile.is-active {
display: block !important;
}
.navbar-burger {
height: 2.35rem;
}
aside {
position: fixed;
padding-right: 0;
z-index: 2;
padding: 1em;
@media screen and (max-width: 768px) {
& {
position: sticky;
top: 0;
left: 0;
right: 0;
background: #222;
}
.tooltip::after,
.tooltip::before {
display: none !important;
}
.menu-label {
margin-top: 1em;
}
}
}
.hide-overflow {
@@ -45,4 +99,8 @@ aside {
white-space: nowrap;
overflow: hidden;
}
.burger.is-white {
color: #fff;
}
</style>
-1
View File
@@ -8,7 +8,6 @@
<link href="styles.scss" rel="stylesheet" />
<script>
window["BASE_PATH"] = "{{ .Base }}";
window["SSL_ENABLED"] = "{{ .SSL }}".toLowerCase() == "true" ? true : false;
</script>
<script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
</head>
+2
View File
@@ -1,10 +1,12 @@
import Vue from "vue";
import VueRouter from "vue-router";
import vueHeadful from "vue-headful";
import App from "./App.vue";
import Container from "./pages/Container.vue";
import Index from "./pages/Index.vue";
Vue.use(VueRouter);
Vue.component("vue-headful", vueHeadful);
const routes = [
{
+15 -17
View File
@@ -6,6 +6,7 @@
</li>
</ul>
<scrollbar-notification :messages="messages"></scrollbar-notification>
<vue-headful :title="title" />
</div>
</template>
@@ -13,7 +14,7 @@
import { formatRelative } from "date-fns";
import ScrollbarNotification from "../components/ScrollbarNotification";
let ws = null;
let es = null;
let nextId = 0;
const parseMessage = data => {
const date = new Date(data.substring(0, 30));
@@ -29,22 +30,25 @@ const parseMessage = data => {
};
export default {
props: ["id"],
props: ["id", "name"],
name: "Container",
components: {
ScrollbarNotification
},
data() {
return {
messages: []
messages: [],
title: ""
};
},
created() {
this.loadLogs(this.id);
},
beforeDestroy() {
ws.close();
ws = null;
if (es) {
es.close();
es = null;
}
},
watch: {
id(newValue, oldValue) {
@@ -55,20 +59,14 @@ export default {
},
methods: {
loadLogs(id) {
if (ws) {
ws.close();
ws = null;
if (es) {
es.close();
es = null;
this.messages = [];
}
const protocol = SSL_ENABLED ? "wss" : "ws";
ws = new WebSocket(`${protocol}://${window.location.host}${BASE_PATH}/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);
};
es = new EventSource(`${BASE_PATH}/api/logs/stream?id=${id}`);
es.onmessage = e => this.messages.push(parseMessage(e.data));
this.title = `${this.name} - Dozzle`;
}
}
};
+69
View File
@@ -0,0 +1,69 @@
package docker
import (
"context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/client"
"io"
"log"
"sort"
"strings"
)
type dockerClient struct {
cli *client.Client
}
type DockerClient interface {
ListContainers() ([]Container, error)
ContainerLogs(ctx context.Context, id string) (io.ReadCloser, error)
Events(ctx context.Context) (<-chan events.Message, <-chan error)
}
func NewDockerClient() DockerClient {
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
})
return containers, nil
}
func (d *dockerClient) ContainerLogs(ctx context.Context, id string) (io.ReadCloser, error) {
options := types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: true, Tail: "300", Timestamps: true}
return d.cli.ContainerLogs(ctx, id, options)
}
func (d *dockerClient) Events(ctx context.Context) (<-chan events.Message, <-chan error) {
return d.cli.Events(ctx, types.EventsOptions{})
}
+13
View File
@@ -0,0 +1,13 @@
package docker
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"`
}
+158 -111
View File
@@ -1,143 +1,190 @@
package main
import (
"context"
"encoding/binary"
"encoding/json"
"fmt"
"html/template"
"log"
"net/http"
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/gobuffalo/packr"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
flag "github.com/spf13/pflag"
"context"
"encoding/binary"
"encoding/json"
"fmt"
"github.com/amir20/dozzle/docker"
"github.com/gobuffalo/packr"
"github.com/gorilla/mux"
flag "github.com/spf13/pflag"
"html/template"
"log"
"net/http"
"strings"
)
var (
cli *client.Client
addr = ""
ssl = false
base = "/"
upgrader = websocket.Upgrader{}
version = "dev"
commit = "none"
date = "unknown"
dockerClient docker.DockerClient
addr = ""
base = "/"
version = "dev"
commit = "none"
date = "unknown"
)
func init() {
flag.StringVar(&addr, "addr", ":8080", "http service address")
flag.StringVar(&base, "base", "/", "base address of the application to mount")
flag.BoolVarP(&ssl, "ssl", "s", false, "Uses websockets over ssl if enabled")
flag.StringVar(&addr, "addr", ":8080", "http service address")
flag.StringVar(&base, "base", "/", "base address of the application to mount")
var err error
cli, err = client.NewClientWithOpts(client.FromEnv)
if err != nil {
log.Fatal(err)
}
flag.Parse()
dockerClient = docker.NewDockerClient()
flag.Parse()
}
func main() {
r := mux.NewRouter()
r := mux.NewRouter()
if base != "/" {
r.HandleFunc(base, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
http.Redirect(w, req, base+"/", http.StatusMovedPermanently)
}))
}
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()
box := packr.NewBox("./static")
s := r.PathPrefix(base).Subrouter()
box := packr.NewBox("./static")
s.HandleFunc("/api/containers.json", listContainers)
s.HandleFunc("/api/logs", logs)
s.HandleFunc("/version", versionHandler)
s.PathPrefix("/").Handler(http.StripPrefix(base, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
fileServer := http.FileServer(box)
if box.Has(req.URL.Path) && req.URL.Path != "" && req.URL.Path != "/" {
fileServer.ServeHTTP(w, req)
} else {
handleIndex(box, w)
}
})))
s.HandleFunc("/api/containers.json", listContainers)
s.HandleFunc("/api/logs/stream", streamLogs)
s.HandleFunc("/api/events/stream", streamEvents)
s.HandleFunc("/version", versionHandler)
s.PathPrefix("/").Handler(http.StripPrefix(base, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
fileServer := http.FileServer(box)
if box.Has(req.URL.Path) && req.URL.Path != "" && req.URL.Path != "/" {
fileServer.ServeHTTP(w, req)
} else {
handleIndex(box, w)
}
})))
log.Fatal(http.ListenAndServe(addr, r))
log.Printf("Accepting connections on %s", addr)
log.Fatal(http.ListenAndServe(addr, r))
}
func versionHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, version)
fmt.Fprintln(w, commit)
fmt.Fprintln(w, date)
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 {
log.Fatal(err)
}
json.NewEncoder(w).Encode(containers)
containers, err := dockerClient.ListContainers()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = json.NewEncoder(w).Encode(containers)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func handleIndex(box packr.Box, w http.ResponseWriter) {
text, _ := box.FindString("index.html")
text = strings.Replace(text, "__BASE__", "{{ .Base }}", -1)
tmpl, err := template.New("index.html").Parse(text)
if err != nil {
panic(err)
}
text, _ := box.FindString("index.html")
text = strings.Replace(text, "__BASE__", "{{ .Base }}", -1)
tmpl, err := template.New("index.html").Parse(text)
if err != nil {
panic(err)
}
path := ""
if base != "/" {
path = base
}
data := struct {
Base string
SSL bool
}{path, ssl}
err = tmpl.Execute(w, data)
if err != nil {
panic(err)
}
path := ""
if base != "/" {
path = base
}
data := struct{ Base string }{path}
err = tmpl.Execute(w, data)
if err != nil {
panic(err)
}
}
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()
func streamLogs(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
if id == "" {
http.Error(w, "id is required", http.StatusBadRequest)
return
}
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 {
log.Fatal(err)
}
f, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
return
}
hdr := make([]byte, 8)
content := make([]byte, 1024, 1024*1024)
for {
_, err := reader.Read(hdr)
if err != nil {
log.Panicln(err)
}
count := binary.BigEndian.Uint32(hdr[4:])
n, err := reader.Read(content[:count])
if err != nil {
log.Println(err)
break
}
err = c.WriteMessage(websocket.TextMessage, content[:n])
if err != nil {
log.Println(err)
break
}
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
reader, err := dockerClient.ContainerLogs(ctx, id)
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer reader.Close()
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")
hdr := make([]byte, 8)
content := make([]byte, 1024, 1024*1024)
for {
_, err := reader.Read(hdr)
if err != nil {
log.Panicln(err)
}
count := binary.BigEndian.Uint32(hdr[4:])
n, err := reader.Read(content[:count])
if err != nil {
log.Println(err)
break
}
_, err = fmt.Fprintf(w, "data: %s\n\n", content[:n])
if err != nil {
log.Println(err)
break
}
f.Flush()
}
}
func streamEvents(w http.ResponseWriter, r *http.Request) {
f, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
w.Header().Set("Transfer-Encoding", "chunked")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
messages, _ := dockerClient.Events(ctx)
for message := range messages {
switch message.Action {
case "connect":
fallthrough
case "disconnect":
fallthrough
case "create":
fallthrough
case "destroy":
_, err := fmt.Fprintf(w, "event: containers-changed\n")
_, err = fmt.Fprintf(w, "data: %s\n\n", message.Action)
if err != nil {
log.Println(err)
break
}
f.Flush()
default:
// Do nothing
}
}
}
+26 -13
View File
@@ -1,6 +1,6 @@
{
"name": "dozzle",
"version": "1.2.1",
"version": "1.3.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -1264,7 +1264,7 @@
},
"ansi-escapes": {
"version": "3.1.0",
"resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz",
"integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==",
"dev": true
},
@@ -4001,7 +4001,7 @@
},
"globby": {
"version": "6.1.0",
"resolved": "http://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
"resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
"integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
"dev": true,
"requires": {
@@ -4130,6 +4130,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",
@@ -4648,9 +4653,9 @@
"dev": true
},
"husky": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/husky/-/husky-1.1.4.tgz",
"integrity": "sha512-cZjGpS7qsaBSo3fOMUuR7erQloX3l5XzL1v/RkIqU6zrQImDdU70z5Re9fGDp7+kbYlM2EtS4aYMlahBeiCUGw==",
"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",
@@ -5195,15 +5200,15 @@
}
},
"lint-staged": {
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-8.0.5.tgz",
"integrity": "sha512-QI2D6lw2teArlr2fmrrCIqHxef7mK2lKjz9e+aZSzFlk5rsy10rg97p3wA9H/vIFR3Fvn34fAgUktD/k896S2A==",
"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",
@@ -9126,6 +9131,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",
@@ -9133,9 +9146,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",
+6 -5
View File
@@ -1,13 +1,13 @@
{
"name": "dozzle",
"version": "1.2.1",
"version": "1.3.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "concurrently 'go run main.go' 'npm run watch-assets'",
"watch-assets": "parcel watch --public-url '__BASE__' assets/index.html -d static",
"prebuild": "npm run clean",
"build": "parcel build --public-url '__BASE__' assets/index.html -d static",
"build": "parcel build --no-source-maps --public-url '__BASE__' assets/index.html -d static",
"clean": "rm -rf static",
"release": "goreleaser --rm-dist"
},
@@ -26,15 +26,16 @@
"bulma-tooltip": "^2.0.2",
"date-fns": "^2.0.0-alpha.25",
"vue": "^2.5.17",
"vue-router": "^3.0.1"
"vue-headful": "^2.0.1",
"vue-router": "^3.0.2"
},
"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",
"husky": "^1.2.0",
"lint-staged": "^8.1.0",
"parcel-bundler": "^1.10.3",
"prettier": "^1.15.2",
"sass": "^1.15.1",