Compare commits
81 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
144bcccc27 | ||
|
|
bc3d2a5c74 | ||
|
|
357b619bef | ||
|
|
12aadd673b | ||
|
|
6bde7be53c | ||
|
|
fc4a3d90d5 | ||
|
|
b9532b4eac | ||
|
|
920acf4256 | ||
|
|
64f744a5d4 | ||
|
|
be66ecf3de | ||
|
|
e065409952 | ||
|
|
f8554155e7 | ||
|
|
28a54b4478 | ||
|
|
738a011b2a | ||
|
|
e3cc51f54b | ||
|
|
05ce19e910 | ||
|
|
04f3f56aa3 | ||
|
|
971a8c2da4 | ||
|
|
069b5581d8 | ||
|
|
0ed7408d6d | ||
|
|
b835506069 | ||
|
|
711f4a74b9 | ||
|
|
0c172e34ae | ||
|
|
4a29f5deb8 | ||
|
|
fbf217b990 | ||
|
|
1078b70190 | ||
|
|
4ccccbc9c8 | ||
|
|
e2fa22148a | ||
|
|
70e5b358fc | ||
|
|
85695a2165 | ||
|
|
600602a976 | ||
|
|
9c5749d6b2 | ||
|
|
ce1df8ec22 | ||
|
|
cb75ebebbd | ||
|
|
3e5eae38c8 | ||
|
|
5ffc667b5d | ||
|
|
d54c1caff8 | ||
|
|
7cad2fabc2 | ||
|
|
02ab509e8d | ||
|
|
fbf4f4f7cb | ||
|
|
4ad2b5413b | ||
|
|
ef63b0bf2f | ||
|
|
56c3cebf44 | ||
|
|
d84c90670b | ||
|
|
b7e64a7599 | ||
|
|
1f8d23c8ee | ||
|
|
0ab802a304 | ||
|
|
039c55ccb6 | ||
|
|
5730aa6fee | ||
|
|
ed56513562 | ||
|
|
e0366fd928 | ||
|
|
a76dc67752 | ||
|
|
d56d27bf14 | ||
|
|
3550724d8a | ||
|
|
b8b92edee1 | ||
|
|
59be9a2a78 | ||
|
|
b852df68d6 | ||
|
|
bd2aea5207 | ||
|
|
194b31b36f | ||
|
|
dfc3254cf4 | ||
|
|
2ce28f81f6 | ||
|
|
f7e9ba348e | ||
|
|
d495c018f5 | ||
|
|
a160796bd0 | ||
|
|
743caf20d1 | ||
|
|
879ad0278a | ||
|
|
106dd88443 | ||
|
|
d4bd807b05 | ||
|
|
dfe232b529 | ||
|
|
e3f9142f7b | ||
|
|
2e3fa0ea78 | ||
|
|
da89162143 | ||
|
|
c84ba2523e | ||
|
|
a28d75aee5 | ||
|
|
500b390073 | ||
|
|
9314bc8b50 | ||
|
|
264f5b71e6 | ||
|
|
738e9cf17a | ||
|
|
713955a98c | ||
|
|
21950ed61b | ||
|
|
0420ff34ae |
2
.github/.kodiak.toml
vendored
Normal file
2
.github/.kodiak.toml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
version = 1
|
||||
merge.notify_on_conflict = false
|
||||
20
.github/workflows/deploy.yml
vendored
20
.github/workflows/deploy.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v2.3.4
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v2.1.5
|
||||
- name: Install dependencies
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
with:
|
||||
go-version: 1.16.x
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v2.3.4
|
||||
- name: Run Go Tests with Coverage
|
||||
run: make test SKIP_ASSET=1
|
||||
int-test:
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v2.3.4
|
||||
- name: Build images
|
||||
run: docker-compose -f integration/docker-compose.test.yml build
|
||||
- name: Run tests
|
||||
@@ -45,27 +45,27 @@ jobs:
|
||||
steps:
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: crazy-max/ghaction-docker-meta@v2
|
||||
uses: crazy-max/ghaction-docker-meta@v3.3.0
|
||||
with:
|
||||
images: amir20/dozzle
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v1.1.0
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v1.3.0
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v1.9.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v2.1.5
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v2.4.0
|
||||
with:
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm/v7,linux/arm64/v8
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install Node
|
||||
|
||||
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@@ -6,7 +6,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v2.3.4
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v2.1.5
|
||||
- name: Install dependencies
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
with:
|
||||
go-version: 1.16.x
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v2.3.4
|
||||
- name: Run Go Tests with Coverage
|
||||
run: make test SKIP_ASSET=1
|
||||
int-test:
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v2.3.4
|
||||
- name: Build images
|
||||
run: docker-compose -f integration/docker-compose.test.yml build
|
||||
- name: Run tests
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
version = 1
|
||||
@@ -85,6 +85,7 @@ export default {
|
||||
}
|
||||
this.menuWidth = this.settings.menuWidth;
|
||||
hotkeys("command+k, ctrl+k", (event, handler) => {
|
||||
event.preventDefault();
|
||||
this.showFuzzySearch();
|
||||
});
|
||||
},
|
||||
|
||||
@@ -33,16 +33,16 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from "vuex";
|
||||
|
||||
import LogViewerWithSource from "./LogViewerWithSource";
|
||||
import ScrollableView from "./ScrollableView";
|
||||
import ContainerTitle from "./ContainerTitle";
|
||||
import ContainerStat from "./ContainerStat";
|
||||
import Icon from "./Icon";
|
||||
import config from "../store/config";
|
||||
import containerMixin from "./mixins/container";
|
||||
|
||||
export default {
|
||||
mixins: [containerMixin],
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
@@ -69,10 +69,6 @@ export default {
|
||||
Icon,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["allContainersById"]),
|
||||
container() {
|
||||
return this.allContainersById[this.id];
|
||||
},
|
||||
base() {
|
||||
return config.base;
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ import LogViewer from "./LogViewer.vue";
|
||||
|
||||
jest.mock("lodash.debounce", () =>
|
||||
jest.fn((fn) => {
|
||||
fn.cancel = () => {};
|
||||
return fn;
|
||||
})
|
||||
);
|
||||
@@ -34,9 +35,17 @@ describe("<LogEventSource />", () => {
|
||||
localVue.component("log-viewer", LogViewer);
|
||||
|
||||
const state = { searchFilter, settings: { size: "medium", showTimestamp: true, hourStyle } };
|
||||
const getters = {
|
||||
allContainersById() {
|
||||
return {
|
||||
abc: { state: "running" },
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const store = new Vuex.Store({
|
||||
state,
|
||||
getters,
|
||||
});
|
||||
|
||||
return mount(LogEventSource, {
|
||||
@@ -57,22 +66,25 @@ describe("<LogEventSource />", () => {
|
||||
});
|
||||
|
||||
test("should connect to EventSource", async () => {
|
||||
shallowMount(LogEventSource);
|
||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
||||
expect(sources["/api/logs/stream?id=abc"].readyState).toBe(1);
|
||||
const wrapper = createLogEventSource();
|
||||
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
|
||||
expect(sources["/api/logs/stream?id=abc&lastEventId="].readyState).toBe(1);
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
test("should close EventSource", async () => {
|
||||
const wrapper = createLogEventSource();
|
||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
||||
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
|
||||
wrapper.destroy();
|
||||
expect(sources["/api/logs/stream?id=abc"].readyState).toBe(2);
|
||||
expect(sources["/api/logs/stream?id=abc&lastEventId="].readyState).toBe(2);
|
||||
});
|
||||
|
||||
test("should parse messages", async () => {
|
||||
const wrapper = createLogEventSource();
|
||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
||||
sources["/api/logs/stream?id=abc"].emitMessage({ data: `2019-06-12T10:55:42.459034602Z "This is a message."` });
|
||||
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
|
||||
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
|
||||
data: `2019-06-12T10:55:42.459034602Z "This is a message."`,
|
||||
});
|
||||
|
||||
const [message, _] = wrapper.vm.messages;
|
||||
const { key, ...messageWithoutKey } = message;
|
||||
@@ -81,15 +93,15 @@ describe("<LogEventSource />", () => {
|
||||
expect(messageWithoutKey).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"date": 2019-06-12T10:55:42.459Z,
|
||||
"message": " \\"This is a message.\\"",
|
||||
"message": "\\"This is a message.\\"",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test("should parse messages with loki's timestamp format", async () => {
|
||||
const wrapper = createLogEventSource();
|
||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
||||
sources["/api/logs/stream?id=abc"].emitMessage({ data: `2020-04-27T12:35:43.272974324+02:00 xxxxx` });
|
||||
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
|
||||
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({ data: `2020-04-27T12:35:43.272974324+02:00 xxxxx` });
|
||||
|
||||
const [message, _] = wrapper.vm.messages;
|
||||
const { key, ...messageWithoutKey } = message;
|
||||
@@ -98,15 +110,17 @@ describe("<LogEventSource />", () => {
|
||||
expect(messageWithoutKey).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"date": 2020-04-27T10:35:43.272Z,
|
||||
"message": " xxxxx",
|
||||
"message": "xxxxx",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test("should pass messages to slot", async () => {
|
||||
const wrapper = createLogEventSource();
|
||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
||||
sources["/api/logs/stream?id=abc"].emitMessage({ data: `2019-06-12T10:55:42.459034602Z "This is a message."` });
|
||||
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
|
||||
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
|
||||
data: `2019-06-12T10:55:42.459034602Z "This is a message."`,
|
||||
});
|
||||
const [message, _] = wrapper.findComponent(LogViewer).vm.messages;
|
||||
|
||||
const { key, ...messageWithoutKey } = message;
|
||||
@@ -116,7 +130,7 @@ describe("<LogEventSource />", () => {
|
||||
expect(messageWithoutKey).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"date": 2019-06-12T10:55:42.459Z,
|
||||
"message": " \\"This is a message.\\"",
|
||||
"message": "\\"This is a message.\\"",
|
||||
}
|
||||
`);
|
||||
});
|
||||
@@ -138,91 +152,93 @@ describe("<LogEventSource />", () => {
|
||||
|
||||
test("should render messages", async () => {
|
||||
const wrapper = createLogEventSource();
|
||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
||||
sources["/api/logs/stream?id=abc"].emitMessage({ data: `2019-06-12T10:55:42.459034602Z "This is a message."` });
|
||||
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
|
||||
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
|
||||
data: `2019-06-12T10:55:42.459034602Z "This is a message."`,
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
||||
<ul class="events medium">
|
||||
<li class=""><span class="date"><time datetime="2019-06-12T10:55:42.459Z">today at 10:55:42 AM</time></span> <span class="text"> "This is a message."</span></li>
|
||||
<li><span class="date"><time datetime="2019-06-12T10:55:42.459Z">today at 10:55:42 AM</time></span> <span class="text">"This is a message."</span></li>
|
||||
</ul>
|
||||
`);
|
||||
});
|
||||
|
||||
test("should render messages with color", async () => {
|
||||
const wrapper = createLogEventSource();
|
||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
||||
sources["/api/logs/stream?id=abc"].emitMessage({
|
||||
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
|
||||
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
|
||||
data: `2019-06-12T10:55:42.459034602Z \x1b[30mblack\x1b[37mwhite`,
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
||||
<ul class="events medium">
|
||||
<li class=""><span class="date"><time datetime="2019-06-12T10:55:42.459Z">today at 10:55:42 AM</time></span> <span class="text"> <span style="color:#000">black<span style="color:#AAA">white</span></span></span></li>
|
||||
<li><span class="date"><time datetime="2019-06-12T10:55:42.459Z">today at 10:55:42 AM</time></span> <span class="text"><span style="color:#000">black<span style="color:#AAA">white</span></span></span></li>
|
||||
</ul>
|
||||
`);
|
||||
});
|
||||
|
||||
test("should render messages with html entities", async () => {
|
||||
const wrapper = createLogEventSource();
|
||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
||||
sources["/api/logs/stream?id=abc"].emitMessage({
|
||||
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
|
||||
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
|
||||
data: `2019-06-12T10:55:42.459034602Z <test>foo bar</test>`,
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
||||
<ul class="events medium">
|
||||
<li class=""><span class="date"><time datetime="2019-06-12T10:55:42.459Z">today at 10:55:42 AM</time></span> <span class="text"> <test>foo bar</test></span></li>
|
||||
<li><span class="date"><time datetime="2019-06-12T10:55:42.459Z">today at 10:55:42 AM</time></span> <span class="text"><test>foo bar</test></span></li>
|
||||
</ul>
|
||||
`);
|
||||
});
|
||||
|
||||
test("should render dates with 12 hour style", async () => {
|
||||
const wrapper = createLogEventSource({ hourStyle: "12" });
|
||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
||||
sources["/api/logs/stream?id=abc"].emitMessage({
|
||||
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
|
||||
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
|
||||
data: `2019-06-12T23:55:42.459034602Z <test>foo bar</test>`,
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
||||
<ul class="events medium">
|
||||
<li class=""><span class="date"><time datetime="2019-06-12T23:55:42.459Z">today at 11:55:42 PM</time></span> <span class="text"> <test>foo bar</test></span></li>
|
||||
<li><span class="date"><time datetime="2019-06-12T23:55:42.459Z">today at 11:55:42 PM</time></span> <span class="text"><test>foo bar</test></span></li>
|
||||
</ul>
|
||||
`);
|
||||
});
|
||||
|
||||
test("should render dates with 24 hour style", async () => {
|
||||
const wrapper = createLogEventSource({ hourStyle: "24" });
|
||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
||||
sources["/api/logs/stream?id=abc"].emitMessage({
|
||||
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
|
||||
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
|
||||
data: `2019-06-12T23:55:42.459034602Z <test>foo bar</test>`,
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
||||
<ul class="events medium">
|
||||
<li class=""><span class="date"><time datetime="2019-06-12T23:55:42.459Z">today at 23:55:42</time></span> <span class="text"> <test>foo bar</test></span></li>
|
||||
<li><span class="date"><time datetime="2019-06-12T23:55:42.459Z">today at 23:55:42</time></span> <span class="text"><test>foo bar</test></span></li>
|
||||
</ul>
|
||||
`);
|
||||
});
|
||||
|
||||
test("should render messages with filter", async () => {
|
||||
const wrapper = createLogEventSource({ searchFilter: "test" });
|
||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
||||
sources["/api/logs/stream?id=abc"].emitMessage({
|
||||
sources["/api/logs/stream?id=abc&lastEventId="].emitOpen();
|
||||
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
|
||||
data: `2019-06-11T10:55:42.459034602Z Foo bar`,
|
||||
});
|
||||
sources["/api/logs/stream?id=abc"].emitMessage({
|
||||
sources["/api/logs/stream?id=abc&lastEventId="].emitMessage({
|
||||
data: `2019-06-12T10:55:42.459034602Z This is a test <hi></hi>`,
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
||||
<ul class="events medium">
|
||||
<li class=""><span class="date"><time datetime="2019-06-12T10:55:42.459Z">today at 10:55:42 AM</time></span> <span class="text"> This is a <mark>test</mark> <hi></hi></span></li>
|
||||
<li><span class="date"><time datetime="2019-06-12T10:55:42.459Z">today at 10:55:42 AM</time></span> <span class="text">This is a <mark>test</mark> <hi></hi></span></li>
|
||||
</ul>
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -9,9 +9,11 @@
|
||||
import debounce from "lodash.debounce";
|
||||
import InfiniteLoader from "./InfiniteLoader";
|
||||
import config from "../store/config";
|
||||
import containerMixin from "./mixins/container";
|
||||
|
||||
export default {
|
||||
props: ["id"],
|
||||
mixins: [containerMixin],
|
||||
name: "LogEventSource",
|
||||
components: {
|
||||
InfiniteLoader,
|
||||
@@ -20,29 +22,49 @@ export default {
|
||||
return {
|
||||
messages: [],
|
||||
buffer: [],
|
||||
es: null,
|
||||
lastEventId: null,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.es = null;
|
||||
this.loadLogs(this.id);
|
||||
this.flushBuffer = debounce(this.flushNow, 250, { maxWait: 1000 });
|
||||
this.loadLogs();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.es.close();
|
||||
},
|
||||
methods: {
|
||||
loadLogs(id) {
|
||||
loadLogs() {
|
||||
this.reset();
|
||||
this.es = new EventSource(`${config.base}/api/logs/stream?id=${id}`);
|
||||
this.es.addEventListener("container-stopped", (e) => {
|
||||
this.es.close();
|
||||
this.buffer.push({ event: "container-stopped", message: "Container stopped", date: new Date() });
|
||||
this.flushBuffer();
|
||||
this.flushBuffer.flush();
|
||||
});
|
||||
this.connect();
|
||||
},
|
||||
onContainerStopped() {
|
||||
this.es.close();
|
||||
this.buffer.push({ event: "container-stopped", message: "Container stopped", date: new Date(), key: new Date() });
|
||||
this.flushBuffer();
|
||||
this.flushBuffer.flush();
|
||||
},
|
||||
onMessage(e) {
|
||||
this.lastEventId = e.lastEventId;
|
||||
this.buffer.push(this.parseMessage(e.data));
|
||||
this.flushBuffer();
|
||||
},
|
||||
onContainerStateChange(newValue, oldValue) {
|
||||
if (newValue == "running" && newValue != oldValue) {
|
||||
this.buffer.push({
|
||||
event: "container-started",
|
||||
message: "Container started",
|
||||
date: new Date(),
|
||||
key: new Date(),
|
||||
});
|
||||
this.connect();
|
||||
}
|
||||
},
|
||||
connect() {
|
||||
this.es = new EventSource(`${config.base}/api/logs/stream?id=${this.id}&lastEventId=${this.lastEventId ?? ""}`);
|
||||
this.es.addEventListener("container-stopped", (e) => this.onContainerStopped());
|
||||
this.es.addEventListener("error", (e) => console.error("EventSource failed: " + JSON.stringify(e)));
|
||||
this.es.onmessage = (e) => {
|
||||
this.buffer.push(this.parseMessage(e.data));
|
||||
this.flushBuffer();
|
||||
};
|
||||
this.$once("hook:beforeDestroy", () => this.es.close());
|
||||
this.es.onmessage = (e) => this.onMessage(e);
|
||||
},
|
||||
flushNow() {
|
||||
this.messages.push(...this.buffer);
|
||||
@@ -51,11 +73,12 @@ export default {
|
||||
reset() {
|
||||
if (this.es) {
|
||||
this.es.close();
|
||||
this.es = null;
|
||||
this.flushBuffer.cancel();
|
||||
}
|
||||
this.flushBuffer.cancel();
|
||||
this.es = null;
|
||||
this.messages = [];
|
||||
this.buffer = [];
|
||||
this.lastEventId = null;
|
||||
},
|
||||
async loadOlderLogs() {
|
||||
if (this.messages.length < 300) return;
|
||||
@@ -84,14 +107,14 @@ export default {
|
||||
}
|
||||
const key = data.substring(0, i);
|
||||
const date = new Date(key);
|
||||
const message = data.substring(i);
|
||||
const message = data.substring(i + 1);
|
||||
return { key, date, message };
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
id(newValue, oldValue) {
|
||||
if (oldValue !== newValue) {
|
||||
this.loadLogs(newValue);
|
||||
this.loadLogs();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<ul class="events" :class="settings.size">
|
||||
<li v-for="item in filtered" :key="item.key" :class="{ event: !!item.event }">
|
||||
<li v-for="item in filtered" :key="item.key" :data-event="item.event">
|
||||
<span class="date" v-if="settings.showTimestamp"><relative-time :date="item.date"></relative-time></span>
|
||||
<span class="text" v-html="colorize(item.message)"></span>
|
||||
</li>
|
||||
@@ -73,6 +73,12 @@ export default {
|
||||
scroll-snap-align: end;
|
||||
scroll-margin-block-end: 5rem;
|
||||
}
|
||||
&[data-event="container-stopped"] {
|
||||
color: #f14668;
|
||||
}
|
||||
&[data-event="container-started"] {
|
||||
color: hsl(141, 53%, 53%);
|
||||
}
|
||||
}
|
||||
|
||||
&.small {
|
||||
@@ -104,10 +110,6 @@ export default {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
li.event {
|
||||
color: #f14668;
|
||||
}
|
||||
|
||||
::v-deep mark {
|
||||
border-radius: 2px;
|
||||
background-color: var(--secondary-color);
|
||||
|
||||
19
assets/components/mixins/container.js
Normal file
19
assets/components/mixins/container.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { mapGetters } from "vuex";
|
||||
export default {
|
||||
computed: {
|
||||
...mapGetters(["allContainersById"]),
|
||||
container() {
|
||||
return this.allContainersById[this.id];
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
["container.state"](newValue, oldValue) {
|
||||
if (newValue == "running" && newValue != oldValue) {
|
||||
this.onContainerStateChange(newValue, oldValue);
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onContainerStateChange(newValue, oldValue) {},
|
||||
},
|
||||
};
|
||||
@@ -23,13 +23,12 @@ const mutations = {
|
||||
SET_CONTAINERS(state, containers) {
|
||||
const containersById = getters.allContainersById({ containers });
|
||||
|
||||
containers.forEach(
|
||||
(container) =>
|
||||
(container.stat =
|
||||
containersById[container.id] && containersById[container.id].stat
|
||||
? containersById[container.id].stat
|
||||
: { memoryUsage: 0, cpu: 0 })
|
||||
);
|
||||
containers.forEach((container) => {
|
||||
container.stat =
|
||||
containersById[container.id] && containersById[container.id].stat
|
||||
? containersById[container.id].stat
|
||||
: { memoryUsage: 0, cpu: 0 };
|
||||
});
|
||||
|
||||
state.containers = containers;
|
||||
},
|
||||
|
||||
2
go.mod
2
go.mod
@@ -2,7 +2,7 @@ module github.com/amir20/dozzle
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.4.18 // indirect
|
||||
github.com/alexflint/go-arg v1.4.1
|
||||
github.com/alexflint/go-arg v1.4.2
|
||||
github.com/beme/abide v0.0.0-20190723115211-635a09831760
|
||||
github.com/containerd/containerd v1.4.4 // indirect
|
||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||
|
||||
4
go.sum
4
go.sum
@@ -4,8 +4,8 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Microsoft/go-winio v0.4.18 h1:yjwCO1nhWEShaA5qsmPOBzAOjRCa2PRLsDNZ5yBWXpg=
|
||||
github.com/Microsoft/go-winio v0.4.18/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||
github.com/alexflint/go-arg v1.4.1 h1:QX73/uQKvH/veFcp6RQY4RFyB4fPGSloi3VJSBE61Uo=
|
||||
github.com/alexflint/go-arg v1.4.1/go.mod h1:9iRbDxne7LcR/GSvEr7ma++GLpdIU1zrghf2y2768kM=
|
||||
github.com/alexflint/go-arg v1.4.2 h1:lDWZAXxpAnZUq4qwb86p/3rIJJ2Li81EoMbTMujhVa0=
|
||||
github.com/alexflint/go-arg v1.4.2/go.mod h1:9iRbDxne7LcR/GSvEr7ma++GLpdIU1zrghf2y2768kM=
|
||||
github.com/alexflint/go-scalar v1.0.0 h1:NGupf1XV/Xb04wXskDFzS0KWOLH632W/EO4fAFi+A70=
|
||||
github.com/alexflint/go-scalar v1.0.0/go.mod h1:GpHzbCOZXEKMEcygYQ5n/aa4Aq84zbxjy3MxYW0gjYw=
|
||||
github.com/beme/abide v0.0.0-20190723115211-635a09831760 h1:FvTM5NSN5HYvfKpgL+8x73U5v063vHsd7AX05eV1DnM=
|
||||
|
||||
@@ -9,6 +9,13 @@
|
||||
dependencies:
|
||||
"@babel/highlight" "^7.8.3"
|
||||
|
||||
"@babel/code-frame@^7.12.13":
|
||||
version "7.12.13"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658"
|
||||
integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==
|
||||
dependencies:
|
||||
"@babel/highlight" "^7.12.13"
|
||||
|
||||
"@babel/core@^7.1.0", "@babel/core@^7.7.5":
|
||||
version "7.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e"
|
||||
@@ -126,6 +133,11 @@
|
||||
dependencies:
|
||||
"@babel/types" "^7.8.3"
|
||||
|
||||
"@babel/helper-validator-identifier@^7.14.0":
|
||||
version "7.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz#d26cad8a47c65286b15df1547319a5d0bcf27288"
|
||||
integrity sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==
|
||||
|
||||
"@babel/helper-validator-identifier@^7.9.0":
|
||||
version "7.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz#ad53562a7fc29b3b9a91bbf7d10397fd146346ed"
|
||||
@@ -145,6 +157,15 @@
|
||||
"@babel/traverse" "^7.9.0"
|
||||
"@babel/types" "^7.9.0"
|
||||
|
||||
"@babel/highlight@^7.12.13":
|
||||
version "7.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.0.tgz#3197e375711ef6bf834e67d0daec88e4f46113cf"
|
||||
integrity sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==
|
||||
dependencies:
|
||||
"@babel/helper-validator-identifier" "^7.14.0"
|
||||
chalk "^2.0.0"
|
||||
js-tokens "^4.0.0"
|
||||
|
||||
"@babel/highlight@^7.8.3":
|
||||
version "7.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079"
|
||||
@@ -381,6 +402,16 @@
|
||||
"@types/node" "*"
|
||||
jest-mock "^26.6.2"
|
||||
|
||||
"@jest/environment@^27.0.1":
|
||||
version "27.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.0.1.tgz#27ed89bf8179c0a030690f063d922d6da7a519ac"
|
||||
integrity sha512-nG+r3uSs2pOTsdhgt6lUm4ZGJLRcTc6HZIkrFsVpPcdSqEpJehEny9r9y2Bmhkn8fKXWdGCYJKF3i4nKO0HSmA==
|
||||
dependencies:
|
||||
"@jest/fake-timers" "^27.0.1"
|
||||
"@jest/types" "^27.0.1"
|
||||
"@types/node" "*"
|
||||
jest-mock "^27.0.1"
|
||||
|
||||
"@jest/fake-timers@^26.6.2":
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad"
|
||||
@@ -393,6 +424,18 @@
|
||||
jest-mock "^26.6.2"
|
||||
jest-util "^26.6.2"
|
||||
|
||||
"@jest/fake-timers@^27.0.1":
|
||||
version "27.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.0.1.tgz#6987a596b0bcf8c07653086076c17058b4c77b5c"
|
||||
integrity sha512-3CyLJQnHzKI4TCJSCo+I9TzIHjSK4RrNEk93jFM6Q9+9WlSJ3mpMq/p2YuKMe0SiHKbmZOd5G/Ll5ofF9Xkw9g==
|
||||
dependencies:
|
||||
"@jest/types" "^27.0.1"
|
||||
"@sinonjs/fake-timers" "^7.0.2"
|
||||
"@types/node" "*"
|
||||
jest-message-util "^27.0.1"
|
||||
jest-mock "^27.0.1"
|
||||
jest-util "^27.0.1"
|
||||
|
||||
"@jest/globals@^26.6.2":
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.6.2.tgz#5b613b78a1aa2655ae908eba638cc96a20df720a"
|
||||
@@ -496,6 +539,17 @@
|
||||
"@types/yargs" "^15.0.0"
|
||||
chalk "^4.0.0"
|
||||
|
||||
"@jest/types@^27.0.1":
|
||||
version "27.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.0.1.tgz#631738c942e70045ebbf42a3f9b433036d3845e4"
|
||||
integrity sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==
|
||||
dependencies:
|
||||
"@types/istanbul-lib-coverage" "^2.0.0"
|
||||
"@types/istanbul-reports" "^3.0.0"
|
||||
"@types/node" "*"
|
||||
"@types/yargs" "^16.0.0"
|
||||
chalk "^4.0.0"
|
||||
|
||||
"@sideway/address@^4.1.0":
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.1.tgz#9e321e74310963fdf8eebfbee09c7bd69972de4d"
|
||||
@@ -527,6 +581,13 @@
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.7.0"
|
||||
|
||||
"@sinonjs/fake-timers@^7.0.2":
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-7.1.0.tgz#8f13af27d842cbf51ad4502e05562fe9391d084e"
|
||||
integrity sha512-hAEzXi6Wbvlb67NnGMGSNOeAflLVnMa4yliPU/ty1qjgW/vAletH15/v/esJwASSIA0YlIyjnloenFbEZc9q9A==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.7.0"
|
||||
|
||||
"@types/babel__core@^7.0.0":
|
||||
version "7.1.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.9.tgz#77e59d438522a6fb898fa43dc3455c6e72f3963d"
|
||||
@@ -641,6 +702,13 @@
|
||||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@types/yargs@^16.0.0":
|
||||
version "16.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.3.tgz#4b6d35bb8e680510a7dc2308518a80ee1ef27e01"
|
||||
integrity sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==
|
||||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@types/yauzl@^2.9.1":
|
||||
version "2.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af"
|
||||
@@ -725,6 +793,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
|
||||
"@types/color-name" "^1.1.1"
|
||||
color-convert "^2.0.1"
|
||||
|
||||
ansi-styles@^5.0.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
|
||||
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
|
||||
|
||||
anymatch@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"
|
||||
@@ -1058,6 +1131,11 @@ ci-info@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
|
||||
integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
|
||||
|
||||
ci-info@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.1.1.tgz#9a32fcefdf7bcdb6f0a7e1c0f8098ec57897b80a"
|
||||
integrity sha512-kdRWLBIJwdsYJWYJFtAFFYxybguqeF91qpZaggjG5Nf8QKdizFG2hjqvaTXbxFIcYbSaD74KpAXv6BSm17DHEQ==
|
||||
|
||||
cjs-module-lexer@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f"
|
||||
@@ -1455,10 +1533,10 @@ expand-tilde@^1.2.2:
|
||||
dependencies:
|
||||
os-homedir "^1.0.1"
|
||||
|
||||
expect-puppeteer@^5.0.3:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/expect-puppeteer/-/expect-puppeteer-5.0.3.tgz#5e2f7afc55ebabb38edcff45480955473e67a2a7"
|
||||
integrity sha512-l5APxyanw4n+zmVCCRKY6BoialeLvNSunTeA4e5QdhGq1QGQCq9WBcw3NVcCO4bAk2yQLL2ZoUmYMD3Q9ZvroQ==
|
||||
expect-puppeteer@^5.0.4:
|
||||
version "5.0.4"
|
||||
resolved "https://registry.yarnpkg.com/expect-puppeteer/-/expect-puppeteer-5.0.4.tgz#54bfdecabb2acb3e3f0d0292cd3dab2dd8ff5a81"
|
||||
integrity sha512-NV7jSiKhK+byocxg9A+0av+Q2RSCP9bcLVRz7zhHaESeCOkuomMvl9oD+uo1K+NdqRCXhNkQlUGWlmtbrpR1qw==
|
||||
|
||||
expect@^26.6.2:
|
||||
version "26.6.2"
|
||||
@@ -1957,6 +2035,13 @@ is-ci@^2.0.0:
|
||||
dependencies:
|
||||
ci-info "^2.0.0"
|
||||
|
||||
is-ci@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.0.tgz#c7e7be3c9d8eef7d0fa144390bd1e4b88dc4c994"
|
||||
integrity sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ==
|
||||
dependencies:
|
||||
ci-info "^3.1.1"
|
||||
|
||||
is-core-module@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.0.0.tgz#58531b70aed1db7c0e8d4eb1a0a2d1ddd64bd12d"
|
||||
@@ -2278,15 +2363,27 @@ jest-environment-node@^26.6.2:
|
||||
jest-mock "^26.6.2"
|
||||
jest-util "^26.6.2"
|
||||
|
||||
jest-environment-puppeteer@^5.0.3:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/jest-environment-puppeteer/-/jest-environment-puppeteer-5.0.3.tgz#5cff9d1aa75c16ad525e3acbddc322e90a87673b"
|
||||
integrity sha512-KHUJKeFKFNgQPitZZCHSlw22FanCSV3EnM8GaqV8v3nPp1CqoPgFFYKrrX/Z5mCZhSgJfo2qMtinG2YAUSLpCg==
|
||||
jest-environment-node@^27.0.1:
|
||||
version "27.0.1"
|
||||
resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.0.1.tgz#7d7df7ae191477a823ffb4fcc0772b4c23ec5c87"
|
||||
integrity sha512-/p94lo0hx+hbKUw1opnRFUPPsjncRBEUU+2Dh7BuxX8Nr4rRiTivLYgXzo79FhaeMYV0uiV5WAbHBq6xC11JJg==
|
||||
dependencies:
|
||||
"@jest/environment" "^27.0.1"
|
||||
"@jest/fake-timers" "^27.0.1"
|
||||
"@jest/types" "^27.0.1"
|
||||
"@types/node" "*"
|
||||
jest-mock "^27.0.1"
|
||||
jest-util "^27.0.1"
|
||||
|
||||
jest-environment-puppeteer@^5.0.4:
|
||||
version "5.0.4"
|
||||
resolved "https://registry.yarnpkg.com/jest-environment-puppeteer/-/jest-environment-puppeteer-5.0.4.tgz#ed64689bf200923828ca98761b4da36eb8ce31bc"
|
||||
integrity sha512-wd4EDOD4QRi11QZ1IV8WsL1wlnnMUtcqtU0BNm+REzRtg78K2XHn3jS6YxGeXIOnsgrJeHxsD7DlRZ/GkFteLg==
|
||||
dependencies:
|
||||
chalk "^4.1.1"
|
||||
cwd "^0.10.0"
|
||||
jest-dev-server "^5.0.3"
|
||||
jest-environment-node "^26.6.2"
|
||||
jest-environment-node "^27.0.1"
|
||||
merge-deep "^3.0.3"
|
||||
|
||||
jest-get-type@^26.3.0:
|
||||
@@ -2387,6 +2484,21 @@ jest-message-util@^26.6.2:
|
||||
slash "^3.0.0"
|
||||
stack-utils "^2.0.2"
|
||||
|
||||
jest-message-util@^27.0.1:
|
||||
version "27.0.1"
|
||||
resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.0.1.tgz#382b7c55d8e0b1aba9eeb41d3cfdd34e451210ed"
|
||||
integrity sha512-w8BfON2GwWORkos8BsxcwwQrLkV2s1ENxSRXK43+6yuquDE2hVxES/jrFqOArpP1ETVqqMmktU6iGkG8ncVzeA==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.12.13"
|
||||
"@jest/types" "^27.0.1"
|
||||
"@types/stack-utils" "^2.0.0"
|
||||
chalk "^4.0.0"
|
||||
graceful-fs "^4.2.4"
|
||||
micromatch "^4.0.4"
|
||||
pretty-format "^27.0.1"
|
||||
slash "^3.0.0"
|
||||
stack-utils "^2.0.3"
|
||||
|
||||
jest-mock@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302"
|
||||
@@ -2395,18 +2507,26 @@ jest-mock@^26.6.2:
|
||||
"@jest/types" "^26.6.2"
|
||||
"@types/node" "*"
|
||||
|
||||
jest-mock@^27.0.1:
|
||||
version "27.0.1"
|
||||
resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.0.1.tgz#8394e297bc3dfed980961622cb51fd042b4acf5a"
|
||||
integrity sha512-fXCSZQDT5hUcAUy8OBnB018x7JFOMQnz4XfpSKEbfpWzL6o5qaLRhgf2Qg2NPuVKmC/fgOf33Edj8wjF4I24CQ==
|
||||
dependencies:
|
||||
"@jest/types" "^27.0.1"
|
||||
"@types/node" "*"
|
||||
|
||||
jest-pnp-resolver@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c"
|
||||
integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==
|
||||
|
||||
jest-puppeteer@^5.0.1:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/jest-puppeteer/-/jest-puppeteer-5.0.3.tgz#f34f91637b42dcf2407f80d18ad4598acc4c6be4"
|
||||
integrity sha512-H432LFp1YlyLr4XIPSWp2xCupNMkgZwp9L5gIbIliwM5cyboronWYjwKb9mfE8ipfzz755MitEWqtw807fXEow==
|
||||
version "5.0.4"
|
||||
resolved "https://registry.yarnpkg.com/jest-puppeteer/-/jest-puppeteer-5.0.4.tgz#c52e3379c11425ce974d025c1a8bf9f599da4b3f"
|
||||
integrity sha512-IUOVKgHEaKsLqahZy/J/DvXB59SQx4AVpZKTRDvJzCdkvdGc3NVsNwUhovr6SK+HOK1TOiqAiXPTAPiIq3mkrg==
|
||||
dependencies:
|
||||
expect-puppeteer "^5.0.3"
|
||||
jest-environment-puppeteer "^5.0.3"
|
||||
expect-puppeteer "^5.0.4"
|
||||
jest-environment-puppeteer "^5.0.4"
|
||||
|
||||
jest-regex-util@^26.0.0:
|
||||
version "26.0.0"
|
||||
@@ -2537,6 +2657,18 @@ jest-util@^26.6.2:
|
||||
is-ci "^2.0.0"
|
||||
micromatch "^4.0.2"
|
||||
|
||||
jest-util@^27.0.1:
|
||||
version "27.0.1"
|
||||
resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.0.1.tgz#324ed9879d129c1e64f9169a739d6d50d7928769"
|
||||
integrity sha512-lEw3waSmEOO4ZkwkUlFSvg4es1+8+LIkSGxp/kF60K0+vMR3Dv3O2HMZhcln9NHqSQzpVbsDT6OeMzUPW7DfRg==
|
||||
dependencies:
|
||||
"@jest/types" "^27.0.1"
|
||||
"@types/node" "*"
|
||||
chalk "^4.0.0"
|
||||
graceful-fs "^4.2.4"
|
||||
is-ci "^3.0.0"
|
||||
picomatch "^2.2.3"
|
||||
|
||||
jest-validate@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec"
|
||||
@@ -2838,6 +2970,14 @@ micromatch@^4.0.2:
|
||||
braces "^3.0.1"
|
||||
picomatch "^2.0.5"
|
||||
|
||||
micromatch@^4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9"
|
||||
integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==
|
||||
dependencies:
|
||||
braces "^3.0.1"
|
||||
picomatch "^2.2.3"
|
||||
|
||||
mime-db@1.43.0:
|
||||
version "1.43.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58"
|
||||
@@ -3153,6 +3293,11 @@ picomatch@^2.0.4, picomatch@^2.0.5:
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
|
||||
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
|
||||
|
||||
picomatch@^2.2.3:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
|
||||
integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
|
||||
|
||||
pirates@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87"
|
||||
@@ -3199,6 +3344,16 @@ pretty-format@^26.6.2:
|
||||
ansi-styles "^4.0.0"
|
||||
react-is "^17.0.1"
|
||||
|
||||
pretty-format@^27.0.1:
|
||||
version "27.0.1"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.0.1.tgz#c4094621dfbd3e8ab751964d1cf01edc6f88474d"
|
||||
integrity sha512-qE+0J6c/gd+R6XTcQgPJMc5hMJNsxzSF5p8iZSbMZ7GQzYGlSLNkh2P80Wa2dbF4gEVUsJEgcrBY+1L2/j265w==
|
||||
dependencies:
|
||||
"@jest/types" "^27.0.1"
|
||||
ansi-regex "^5.0.0"
|
||||
ansi-styles "^5.0.0"
|
||||
react-is "^17.0.1"
|
||||
|
||||
progress@^2.0.1:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
|
||||
@@ -3731,6 +3886,13 @@ stack-utils@^2.0.2:
|
||||
dependencies:
|
||||
escape-string-regexp "^2.0.0"
|
||||
|
||||
stack-utils@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz#cd5f030126ff116b78ccb3c027fe302713b61277"
|
||||
integrity sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==
|
||||
dependencies:
|
||||
escape-string-regexp "^2.0.0"
|
||||
|
||||
static-extend@^0.1.1:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
module.exports = {
|
||||
clearMocks: true,
|
||||
testEnvironment: "jsdom",
|
||||
moduleFileExtensions: ["js", "json", "vue"],
|
||||
coveragePathIgnorePatterns: ["node_modules"],
|
||||
testPathIgnorePatterns: ["node_modules", "<rootDir>/integration/"],
|
||||
|
||||
32
package.json
32
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dozzle",
|
||||
"version": "3.6.6",
|
||||
"version": "3.7.0",
|
||||
"description": "Realtime log viewer for docker containers. ",
|
||||
"scripts": {
|
||||
"watch": "npm-run-all -p watch:*",
|
||||
@@ -27,13 +27,13 @@
|
||||
},
|
||||
"homepage": "https://github.com/amir20/dozzle#readme",
|
||||
"dependencies": {
|
||||
"ansi-to-html": "^0.6.14",
|
||||
"ansi-to-html": "^0.6.15",
|
||||
"buefy": "^0.9.7",
|
||||
"bulma": "^0.9.2",
|
||||
"date-fns": "^2.21.3",
|
||||
"dompurify": "^2.2.8",
|
||||
"fuzzysort": "^1.1.4",
|
||||
"hotkeys-js": "^3.8.3",
|
||||
"hotkeys-js": "^3.8.5",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"semver": "^7.3.5",
|
||||
@@ -45,36 +45,36 @@
|
||||
"vuex": "^3.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.0",
|
||||
"@babel/plugin-transform-runtime": "^7.13.15",
|
||||
"@babel/core": "^7.14.3",
|
||||
"@babel/plugin-transform-runtime": "^7.14.3",
|
||||
"@vue/component-compiler-utils": "^3.2.0",
|
||||
"@vue/test-utils": "^1.2.0",
|
||||
"autoprefixer": "^10.2.5",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-jest": "^26.6.3",
|
||||
"babel-jest": "^27.0.1",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"caniuse-lite": "^1.0.30001223",
|
||||
"css-loader": "^5.2.4",
|
||||
"caniuse-lite": "^1.0.30001230",
|
||||
"css-loader": "^5.2.6",
|
||||
"eventsourcemock": "^2.0.0",
|
||||
"html-webpack-plugin": "^5.3.1",
|
||||
"husky": "^6.0.0",
|
||||
"jest": "^26.6.3",
|
||||
"jest": "^27.0.1",
|
||||
"jest-serializer-vue": "^2.0.2",
|
||||
"lint-staged": "^11.0.0",
|
||||
"mini-css-extract-plugin": "^1.6.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8.2.14",
|
||||
"postcss-loader": "^5.2.0",
|
||||
"postcss": "^8.3.0",
|
||||
"postcss-loader": "^5.3.0",
|
||||
"prettier": "^2.3.0",
|
||||
"release-it": "^14.6.2",
|
||||
"sass": "^1.32.12",
|
||||
"sass-loader": "^11.0.1",
|
||||
"release-it": "^14.7.0",
|
||||
"sass": "^1.34.0",
|
||||
"sass-loader": "^11.1.1",
|
||||
"vue-hot-reload-api": "^2.3.4",
|
||||
"vue-jest": "^3.0.7",
|
||||
"vue-loader": "^15.9.6",
|
||||
"vue-loader": "^15.9.7",
|
||||
"vue-style-loader": "^4.1.3",
|
||||
"vue-template-compiler": "^2.6.12",
|
||||
"webpack": "^5.36.2",
|
||||
"webpack": "^5.37.1",
|
||||
"webpack-cli": "^4.7.0",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-pwa-manifest": "^4.3.0"
|
||||
|
||||
11
web/logs.go
11
web/logs.go
@@ -1,4 +1,3 @@
|
||||
|
||||
package web
|
||||
|
||||
import (
|
||||
@@ -88,7 +87,12 @@ func (h *handler) streamLogs(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Connection", "keep-alive")
|
||||
w.Header().Set("X-Accel-Buffering", "no")
|
||||
|
||||
reader, err := h.client.ContainerLogs(r.Context(), container.ID, h.config.TailSize, r.Header.Get("Last-Event-ID"))
|
||||
lastEventId := r.Header.Get("Last-Event-ID")
|
||||
if len(r.URL.Query().Get("lastEventId")) > 0 {
|
||||
lastEventId = r.URL.Query().Get("lastEventId")
|
||||
}
|
||||
|
||||
reader, err := h.client.ContainerLogs(r.Context(), container.ID, h.config.TailSize, lastEventId)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
fmt.Fprintf(w, "event: container-stopped\ndata: end of stream\n\n")
|
||||
@@ -100,11 +104,10 @@ func (h *handler) streamLogs(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
|
||||
buffered := bufio.NewReader(reader)
|
||||
var readerError error
|
||||
var message string
|
||||
for {
|
||||
for {
|
||||
message, readerError = buffered.ReadString('\n')
|
||||
fmt.Fprintf(w, "data: %s\n", message)
|
||||
if index := strings.IndexAny(message, " "); index != -1 {
|
||||
|
||||
Reference in New Issue
Block a user