Compare commits
378 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 919bb8249d | |||
| 3e1432007a | |||
| 9f510c3b71 | |||
| 9198f5d34f | |||
| 50f54ab49e | |||
| efff2a8d55 | |||
| b1692e459e | |||
| 961e376c6e | |||
| c4c18d5f3e | |||
| 6a8cbe37d6 | |||
| 0d6ba2ddcc | |||
| 62e3d96699 | |||
| c233272016 | |||
| 79ab206617 | |||
| 6bf6e0f450 | |||
| 2ff7cfcd0b | |||
| 6ba65014d6 | |||
| c96efe44c2 | |||
| 902fde9c82 | |||
| a75b64a1e2 | |||
| 49ca0732a5 | |||
| 49249059b0 | |||
| c1db4587a0 | |||
| d2ce09cf47 | |||
| 8223597430 | |||
| d37f5d9b45 | |||
| 260176a3b8 | |||
| cdcd598abd | |||
| 934f9f6a1b | |||
| 0f70890985 | |||
| 7c68864d95 | |||
| 0b8a272db8 | |||
| 9e5b12a1ff | |||
| a0a1ca187d | |||
| 95020f1ecf | |||
| 7c84b7bb69 | |||
| 64e244cb6f | |||
| ec7dfe1fd6 | |||
| 0e425cea04 | |||
| 5c2a7a509b | |||
| 862b34283c | |||
| fe7427d91b | |||
| 636fc72235 | |||
| b1cb7cbd5f | |||
| e320ad4114 | |||
| 680d53df69 | |||
| 891ffca04c | |||
| 80a51d4968 | |||
| f5f53561bb | |||
| 766d4d5015 | |||
| 4cc5870ea4 | |||
| 2cd92016fd | |||
| 333b99113e | |||
| f1f949c792 | |||
| 20ee71765c | |||
| d6762f3e7e | |||
| c260d76cff | |||
| 798f83b9e3 | |||
| c67b18cae1 | |||
| 544cc678f5 | |||
| c8da00c7c4 | |||
| 8e6e645036 | |||
| ef8596aff7 | |||
| a0d3a86afc | |||
| 6d5d0094c4 | |||
| bddb1cacf6 | |||
| c666d5ac46 | |||
| 566ab822bc | |||
| f0524a748e | |||
| e84823ac6b | |||
| 54b3e69dd7 | |||
| 8ad48fdcaa | |||
| f35cb4f4b7 | |||
| c2f92ea500 | |||
| 948bbaac41 | |||
| 84af2fa0d3 | |||
| 62663c39c1 | |||
| 730923cb60 | |||
| 2dc4761d65 | |||
| 0a8ee92c68 | |||
| d967ebcade | |||
| 9a844d98e1 | |||
| d4afd9eae3 | |||
| 62cafc58f2 | |||
| 4580757c86 | |||
| c74630621c | |||
| e80dad3e19 | |||
| d9ab5d1ca7 | |||
| 01742cf3cb | |||
| 47f4e8382e | |||
| a261080e60 | |||
| 75a0056e1f | |||
| 4bf036f629 | |||
| 85a393e3ff | |||
| 8b77687ed3 | |||
| afc4c4b974 | |||
| 020862682d | |||
| f4eccc92cd | |||
| 7f5b68a7cc | |||
| 6c9194a336 | |||
| 94e6e80058 | |||
| 38c0dd9462 | |||
| 681a5900ec | |||
| 66c88858e7 | |||
| 4b268976e0 | |||
| 5564eff07a | |||
| 06b70faadc | |||
| 68e730352b | |||
| 71d8024f7d | |||
| 98a4124de4 | |||
| c3eeba18a2 | |||
| 82d5e3e190 | |||
| 5a834c4821 | |||
| 724b6d2015 | |||
| 8df19dd9b0 | |||
| 4c386e9a2f | |||
| ee29d6b872 | |||
| aa74575244 | |||
| 16c9f8ccba | |||
| be6c7e1728 | |||
| 39b31dd38a | |||
| f47b5dea9a | |||
| 22985b7af1 | |||
| b3289e045a | |||
| 346252dd91 | |||
| 8f7578a03b | |||
| acce5c20b7 | |||
| 8059ef3aed | |||
| 5509084542 | |||
| 55d6125456 | |||
| ad8f5943e3 | |||
| 7447cea06a | |||
| 32bf52b349 | |||
| 1343e8b54d | |||
| 79002580ef | |||
| deb6680843 | |||
| 27f1bd7819 | |||
| ae8b57320b | |||
| d6165593bd | |||
| a121562c49 | |||
| ae5918d44b | |||
| c80ce1b539 | |||
| fed8b213d4 | |||
| c144a6ad28 | |||
| ab963049a0 | |||
| 1d42b957d9 | |||
| d22202e738 | |||
| 9352d27748 | |||
| 739631a7b9 | |||
| 6e1f8699d5 | |||
| f3a4ee54fb | |||
| 66ebbad63a | |||
| f2f0ac749e | |||
| 52a48fb674 | |||
| 0b5ab2ad4a | |||
| baf34138d5 | |||
| a5f6e8ad38 | |||
| 3a61ae7558 | |||
| e9649d6fae | |||
| dde232c581 | |||
| 1711ea7fca | |||
| 6c8c6d5fa6 | |||
| 15467eda9b | |||
| 13fbd612e3 | |||
| b5cace3508 | |||
| 7ff2665be8 | |||
| 39b0887f80 | |||
| 3eddc38220 | |||
| fed335fb64 | |||
| f59c65b4d9 | |||
| 640cae5c91 | |||
| 8a2166cf5b | |||
| 014a4eac9a | |||
| 762ad472ee | |||
| 891ade3545 | |||
| c8d5c98892 | |||
| e239aa9596 | |||
| 66e5b67796 | |||
| 6d99deb686 | |||
| 3b5a2bc8d0 | |||
| 303e0a7727 | |||
| 744740c2d4 | |||
| 2ad5c3eafb | |||
| ddb54bff81 | |||
| 0665c886cb | |||
| c0ca6c587e | |||
| 79f32bcf3c | |||
| 666af6358a | |||
| eadbd59f85 | |||
| 1e6d043834 | |||
| 73109d45cf | |||
| 8c16de488a | |||
| a5062ceaef | |||
| 2ad48a3213 | |||
| ff249c51ef | |||
| 8dee00d8f7 | |||
| c7b2df58e4 | |||
| a0fe986498 | |||
| 140e957b5d | |||
| b91c505065 | |||
| 36a975e340 | |||
| 9ab52965e1 | |||
| 33151417a6 | |||
| 347783e07f | |||
| f704f845e0 | |||
| 1b8331b49f | |||
| be5e967e90 | |||
| 1c93f60387 | |||
| 2b561e88ae | |||
| 4f9e6a2687 | |||
| 9a2960ff29 | |||
| 7b4d8e8bda | |||
| 39a1cc78c0 | |||
| 38163a2722 | |||
| 50c82953e2 | |||
| 6dd941c2e9 | |||
| 37cbad29e1 | |||
| d28c53cdf0 | |||
| faf9cd80e6 | |||
| 10fac9a198 | |||
| b3d80eebe2 | |||
| d6c98df84f | |||
| d1a57e8daf | |||
| a00399f248 | |||
| 9046c06dd4 | |||
| 1e0b39ae20 | |||
| f9775b09ac | |||
| 8edcdce628 | |||
| 5a82e477cd | |||
| 38f27e4826 | |||
| 7538238fb5 | |||
| a730860c33 | |||
| 048e304446 | |||
| 57a51be28c | |||
| 295374b488 | |||
| a5dec74438 | |||
| 93bb7afbfe | |||
| 4b5a9e4dbb | |||
| 514558c4d4 | |||
| 3cdf8a21dc | |||
| 160c9c535a | |||
| 1962365e9c | |||
| c88e22b843 | |||
| 15ae7e079c | |||
| fdb9de2977 | |||
| e0735ee98b | |||
| c8728302fb | |||
| 937e42b88e | |||
| b317a95719 | |||
| 7ffb88f633 | |||
| cac90de16c | |||
| 02b7846d4c | |||
| 5a71bdbd17 | |||
| aea84757f9 | |||
| 3971786a04 | |||
| 24ef96a586 | |||
| 90506d2cc0 | |||
| e212aa3409 | |||
| 0a9aa6b94f | |||
| 0ff21d6c57 | |||
| 73a0feb1d6 | |||
| c252991d24 | |||
| 2231551c60 | |||
| 4c00ffe18f | |||
| c86e770c92 | |||
| 481ff22efe | |||
| dcfd6ec476 | |||
| f13297a7dc | |||
| 220722deaa | |||
| b7af2b7826 | |||
| edc200cd9e | |||
| dcc661c761 | |||
| bbb20b5d39 | |||
| 214c9de4f8 | |||
| 91a29165c7 | |||
| 4d331742e5 | |||
| 0f8639abad | |||
| 36585a4622 | |||
| 1190d31ef0 | |||
| 68724639b5 | |||
| 5bf3bf8676 | |||
| 8f719278aa | |||
| 7bc775eb7c | |||
| 6abdb276e8 | |||
| bc14f23b37 | |||
| 005336cf56 | |||
| 649d97739e | |||
| 3f38e05e4b | |||
| 877dd9be14 | |||
| 73a8707f54 | |||
| efd7f2fe85 | |||
| 66bc08cbe3 | |||
| 95732c0149 | |||
| 8af1b3a463 | |||
| 7bfe7adb73 | |||
| 2a5286764e | |||
| a604d309a4 | |||
| 3b3d98e763 | |||
| a65aea0680 | |||
| 3c23631df7 | |||
| 07b2f7bd0e | |||
| efd29ef7ae | |||
| cd6564ce93 | |||
| 5205790b3e | |||
| aaf2f2fc20 | |||
| b3e7bcae79 | |||
| 826efa2728 | |||
| 6aacc7b3c5 | |||
| 834317720b | |||
| 7e2af57aa6 | |||
| cb6d94dee5 | |||
| 366c15d1b7 | |||
| 2cfd5879cd | |||
| 541394d310 | |||
| 1057f45887 | |||
| 74dba50340 | |||
| dae0886456 | |||
| b04a85a10a | |||
| bde5b4287d | |||
| 078b9a11fd | |||
| bf89411ebf | |||
| 24f19fadec | |||
| 0caf7da96b | |||
| 0fd5d91f12 | |||
| 9bd1008291 | |||
| 2327b26bc7 | |||
| 807a044ebb | |||
| b658337b24 | |||
| 23231f9e25 | |||
| 6fa20c3223 | |||
| f12b1ba7ee | |||
| 88de77cce0 | |||
| 30bbd333af | |||
| a371a68d54 | |||
| e2340d6656 | |||
| 9cd0e932bd | |||
| 21bec08327 | |||
| 9213a7e4d0 | |||
| 4325810d94 | |||
| 33e6830962 | |||
| c70d196e6d | |||
| 9357b49a92 | |||
| d880800e17 | |||
| 0d37384259 | |||
| 7a778ee93c | |||
| 8744aee484 | |||
| 4b6aaeb42e | |||
| 0eb5d97a59 | |||
| 0a95b58ec9 | |||
| c9891ecc9d | |||
| 53f6e7f27c | |||
| 13068244b7 | |||
| 7e7029a2d1 | |||
| 64855680f6 | |||
| 0968e59ccb | |||
| 9cdb59dc10 | |||
| 928b5bb2f2 | |||
| 50930c0eff | |||
| f7c92f9457 | |||
| 13e57ac095 | |||
| b148367618 | |||
| 0c2e2430fd | |||
| 575dc03631 | |||
| 95fa2453a9 | |||
| 06e4202cba | |||
| a995dd40f0 | |||
| 2d2883db54 | |||
| 5180ef2a22 | |||
| d9b3d9c0d0 | |||
| ebb3ea3af5 | |||
| 174d9a5e45 | |||
| a35ba9a9ee | |||
| f5e74226a2 | |||
| a90c29c9c4 | |||
| 24425b2643 | |||
| 439b8c27c8 | |||
| fe8b9a7b6e | |||
| d48704fe06 |
@@ -31,20 +31,16 @@ Content-Type: text/plain; charset=utf-8
|
||||
|
||||
dev
|
||||
|
||||
/* snapshot: Test_handler_listContainers_happy */
|
||||
HTTP/1.1 200 OK
|
||||
Connection: close
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
[{"id":"1234567890","names":null,"name":"test","image":"image","imageId":"image_id","command":"command","created":0,"state":"state","status":"status"}]
|
||||
|
||||
/* snapshot: Test_handler_streamEvents_error */
|
||||
HTTP/1.1 200 OK
|
||||
Connection: close
|
||||
Cache-Control: no-cache
|
||||
Connection: keep-alive
|
||||
Content-Type: text/event-stream
|
||||
X-Accel-Buffering: no
|
||||
X-Accel-Buffering: no
|
||||
|
||||
event: containers-changed
|
||||
data: []
|
||||
|
||||
/* snapshot: Test_handler_streamEvents_error_request */
|
||||
HTTP/1.1 200 OK
|
||||
@@ -52,7 +48,10 @@ Connection: close
|
||||
Cache-Control: no-cache
|
||||
Connection: keep-alive
|
||||
Content-Type: text/event-stream
|
||||
X-Accel-Buffering: no
|
||||
X-Accel-Buffering: no
|
||||
|
||||
event: containers-changed
|
||||
data: []
|
||||
|
||||
/* snapshot: Test_handler_streamEvents_happy */
|
||||
HTTP/1.1 200 OK
|
||||
@@ -63,7 +62,7 @@ Content-Type: text/event-stream
|
||||
X-Accel-Buffering: no
|
||||
|
||||
event: containers-changed
|
||||
data: start
|
||||
data: []
|
||||
|
||||
/* snapshot: Test_handler_streamLogs_error_finding_container */
|
||||
HTTP/1.1 404 Not Found
|
||||
|
||||
@@ -17,31 +17,32 @@ describe("<App />", () => {
|
||||
global.EventSource = EventSource;
|
||||
const state = {
|
||||
settings: { menuWidth: 15 },
|
||||
containers: [{ id: "abc", name: "Test 1" }],
|
||||
};
|
||||
|
||||
const getters = {
|
||||
visibleContainers() {
|
||||
return [
|
||||
{ id: "abc", name: "Test 1" },
|
||||
{ id: "xyz", name: "Test 2" },
|
||||
];
|
||||
visibleContainers(store) {
|
||||
return store.containers;
|
||||
},
|
||||
activeContainers() {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
||||
const actions = {
|
||||
FETCH_CONTAINERS: () => Promise.resolve(),
|
||||
};
|
||||
|
||||
store = new Vuex.Store({
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
});
|
||||
});
|
||||
|
||||
test("has right title", async () => {
|
||||
const wrapper = shallowMount(App, { stubs, store, localVue });
|
||||
wrapper.vm.$store.state.containers = [
|
||||
{ id: "abc", name: "Test 1" },
|
||||
{ id: "xyz", name: "Test 2" },
|
||||
];
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.vm.title).toContain("2 containers");
|
||||
});
|
||||
|
||||
|
||||
@@ -13,12 +13,13 @@
|
||||
<router-view></router-view>
|
||||
</pane>
|
||||
<pane v-for="other in activeContainers" :key="other.id" v-if="!isMobile">
|
||||
<scrollable-view>
|
||||
<template v-slot:header>
|
||||
<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>
|
||||
<log-container
|
||||
:id="other.id"
|
||||
show-title
|
||||
scrollable
|
||||
closable
|
||||
@close="removeActiveContainer(other)"
|
||||
></log-container>
|
||||
</pane>
|
||||
</splitpanes>
|
||||
</pane>
|
||||
@@ -41,26 +42,22 @@
|
||||
import { mapActions, mapGetters, mapState } from "vuex";
|
||||
import { Splitpanes, Pane } from "splitpanes";
|
||||
|
||||
import LogViewerWithSource from "./components/LogViewerWithSource";
|
||||
import ScrollableView from "./components/ScrollableView";
|
||||
import LogContainer from "./components/LogContainer";
|
||||
import SideMenu from "./components/SideMenu";
|
||||
import MobileMenu from "./components/MobileMenu";
|
||||
import Search from "./components/Search";
|
||||
import ContainerTitle from "./components/ContainerTitle";
|
||||
import Icon from "./components/Icon";
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
Icon,
|
||||
LogViewerWithSource,
|
||||
SideMenu,
|
||||
LogContainer,
|
||||
MobileMenu,
|
||||
ScrollableView,
|
||||
Splitpanes,
|
||||
Pane,
|
||||
Search,
|
||||
ContainerTitle,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -74,10 +71,6 @@ export default {
|
||||
titleTemplate: "%s - Dozzle",
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
await this.fetchContainerList();
|
||||
this.title = `${this.visibleContainers.length} containers`;
|
||||
},
|
||||
mounted() {
|
||||
if (this.hasSmallerScrollbars) {
|
||||
document.documentElement.classList.add("has-custom-scrollbars");
|
||||
@@ -102,10 +95,13 @@ export default {
|
||||
document.documentElement.removeAttribute("data-theme");
|
||||
}
|
||||
},
|
||||
visibleContainers() {
|
||||
this.title = `${this.visibleContainers.length} containers`;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(["activeContainers", "isMobile", "settings"]),
|
||||
...mapGetters(["visibleContainers"]),
|
||||
...mapState(["isMobile", "settings"]),
|
||||
...mapGetters(["visibleContainers", "activeContainers"]),
|
||||
hasSmallerScrollbars() {
|
||||
return this.settings.smallerScrollbars;
|
||||
},
|
||||
@@ -115,7 +111,6 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
fetchContainerList: "FETCH_CONTAINERS",
|
||||
removeActiveContainer: "REMOVE_ACTIVE_CONTAINER",
|
||||
updateSetting: "UPDATE_SETTING",
|
||||
}),
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<div class="is-size-7 is-uppercase columns is-marginless is-mobile">
|
||||
<div class="column is-narrow has-text-weight-bold">
|
||||
{{ state }}
|
||||
</div>
|
||||
<div class="column is-narrow" v-if="stat.memoryUsage !== null">
|
||||
<span class="has-text-weight-light">mem</span>
|
||||
<span class="has-text-weight-bold">
|
||||
{{ formatBytes(stat.memoryUsage) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="column is-narrow" v-if="stat.cpu !== null">
|
||||
<span class="has-text-weight-light">load</span>
|
||||
<span class="has-text-weight-bold"> {{ stat.cpu }}% </span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
stat: Object,
|
||||
state: String,
|
||||
},
|
||||
name: "ContainerStat",
|
||||
methods: {
|
||||
formatBytes(bytes, decimals = 2) {
|
||||
if (bytes === 0) return "0 Bytes";
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -1,9 +1,6 @@
|
||||
<template>
|
||||
<div class="name columns is-marginless">
|
||||
<div class="columns is-marginless has-text-weight-bold is-family-monospace">
|
||||
<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>
|
||||
|
||||
@@ -11,33 +8,9 @@
|
||||
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;
|
||||
|
||||
button.delete {
|
||||
background-color: var(--scheme-main-ter);
|
||||
opacity: 0.6;
|
||||
&:after,
|
||||
&:before {
|
||||
background-color: var(--text-color);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<div ref="observer" class="control" :class="{ 'is-loading': isLoading }"></div>
|
||||
<div ref="observer" class="infinte-loader">
|
||||
<div class="spinner" v-show="isLoading">
|
||||
<div class="bounce1"></div>
|
||||
<div class="bounce2"></div>
|
||||
<div class="bounce3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -36,4 +42,40 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
||||
<style scoped lang="scss">
|
||||
.infinte-loader {
|
||||
min-height: 1px;
|
||||
}
|
||||
.spinner {
|
||||
margin: 10px auto 0;
|
||||
width: 70px;
|
||||
text-align: center;
|
||||
|
||||
& > div {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background-color: var(--primary-color);
|
||||
border-radius: 100%;
|
||||
display: inline-block;
|
||||
animation: sk-bouncedelay 0.8s infinite ease-in-out both;
|
||||
}
|
||||
& .bounce1 {
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
& .bounce2 {
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes sk-bouncedelay {
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
transform: scale(0);
|
||||
}
|
||||
40% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<scrollable-view :scrollable="scrollable" v-if="container">
|
||||
<template v-slot:header v-if="showTitle">
|
||||
<div class="columns is-vcentered mr-0">
|
||||
<div class="column is-narrow">
|
||||
<container-title :value="container.name" @close="$emit('close')"></container-title>
|
||||
</div>
|
||||
<div class="column is-clipped">
|
||||
<container-stat :stat="container.stat" :state="container.state"></container-stat>
|
||||
</div>
|
||||
<div class="column is-narrow" v-if="closable">
|
||||
<button class="delete is-medium" @click="$emit('close')"></button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot="{ setLoading }">
|
||||
<log-viewer-with-source :id="id" @loading-more="setLoading($event)"></log-viewer-with-source>
|
||||
</template>
|
||||
</scrollable-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
|
||||
import LogViewerWithSource from "./LogViewerWithSource";
|
||||
import ScrollableView from "./ScrollableView";
|
||||
import ContainerTitle from "./ContainerTitle";
|
||||
import ContainerStat from "./ContainerStat";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
},
|
||||
showTitle: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
scrollable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
closable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
name: "LogContainer",
|
||||
components: {
|
||||
LogViewerWithSource,
|
||||
ScrollableView,
|
||||
ContainerTitle,
|
||||
ContainerStat,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["allContainersById"]),
|
||||
container() {
|
||||
return this.allContainersById[this.id];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
button.delete {
|
||||
background-color: var(--scheme-main-ter);
|
||||
opacity: 0.6;
|
||||
&:after,
|
||||
&:before {
|
||||
background-color: var(--text-color);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -60,6 +60,7 @@ export default {
|
||||
async loadOlderLogs() {
|
||||
if (this.messages.length < 300) return;
|
||||
|
||||
this.$emit("loading-more", true);
|
||||
const to = this.messages[0].date;
|
||||
const last = this.messages[299].date;
|
||||
const delta = to - last;
|
||||
@@ -74,6 +75,7 @@ export default {
|
||||
.map((line) => this.parseMessage(line));
|
||||
this.messages.unshift(...newMessages);
|
||||
}
|
||||
this.$emit("loading-more", false);
|
||||
},
|
||||
parseMessage(data) {
|
||||
let i = data.indexOf(" ");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<log-event-source :id="id" v-slot="eventSource">
|
||||
<log-event-source :id="id" v-slot="eventSource" @loading-more="$emit('loading-more', $event)">
|
||||
<log-viewer :messages="eventSource.messages"></log-viewer>
|
||||
</log-event-source>
|
||||
</template>
|
||||
|
||||
@@ -1,16 +1,29 @@
|
||||
<template>
|
||||
<aside>
|
||||
<a
|
||||
role="button"
|
||||
class="navbar-burger burger is-hidden-tablet is-pulled-right"
|
||||
@click="showNav = !showNav"
|
||||
:class="{ 'is-active': showNav }"
|
||||
>
|
||||
<span></span> <span></span> <span></span>
|
||||
</a>
|
||||
<svg class="logo">
|
||||
<use href="#logo"></use>
|
||||
</svg>
|
||||
<div class="columns is-marginless is-gapless is-mobile is-vcentered">
|
||||
<div class="column is-narrow">
|
||||
<router-link :to="{ name: 'default' }">
|
||||
<svg class="logo">
|
||||
<use href="#logo"></use>
|
||||
</svg>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="column ml-4 is-family-monospace is-ellipsis" v-if="$route.name == 'container'">
|
||||
{{ allContainersById[$route.params.id].name }}
|
||||
</div>
|
||||
|
||||
<div class="column is-narrow push-right">
|
||||
<a
|
||||
role="button"
|
||||
class="navbar-burger burger is-hidden-tablet is-pulled-right"
|
||||
@click="showNav = !showNav"
|
||||
:class="{ 'is-active': showNav }"
|
||||
>
|
||||
<span></span> <span></span> <span></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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 visibleContainers" :key="item.id">
|
||||
@@ -19,7 +32,7 @@
|
||||
active-class="is-active"
|
||||
:title="item.name"
|
||||
>
|
||||
<div class="hide-overflow">
|
||||
<div class="is-ellipsis">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</router-link>
|
||||
@@ -40,7 +53,7 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["activeContainersById", "visibleContainers"]),
|
||||
...mapGetters(["visibleContainers", "allContainersById"]),
|
||||
},
|
||||
watch: {
|
||||
$route(to, from) {
|
||||
@@ -64,11 +77,6 @@ aside {
|
||||
.menu-label {
|
||||
margin-top: 1em;
|
||||
}
|
||||
.hide-overflow {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.title {
|
||||
text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.2);
|
||||
@@ -91,5 +99,9 @@ aside {
|
||||
height: 36px;
|
||||
fill: var(--logo-color);
|
||||
}
|
||||
|
||||
.column.push-right {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,25 +1,38 @@
|
||||
<template>
|
||||
<div class="scroll-progress">
|
||||
<svg width="100" height="100" viewBox="0 0 100 100">
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" :class="{ indeterminate }">
|
||||
<circle r="44" cx="50" cy="50" :style="{ '--progress': scrollProgress }" />
|
||||
</svg>
|
||||
<div class="is-overlay columns is-vcentered is-centered has-text-weight-light">
|
||||
<span class="column is-narrow is-paddingless is-size-2">
|
||||
{{ Math.ceil(scrollProgress * 100) }}
|
||||
</span>
|
||||
<span class="column is-narrow is-paddingless">
|
||||
%
|
||||
</span>
|
||||
<template v-if="indeterminate">
|
||||
<div class="column is-narrow is-paddingless is-size-2">∞</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="column is-narrow is-paddingless is-size-2">
|
||||
{{ Math.ceil(scrollProgress * 100) }}
|
||||
</span>
|
||||
<span class="column is-narrow is-paddingless"> % </span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import { mapGetters } from "vuex";
|
||||
import throttle from "lodash.throttle";
|
||||
|
||||
export default {
|
||||
name: "ScrollProgress",
|
||||
props: {
|
||||
indeterminate: {
|
||||
default: false,
|
||||
type: Boolean,
|
||||
},
|
||||
autoHide: {
|
||||
default: true,
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
scrollProgress: 0,
|
||||
@@ -39,9 +52,12 @@ export default {
|
||||
this.detachEvents();
|
||||
this.attachEvents();
|
||||
},
|
||||
indeterminate() {
|
||||
this.$nextTick(() => this.onScroll());
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(["activeContainers"]),
|
||||
...mapGetters(["activeContainers"]),
|
||||
},
|
||||
methods: {
|
||||
attachEvents() {
|
||||
@@ -55,15 +71,17 @@ export default {
|
||||
const p = this.parentElement == document ? document.documentElement : this.parentElement;
|
||||
this.scrollProgress = p.scrollTop / (p.scrollHeight - p.clientHeight);
|
||||
this.animation.cancel();
|
||||
this.animation = this.$el.animate(
|
||||
{ opacity: [1, 0] },
|
||||
{
|
||||
duration: 500,
|
||||
delay: 2000,
|
||||
fill: "both",
|
||||
easing: "ease-out",
|
||||
}
|
||||
);
|
||||
if (this.autoHide) {
|
||||
this.animation = this.$el.animate(
|
||||
{ opacity: [1, 0] },
|
||||
{
|
||||
duration: 500,
|
||||
delay: 2000,
|
||||
fill: "both",
|
||||
easing: "ease-out",
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -72,17 +90,57 @@ export default {
|
||||
.scroll-progress {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
circle {
|
||||
fill: var(--scheme-main-ter);
|
||||
fill-opacity: 0.8;
|
||||
transition: stroke-dashoffset 250ms ease-out;
|
||||
transform: rotate(-90deg);
|
||||
transform-origin: 50% 50%;
|
||||
stroke: var(--primary-color);
|
||||
stroke-dashoffset: calc(276.32px - var(--progress) * 276.32px);
|
||||
stroke-dasharray: 276.32px 276.32px;
|
||||
stroke-width: 3;
|
||||
will-change: stroke-dashoffset;
|
||||
|
||||
svg {
|
||||
filter: drop-shadow(0px 1px 1px rgba(0, 0, 0, 0.2));
|
||||
margin-top: 5px;
|
||||
&.indeterminate {
|
||||
animation: 2s linear infinite svg-animation;
|
||||
|
||||
circle {
|
||||
animation: 1.4s ease-in-out infinite both circle-animation;
|
||||
}
|
||||
}
|
||||
circle {
|
||||
fill: var(--scheme-main-ter);
|
||||
fill-opacity: 0.8;
|
||||
transition: stroke-dashoffset 250ms ease-out;
|
||||
transform: rotate(-90deg);
|
||||
transform-origin: 50% 50%;
|
||||
stroke: var(--primary-color);
|
||||
stroke-dashoffset: calc(276.32px - var(--progress) * 276.32px);
|
||||
stroke-dasharray: 276.32px 276.32px;
|
||||
stroke-linecap: round;
|
||||
stroke-width: 3;
|
||||
will-change: stroke-dashoffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes svg-animation {
|
||||
0% {
|
||||
transform: rotateZ(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotateZ(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes circle-animation {
|
||||
0%,
|
||||
25% {
|
||||
stroke-dashoffset: 275px;
|
||||
transform: rotate(0);
|
||||
}
|
||||
50%,
|
||||
75% {
|
||||
stroke-dashoffset: 70px;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
stroke-dashoffset: 275px;
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
</header>
|
||||
<main ref="content" :data-scrolling="scrollable">
|
||||
<div class="is-scrollbar-progress is-hidden-mobile">
|
||||
<scroll-progress v-show="paused"></scroll-progress>
|
||||
<scroll-progress v-show="paused" :indeterminate="loading" :auto-hide="!loading"></scroll-progress>
|
||||
</div>
|
||||
<slot></slot>
|
||||
<slot :setLoading="setLoading"></slot>
|
||||
<div ref="scrollObserver" class="is-scroll-observer"></div>
|
||||
</main>
|
||||
|
||||
@@ -41,6 +41,7 @@ export default {
|
||||
return {
|
||||
paused: false,
|
||||
hasMore: false,
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
@@ -49,7 +50,12 @@ export default {
|
||||
if (!this.paused) {
|
||||
this.scrollToBottom("instant");
|
||||
} else {
|
||||
this.hasMore = true;
|
||||
const record = e[e.length - 1];
|
||||
if (
|
||||
record.target.children[record.target.children.length - 1] == record.addedNodes[record.addedNodes.length - 1]
|
||||
) {
|
||||
this.hasMore = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
mutationObserver.observe(content, { childList: true, subtree: true });
|
||||
@@ -68,6 +74,9 @@ export default {
|
||||
this.$refs.scrollObserver.scrollIntoView({ behavior });
|
||||
this.hasMore = false;
|
||||
},
|
||||
setLoading(loading) {
|
||||
this.loading = loading;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -76,6 +85,13 @@ section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: var(--body-background-color);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
&.is-full-height-scrollable {
|
||||
height: 100vh;
|
||||
min-height: 0;
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
active-class="is-active"
|
||||
:title="item.name"
|
||||
>
|
||||
<div class="hide-overflow">
|
||||
<div class="is-ellipsis">
|
||||
<span
|
||||
@click.stop.prevent="appendActiveContainer(item)"
|
||||
class="icon is-small will-append-container"
|
||||
@@ -59,8 +59,13 @@ export default {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["activeContainers"]),
|
||||
...mapGetters(["activeContainersById", "visibleContainers"]),
|
||||
...mapGetters(["visibleContainers", "activeContainers"]),
|
||||
activeContainersById() {
|
||||
return this.activeContainers.reduce((map, obj) => {
|
||||
map[obj.id] = obj;
|
||||
return map;
|
||||
}, {});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
@@ -77,12 +82,6 @@ aside {
|
||||
position: fixed;
|
||||
width: inherit;
|
||||
|
||||
.hide-overflow {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.is-hidden-mobile.is-active {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,25 @@
|
||||
exports[`<LogEventSource /> renders correctly 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="control"
|
||||
/>
|
||||
class="infinte-loader"
|
||||
>
|
||||
<div
|
||||
class="spinner"
|
||||
style="display: none;"
|
||||
>
|
||||
<div
|
||||
class="bounce1"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="bounce2"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="bounce3"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul
|
||||
class="events medium"
|
||||
|
||||
@@ -1,27 +1,18 @@
|
||||
<template>
|
||||
<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>
|
||||
<log-container :id="id" show-title :scrollable="activeContainers.length > 0"> </log-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters, mapState } from "vuex";
|
||||
|
||||
import LogViewerWithSource from "../components/LogViewerWithSource";
|
||||
import ScrollableView from "../components/ScrollableView";
|
||||
import ContainerTitle from "../components/ContainerTitle";
|
||||
import LogContainer from "../components/LogContainer";
|
||||
import store from "../store";
|
||||
|
||||
export default {
|
||||
props: ["id", "name"],
|
||||
props: ["id"],
|
||||
name: "Container",
|
||||
components: {
|
||||
LogViewerWithSource,
|
||||
ScrollableView,
|
||||
ContainerTitle,
|
||||
LogContainer,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -39,8 +30,7 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(["activeContainers"]),
|
||||
...mapGetters(["allContainersById"]),
|
||||
...mapGetters(["allContainersById", "activeContainers"]),
|
||||
},
|
||||
watch: {
|
||||
id() {
|
||||
|
||||
@@ -3,13 +3,11 @@
|
||||
<section class="hero is-small mt-4">
|
||||
<div class="hero-body">
|
||||
<div class="container">
|
||||
<h1 class="title">
|
||||
Hello, there!
|
||||
</h1>
|
||||
<h1 class="title">Hello, there!</h1>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="level section">
|
||||
<section class="level section is-mobile">
|
||||
<div class="level-item has-text-centered">
|
||||
<div>
|
||||
<p class="title">{{ containers.length }}</p>
|
||||
@@ -33,9 +31,7 @@
|
||||
<section class="columns is-centered section">
|
||||
<div class="column is-4">
|
||||
<div class="panel">
|
||||
<p class="panel-heading">
|
||||
Containers
|
||||
</p>
|
||||
<p class="panel-heading">Containers</p>
|
||||
<div class="panel-block">
|
||||
<p class="control has-icons-left">
|
||||
<input
|
||||
@@ -51,7 +47,7 @@
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<p class="panel-tabs">
|
||||
<p class="panel-tabs" v-if="!search">
|
||||
<a :class="{ 'is-active': sort === 'running' }" @click="sort = 'running'">Running</a>
|
||||
<a :class="{ 'is-active': sort === 'all' }" @click="sort = 'all'">All</a>
|
||||
</p>
|
||||
|
||||
@@ -12,7 +12,7 @@ storage.set(DOZZLE_SETTINGS_KEY, { ...DEFAULT_SETTINGS, ...storage.get(DOZZLE_SE
|
||||
|
||||
const state = {
|
||||
containers: [],
|
||||
activeContainers: [],
|
||||
activeContainerIds: [],
|
||||
searchFilter: null,
|
||||
isMobile: mql.matches,
|
||||
settings: storage.get(DOZZLE_SETTINGS_KEY),
|
||||
@@ -20,13 +20,26 @@ const state = {
|
||||
|
||||
const mutations = {
|
||||
SET_CONTAINERS(state, containers) {
|
||||
const containersById = state.containers.reduce((map, obj) => {
|
||||
map[obj.id] = obj;
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
containers.forEach(
|
||||
(container) =>
|
||||
(container.stat =
|
||||
containersById[container.id] && containersById[container.id].stat
|
||||
? containersById[container.id].stat
|
||||
: { memoryUsage: 0, cpu: 0 })
|
||||
);
|
||||
|
||||
state.containers = containers;
|
||||
},
|
||||
ADD_ACTIVE_CONTAINERS(state, container) {
|
||||
state.activeContainers.push(container);
|
||||
ADD_ACTIVE_CONTAINERS(state, { id }) {
|
||||
state.activeContainerIds.push(id);
|
||||
},
|
||||
REMOVE_ACTIVE_CONTAINER(state, container) {
|
||||
state.activeContainers.splice(state.activeContainers.indexOf(container), 1);
|
||||
REMOVE_ACTIVE_CONTAINER(state, { id }) {
|
||||
state.activeContainerIds.splice(state.activeContainerIds.indexOf(id), 1);
|
||||
},
|
||||
SET_SEARCH(state, filter) {
|
||||
state.searchFilter = filter;
|
||||
@@ -38,6 +51,9 @@ const mutations = {
|
||||
state.settings = { ...state.settings, ...newValues };
|
||||
storage.set(DOZZLE_SETTINGS_KEY, state.settings);
|
||||
},
|
||||
UPDATE_STAT(state, { container, stat }) {
|
||||
Vue.set(container, "stat", stat);
|
||||
},
|
||||
};
|
||||
|
||||
const actions = {
|
||||
@@ -50,21 +66,18 @@ const actions = {
|
||||
SET_SEARCH({ commit }, filter) {
|
||||
commit("SET_SEARCH", filter);
|
||||
},
|
||||
async FETCH_CONTAINERS({ commit }) {
|
||||
const containers = await (await fetch(`${config.base}/api/containers.json`)).json();
|
||||
commit("SET_CONTAINERS", containers);
|
||||
},
|
||||
UPDATE_SETTING({ commit }, setting) {
|
||||
commit("UPDATE_SETTINGS", setting);
|
||||
},
|
||||
UPDATE_STATS({ commit, getters }, stat) {
|
||||
const { allContainersById } = getters;
|
||||
const container = allContainersById[stat.id];
|
||||
if (container) {
|
||||
commit("UPDATE_STAT", { container, stat });
|
||||
}
|
||||
},
|
||||
};
|
||||
const getters = {
|
||||
activeContainersById({ activeContainers }) {
|
||||
return activeContainers.reduce((map, obj) => {
|
||||
map[obj.id] = obj;
|
||||
return map;
|
||||
}, {});
|
||||
},
|
||||
allContainersById({ containers }) {
|
||||
return containers.reduce((map, obj) => {
|
||||
map[obj.id] = obj;
|
||||
@@ -75,10 +88,15 @@ const getters = {
|
||||
const filter = showAllContainers ? () => true : (c) => c.state === "running";
|
||||
return containers.filter(filter);
|
||||
},
|
||||
activeContainers({ activeContainerIds }, { allContainersById }) {
|
||||
return activeContainerIds.map((id) => allContainersById[id]);
|
||||
},
|
||||
};
|
||||
|
||||
const es = new EventSource(`${config.base}/api/events/stream`);
|
||||
es.addEventListener("containers-changed", (e) => setTimeout(() => store.dispatch("FETCH_CONTAINERS"), 1000), false);
|
||||
es.addEventListener("containers-changed", (e) => store.commit("SET_CONTAINERS", JSON.parse(e.data)), false);
|
||||
es.addEventListener("container-stat", (e) => store.dispatch("UPDATE_STATS", JSON.parse(e.data)), false);
|
||||
|
||||
mql.addListener((e) => store.commit("SET_MOBILE_WIDTH", e.matches));
|
||||
|
||||
const store = new Vuex.Store({
|
||||
|
||||
@@ -135,3 +135,17 @@ html.has-custom-scrollbars {
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.splitpanes__pane {
|
||||
overflow: unset;
|
||||
}
|
||||
|
||||
.splitpanes__splitter {
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.is-ellipsis {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
@@ -29,6 +30,7 @@ type dockerProxy interface {
|
||||
ContainerLogs(context.Context, string, types.ContainerLogsOptions) (io.ReadCloser, error)
|
||||
Events(context.Context, types.EventsOptions) (<-chan events.Message, <-chan error)
|
||||
ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error)
|
||||
ContainerStats(ctx context.Context, containerID string, stream bool) (types.ContainerStats, error)
|
||||
}
|
||||
|
||||
// Client is a proxy around the docker client
|
||||
@@ -38,6 +40,7 @@ type Client interface {
|
||||
ContainerLogs(context.Context, string, int, string) (<-chan string, <-chan error)
|
||||
Events(context.Context) (<-chan events.Message, <-chan error)
|
||||
ContainerLogsBetweenDates(context.Context, string, time.Time, time.Time) ([]string, error)
|
||||
ContainerStats(context.Context, string, chan<- ContainerStat) error
|
||||
}
|
||||
|
||||
// NewClient creates a new instance of Client
|
||||
@@ -146,6 +149,48 @@ func logReader(reader io.ReadCloser, tty bool) func() (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dockerClient) ContainerStats(ctx context.Context, id string, stats chan<- ContainerStat) error {
|
||||
response, err := d.cli.ContainerStats(ctx, id, true)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer response.Body.Close()
|
||||
decoder := json.NewDecoder(response.Body)
|
||||
var v *types.StatsJSON
|
||||
for {
|
||||
if err := decoder.Decode(&v); err != nil {
|
||||
if err == context.Canceled || err == io.EOF {
|
||||
log.Debugf("stopping stats streaming for container %s", id)
|
||||
break
|
||||
}
|
||||
log.Errorf("decoder for stats api returned an unknown error %v", err)
|
||||
}
|
||||
|
||||
var (
|
||||
cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(v.PreCPUStats.CPUUsage.TotalUsage)
|
||||
systemDelta = float64(v.CPUStats.SystemUsage) - float64(v.PreCPUStats.SystemUsage)
|
||||
cpuPercent = int64((cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100)
|
||||
memUsage = int64(v.MemoryStats.Usage - v.MemoryStats.Stats["cache"])
|
||||
memPercent = int64(float64(memUsage) / float64(v.MemoryStats.Limit) * 100)
|
||||
)
|
||||
|
||||
if cpuPercent > 0 || memUsage > 0 {
|
||||
stats <- ContainerStat{
|
||||
ID: id,
|
||||
CPUPercent: cpuPercent,
|
||||
MemoryPercent: memPercent,
|
||||
MemoryUsage: memUsage,
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dockerClient) ContainerLogs(ctx context.Context, id string, tailSize int, since string) (<-chan string, <-chan error) {
|
||||
log.WithField("id", id).WithField("since", since).Debug("Streaming logs for container")
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ func (m *mockedProxy) ContainerLogs(ctx context.Context, id string, options type
|
||||
}
|
||||
return reader, args.Error(1)
|
||||
}
|
||||
|
||||
func (m *mockedProxy) ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) {
|
||||
args := m.Called(ctx, containerID)
|
||||
json, ok := args.Get(0).(types.ContainerJSON)
|
||||
@@ -50,6 +51,10 @@ func (m *mockedProxy) ContainerInspect(ctx context.Context, containerID string)
|
||||
return json, args.Error(1)
|
||||
}
|
||||
|
||||
func (m *mockedProxy) ContainerStats(ctx context.Context, containerID string, stream bool) (types.ContainerStats, error) {
|
||||
return types.ContainerStats{}, nil
|
||||
}
|
||||
|
||||
func Test_dockerClient_ListContainers_null(t *testing.T) {
|
||||
proxy := new(mockedProxy)
|
||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil)
|
||||
|
||||
@@ -12,3 +12,11 @@ type Container struct {
|
||||
State string `json:"state"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// ContainerStat represent stats instant for a container
|
||||
type ContainerStat struct {
|
||||
ID string `json:"id"`
|
||||
CPUPercent int64 `json:"cpu"`
|
||||
MemoryPercent int64 `json:"memory"`
|
||||
MemoryUsage int64 `json:"memoryUsage"`
|
||||
}
|
||||
|
||||
@@ -14,40 +14,41 @@ require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
||||
github.com/Microsoft/go-winio v0.4.14 // indirect
|
||||
github.com/beme/abide v0.0.0-20190723115211-635a09831760
|
||||
github.com/containerd/containerd v1.3.3 // indirect
|
||||
github.com/containerd/containerd v1.4.0 // indirect
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
github.com/docker/docker v0.0.0-20190827232753-32688a47f341
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/gobuffalo/envy v1.9.0 // indirect
|
||||
github.com/gobuffalo/packd v1.0.0 // indirect
|
||||
github.com/gobuffalo/packr v1.30.1
|
||||
github.com/gogo/protobuf v1.3.1 // indirect
|
||||
github.com/golang/protobuf v1.3.4 // indirect
|
||||
github.com/google/go-cmp v0.3.1 // indirect
|
||||
github.com/gorilla/mux v1.7.4
|
||||
github.com/magiconair/properties v1.8.1
|
||||
github.com/golang/protobuf v1.4.2 // indirect
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/magiconair/properties v1.8.4
|
||||
github.com/mitchellh/mapstructure v1.3.3 // indirect
|
||||
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||
github.com/pelletier/go-toml v1.6.0 // indirect
|
||||
github.com/pelletier/go-toml v1.8.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/rogpeppe/go-internal v1.5.2 // indirect
|
||||
github.com/rogpeppe/go-internal v1.6.2 // indirect
|
||||
github.com/sergi/go-diff v1.0.0 // indirect
|
||||
github.com/sirupsen/logrus v1.6.0
|
||||
github.com/spf13/afero v1.2.2 // indirect
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/spf13/afero v1.3.5 // indirect
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.7.1
|
||||
github.com/stretchr/objx v0.2.0 // indirect
|
||||
github.com/stretchr/testify v1.6.1
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b // indirect
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae // indirect
|
||||
google.golang.org/genproto v0.0.0-20200226201735-46b91f19d98c // indirect
|
||||
google.golang.org/grpc v1.27.1 // indirect
|
||||
gopkg.in/ini.v1 v1.52.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.8 // indirect
|
||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f // indirect
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d // indirect
|
||||
google.golang.org/grpc v1.32.0 // indirect
|
||||
google.golang.org/protobuf v1.25.0 // indirect
|
||||
gopkg.in/ini.v1 v1.61.0 // indirect
|
||||
gotest.tools v2.2.0+incompatible // indirect
|
||||
)
|
||||
|
||||
|
||||
@@ -45,8 +45,9 @@ github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3k
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/containerd/containerd v1.3.3 h1:LoIzb5y9x5l8VKAlyrbusNPXqBY0+kviRloxFUMFwKc=
|
||||
github.com/containerd/containerd v1.3.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/containerd/containerd v1.4.0 h1:aWJB3lbDEaOxg1mkTBAINY2a+NsoKbAeRYefJPPRY+o=
|
||||
github.com/containerd/containerd v1.4.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
@@ -74,11 +75,15 @@ github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw
|
||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
@@ -117,15 +122,23 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
@@ -136,9 +149,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||
github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
|
||||
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
@@ -171,7 +183,6 @@ github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
@@ -185,6 +196,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
@@ -194,6 +206,12 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.2 h1:znVR8Q4g7/WlcvsxLBRWvo+vtFJUAbDn3w+Yak2xVMI=
|
||||
github.com/magiconair/properties v1.8.2/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/magiconair/properties v1.8.3 h1:kJSsc6EXkBLgr3SphHk9w5mtjn0bjlR4JYEXKrJ45rQ=
|
||||
github.com/magiconair/properties v1.8.3/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/magiconair/properties v1.8.4 h1:8KGKTcQQGm0Kv7vEbKFErAoAOFyyacLStRtQSeYtvkY=
|
||||
github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
@@ -208,10 +226,10 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
|
||||
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE=
|
||||
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
@@ -219,22 +237,23 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
|
||||
github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
|
||||
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
||||
github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw=
|
||||
github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=
|
||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
@@ -258,8 +277,8 @@ github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhg
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.2 h1:XU784Pr0wdahMY2bYcyK6N1KuaRAdLtqD4qd8D18Bfs=
|
||||
github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.5.2 h1:qLvObTrvO/XRCqmkKxUlOBc48bI3efyDuAZe25QiF0w=
|
||||
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.6.2 h1:aIihoIOHCiLZHxyoNQ+ABL4NKhFTgKLBdMLyEAh98m0=
|
||||
github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
@@ -270,10 +289,10 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q=
|
||||
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
|
||||
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
@@ -283,8 +302,8 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/afero v1.3.5 h1:AWZ/w4lcfxuh52NVL78p9Eh8j6r1mCTEGSRFBJyIHAE=
|
||||
github.com/spf13/afero v1.3.5/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
@@ -301,10 +320,6 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/spf13/viper v1.6.3 h1:pDDu1OyEDTKzpJwdq4TiuLyMsUgRa/BT5cn5O62NoHs=
|
||||
github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw=
|
||||
github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
|
||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
|
||||
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -316,10 +331,6 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgho=
|
||||
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
@@ -344,6 +355,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -375,13 +388,11 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
|
||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -410,12 +421,17 @@ golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f h1:Fqb3ao1hUmOR3GkUOg/Y+BadLwykBIzs5q8Ez2SbHyc=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -440,6 +456,8 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
@@ -461,18 +479,30 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200226201735-46b91f19d98c h1:xFOdgVPpeowWAH0MJ5i0XMp+3yWiWamMtN/kx9xThIQ=
|
||||
google.golang.org/genproto v0.0.0-20200226201735-46b91f19d98c/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d h1:92D1fum1bJLKSdr11OJ+54YeCMCGYIygTA7R/YZxH5M=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=
|
||||
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -481,8 +511,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.52.0 h1:j+Lt/M1oPPejkniCg1TkWE2J3Eh1oZTsHSXzMTzUXn4=
|
||||
gopkg.in/ini.v1 v1.52.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.61.0 h1:LBCdW4FmFYL4s/vDZD1RQYX7oAR6IjujCYgMdbHBR10=
|
||||
gopkg.in/ini.v1 v1.61.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
@@ -490,8 +520,8 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||
|
||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 76 KiB |
@@ -70,32 +70,8 @@ func (m *MockedClient) Events(ctx context.Context) (<-chan events.Message, <-cha
|
||||
return channel, err
|
||||
}
|
||||
|
||||
func Test_handler_listContainers_happy(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "/api/containers.json", nil)
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
mockedClient := new(MockedClient)
|
||||
containers := []docker.Container{
|
||||
{
|
||||
ID: "1234567890",
|
||||
Status: "status",
|
||||
State: "state",
|
||||
Name: "test",
|
||||
Created: 0,
|
||||
Command: "command",
|
||||
ImageID: "image_id",
|
||||
Image: "image",
|
||||
},
|
||||
}
|
||||
mockedClient.On("ListContainers", mock.Anything).Return(containers, nil)
|
||||
|
||||
h := handler{client: mockedClient}
|
||||
handler := http.HandlerFunc(h.listContainers)
|
||||
handler.ServeHTTP(rr, req)
|
||||
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
|
||||
mockedClient.AssertExpectations(t)
|
||||
func (m *MockedClient) ContainerStats(context.Context, string, chan<- docker.ContainerStat) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func Test_handler_streamLogs_happy(t *testing.T) {
|
||||
@@ -232,6 +208,7 @@ func Test_handler_streamEvents_happy(t *testing.T) {
|
||||
messages := make(chan events.Message)
|
||||
errChannel := make(chan error)
|
||||
mockedClient.On("Events", mock.Anything).Return(messages, errChannel)
|
||||
mockedClient.On("ListContainers").Return([]docker.Container{}, nil)
|
||||
|
||||
go func() {
|
||||
messages <- events.Message{
|
||||
@@ -258,6 +235,7 @@ func Test_handler_streamEvents_error(t *testing.T) {
|
||||
messages := make(chan events.Message)
|
||||
errChannel := make(chan error)
|
||||
mockedClient.On("Events", mock.Anything).Return(messages, errChannel)
|
||||
mockedClient.On("ListContainers").Return([]docker.Container{}, nil)
|
||||
|
||||
go func() {
|
||||
errChannel <- errors.New("fake error")
|
||||
@@ -281,6 +259,7 @@ func Test_handler_streamEvents_error_request(t *testing.T) {
|
||||
messages := make(chan events.Message)
|
||||
errChannel := make(chan error)
|
||||
mockedClient.On("Events", mock.Anything).Return(messages, errChannel)
|
||||
mockedClient.On("ListContainers").Return([]docker.Container{}, nil)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dozzle",
|
||||
"version": "2.1.5",
|
||||
"version": "3.0.2",
|
||||
"description": "Realtime log viewer for docker containers. ",
|
||||
"scripts": {
|
||||
"prestart": "yarn clean",
|
||||
@@ -8,7 +8,7 @@
|
||||
"watch:assets": "webpack --mode=development --watch",
|
||||
"watch:server": "reflex -c .reflex",
|
||||
"dev": "npm-run-all -p dev-server watch:server",
|
||||
"dev-server": "webpack-dev-server --open",
|
||||
"dev-server": "webpack serve",
|
||||
"prebuild": "yarn clean",
|
||||
"build": "yarn webpack --mode=production",
|
||||
"clean": "rm -rf static/ a_main-packr.go",
|
||||
@@ -28,53 +28,53 @@
|
||||
"homepage": "https://github.com/amir20/dozzle#readme",
|
||||
"dependencies": {
|
||||
"ansi-to-html": "^0.6.14",
|
||||
"buefy": "^0.8.20",
|
||||
"bulma": "^0.9.0",
|
||||
"date-fns": "^2.15.0",
|
||||
"dompurify": "^2.0.12",
|
||||
"buefy": "^0.9.4",
|
||||
"bulma": "^0.9.1",
|
||||
"date-fns": "^2.16.1",
|
||||
"dompurify": "^2.2.2",
|
||||
"hotkeys-js": "^3.8.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"semver": "^7.3.2",
|
||||
"splitpanes": "^2.2.1",
|
||||
"splitpanes": "^2.3.4",
|
||||
"store": "^2.0.12",
|
||||
"vue": "^2.6.11",
|
||||
"vue": "^2.6.12",
|
||||
"vue-meta": "^2.4.0",
|
||||
"vue-router": "^3.4.3",
|
||||
"vue-router": "^3.4.9",
|
||||
"vuex": "^3.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.11.1",
|
||||
"@babel/plugin-transform-runtime": "^7.11.0",
|
||||
"@babel/core": "^7.12.3",
|
||||
"@babel/plugin-transform-runtime": "^7.12.1",
|
||||
"@vue/component-compiler-utils": "^3.2.0",
|
||||
"@vue/test-utils": "^1.0.3",
|
||||
"@vue/test-utils": "^1.1.1",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-jest": "^26.3.0",
|
||||
"babel-jest": "^26.6.3",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"caniuse-lite": "^1.0.30001113",
|
||||
"css-loader": "^4.2.1",
|
||||
"caniuse-lite": "^1.0.30001157",
|
||||
"css-loader": "^5.0.1",
|
||||
"eventsourcemock": "^2.0.0",
|
||||
"html-webpack-plugin": "^4.3.0",
|
||||
"husky": "^4.2.5",
|
||||
"jest": "^26.4.0",
|
||||
"html-webpack-plugin": "^4.5.0",
|
||||
"husky": "^4.3.0",
|
||||
"jest": "^26.6.3",
|
||||
"jest-serializer-vue": "^2.0.2",
|
||||
"lint-staged": "^10.2.11",
|
||||
"mini-css-extract-plugin": "^0.10.0",
|
||||
"lint-staged": "^10.5.1",
|
||||
"mini-css-extract-plugin": "^1.3.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss-cssnext": "^3.1.0",
|
||||
"postcss-import": "^12.0.1",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"prettier": "^2.0.5",
|
||||
"release-it": "^13.6.6",
|
||||
"sass": "^1.26.10",
|
||||
"sass-loader": "^9.0.3",
|
||||
"prettier": "^2.1.2",
|
||||
"release-it": "^14.2.1",
|
||||
"sass": "^1.29.0",
|
||||
"sass-loader": "^10.0.5",
|
||||
"vue-hot-reload-api": "^2.3.4",
|
||||
"vue-jest": "^3.0.6",
|
||||
"vue-loader": "^15.9.3",
|
||||
"vue-jest": "^3.0.7",
|
||||
"vue-loader": "^15.9.5",
|
||||
"vue-style-loader": "^4.1.2",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"webpack": "^4.44.1",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"vue-template-compiler": "^2.6.12",
|
||||
"webpack": "^5.4.0",
|
||||
"webpack-cli": "^4.2.0",
|
||||
"webpack-dev-server": "^3.11.0",
|
||||
"webpack-pwa-manifest": "^4.2.0"
|
||||
},
|
||||
|
||||
@@ -8,8 +8,10 @@ import (
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/docker"
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -23,7 +25,6 @@ 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)
|
||||
@@ -69,19 +70,6 @@ func (h *handler) index(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) listContainers(w http.ResponseWriter, r *http.Request) {
|
||||
containers, err := h.client.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 (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
|
||||
|
||||
@@ -166,24 +154,65 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Accel-Buffering", "no")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
messages, err := h.client.Events(ctx)
|
||||
stats := make(chan docker.ContainerStat)
|
||||
|
||||
runningContainers := map[string]docker.Container{}
|
||||
if containers, err := h.client.ListContainers(); err == nil {
|
||||
for _, c := range containers {
|
||||
if c.State == "running" {
|
||||
h.client.ContainerStats(ctx, c.ID, stats)
|
||||
runningContainers[c.ID] = c
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := sendContainersJSON(h.client, w); err != nil {
|
||||
log.Errorf("Error while encoding containers to stream: %v", err)
|
||||
}
|
||||
|
||||
f.Flush()
|
||||
|
||||
delayer := delayedFunc(time.Second)
|
||||
|
||||
Loop:
|
||||
for {
|
||||
select {
|
||||
case stat := <-stats:
|
||||
bytes, _ := json.Marshal(stat)
|
||||
_, err := fmt.Fprintf(w, "event: container-stat\ndata: %s\n\n", string(bytes))
|
||||
if err != nil {
|
||||
log.Debugf("Error while writing to event stream: %v", err)
|
||||
break
|
||||
}
|
||||
f.Flush()
|
||||
case message, ok := <-messages:
|
||||
if !ok {
|
||||
break Loop
|
||||
}
|
||||
switch message.Action {
|
||||
case "connect", "disconnect", "create", "destroy", "start", "stop":
|
||||
case "start", "connect", "disconnect", "die":
|
||||
log.Debugf("Triggering docker event: %v", message.Action)
|
||||
_, err := fmt.Fprintf(w, "event: containers-changed\ndata: %s\n\n", message.Action)
|
||||
|
||||
if err != nil {
|
||||
log.Debugf("Error while writing to event stream: %v", err)
|
||||
break
|
||||
if message.Action == "start" {
|
||||
log.Debugf("Scanning for new containers")
|
||||
if containers, err := h.client.ListContainers(); err == nil {
|
||||
for _, c := range containers {
|
||||
if _, ok = runningContainers[c.ID]; c.State == "running" && !ok {
|
||||
log.Debugf("Found a new container %v", c.ID)
|
||||
h.client.ContainerStats(ctx, c.ID, stats)
|
||||
runningContainers[c.ID] = c
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delayer(func() {
|
||||
if err := sendContainersJSON(h.client, w); err != nil {
|
||||
log.Errorf("Error while encoding containers to stream: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
f.Flush()
|
||||
default:
|
||||
log.Debugf("Ignoring docker event: %v", message.Action)
|
||||
@@ -199,3 +228,47 @@ Loop:
|
||||
func (h *handler) version(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, version)
|
||||
}
|
||||
|
||||
func sendContainersJSON(client docker.Client, w http.ResponseWriter) error {
|
||||
if containers, err := client.ListContainers(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if _, err := fmt.Fprint(w, "event: containers-changed\ndata: "); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(containers); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprint(w, "\n\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type delayedFun struct {
|
||||
mu sync.Mutex
|
||||
after time.Duration
|
||||
timer *time.Timer
|
||||
}
|
||||
|
||||
func delayedFunc(after time.Duration) func(f func()) {
|
||||
d := &delayedFun{after: after}
|
||||
|
||||
return func(f func()) {
|
||||
d.add(f)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *delayedFun) add(f func()) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
if d.timer != nil {
|
||||
d.timer.Stop()
|
||||
}
|
||||
d.timer = time.AfterFunc(d.after, f)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ module.exports = (env, argv) => ({
|
||||
output: {
|
||||
path: path.resolve(__dirname, "./static"),
|
||||
filename: "[name].js",
|
||||
publicPath: process.env.WEBPACK_DEV_SERVER ? "/" : "{{ .Base }}",
|
||||
publicPath: argv.mode === "production" ? "{{ .Base }}" : "/",
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
@@ -28,9 +28,6 @@ module.exports = (env, argv) => ({
|
||||
use: [
|
||||
{
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
options: {
|
||||
hmr: argv.mode !== "production",
|
||||
},
|
||||
},
|
||||
"css-loader",
|
||||
{
|
||||
@@ -78,6 +75,7 @@ module.exports = (env, argv) => ({
|
||||
port: 8081,
|
||||
inline: true,
|
||||
hot: true,
|
||||
open: true,
|
||||
historyApiFallback: true,
|
||||
proxy: {
|
||||
"/api": {
|
||||
|
||||