mirror of
https://github.com/amir20/dozzle.git
synced 2025-12-21 21:33:18 +01:00
Makes changes to show all containers and event streams (#448)
This commit is contained in:
@@ -66,7 +66,7 @@ event: containers-changed
|
|||||||
data: start
|
data: start
|
||||||
|
|
||||||
/* snapshot: Test_handler_streamLogs_error_finding_container */
|
/* snapshot: Test_handler_streamLogs_error_finding_container */
|
||||||
HTTP/1.1 500 Internal Server Error
|
HTTP/1.1 404 Not Found
|
||||||
Connection: close
|
Connection: close
|
||||||
Content-Type: text/plain; charset=utf-8
|
Content-Type: text/plain; charset=utf-8
|
||||||
X-Content-Type-Options: nosniff
|
X-Content-Type-Options: nosniff
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ describe("<LogEventSource />", () => {
|
|||||||
await wrapper.vm.$nextTick();
|
await wrapper.vm.$nextTick();
|
||||||
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
||||||
<ul class="events medium">
|
<ul class="events medium">
|
||||||
<li><span class="date">today at 10:55 AM</span> <span class="text">"This is a message."</span></li>
|
<li class=""><span class="date">today at 10:55 AM</span> <span class="text">"This is a message."</span></li>
|
||||||
</ul>
|
</ul>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
@@ -149,7 +149,7 @@ describe("<LogEventSource />", () => {
|
|||||||
await wrapper.vm.$nextTick();
|
await wrapper.vm.$nextTick();
|
||||||
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
||||||
<ul class="events medium">
|
<ul class="events medium">
|
||||||
<li><span class="date">today at 10:55 AM</span> <span class="text"><span style="color:#000">black<span style="color:#AAA">white</span></span></span></li>
|
<li class=""><span class="date">today at 10:55 AM</span> <span class="text"><span style="color:#000">black<span style="color:#AAA">white</span></span></span></li>
|
||||||
</ul>
|
</ul>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
@@ -164,7 +164,7 @@ describe("<LogEventSource />", () => {
|
|||||||
await wrapper.vm.$nextTick();
|
await wrapper.vm.$nextTick();
|
||||||
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
||||||
<ul class="events medium">
|
<ul class="events medium">
|
||||||
<li><span class="date">today at 10:55 AM</span> <span class="text"><test>foo bar</test></span></li>
|
<li class=""><span class="date">today at 10:55 AM</span> <span class="text"><test>foo bar</test></span></li>
|
||||||
</ul>
|
</ul>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
@@ -182,7 +182,7 @@ describe("<LogEventSource />", () => {
|
|||||||
await wrapper.vm.$nextTick();
|
await wrapper.vm.$nextTick();
|
||||||
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
||||||
<ul class="events medium">
|
<ul class="events medium">
|
||||||
<li><span class="date">today at 10:55 AM</span> <span class="text">This is a <mark>test</mark> <hi></hi></span></li>
|
<li class=""><span class="date">today at 10:55 AM</span> <span class="text">This is a <mark>test</mark> <hi></hi></span></li>
|
||||||
</ul>
|
</ul>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -35,19 +35,23 @@ export default {
|
|||||||
this.es = null;
|
this.es = null;
|
||||||
}
|
}
|
||||||
this.es = new EventSource(`${config.base}/api/logs/stream?id=${this.id}`);
|
this.es = new EventSource(`${config.base}/api/logs/stream?id=${this.id}`);
|
||||||
const flushBuffer = debounce(
|
|
||||||
() => {
|
this.es.addEventListener("container-stopped", (e) => {
|
||||||
this.messages.push(...this.buffer);
|
this.es.close();
|
||||||
this.buffer = [];
|
this.buffer.push({ event: "container-stopped", message: "Container stopped", date: new Date() });
|
||||||
},
|
flushNow();
|
||||||
250,
|
});
|
||||||
{ maxWait: 1000 }
|
this.es.addEventListener("error", (e) => console.log("EventSource failed: " + JSON.stringify(e)));
|
||||||
);
|
|
||||||
|
const flushBuffer = debounce(() => flushNow(), 250, { maxWait: 1000 });
|
||||||
|
const flushNow = () => {
|
||||||
|
this.messages.push(...this.buffer);
|
||||||
|
this.buffer = [];
|
||||||
|
};
|
||||||
this.es.onmessage = (e) => {
|
this.es.onmessage = (e) => {
|
||||||
this.buffer.push(this.parseMessage(e.data));
|
this.buffer.push(this.parseMessage(e.data));
|
||||||
flushBuffer();
|
flushBuffer();
|
||||||
};
|
};
|
||||||
this.es.onerror = (e) => console.log("EventSource failed." + e);
|
|
||||||
this.$once("hook:beforeDestroy", () => this.es.close());
|
this.$once("hook:beforeDestroy", () => this.es.close());
|
||||||
},
|
},
|
||||||
async loadOlderLogs() {
|
async loadOlderLogs() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<ul class="events" :class="settings.size">
|
<ul class="events" :class="settings.size">
|
||||||
<li v-for="item in filtered" :key="item.key">
|
<li v-for="item in filtered" :key="item.key" :class="{ event: !!item.event }">
|
||||||
<span class="date" v-if="settings.showTimestamp">{{ item.date | relativeTime }}</span>
|
<span class="date" v-if="settings.showTimestamp">{{ item.date | relativeTime }}</span>
|
||||||
<span class="text" v-html="colorize(item.message)"></span>
|
<span class="text" v-html="colorize(item.message)"></span>
|
||||||
</li>
|
</li>
|
||||||
@@ -103,6 +103,10 @@ export default {
|
|||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
li.event {
|
||||||
|
color: #f14668;
|
||||||
|
}
|
||||||
|
|
||||||
::v-deep mark {
|
::v-deep mark {
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background-color: #ffdd57;
|
background-color: #ffdd57;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapActions, mapGetters, mapState } from "vuex";
|
import { mapGetters } from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: [],
|
props: [],
|
||||||
@@ -37,13 +37,8 @@ export default {
|
|||||||
showNav: false,
|
showNav: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["containers"]),
|
...mapGetters(["activeContainersById", "visibleContainers"]),
|
||||||
...mapGetters(["activeContainersById"]),
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
...mapActions({}),
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
$route(to, from) {
|
$route(to, from) {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<p class="menu-label is-hidden-mobile">Containers</p>
|
<p class="menu-label is-hidden-mobile">Containers</p>
|
||||||
<ul class="menu-list is-hidden-mobile">
|
<ul class="menu-list is-hidden-mobile">
|
||||||
<li v-for="item in containers">
|
<li v-for="item in visibleContainers" :key="item.id" :class="item.state">
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'container', params: { id: item.id, name: item.name } }"
|
:to="{ name: 'container', params: { id: item.id, name: item.name } }"
|
||||||
active-class="is-active"
|
active-class="is-active"
|
||||||
@@ -55,8 +55,8 @@ export default {
|
|||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["containers", "activeContainers"]),
|
...mapState(["activeContainers"]),
|
||||||
...mapGetters(["activeContainersById"]),
|
...mapGetters(["activeContainersById", "visibleContainers"]),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions({
|
...mapActions({
|
||||||
@@ -88,6 +88,10 @@ aside {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
li.exited a {
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
|
||||||
.will-append-container.icon {
|
.will-append-container.icon {
|
||||||
transition: transform 0.2s ease-out;
|
transition: transform 0.2s ease-out;
|
||||||
&.is-active {
|
&.is-active {
|
||||||
|
|||||||
@@ -37,6 +37,12 @@
|
|||||||
</b-switch>
|
</b-switch>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="item">
|
||||||
|
<b-switch v-model="showAllContainers">
|
||||||
|
Show stopped containers
|
||||||
|
</b-switch>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<h2 class="title is-6 is-marginless">Font size</h2>
|
<h2 class="title is-6 is-marginless">Font size</h2>
|
||||||
Modify the font size when viewing logs.
|
Modify the font size when viewing logs.
|
||||||
@@ -104,7 +110,7 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["settings"]),
|
...mapState(["settings"]),
|
||||||
...["search", "size", "smallerScrollbars", "showTimestamp"].reduce((map, name) => {
|
...["search", "size", "smallerScrollbars", "showTimestamp", "showAllContainers"].reduce((map, name) => {
|
||||||
map[name] = {
|
map[name] = {
|
||||||
get() {
|
get() {
|
||||||
return this.settings[name];
|
return this.settings[name];
|
||||||
|
|||||||
@@ -59,18 +59,22 @@ const actions = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
const getters = {
|
const getters = {
|
||||||
activeContainersById(state) {
|
activeContainersById({ activeContainers }) {
|
||||||
return state.activeContainers.reduce((map, obj) => {
|
return activeContainers.reduce((map, obj) => {
|
||||||
map[obj.id] = obj;
|
map[obj.id] = obj;
|
||||||
return map;
|
return map;
|
||||||
}, {});
|
}, {});
|
||||||
},
|
},
|
||||||
allContainersById(state) {
|
allContainersById({ containers }) {
|
||||||
return state.containers.reduce((map, obj) => {
|
return containers.reduce((map, obj) => {
|
||||||
map[obj.id] = obj;
|
map[obj.id] = obj;
|
||||||
return map;
|
return map;
|
||||||
}, {});
|
}, {});
|
||||||
},
|
},
|
||||||
|
visibleContainers({ containers, settings: { showAllContainers } }) {
|
||||||
|
const filter = showAllContainers ? () => true : (c) => c.state === "running";
|
||||||
|
return containers.filter(filter);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const es = new EventSource(`${config.base}/api/events/stream`);
|
const es = new EventSource(`${config.base}/api/events/stream`);
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ export const DEFAULT_SETTINGS = {
|
|||||||
menuWidth: 15,
|
menuWidth: 15,
|
||||||
smallerScrollbars: false,
|
smallerScrollbars: false,
|
||||||
showTimestamp: true,
|
showTimestamp: true,
|
||||||
|
showAllContainers: false,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ type dockerProxy interface {
|
|||||||
|
|
||||||
// Client is a proxy around the docker client
|
// Client is a proxy around the docker client
|
||||||
type Client interface {
|
type Client interface {
|
||||||
ListContainers(showAll bool) ([]Container, error)
|
ListContainers() ([]Container, error)
|
||||||
FindContainer(string) (Container, error)
|
FindContainer(string) (Container, error)
|
||||||
ContainerLogs(context.Context, string, int) (<-chan string, <-chan error)
|
ContainerLogs(context.Context, string, int) (<-chan string, <-chan error)
|
||||||
Events(context.Context) (<-chan events.Message, <-chan error)
|
Events(context.Context) (<-chan events.Message, <-chan error)
|
||||||
@@ -65,7 +65,7 @@ func NewClientWithFilters(f map[string]string) Client {
|
|||||||
|
|
||||||
func (d *dockerClient) FindContainer(id string) (Container, error) {
|
func (d *dockerClient) FindContainer(id string) (Container, error) {
|
||||||
var container Container
|
var container Container
|
||||||
containers, err := d.ListContainers(true)
|
containers, err := d.ListContainers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return container, err
|
return container, err
|
||||||
}
|
}
|
||||||
@@ -85,10 +85,10 @@ func (d *dockerClient) FindContainer(id string) (Container, error) {
|
|||||||
return container, nil
|
return container, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dockerClient) ListContainers(showAll bool) ([]Container, error) {
|
func (d *dockerClient) ListContainers() ([]Container, error) {
|
||||||
containerListOptions := types.ContainerListOptions{
|
containerListOptions := types.ContainerListOptions{
|
||||||
Filters: d.filters,
|
Filters: d.filters,
|
||||||
All: showAll,
|
All: true,
|
||||||
}
|
}
|
||||||
list, err := d.cli.ContainerList(context.Background(), containerListOptions)
|
list, err := d.cli.ContainerList(context.Background(), containerListOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ func Test_dockerClient_ListContainers_null(t *testing.T) {
|
|||||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil)
|
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil)
|
||||||
client := &dockerClient{proxy, filters.NewArgs()}
|
client := &dockerClient{proxy, filters.NewArgs()}
|
||||||
|
|
||||||
list, err := client.ListContainers(true)
|
list, err := client.ListContainers()
|
||||||
assert.Empty(t, list, "list should be empty")
|
assert.Empty(t, list, "list should be empty")
|
||||||
require.NoError(t, err, "error should not return an error.")
|
require.NoError(t, err, "error should not return an error.")
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ func Test_dockerClient_ListContainers_error(t *testing.T) {
|
|||||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, errors.New("test"))
|
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, errors.New("test"))
|
||||||
client := &dockerClient{proxy, filters.NewArgs()}
|
client := &dockerClient{proxy, filters.NewArgs()}
|
||||||
|
|
||||||
list, err := client.ListContainers(true)
|
list, err := client.ListContainers()
|
||||||
assert.Nil(t, list, "list should be nil")
|
assert.Nil(t, list, "list should be nil")
|
||||||
require.Error(t, err, "test.")
|
require.Error(t, err, "test.")
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ func Test_dockerClient_ListContainers_happy(t *testing.T) {
|
|||||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
|
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
|
||||||
client := &dockerClient{proxy, filters.NewArgs()}
|
client := &dockerClient{proxy, filters.NewArgs()}
|
||||||
|
|
||||||
list, err := client.ListContainers(true)
|
list, err := client.ListContainers()
|
||||||
require.NoError(t, err, "error should not return an error.")
|
require.NoError(t, err, "error should not return an error.")
|
||||||
|
|
||||||
assert.Equal(t, list, []Container{
|
assert.Equal(t, list, []Container{
|
||||||
|
|||||||
18
main.go
18
main.go
@@ -27,15 +27,13 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type handler struct {
|
type handler struct {
|
||||||
client docker.Client
|
client docker.Client
|
||||||
showAll bool
|
box packr.Box
|
||||||
box packr.Box
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
pflag.String("addr", ":8080", "http service address")
|
pflag.String("addr", ":8080", "http service address")
|
||||||
pflag.String("base", "/", "base address of the application to mount")
|
pflag.String("base", "/", "base address of the application to mount")
|
||||||
pflag.Bool("showAll", false, "show all containers, even stopped")
|
|
||||||
pflag.String("level", "info", "logging level")
|
pflag.String("level", "info", "logging level")
|
||||||
pflag.Int("tailSize", 300, "Tail size to use for initial container logs")
|
pflag.Int("tailSize", 300, "Tail size to use for initial container logs")
|
||||||
pflag.StringToStringVar(&filters, "filter", map[string]string{}, "Container filters to use for showing logs")
|
pflag.StringToStringVar(&filters, "filter", map[string]string{}, "Container filters to use for showing logs")
|
||||||
@@ -49,10 +47,9 @@ func init() {
|
|||||||
base = viper.GetString("base")
|
base = viper.GetString("base")
|
||||||
level = viper.GetString("level")
|
level = viper.GetString("level")
|
||||||
tailSize = viper.GetInt("tailSize")
|
tailSize = viper.GetInt("tailSize")
|
||||||
showAll = viper.GetBool("showAll")
|
|
||||||
|
|
||||||
// Until https://github.com/spf13/viper/issues/608 is fixed. We have to use this hacky way.
|
// Until https://github.com/spf13/viper/issues/911 is fixed. We have to use this hacky way.
|
||||||
// filters = viper.GetStringSlice("filter")
|
// filters = viper.GetStringMapString("filter")
|
||||||
if value, ok := os.LookupEnv("DOZZLE_FILTER"); ok {
|
if value, ok := os.LookupEnv("DOZZLE_FILTER"); ok {
|
||||||
log.Infof("Parsing %s", value)
|
log.Infof("Parsing %s", value)
|
||||||
urlValues, err := url.ParseQuery(strings.ReplaceAll(value, ",", "&"))
|
urlValues, err := url.ParseQuery(strings.ReplaceAll(value, ",", "&"))
|
||||||
@@ -77,7 +74,7 @@ func init() {
|
|||||||
func main() {
|
func main() {
|
||||||
log.Infof("Dozzle version %s", version)
|
log.Infof("Dozzle version %s", version)
|
||||||
dockerClient := docker.NewClientWithFilters(filters)
|
dockerClient := docker.NewClientWithFilters(filters)
|
||||||
_, err := dockerClient.ListContainers(true)
|
_, err := dockerClient.ListContainers()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Could not connect to Docker Engine: %v", err)
|
log.Fatalf("Could not connect to Docker Engine: %v", err)
|
||||||
@@ -85,9 +82,8 @@ func main() {
|
|||||||
|
|
||||||
box := packr.NewBox("./static")
|
box := packr.NewBox("./static")
|
||||||
r := createRoutes(base, &handler{
|
r := createRoutes(base, &handler{
|
||||||
client: dockerClient,
|
client: dockerClient,
|
||||||
showAll: showAll,
|
box: box,
|
||||||
box: box,
|
|
||||||
})
|
})
|
||||||
srv := &http.Server{Addr: addr, Handler: r}
|
srv := &http.Server{Addr: addr, Handler: r}
|
||||||
|
|
||||||
|
|||||||
12
main_test.go
12
main_test.go
@@ -32,7 +32,7 @@ func (m *MockedClient) FindContainer(id string) (docker.Container, error) {
|
|||||||
return container, args.Error(1)
|
return container, args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockedClient) ListContainers(showAll bool) ([]docker.Container, error) {
|
func (m *MockedClient) ListContainers() ([]docker.Container, error) {
|
||||||
args := m.Called()
|
args := m.Called()
|
||||||
containers, ok := args.Get(0).([]docker.Container)
|
containers, ok := args.Get(0).([]docker.Container)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -246,7 +246,7 @@ func Test_createRoutes_index(t *testing.T) {
|
|||||||
box := packr.NewBox("./virtual")
|
box := packr.NewBox("./virtual")
|
||||||
require.NoError(t, box.AddString("index.html", "index page"), "AddString should have no error.")
|
require.NoError(t, box.AddString("index.html", "index page"), "AddString should have no error.")
|
||||||
|
|
||||||
handler := createRoutes("/", &handler{mockedClient, true, box})
|
handler := createRoutes("/", &handler{mockedClient, box})
|
||||||
req, err := http.NewRequest("GET", "/", nil)
|
req, err := http.NewRequest("GET", "/", nil)
|
||||||
require.NoError(t, err, "NewRequest should not return an error.")
|
require.NoError(t, err, "NewRequest should not return an error.")
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
@@ -259,7 +259,7 @@ func Test_createRoutes_redirect(t *testing.T) {
|
|||||||
mockedClient := new(MockedClient)
|
mockedClient := new(MockedClient)
|
||||||
box := packr.NewBox("./virtual")
|
box := packr.NewBox("./virtual")
|
||||||
|
|
||||||
handler := createRoutes("/foobar", &handler{mockedClient, true, box})
|
handler := createRoutes("/foobar", &handler{mockedClient, box})
|
||||||
req, err := http.NewRequest("GET", "/foobar", nil)
|
req, err := http.NewRequest("GET", "/foobar", nil)
|
||||||
require.NoError(t, err, "NewRequest should not return an error.")
|
require.NoError(t, err, "NewRequest should not return an error.")
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
@@ -273,7 +273,7 @@ func Test_createRoutes_foobar(t *testing.T) {
|
|||||||
box := packr.NewBox("./virtual")
|
box := packr.NewBox("./virtual")
|
||||||
require.NoError(t, box.AddString("index.html", "foo page"), "AddString should have no error.")
|
require.NoError(t, box.AddString("index.html", "foo page"), "AddString should have no error.")
|
||||||
|
|
||||||
handler := createRoutes("/foobar", &handler{mockedClient, true, box})
|
handler := createRoutes("/foobar", &handler{mockedClient, box})
|
||||||
req, err := http.NewRequest("GET", "/foobar/", nil)
|
req, err := http.NewRequest("GET", "/foobar/", nil)
|
||||||
require.NoError(t, err, "NewRequest should not return an error.")
|
require.NoError(t, err, "NewRequest should not return an error.")
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
@@ -287,7 +287,7 @@ func Test_createRoutes_foobar_file(t *testing.T) {
|
|||||||
box := packr.NewBox("./virtual")
|
box := packr.NewBox("./virtual")
|
||||||
require.NoError(t, box.AddString("/test", "test page"), "AddString should have no error.")
|
require.NoError(t, box.AddString("/test", "test page"), "AddString should have no error.")
|
||||||
|
|
||||||
handler := createRoutes("/foobar", &handler{mockedClient, true, box})
|
handler := createRoutes("/foobar", &handler{mockedClient, box})
|
||||||
req, err := http.NewRequest("GET", "/foobar/test", nil)
|
req, err := http.NewRequest("GET", "/foobar/test", nil)
|
||||||
require.NoError(t, err, "NewRequest should not return an error.")
|
require.NoError(t, err, "NewRequest should not return an error.")
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
@@ -300,7 +300,7 @@ func Test_createRoutes_version(t *testing.T) {
|
|||||||
mockedClient := new(MockedClient)
|
mockedClient := new(MockedClient)
|
||||||
box := packr.NewBox("./virtual")
|
box := packr.NewBox("./virtual")
|
||||||
|
|
||||||
handler := createRoutes("/", &handler{mockedClient, true, box})
|
handler := createRoutes("/", &handler{mockedClient, box})
|
||||||
req, err := http.NewRequest("GET", "/version", nil)
|
req, err := http.NewRequest("GET", "/version", nil)
|
||||||
require.NoError(t, err, "NewRequest should not return an error.")
|
require.NoError(t, err, "NewRequest should not return an error.")
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
|
|||||||
15
routes.go
15
routes.go
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
@@ -68,7 +69,7 @@ func (h *handler) index(w http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) listContainers(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) listContainers(w http.ResponseWriter, r *http.Request) {
|
||||||
containers, err := h.client.ListContainers(h.showAll)
|
containers, err := h.client.ListContainers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@@ -109,7 +110,7 @@ func (h *handler) streamLogs(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
container, e := h.client.FindContainer(id)
|
container, e := h.client.FindContainer(id)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
http.Error(w, e.Error(), http.StatusInternalServerError)
|
http.Error(w, e.Error(), http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,8 +136,14 @@ Loop:
|
|||||||
}
|
}
|
||||||
f.Flush()
|
f.Flush()
|
||||||
case e := <-err:
|
case e := <-err:
|
||||||
log.Debugf("Error while reading from log stream: %v", e)
|
if e == io.EOF {
|
||||||
break Loop
|
log.Debugf("Container stopped: %v", container.ID)
|
||||||
|
fmt.Fprintf(w, "event: container-stopped\ndata: end of stream\n\n")
|
||||||
|
f.Flush()
|
||||||
|
} else {
|
||||||
|
log.Debugf("Error while reading from log stream: %v", e)
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user