@@ -40,6 +40,7 @@ export default {
}
}).observe(content, { childList: true, subtree: true });
},
+
methods: {
scrollToBottom(behavior = "instant") {
const { content } = this.$refs;
@@ -54,8 +55,7 @@ export default {
const { content } = this.$refs;
this.paused = content.scrollTop + content.clientHeight + 1 < content.scrollHeight;
}
- },
- watch: {}
+ }
};
diff --git a/assets/styles.scss b/assets/styles.scss
index bc5c16cb..c62d2834 100644
--- a/assets/styles.scss
+++ b/assets/styles.scss
@@ -18,7 +18,3 @@ body {
h1.title {
font-family: "Gafata", sans-serif;
}
-
-.column {
- min-width: 0;
-}
diff --git a/docker/client.go b/docker/client.go
index 9a8bbfea..90b69a74 100644
--- a/docker/client.go
+++ b/docker/client.go
@@ -10,6 +10,7 @@ import (
"sort"
"strconv"
"strings"
+ "time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/events"
@@ -36,6 +37,7 @@ type Client interface {
FindContainer(string) (Container, error)
ContainerLogs(context.Context, string, int) (<-chan string, <-chan error)
Events(context.Context) (<-chan events.Message, <-chan error)
+ ContainerLogsBetweenDates(context.Context, string, time.Time, time.Time) ([]string, error)
}
// NewClient creates a new instance of Client
@@ -190,3 +192,45 @@ func (d *dockerClient) ContainerLogs(ctx context.Context, id string, tailSize in
func (d *dockerClient) Events(ctx context.Context) (<-chan events.Message, <-chan error) {
return d.cli.Events(ctx, types.EventsOptions{})
}
+
+func (d *dockerClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time) ([]string, error) {
+ options := types.ContainerLogsOptions{
+ ShowStdout: true,
+ ShowStderr: true,
+ Timestamps: true,
+ Since: strconv.FormatInt(from.Unix(), 10),
+ Until: strconv.FormatInt(to.Unix(), 10),
+ }
+ reader, _ := d.cli.ContainerLogs(ctx, id, options)
+
+ defer reader.Close()
+
+ var messages []string
+ hdr := make([]byte, 8)
+ var buffer bytes.Buffer
+
+ for {
+ _, err := reader.Read(hdr)
+ if err != nil {
+ if err == io.EOF {
+ break
+ } else {
+ return nil, err
+ }
+ }
+ count := binary.BigEndian.Uint32(hdr[4:])
+ _, err = io.CopyN(&buffer, reader, int64(count))
+
+ if err != nil {
+ if err == io.EOF {
+ break
+ } else {
+ return nil, err
+ }
+ }
+ messages = append(messages, strings.TrimSpace(buffer.String()))
+ buffer.Reset()
+ }
+
+ return messages, nil
+}
diff --git a/main.go b/main.go
index 648189f7..ed29a1f8 100644
--- a/main.go
+++ b/main.go
@@ -34,9 +34,9 @@ var (
)
type handler struct {
- client docker.Client
+ client docker.Client
showAll bool
- box packr.Box
+ box packr.Box
}
func init() {
@@ -91,6 +91,7 @@ func createRoutes(base string, h *handler) *mux.Router {
s := r.PathPrefix(base).Subrouter()
s.HandleFunc("/api/containers.json", h.listContainers)
s.HandleFunc("/api/logs/stream", h.streamLogs)
+ s.HandleFunc("/api/logs", h.fetchLogsBetweenDates)
s.HandleFunc("/api/events/stream", h.streamEvents)
s.HandleFunc("/version", h.version)
s.PathPrefix("/").Handler(http.StripPrefix(base, http.HandlerFunc(h.index)))
@@ -108,9 +109,9 @@ func main() {
box := packr.NewBox("./static")
r := createRoutes(base, &handler{
- client: dockerClient,
+ client: dockerClient,
showAll: showAll,
- box: box,
+ box: box,
})
srv := &http.Server{Addr: addr, Handler: r}
@@ -170,6 +171,20 @@ func (h *handler) listContainers(w http.ResponseWriter, r *http.Request) {
}
}
+func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
+
+ from, _ := time.Parse(time.RFC3339, r.URL.Query().Get("from"))
+ to, _ := time.Parse(time.RFC3339, r.URL.Query().Get("to"))
+ id := r.URL.Query().Get("id")
+
+ messages, _ := h.client.ContainerLogsBetweenDates(r.Context(), id, from, to)
+
+ for _, m := range messages {
+ fmt.Fprintln(w, m)
+ }
+}
+
func (h *handler) streamLogs(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
if id == "" {
diff --git a/package-lock.json b/package-lock.json
index 09f1bd37..9bdcec9c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7647,6 +7647,11 @@
"integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=",
"dev": true
},
+ "lodash.debounce": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+ "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
+ },
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@@ -10530,9 +10535,9 @@
}
},
"splitpanes": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/splitpanes/-/splitpanes-2.1.1.tgz",
- "integrity": "sha512-K4jlE6gLKElsjtoUqzX6x0Uq4k7rnSBAMmxOJNWYTTIAuO7seC0+x3ADGpUgqtazMYdq0nAfWB9+NI/t/tW0mw=="
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/splitpanes/-/splitpanes-2.1.2.tgz",
+ "integrity": "sha512-IiVeC1wk7yVq4QZ3VTj6FbOTUJQmy/gFxpR1pDk67P+Pj8V0daUsPeoBNcKmcvJp2v3272GHSeLTNEyDCKRXSQ=="
},
"sprintf-js": {
"version": "1.0.3",
@@ -11549,9 +11554,9 @@
"dev": true
},
"vue": {
- "version": "2.6.10",
- "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.10.tgz",
- "integrity": "sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ=="
+ "version": "2.6.11",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz",
+ "integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ=="
},
"vue-hot-reload-api": {
"version": "2.3.4",
@@ -11591,9 +11596,9 @@
"integrity": "sha512-8iSa4mGNXBjyuSZFCCO4fiKfvzqk+mhL0lnKuGcQtO1eoj8nq3CmbEG8FwK5QqoqwDgsjsf1GDuisDX4cdb/aQ=="
},
"vue-template-compiler": {
- "version": "2.6.10",
- "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz",
- "integrity": "sha512-jVZkw4/I/HT5ZMvRnhv78okGusqe0+qH2A0Em0Cp8aq78+NK9TII263CDVz2QXZsIT+yyV/gZc/j/vlwa+Epyg==",
+ "version": "2.6.11",
+ "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz",
+ "integrity": "sha512-KIq15bvQDrcCjpGjrAhx4mUlyyHfdmTaoNfeoATHLAiWB+MU3cx4lOzMwrnUh9cCxy0Lt1T11hAFY6TQgroUAA==",
"dev": true,
"requires": {
"de-indent": "^1.0.2",
diff --git a/package.json b/package.json
index 16f48edb..20a6cafb 100644
--- a/package.json
+++ b/package.json
@@ -27,9 +27,10 @@
"ansi-to-html": "^0.6.13",
"bulma": "^0.8.0",
"date-fns": "^2.8.1",
- "splitpanes": "^2.1.1",
+ "lodash.debounce": "^4.0.8",
+ "splitpanes": "^2.1.2",
"store": "^2.0.12",
- "vue": "^2.6.10",
+ "vue": "^2.6.11",
"vue-meta": "^2.3.1",
"vue-router": "^3.1.3",
"vuex": "^3.1.2"
@@ -54,7 +55,7 @@
"sass": "^1.23.7",
"vue-hot-reload-api": "^2.3.4",
"vue-jest": "^3.0.5",
- "vue-template-compiler": "^2.6.10"
+ "vue-template-compiler": "^2.6.11"
},
"husky": {
"hooks": {