Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be7012a860 | ||
|
|
4892dcd892 | ||
|
|
8538fb2f55 | ||
|
|
2592d62ed9 | ||
|
|
f91c7ccb21 | ||
|
|
24ade2f856 | ||
|
|
756a8e4643 | ||
|
|
34533cd830 | ||
|
|
21e88b645e | ||
|
|
775715a17c | ||
|
|
a59f7caafc | ||
|
|
6903299523 | ||
|
|
1f34ebfdc1 | ||
|
|
98ee491865 | ||
|
|
d408cfca1d | ||
|
|
a8366174e9 | ||
|
|
1b97d18ef0 | ||
|
|
678b197d6a | ||
|
|
86bb4e12b3 | ||
|
|
32dd847f4f | ||
|
|
35a5093f8e | ||
|
|
6b5f5aeae3 | ||
|
|
b41f315a25 | ||
|
|
376ee2d730 | ||
|
|
79a42bf9fb | ||
|
|
2eff0dbeee | ||
|
|
da9cddb691 | ||
|
|
184e742b1b | ||
|
|
42287f8848 | ||
|
|
6495531d45 | ||
|
|
3045d6011f | ||
|
|
8a78db30c6 | ||
|
|
cbe8aede9c | ||
|
|
a0019b1019 | ||
|
|
4e6d9c4c40 | ||
|
|
54a636163c | ||
|
|
cc99eaa819 |
@@ -2,7 +2,7 @@ before:
|
||||
hooks:
|
||||
- npm run clean
|
||||
- npm run build
|
||||
- packr
|
||||
- packr -z
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
|
||||
@@ -1,25 +1,20 @@
|
||||
<template lang="html">
|
||||
<main>
|
||||
<mobile-menu v-if="isMobile"></mobile-menu>
|
||||
<splitpanes @resized="updateSetting({ menuWidth: $event[0].size })">
|
||||
<splitpanes @resized="onResize($event)">
|
||||
<pane min-size="10" :size="settings.menuWidth" v-if="!isMobile">
|
||||
<side-menu></side-menu>
|
||||
</pane>
|
||||
<pane :size="isMobile ? 100 : 100 - settings.menuWidth" min-size="10">
|
||||
<pane min-size="10">
|
||||
<splitpanes>
|
||||
<pane>
|
||||
<pane class="has-min-height">
|
||||
<search></search>
|
||||
<router-view></router-view>
|
||||
</pane>
|
||||
<pane v-for="other in activeContainers" :key="other.id">
|
||||
<pane v-for="other in activeContainers" :key="other.id" v-if="!isMobile">
|
||||
<scrollable-view>
|
||||
<template v-slot:header>
|
||||
<div class="name columns is-marginless">
|
||||
<span class="column">{{ other.name }}</span>
|
||||
<span class="column is-narrow">
|
||||
<button class="delete is-medium" @click="removeActiveContainer(other)"></button>
|
||||
</span>
|
||||
</div>
|
||||
<container-title :value="other.name" closable @close="removeActiveContainer(other)"></container-title>
|
||||
</template>
|
||||
<log-viewer-with-source :id="other.id"></log-viewer-with-source>
|
||||
</scrollable-view>
|
||||
@@ -39,6 +34,7 @@ import ScrollableView from "./components/ScrollableView";
|
||||
import SideMenu from "./components/SideMenu";
|
||||
import MobileMenu from "./components/MobileMenu";
|
||||
import Search from "./components/Search";
|
||||
import ContainerTitle from "./components/ContainerTitle";
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
@@ -49,7 +45,8 @@ export default {
|
||||
ScrollableView,
|
||||
Splitpanes,
|
||||
Pane,
|
||||
Search
|
||||
Search,
|
||||
ContainerTitle
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -75,19 +72,17 @@ export default {
|
||||
fetchContainerList: "FETCH_CONTAINERS",
|
||||
removeActiveContainer: "REMOVE_ACTIVE_CONTAINER",
|
||||
updateSetting: "UPDATE_SETTING"
|
||||
})
|
||||
}),
|
||||
onResize(e) {
|
||||
if (e.length == 2) {
|
||||
this.updateSetting({ menuWidth: Math.min(90, e[0].size) });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.name {
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
font-weight: bold;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
::v-deep .splitpanes__splitter {
|
||||
min-width: 4px;
|
||||
background: #666;
|
||||
@@ -99,4 +94,8 @@ export default {
|
||||
.button.has-no-border {
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
.has-min-height {
|
||||
min-height: 100vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,13 +19,13 @@ exports[`<App /> renders correctly 1`] = `
|
||||
<pane-stub
|
||||
maxsize="100"
|
||||
minsize="10"
|
||||
size="85"
|
||||
>
|
||||
<splitpanes-stub
|
||||
dblclicksplitter="true"
|
||||
pushotherpanes="true"
|
||||
>
|
||||
<pane-stub
|
||||
class="has-min-height"
|
||||
maxsize="100"
|
||||
minsize="0"
|
||||
>
|
||||
|
||||
30
assets/components/ContainerTitle.vue
Normal file
30
assets/components/ContainerTitle.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template lang="html">
|
||||
<div class="name columns is-marginless">
|
||||
<span class="column">{{ value }}</span>
|
||||
<span class="column is-narrow" v-if="closable">
|
||||
<button class="delete is-medium" @click="$emit('close')"></button>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: String,
|
||||
closable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
name: "ContainerTitle"
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.name {
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
font-weight: bold;
|
||||
font-family: monospace;
|
||||
}
|
||||
</style>
|
||||
@@ -7,7 +7,6 @@ export default {
|
||||
name: "InfiniteLoader",
|
||||
data() {
|
||||
return {
|
||||
scrollingParent: null,
|
||||
isLoading: false
|
||||
};
|
||||
},
|
||||
@@ -16,16 +15,16 @@ export default {
|
||||
enabled: Boolean
|
||||
},
|
||||
mounted() {
|
||||
this.scrollingParent = this.$el.closest("[data-scrolling]");
|
||||
const intersectionObserver = new IntersectionObserver(
|
||||
async entries => {
|
||||
if (entries[0].intersectionRatio <= 0) return;
|
||||
if (this.onLoadMore && this.enabled) {
|
||||
const previousHeight = this.scrollingParent.scrollHeight;
|
||||
const scrollingParent = this.$el.closest("[data-scrolling]") || document.documentElement;
|
||||
const previousHeight = scrollingParent.scrollHeight;
|
||||
this.isLoading = true;
|
||||
await this.onLoadMore();
|
||||
this.isLoading = false;
|
||||
this.$nextTick(() => (this.scrollingParent.scrollTop += this.scrollingParent.scrollHeight - previousHeight));
|
||||
this.$nextTick(() => (scrollingParent.scrollTop += scrollingParent.scrollHeight - previousHeight));
|
||||
}
|
||||
},
|
||||
{ threshholds: 1 }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template lang="html">
|
||||
<div>
|
||||
<infinite-loader :onLoadMore="loadOlderLogs" :enabled="messages.length > 100"></infinite-loader>
|
||||
<slot v-bind:messages="messages"></slot>
|
||||
<slot :messages="messages"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -40,12 +40,7 @@ export default {
|
||||
|
||||
computed: {
|
||||
...mapState(["containers"]),
|
||||
activeContainersById() {
|
||||
return this.activeContainers.reduce((map, obj) => {
|
||||
map[obj.id] = obj;
|
||||
return map;
|
||||
}, {});
|
||||
}
|
||||
...mapGetters(["activeContainersById"])
|
||||
},
|
||||
methods: {
|
||||
...mapActions({})
|
||||
@@ -60,12 +55,14 @@ export default {
|
||||
<style scoped lang="scss">
|
||||
aside {
|
||||
padding: 1em;
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #222;
|
||||
z-index: 2;
|
||||
max-height: 100vh;
|
||||
overflow: auto;
|
||||
|
||||
.menu-label {
|
||||
margin-top: 1em;
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
<template lang="html">
|
||||
<scrollable-view>
|
||||
<log-viewer-with-source :id="id"></log-viewer-with-source>
|
||||
</scrollable-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ScrollableView from "./ScrollableView";
|
||||
import LogViewerWithSource from "./LogViewerWithSource";
|
||||
|
||||
export default {
|
||||
props: ["id"],
|
||||
name: "ScrollableLogsWithSource",
|
||||
components: {
|
||||
LogViewerWithSource,
|
||||
ScrollableView
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -1,17 +1,18 @@
|
||||
<template lang="html">
|
||||
<section>
|
||||
<section :class="{ 'is-full-height-scrollable': scrollable }">
|
||||
<header v-if="$slots.header">
|
||||
<slot name="header"></slot>
|
||||
</header>
|
||||
<main ref="content" @scroll.passive="onScroll" data-scrolling>
|
||||
<main ref="content" :data-scrolling="scrollable">
|
||||
<slot></slot>
|
||||
<div ref="scrollObserver"></div>
|
||||
</main>
|
||||
<div class="scroll-bar-notification">
|
||||
<transition name="fade">
|
||||
<button
|
||||
class="button"
|
||||
:class="hasMore ? 'is-warning' : 'is-primary'"
|
||||
@click="scrollToBottom('smooth')"
|
||||
@click="scrollToBottom('instant')"
|
||||
v-show="paused"
|
||||
>
|
||||
<ion-icon name="download"></ion-icon>
|
||||
@@ -23,6 +24,12 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
scrollable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
name: "ScrollableView",
|
||||
data() {
|
||||
return {
|
||||
@@ -39,21 +46,19 @@ export default {
|
||||
this.hasMore = true;
|
||||
}
|
||||
}).observe(content, { childList: true, subtree: true });
|
||||
|
||||
const intersectionObserver = new IntersectionObserver(
|
||||
entries => (this.paused = entries[0].intersectionRatio == 0),
|
||||
{ threshholds: [0, 1] }
|
||||
);
|
||||
|
||||
intersectionObserver.observe(this.$refs.scrollObserver);
|
||||
},
|
||||
|
||||
methods: {
|
||||
scrollToBottom(behavior = "instant") {
|
||||
const { content } = this.$refs;
|
||||
if (typeof content.scroll === "function") {
|
||||
content.scroll({ top: content.scrollHeight, behavior });
|
||||
} else {
|
||||
content.scrollTop = content.scrollHeight;
|
||||
}
|
||||
this.$refs.scrollObserver.scrollIntoView({ behavior });
|
||||
this.hasMore = false;
|
||||
},
|
||||
onScroll(e) {
|
||||
const { content } = this.$refs;
|
||||
this.paused = content.scrollTop + content.clientHeight + 1 < content.scrollHeight;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -62,12 +67,16 @@ export default {
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
|
||||
&.is-full-height-scrollable {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.scroll-bar-notification {
|
||||
text-align: right;
|
||||
margin-right: 65px;
|
||||
|
||||
@@ -5,12 +5,14 @@
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder="Filter"
|
||||
placeholder="Find / RegEx"
|
||||
ref="filter"
|
||||
v-model="filter"
|
||||
@keyup.esc="resetSearch()"
|
||||
/>
|
||||
<span class="icon is-small is-left"><ion-icon name="search"></ion-icon></span>
|
||||
<span class="icon is-small is-left">
|
||||
<ion-icon name="search"></ion-icon>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="column is-1 has-text-centered">
|
||||
|
||||
@@ -49,12 +49,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapState(["containers", "activeContainers"]),
|
||||
activeContainersById() {
|
||||
return this.activeContainers.reduce((map, obj) => {
|
||||
map[obj.id] = obj;
|
||||
return map;
|
||||
}, {});
|
||||
}
|
||||
...mapGetters(["activeContainersById"])
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
@@ -68,6 +63,8 @@ aside {
|
||||
padding: 1em;
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
position: fixed;
|
||||
width: inherit;
|
||||
|
||||
.hide-overflow {
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@@ -1,23 +1,53 @@
|
||||
<template lang="html">
|
||||
<div>
|
||||
<scrollable-logs-with-source :id="id"></scrollable-logs-with-source>
|
||||
</div>
|
||||
<scrollable-view :scrollable="activeContainers.length > 0">
|
||||
<template v-slot:header v-if="activeContainers.length > 0">
|
||||
<container-title :value="allContainersById[id].name"></container-title>
|
||||
</template>
|
||||
<log-viewer-with-source :id="id"></log-viewer-with-source>
|
||||
</scrollable-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ScrollableLogsWithSource from "../components/ScrollableLogsWithSource";
|
||||
import { mapActions, mapGetters, mapState } from "vuex";
|
||||
|
||||
import LogViewerWithSource from "../components/LogViewerWithSource";
|
||||
import ScrollableView from "../components/ScrollableView";
|
||||
import ContainerTitle from "../components/ContainerTitle";
|
||||
|
||||
export default {
|
||||
props: ["id", "name"],
|
||||
name: "Container",
|
||||
components: {
|
||||
ScrollableLogsWithSource
|
||||
LogViewerWithSource,
|
||||
ScrollableView,
|
||||
ContainerTitle
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
title: "loading"
|
||||
};
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.name,
|
||||
titleTemplate: "%s - Dozzle"
|
||||
title: this.title
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
if (this.allContainersById[this.id]) {
|
||||
this.title = this.allContainersById[this.id].name;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(["activeContainers"]),
|
||||
...mapGetters(["allContainersById"])
|
||||
},
|
||||
watch: {
|
||||
id() {
|
||||
this.title = this.allContainersById[this.id].name;
|
||||
},
|
||||
allContainersById() {
|
||||
this.title = this.allContainersById[this.id].name;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
<template lang="html">
|
||||
<div class="is-fullheight">
|
||||
<div>
|
||||
<section class="section">
|
||||
<div class="has-underline">
|
||||
<h2 class="title is-4">About</h2>
|
||||
</div>
|
||||
|
||||
<h2 class="title is-6 is-marginless">Version</h2>
|
||||
<div>
|
||||
You are using Dozzle <i>{{ currentVersion }}</i
|
||||
>.
|
||||
@@ -57,20 +56,6 @@ import gt from "semver/functions/gt";
|
||||
import valid from "semver/functions/valid";
|
||||
import { mapActions, mapState } from "vuex";
|
||||
|
||||
function computedSettings(names) {
|
||||
return names.reduce((map, name) => {
|
||||
map[name] = {
|
||||
get() {
|
||||
return this.settings[name];
|
||||
},
|
||||
set(value) {
|
||||
this.updateSetting({ [name]: value });
|
||||
}
|
||||
};
|
||||
return map;
|
||||
}, {});
|
||||
}
|
||||
|
||||
export default {
|
||||
props: [],
|
||||
name: "Settings",
|
||||
@@ -89,8 +74,7 @@ export default {
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
title: "Settings",
|
||||
titleTemplate: "%s - Dozzle"
|
||||
title: "Settings"
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
@@ -100,15 +84,21 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapState(["settings"]),
|
||||
...computedSettings.bind(this)(["search", "size"])
|
||||
...["search", "size"].reduce((map, name) => {
|
||||
map[name] = {
|
||||
get() {
|
||||
return this.settings[name];
|
||||
},
|
||||
set(value) {
|
||||
this.updateSetting({ [name]: value });
|
||||
}
|
||||
};
|
||||
return map;
|
||||
}, {})
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.is-fullheight {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
@@ -57,14 +57,26 @@ const actions = {
|
||||
commit("UPDATE_SETTINGS", setting);
|
||||
}
|
||||
};
|
||||
const getters = {};
|
||||
const getters = {
|
||||
activeContainersById(state) {
|
||||
return state.activeContainers.reduce((map, obj) => {
|
||||
map[obj.id] = obj;
|
||||
return map;
|
||||
}, {});
|
||||
},
|
||||
allContainersById(state) {
|
||||
return state.containers.reduce((map, obj) => {
|
||||
map[obj.id] = obj;
|
||||
return map;
|
||||
}, {});
|
||||
}
|
||||
};
|
||||
|
||||
const es = new EventSource(`${BASE_PATH}/api/events/stream`);
|
||||
es.addEventListener("containers-changed", e => setTimeout(() => store.dispatch("FETCH_CONTAINERS"), 1000), false);
|
||||
mql.addListener(e => store.commit("SET_MOBILE_WIDTH", e.matches));
|
||||
|
||||
const store = new Vuex.Store({
|
||||
strict: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
|
||||
107
docker/client.go
107
docker/client.go
@@ -123,6 +123,34 @@ func (d *dockerClient) ListContainers(showAll bool) ([]Container, error) {
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
func logReader(reader io.ReadCloser, tty bool) func() (string, error) {
|
||||
if tty {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
return func() (string, error) {
|
||||
if scanner.Scan() {
|
||||
return scanner.Text(), nil
|
||||
}
|
||||
|
||||
return "", io.EOF
|
||||
}
|
||||
}
|
||||
hdr := make([]byte, 8)
|
||||
var buffer bytes.Buffer
|
||||
return func() (string, error) {
|
||||
buffer.Reset()
|
||||
_, err := reader.Read(hdr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
count := binary.BigEndian.Uint32(hdr[4:])
|
||||
_, err = io.CopyN(&buffer, reader, int64(count))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(buffer.String()), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dockerClient) ContainerLogs(ctx context.Context, id string, tailSize int) (<-chan string, <-chan error) {
|
||||
options := types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: true, Tail: strconv.Itoa(tailSize), Timestamps: true}
|
||||
reader, err := d.cli.ContainerLogs(ctx, id, options)
|
||||
@@ -142,49 +170,23 @@ func (d *dockerClient) ContainerLogs(ctx context.Context, id string, tailSize in
|
||||
|
||||
containerJSON, _ := d.cli.ContainerInspect(ctx, id)
|
||||
|
||||
if containerJSON.Config.Tty {
|
||||
go func() {
|
||||
defer close(messages)
|
||||
defer close(errChannel)
|
||||
defer reader.Close()
|
||||
scanner := bufio.NewScanner(reader)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
select {
|
||||
case messages <- line:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
go func() {
|
||||
defer close(messages)
|
||||
defer close(errChannel)
|
||||
defer reader.Close()
|
||||
nextEntry := logReader(reader, containerJSON.Config.Tty)
|
||||
for {
|
||||
line, err := nextEntry()
|
||||
if err != nil {
|
||||
errChannel <- err
|
||||
break
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
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()
|
||||
select {
|
||||
case messages <- line:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return messages, errChannel
|
||||
}
|
||||
@@ -202,15 +204,15 @@ func (d *dockerClient) ContainerLogsBetweenDates(ctx context.Context, id string,
|
||||
Until: strconv.FormatInt(to.Unix(), 10),
|
||||
}
|
||||
reader, _ := d.cli.ContainerLogs(ctx, id, options)
|
||||
|
||||
defer reader.Close()
|
||||
|
||||
containerJSON, _ := d.cli.ContainerInspect(ctx, id)
|
||||
|
||||
nextEntry := logReader(reader, containerJSON.Config.Tty)
|
||||
|
||||
var messages []string
|
||||
hdr := make([]byte, 8)
|
||||
var buffer bytes.Buffer
|
||||
|
||||
for {
|
||||
_, err := reader.Read(hdr)
|
||||
line, err := nextEntry()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
@@ -218,18 +220,7 @@ func (d *dockerClient) ContainerLogsBetweenDates(ctx context.Context, id string,
|
||||
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()
|
||||
messages = append(messages, line)
|
||||
}
|
||||
|
||||
return messages, nil
|
||||
|
||||
815
package-lock.json
generated
815
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
20
package.json
20
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dozzle",
|
||||
"version": "1.20.0",
|
||||
"version": "1.20.15",
|
||||
"description": "Realtime log viewer for docker containers. ",
|
||||
"scripts": {
|
||||
"prestart": "npm run clean",
|
||||
@@ -25,13 +25,13 @@
|
||||
"homepage": "https://github.com/amir20/dozzle#readme",
|
||||
"dependencies": {
|
||||
"ansi-to-html": "^0.6.13",
|
||||
"buefy": "^0.8.8",
|
||||
"buefy": "^0.8.9",
|
||||
"bulma": "^0.8.0",
|
||||
"date-fns": "^2.8.1",
|
||||
"date-fns": "^2.9.0",
|
||||
"hotkeys-js": "^3.7.3",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"semver": "^7.1.1",
|
||||
"splitpanes": "^2.2.0",
|
||||
"splitpanes": "^2.2.1",
|
||||
"store": "^2.0.12",
|
||||
"vue": "^2.6.11",
|
||||
"vue-meta": "^2.3.1",
|
||||
@@ -41,13 +41,14 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.7.7",
|
||||
"@babel/plugin-transform-runtime": "^7.7.6",
|
||||
"@vue/component-compiler-utils": "^3.1.0",
|
||||
"@vue/component-compiler-utils": "^3.1.1",
|
||||
"@vue/test-utils": "^1.0.0-beta.29",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-jest": "^24.9.0",
|
||||
"concurrently": "^5.0.2",
|
||||
"cz-conventional-changelog": "^3.0.2",
|
||||
"eventsourcemock": "^2.0.0",
|
||||
"husky": "^3.1.0",
|
||||
"husky": "^4.0.6",
|
||||
"jest": "^24.9.0",
|
||||
"jest-serializer-vue": "^2.0.2",
|
||||
"lint-staged": "^9.5.0",
|
||||
@@ -55,7 +56,7 @@
|
||||
"node-fetch": "^2.6.0",
|
||||
"parcel-bundler": "^1.12.4",
|
||||
"prettier": "^1.19.1",
|
||||
"sass": "^1.24.0",
|
||||
"sass": "^1.24.4",
|
||||
"vue-hot-reload-api": "^2.3.4",
|
||||
"vue-jest": "^3.0.5",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
@@ -104,5 +105,10 @@
|
||||
".*\\.vue$": "vue-jest",
|
||||
".+\\.js$": "babel-jest"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "./node_modules/cz-conventional-changelog"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user