Compare commits

...

64 Commits

Author SHA1 Message Date
Amir Raminfar
7dcf1d4f15 Release 4.2.2 2022-10-21 13:14:07 -07:00
Amir Raminfar
d0af303d6f Updates colors (#1919)
* Updates colors

* Updates colors int tests
2022-10-21 13:13:48 -07:00
Amir Raminfar
fd31d394a5 Fixes tooltips 2022-10-19 15:16:42 -07:00
Amir Raminfar
1c0af19c88 Release 4.2.1 2022-10-19 08:31:26 -07:00
Amir Raminfar
9d77613ee9 Updates node 2022-10-19 08:31:13 -07:00
kodiakhq[bot]
62f747797c Merge pull request #1917 from amir20/dependabot/go_modules/github.com/docker/docker-20.10.20incompatible
Bump github.com/docker/docker from 20.10.19+incompatible to 20.10.20+incompatible
2022-10-19 09:18:28 +00:00
kodiakhq[bot]
fccb7fc2d4 Merge pull request #1916 from amir20/dependabot/github_actions/docker/setup-buildx-action-2.2.1
Bump docker/setup-buildx-action from 2.2.0 to 2.2.1
2022-10-19 09:17:37 +00:00
kodiakhq[bot]
7fecbdd000 Merge pull request #1915 from amir20/dependabot/docker/node-19-alpine
Bump node from 18-alpine to 19-alpine
2022-10-19 09:17:24 +00:00
dependabot[bot]
46a2f2b810 Bump github.com/docker/docker
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 20.10.19+incompatible to 20.10.20+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Changelog](https://github.com/moby/moby/blob/master/CHANGELOG.md)
- [Commits](https://github.com/docker/docker/compare/v20.10.19...v20.10.20)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-19 09:12:59 +00:00
dependabot[bot]
e9dcdda64d Bump docker/setup-buildx-action from 2.2.0 to 2.2.1
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.2.0 to 2.2.1.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.2.0...v2.2.1)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-19 09:12:50 +00:00
dependabot[bot]
67a0644c37 Bump node from 18-alpine to 19-alpine
Bumps node from 18-alpine to 19-alpine.

---
updated-dependencies:
- dependency-name: node
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-19 09:12:48 +00:00
Amir Raminfar
21fc2ce2fd Adds on mouse over effect for stats (#1914) 2022-10-18 15:29:19 -07:00
kodiakhq[bot]
20425bf6b1 Merge pull request #1913 from amir20/dependabot/github_actions/docker/setup-buildx-action-2.2.0
Bump docker/setup-buildx-action from 2.1.0 to 2.2.0
2022-10-18 09:17:58 +00:00
dependabot[bot]
828c288570 Bump docker/setup-buildx-action from 2.1.0 to 2.2.0
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.1.0 to 2.2.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.1.0...v2.2.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-18 09:15:02 +00:00
Amir Raminfar
cde2589755 Fixes iOS font size changing 2022-10-17 10:48:11 -07:00
kodiakhq[bot]
5fb2f452e2 Merge pull request #1911 from amir20/dependabot/github_actions/pnpm/action-setup-2.2.4
Bump pnpm/action-setup from 2.2.3 to 2.2.4
2022-10-17 09:16:40 +00:00
dependabot[bot]
6aea252d3e Bump pnpm/action-setup from 2.2.3 to 2.2.4
Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 2.2.3 to 2.2.4.
- [Release notes](https://github.com/pnpm/action-setup/releases)
- [Commits](https://github.com/pnpm/action-setup/compare/v2.2.3...v2.2.4)

---
updated-dependencies:
- dependency-name: pnpm/action-setup
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-17 09:13:39 +00:00
Amir Raminfar
7337dcb5d4 Updates modules 2022-10-14 14:57:46 -07:00
Amir Raminfar
deeb5fc100 Release 4.2.0 2022-10-14 13:23:59 -07:00
Amir Raminfar
5b15a0b29d stat cleanup (#1910)
* Updates modules

* Cleans up stats
2022-10-14 13:00:57 -07:00
kodiakhq[bot]
e2ad2e0193 Merge pull request #1909 from amir20/dependabot/go_modules/github.com/docker/docker-20.10.19incompatible
Bump github.com/docker/docker from 20.10.18+incompatible to 20.10.19+incompatible
2022-10-14 09:11:40 +00:00
dependabot[bot]
5b48426fc1 Bump github.com/docker/docker
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 20.10.18+incompatible to 20.10.19+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Changelog](https://github.com/moby/moby/blob/master/CHANGELOG.md)
- [Commits](https://github.com/docker/docker/compare/v20.10.18...v20.10.19)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-14 09:08:05 +00:00
kodiakhq[bot]
a24947ab3b Merge pull request #1908 from amir20/dependabot/github_actions/docker/setup-buildx-action-2.1.0
Bump docker/setup-buildx-action from 2.0.0 to 2.1.0
2022-10-13 09:19:44 +00:00
dependabot[bot]
8926b451d0 Bump docker/setup-buildx-action from 2.0.0 to 2.1.0
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.0.0...v2.1.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-13 09:16:51 +00:00
Amir Raminfar
afd37d3455 Add trending CPU and Memory usage (#1896)
* Refactors to stat history

* Uses markRaw

* Cleans up proxies

* Removes id from snapshots

* Adds d3

* Adds more d3 modules

* Fixes package

* Cleans up packages

* Updates modules

* Adds initital d3 chart

* Cleans up svg

* Fixes @types/d3-array

* Adds memory

* Moves charts around
2022-10-12 14:21:41 -07:00
kodiakhq[bot]
4f84beb835 Merge pull request #1906 from amir20/dependabot/docker/e2e/cypress/included-10.10.0
Bump cypress/included from 10.9.0 to 10.10.0 in /e2e
2022-10-12 10:43:39 +00:00
dependabot[bot]
d54d894a66 Bump cypress/included from 10.9.0 to 10.10.0 in /e2e
Bumps cypress/included from 10.9.0 to 10.10.0.

---
updated-dependencies:
- dependency-name: cypress/included
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-12 10:39:36 +00:00
kodiakhq[bot]
7c854d31a7 Merge pull request #1905 from amir20/dependabot/github_actions/docker/login-action-2.1.0
Bump docker/login-action from 2.0.0 to 2.1.0
2022-10-12 09:33:27 +00:00
kodiakhq[bot]
4c2caad4a0 Merge pull request #1904 from amir20/dependabot/github_actions/docker/build-push-action-3.2.0
Bump docker/build-push-action from 3.1.1 to 3.2.0
2022-10-12 09:33:14 +00:00
dependabot[bot]
9684fd978b Bump docker/login-action from 2.0.0 to 2.1.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v2.0.0...v2.1.0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-12 09:29:20 +00:00
dependabot[bot]
a79a3f680f Bump docker/build-push-action from 3.1.1 to 3.2.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3.1.1 to 3.2.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v3.1.1...v3.2.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-12 09:29:17 +00:00
kodiakhq[bot]
1e18423b04 Merge pull request #1903 from amir20/dependabot/github_actions/pnpm/action-setup-2.2.3
Bump pnpm/action-setup from 2.2.2 to 2.2.3
2022-10-11 09:14:13 +00:00
dependabot[bot]
82b208c0bf Bump pnpm/action-setup from 2.2.2 to 2.2.3
Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 2.2.2 to 2.2.3.
- [Release notes](https://github.com/pnpm/action-setup/releases)
- [Commits](https://github.com/pnpm/action-setup/compare/v2.2.2...v2.2.3)

---
updated-dependencies:
- dependency-name: pnpm/action-setup
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-11 09:08:59 +00:00
Amir Raminfar
8a3e9504a4 Updates modules 2022-10-10 15:38:05 -07:00
Amir Raminfar
d8748c6e27 Updats modules 2022-10-08 13:26:47 -07:00
kodiakhq[bot]
af8192fbc0 Merge pull request #1897 from amir20/dependabot/docker/golang-1.19.2-alpine
Bump golang from 1.19.1-alpine to 1.19.2-alpine
2022-10-05 09:20:23 +00:00
dependabot[bot]
3492587d63 Bump golang from 1.19.1-alpine to 1.19.2-alpine
Bumps golang from 1.19.1-alpine to 1.19.2-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-05 09:16:19 +00:00
Amir Raminfar
b8c522021d Updates modules 2022-10-04 13:26:11 -07:00
James Cote
5850c319f2 Fix json log line still says "expand json" when expanded (#1894) 2022-10-01 15:06:59 -07:00
Amir Raminfar
afbed43185 Release 4.1.9 2022-09-28 12:29:26 -07:00
Altynbek Kaliakbarov
d083430c73 Change calculation of memory usage. (#1892) 2022-09-28 12:23:24 -07:00
kodiakhq[bot]
79f553ff0c Merge pull request #1890 from amir20/dependabot/docker/e2e/cypress/included-10.9.0
Bump cypress/included from 10.8.0 to 10.9.0 in /e2e
2022-09-28 09:38:56 +00:00
dependabot[bot]
a02551f5ec Bump cypress/included from 10.8.0 to 10.9.0 in /e2e
Bumps cypress/included from 10.8.0 to 10.9.0.

---
updated-dependencies:
- dependency-name: cypress/included
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-28 09:33:10 +00:00
Amir Raminfar
7c486d57fc Release 4.1.8 2022-09-27 10:27:57 -07:00
Amir Raminfar
41acf28be9 Updates modules 2022-09-26 09:54:25 -07:00
Amir Raminfar
21e5f4fc56 Removes fuzzysearch and uses fuse.js (#1888)
* Removes fuzzysearch and uses fuse.js

* Fix search bug

* Adds locale

* Fixes more locale

* Fixes search to not update with stats

* Fixes sort order
2022-09-23 09:54:55 -07:00
kodiakhq[bot]
2d54cbba9c Merge pull request #1887 from amir20/keyboard
Improves keyboard short cuts and adds a test
2022-09-22 18:19:41 +00:00
Amir Raminfar
88844c895c Fixes comment 2022-09-22 11:15:43 -07:00
Amir Raminfar
7aa7f42c52 Improves keyboard short cuts and adds a test 2022-09-22 11:13:28 -07:00
Amir Raminfar
59f4b0da4f Release 4.1.7 2022-09-20 14:31:07 -07:00
Amir Raminfar
e99e6ebd49 Fixes bubble for search and #1886 2022-09-20 14:30:54 -07:00
Amir Raminfar
3268c32627 Release 4.1.6 2022-09-20 14:03:27 -07:00
Amir Raminfar
5cb5cae113 Fixes spacing bug 2022-09-20 14:03:20 -07:00
Amir Raminfar
dbd1050948 Release 4.1.5 2022-09-20 13:15:04 -07:00
Amir Raminfar
69c647336e Cleans up log viewer and adds zigzag line for skipped logs (#1885)
* Cleans up log viewer and adds zigzag line for skipped logs

* Updates components

* Cleans up css

* Cleans up more css

* Fixes tests

* Fixes typing

* Fixes typescript errors

* Fixes selected color
2022-09-20 13:14:31 -07:00
Amir Raminfar
1a1dd74142 Updates snapshots 2022-09-19 10:32:35 -07:00
Amir Raminfar
307dcd1929 Updates modules 2022-09-19 09:57:16 -07:00
Amir Raminfar
06bde85e03 Removes hotkeyjs in favor of vueuse (#1883) 2022-09-16 09:09:21 -07:00
Amir Raminfar
9cbb55d780 Updates tests 2022-09-16 08:37:03 -07:00
Amir Raminfar
744bc11a2e Release 4.1.4 2022-09-15 15:29:32 -07:00
Amir Raminfar
4fe8964d66 Fixes search for test 2022-09-15 15:29:26 -07:00
Amir Raminfar
3f13ef28a9 Release 4.1.3 2022-09-15 15:21:32 -07:00
Amir Raminfar
28c569ce2a Fixes find in context bug 2022-09-15 15:21:15 -07:00
Amir Raminfar
c1dd3c1131 Search is broken with unknow types. See #1881 2022-09-15 11:16:21 -07:00
56 changed files with 1696 additions and 1083 deletions

View File

@@ -13,7 +13,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v3
- name: Install pnpm
uses: pnpm/action-setup@v2.2.2
uses: pnpm/action-setup@v2.2.4
with:
version: 6.20.1
- name: Install dependencies
@@ -53,14 +53,14 @@ jobs:
with:
images: amir20/dozzle
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.0.0
uses: docker/setup-buildx-action@v2.2.1
- name: Login to DockerHub
uses: docker/login-action@v2.0.0
uses: docker/login-action@v2.1.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v3.1.1
uses: docker/build-push-action@v3.2.0
with:
push: true
platforms: linux/amd64,linux/arm/v7,linux/arm64/v8
@@ -81,7 +81,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v3
- name: Install pnpm
uses: pnpm/action-setup@v2.2.2
uses: pnpm/action-setup@v2.2.4
with:
version: 6.20.1
- name: Install dependencies

View File

@@ -18,14 +18,14 @@ jobs:
with:
images: amir20/dozzle
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.0.0
uses: docker/setup-buildx-action@v2.2.1
- name: Login to DockerHub
uses: docker/login-action@v2.0.0
uses: docker/login-action@v2.1.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v3.1.1
uses: docker/build-push-action@v3.2.0
with:
push: true
platforms: linux/amd64,linux/arm/v7,linux/arm64/v8

View File

@@ -16,7 +16,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v3
- name: Install pnpm
uses: pnpm/action-setup@v2.2.2
uses: pnpm/action-setup@v2.2.4
with:
version: 6.20.1
- name: Install dependencies
@@ -44,9 +44,9 @@ jobs:
with:
fetch-depth: 2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.0.0
uses: docker/setup-buildx-action@v2.2.1
- name: Login to DockerHub
uses: docker/login-action@v2.0.0
uses: docker/login-action@v2.1.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

View File

@@ -1,5 +1,5 @@
# Build assets
FROM --platform=$BUILDPLATFORM node:18-alpine as node
FROM --platform=$BUILDPLATFORM node:19-alpine as node
RUN npm install -g pnpm
@@ -8,19 +8,21 @@ WORKDIR /build
# Install dependencies from lock file
COPY pnpm-lock.yaml ./
RUN pnpm fetch --prod
RUN pnpm fetch
# Copy files
COPY package.json .* vite.config.ts index.html ./
# Copy package.json and install dependencies
COPY package.json ./
RUN pnpm install -r --offline --ignore-scripts
# Copy assets and translations to build
COPY .* vite.config.ts index.html ./
COPY assets ./assets
COPY locales ./locales
# Install dependencies
RUN pnpm install -r --offline --prod --ignore-scripts && pnpm build
# Build assets
RUN pnpm build
FROM --platform=$BUILDPLATFORM golang:1.19.1-alpine AS builder
FROM --platform=$BUILDPLATFORM golang:1.19.2-alpine AS builder
RUN apk add --no-cache ca-certificates && mkdir /dozzle

View File

@@ -250,6 +250,7 @@ declare global {
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
const useShare: typeof import('@vueuse/core')['useShare']
const useSlots: typeof import('vue')['useSlots']
const useSorted: typeof import('@vueuse/core')['useSorted']
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
const useStepper: typeof import('@vueuse/core')['useStepper']
@@ -558,6 +559,7 @@ declare module '@vue/runtime-core' {
readonly useSessionStorage: UnwrapRef<typeof import('@vueuse/core')['useSessionStorage']>
readonly useShare: UnwrapRef<typeof import('@vueuse/core')['useShare']>
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
readonly useSorted: UnwrapRef<typeof import('@vueuse/core')['useSorted']>
readonly useSpeechRecognition: UnwrapRef<typeof import('@vueuse/core')['useSpeechRecognition']>
readonly useSpeechSynthesis: UnwrapRef<typeof import('@vueuse/core')['useSpeechSynthesis']>
readonly useStepper: UnwrapRef<typeof import('@vueuse/core')['useStepper']>

View File

@@ -11,18 +11,16 @@ declare module '@vue/runtime-core' {
CilColumns: typeof import('~icons/cil/columns')['default']
CilFindInPage: typeof import('~icons/cil/find-in-page')['default']
ComplexLogItem: typeof import('./components/LogViewer/ComplexLogItem.vue')['default']
ComplexPayload: typeof import('./components/LogViewer/ComplexPayload.vue')['default']
ContainerStat: typeof import('./components/LogViewer/ContainerStat.vue')['default']
ContainerTitle: typeof import('./components/LogViewer/ContainerTitle.vue')['default']
copy: typeof import('./components/LogViewer/DockerEventLogItem copy.vue')['default']
DockerEventLogItem: typeof import('./components/LogViewer/DockerEventLogItem.vue')['default']
DropdownMenu: typeof import('./components/DropdownMenu.vue')['default']
FieldList: typeof import('./components/LogViewer/FieldList.vue')['default']
FuzzySearchModal: typeof import('./components/FuzzySearchModal.vue')['default']
InfiniteLoader: typeof import('./components/InfiniteLoader.vue')['default']
JSONPayload: typeof import('./components/LogViewer/JSONPayload.vue')['default']
LogActionsToolbar: typeof import('./components/LogActionsToolbar.vue')['default']
LogActionsToolbar: typeof import('./components/LogViewer/LogActionsToolbar.vue')['default']
LogContainer: typeof import('./components/LogViewer/LogContainer.vue')['default']
LogDate: typeof import('./components/LogViewer/LogDate.vue')['default']
LogEventSource: typeof import('./components/LogViewer/LogEventSource.vue')['default']
LogViewer: typeof import('./components/LogViewer/LogViewer.vue')['default']
LogViewerWithSource: typeof import('./components/LogViewer/LogViewerWithSource.vue')['default']
@@ -46,6 +44,8 @@ declare module '@vue/runtime-core' {
SideMenu: typeof import('./components/SideMenu.vue')['default']
SimpleLogItem: typeof import('./components/LogViewer/SimpleLogItem.vue')['default']
SkippedEntriesLogItem: typeof import('./components/LogViewer/SkippedEntriesLogItem.vue')['default']
StringPayload: typeof import('./components/LogViewer/StringPayload.vue')['default']
StatMonitor: typeof import('./components/LogViewer/StatMonitor.vue')['default']
StatSparkline: typeof import('./components/LogViewer/StatSparkline.vue')['default']
ZigZag: typeof import('./components/LogViewer/ZigZag.vue')['default']
}
}

View File

@@ -3,26 +3,29 @@
<o-autocomplete
ref="autocomplete"
v-model="query"
placeholder="Search containers using ⌘ + k or ctrl + k"
field="name"
:placeholder="$t('placeholder.search-containers')"
open-on-focus
keep-first
expanded
:data="results"
:data="data"
@select="selected"
>
<template #default="props">
<template #default="{ option: item }">
<div class="media">
<div class="media-left">
<span class="icon is-small" :class="props.option.state">
<span class="icon is-small" :class="item.state">
<octicon-container-24 />
</span>
</div>
<div class="media-content">
{{ props.option.name }}
{{ item.name }}
</div>
<div class="media-right">
<span class="icon is-small column-icon" @click.stop.prevent="addColumn(props.option)" title="Pin as column">
<span
class="icon is-small column-icon"
@click.stop.prevent="addColumn(item)"
:title="$t('tooltip.pin-column')"
>
<cil-columns />
</span>
</div>
@@ -33,55 +36,61 @@
</template>
<script lang="ts" setup>
import fuzzysort from "fuzzysort";
import { type Container } from "@/types/Container";
import { Container } from "@/models/Container";
import { useFuse } from "@vueuse/integrations/useFuse";
const { maxResults = 20 } = defineProps<{
maxResults: number;
const { maxResults: resultLimit = 20 } = defineProps<{
maxResults?: number;
}>();
const emit = defineEmits(["close"]);
const emit = defineEmits<{
(e: "close"): void;
}>();
const query = ref("");
const autocomplete = ref<HTMLElement>();
const router = useRouter();
const store = useContainerStore();
const { containers } = storeToRefs(store);
const preparedContainers = computed(() =>
containers.value.map(({ name, id, created, state }) =>
reactive({
name,
const list = computed(() => {
return containers.value.map(({ id, created, name, state }) => {
return {
id,
created,
name,
state,
preparedName: fuzzysort.prepare(name),
})
)
);
const results = computed(() => {
const options = {
limit: maxResults,
key: "preparedName",
};
if (query.value) {
const results = fuzzysort.go(query.value, preparedContainers.value, options);
results.forEach((result) => {
if (result.obj.state === "running") {
// @ts-ignore
result.score += 1;
}
});
return [...results].sort((a, b) => b.score - a.score).map((i) => i.obj);
} else {
return [...preparedContainers.value].sort((a, b) => b.created - a.created);
}
};
});
});
onMounted(() => nextTick(() => autocomplete.value?.focus()));
const { results } = useFuse(query, list, {
fuseOptions: { keys: ["name"], includeScore: true },
resultLimit,
matchAllWhenSearchEmpty: true,
});
function selected(item: { id: string; name: string }) {
router.push({ name: "container-id", params: { id: item.id } });
const data = computed(() => {
return results.value
.sort((a, b) => {
if (a.score === b.score) {
if (a.item.state === "running" && b.item.state !== "running") {
return -1;
} else {
return 1;
}
} else if (a.score && b.score) {
return a.score - b.score;
} else {
return 0;
}
})
.map(({ item }) => item);
});
watchOnce(autocomplete, () => autocomplete.value?.focus());
function selected({ id }: { id: string }) {
router.push({ name: "container-id", params: { id } });
emit("close");
}
function addColumn(container: Container) {

View File

@@ -1,14 +1,17 @@
<template>
<div>
<ul class="fields" @click="expanded = !expanded">
<li v-for="(value, name) in logEntry.message">
<template v-if="value">
<div class="columns is-1 is-variable">
<div class="column is-narrow" v-if="showTimestamp">
<log-date :date="logEntry.date"></log-date>
</div>
<div class="column">
<ul class="fields" :class="{ expanded }" @click="expanded = !expanded">
<li v-for="(value, name) in validValues(logEntry.message)">
<span class="has-text-grey">{{ name }}=</span>
<span class="has-text-weight-bold" v-html="markSearch(value)"></span>
</template>
</li>
</ul>
<field-list :fields="logEntry.unfilteredMessage" :expanded="expanded" :visible-keys="visibleKeys"></field-list>
</li>
</ul>
<field-list :fields="logEntry.unfilteredMessage" :expanded="expanded" :visible-keys="visibleKeys"></field-list>
</div>
</div>
</template>
<script lang="ts" setup>
@@ -16,12 +19,16 @@ import { type ComplexLogEntry } from "@/models/LogEntry";
const { markSearch } = useSearchFilter();
defineProps<{
const { logEntry } = defineProps<{
logEntry: ComplexLogEntry;
visibleKeys: string[][];
}>();
let expanded = $ref(false);
function validValues(obj: Record<string, any>) {
return Object.fromEntries(Object.entries(obj).filter(([_, value]) => value !== undefined));
}
</script>
<style lang="scss" scoped>
@@ -40,9 +47,17 @@ let expanded = $ref(false);
}
}
&.expanded:hover {
&::after {
content: "collapse json";
}
}
li {
display: inline-block;
margin-left: 1em;
& + li {
margin-left: 1em;
}
}
}
</style>

View File

@@ -1,27 +1,51 @@
<template>
<div class="is-size-7 is-uppercase columns is-marginless is-mobile" v-if="container.stat">
<div class="column is-narrow has-text-weight-bold">
{{ container.state }}
</div>
<div class="column is-narrow" v-if="container.stat.memoryUsage !== null">
<span class="has-text-weight-light has-spacer">mem</span>
<span class="has-text-weight-bold">
{{ formatBytes(container.stat.memoryUsage) }}
</span>
</div>
<div class="column is-narrow" v-if="container.stat.cpu !== null">
<span class="has-text-weight-light has-spacer">load</span>
<span class="has-text-weight-bold"> {{ container.stat.cpu }}% </span>
</div>
<div class="is-size-7 is-uppercase columns is-marginless is-mobile is-vcentered" v-if="container.stat">
<stat-monitor
class="column is-narrow"
:data="memoryData"
label="mem"
:stat-value="formatBytes(container.stat.memoryUsage)"
></stat-monitor>
<stat-monitor
class="column is-narrow"
:data="cpuData"
label="load"
:stat-value="container.stat.cpu + '%'"
></stat-monitor>
</div>
</template>
<script lang="ts" setup>
import { type Container } from "@/types/Container";
import { Container } from "@/models/Container";
import { type ComputedRef } from "vue";
const container = inject("container") as ComputedRef<Container>;
const cpuData = computedWithControl(
() => container.value.getLastStat(),
() => {
const history = container.value.getStatHistory();
const points: Point<unknown>[] = history.map((stat, i) => ({
x: i,
y: stat.snapshot.cpu,
value: stat.snapshot.cpu + "%",
}));
return points;
}
);
const memoryData = computedWithControl(
() => container.value.getLastStat(),
() => {
const history = container.value.getStatHistory();
const points: Point<string>[] = history.map((stat, i) => ({
x: i,
y: stat.snapshot.memory,
value: formatBytes(stat.snapshot.memoryUsage),
}));
return points;
}
);
</script>
<style lang="scss" scoped>
@@ -30,4 +54,23 @@ const container = inject("container") as ComputedRef<Container>;
content: " ";
}
}
.has-border {
border: 1px solid var(--primary-color);
border-radius: 3px;
padding: 1px 1px 0 1px;
display: flex;
overflow: hidden;
padding-top: 0.25em;
}
.has-background-body-color {
background-color: var(--body-background-color);
}
.is-top-left {
position: absolute;
top: 0;
left: 0.75em;
}
</style>

View File

@@ -8,7 +8,7 @@
</template>
<script lang="ts" setup>
import { type Container } from "@/types/Container";
import { Container } from "@/models/Container";
import { type ComputedRef } from "vue";
const container = inject("container") as ComputedRef<Container>;

View File

@@ -19,9 +19,6 @@ span {
}
&.text {
white-space: pre-wrap;
&::before {
content: " ";
}
}
}
</style>

View File

@@ -42,8 +42,7 @@
<script lang="ts" setup>
import { type ComputedRef } from "vue";
import { type Container } from "@/types/Container";
import hotkeys from "hotkeys-js";
import { Container } from "@/models/Container";
const { showSearch } = useSearchFilter();
const { base } = config;
@@ -52,15 +51,7 @@ const { onClearClicked = (e: Event) => {} } = defineProps<{
onClearClicked: (e: Event) => void;
}>();
const onHotkey = (event: Event) => {
onClearClicked(event);
event.preventDefault();
};
const container = inject("container") as ComputedRef<Container>;
onMounted(() => hotkeys("shift+command+l, shift+ctrl+l", onHotkey));
onUnmounted(() => hotkeys.unbind("shift+command+l, shift+ctrl+l", onHotkey));
</script>
<style lang="scss" scoped>

View File

@@ -1,12 +1,12 @@
<template>
<scrollable-view :scrollable="scrollable" v-if="container">
<template #header v-if="showTitle">
<div class="mr-0 columns is-vcentered is-marginless is-hidden-mobile">
<div class="mr-0 columns is-vcentered is-marginless is-hidden-mobile has-boxshadow">
<div class="column is-clipped is-paddingless">
<container-title @close="$emit('close')" />
</div>
<div class="column is-narrow is-paddingless">
<container-stat v-if="container.stat" />
<container-stat />
</div>
<div class="mr-2 column is-narrow is-paddingless">

View File

@@ -0,0 +1,19 @@
<template>
<relative-time :date="date" class="date"></relative-time>
</template>
<script lang="ts" setup>
defineProps<{
date: Date;
}>();
</script>
<style lang="scss" scoped>
.date {
padding-left: 5px;
padding-right: 5px;
border-radius: 3px;
white-space: nowrap;
background-color: var(--scheme-main-ter);
color: #258ccd;
}
</style>

View File

@@ -6,7 +6,7 @@ import LogEventSource from "./LogEventSource.vue";
import LogViewer from "./LogViewer.vue";
import { settings } from "../../composables/settings";
import { useSearchFilter } from "@/composables/search";
import { vi, describe, expect, beforeEach, test, beforeAll, afterAll, afterEach } from "vitest";
import { vi, describe, expect, beforeEach, test, afterEach } from "vitest";
import { computed, nextTick } from "vue";
import { createRouter, createWebHistory } from "vue-router";
@@ -23,6 +23,7 @@ describe("<LogEventSource />", () => {
beforeEach(() => {
global.EventSource = EventSource;
// @ts-ignore
window.scrollTo = vi.fn();
global.IntersectionObserver = vi.fn().mockImplementation(() => ({
observe: vi.fn(),
@@ -47,6 +48,9 @@ describe("<LogEventSource />", () => {
) {
settings.value.hourStyle = hourStyle;
search.searchFilter.value = searchFilter;
if(searchFilter){
search.showSearch.value = true;
}
const router = createRouter({
history: createWebHistory("/"),
@@ -109,6 +113,7 @@ describe("<LogEventSource />", () => {
vi.runAllTimers();
await nextTick();
// @ts-ignore
const [message, _] = wrapper.vm.messages;
expect(message).toMatchSnapshot();
});

View File

@@ -4,7 +4,7 @@
</template>
<script lang="ts" setup>
import { type Container } from "@/types/Container";
import { Container } from "@/models/Container";
import { type ComputedRef } from "vue";
const emit = defineEmits<{
@@ -12,7 +12,7 @@ const emit = defineEmits<{
}>();
const container = inject("container") as ComputedRef<Container>;
const { connect, messages, loadOlderLogs } = useLogStream(container);
const { messages, loadOlderLogs } = useLogStream(container);
const beforeLoading = () => emit("loading-more", true);
const afterLoading = () => emit("loading-more", false);
@@ -22,6 +22,4 @@ defineExpose({
});
const fetchMore = () => loadOlderLogs({ beforeLoading, afterLoading });
connect();
</script>

View File

@@ -22,10 +22,7 @@
</a>
</dropdown-menu>
</div>
<div class="line">
<span class="date" v-if="showTimestamp"> <relative-time :date="item.date"></relative-time></span>
<component :is="item.getComponent()" :log-entry="item" :visible-keys="visibleKeys.value"></component>
</div>
<component :is="item.getComponent()" :log-entry="item" :visible-keys="visibleKeys.value"></component>
</li>
</ul>
</template>
@@ -33,8 +30,8 @@
<script lang="ts" setup>
import { type ComputedRef, toRaw } from "vue";
import { useRouteHash } from "@vueuse/router";
import { type Container } from "@/types/Container";
import { type JSONObject, type LogEntry } from "@/models/LogEntry";
import { Container } from "@/models/Container";
import { type JSONObject, LogEntry } from "@/models/LogEntry";
const props = defineProps<{
messages: LogEntry<string | JSONObject>[];
@@ -50,10 +47,10 @@ const visible = filteredPayload(messages);
const filtered = filteredMessages(visible);
const events = ref<HTMLElement>();
let lastSelectedItem = ref<LogEntry<string | JSONObject>>();
let lastSelectedItem: LogEntry<string | JSONObject> | undefined = $ref(undefined);
function handleJumpLineSelected(e: Event, item: LogEntry<string | JSONObject>) {
lastSelectedItem.value = item;
lastSelectedItem = item;
resetSearch();
}
@@ -71,12 +68,6 @@ watch(
padding: 1em 0;
font-family: SFMono-Regular, Consolas, Liberation Mono, monaco, Menlo, monospace;
&.disable-wrap {
.line {
white-space: nowrap;
}
}
& > li {
display: flex;
word-wrap: break-word;
@@ -89,19 +80,10 @@ watch(
background-color: rgba(125, 125, 125, 0.08);
}
&.selected .date {
background-color: var(--menu-item-active-background-color);
&.selected {
border: 1px var(--secondary-color) solid;
}
color: var(--text-color);
}
&.selected > .date {
background-color: white;
}
& > .line {
margin: auto 0;
width: 100%;
display: flex;
}
& > .line-options {
display: flex;
flex-direction: row-reverse;
@@ -121,55 +103,4 @@ watch(
font-size: 120%;
}
}
@media (prefers-color-scheme: dark) {
.date {
background-color: #262626;
color: #258ccd;
}
}
[data-theme="dark"] {
.date {
background-color: #262626;
color: #258ccd;
}
}
@media (prefers-color-scheme: light) {
.date {
background-color: #f0f0f0;
color: #009900;
}
}
[data-theme="light"] {
.date {
background-color: #f0f0f0;
color: #009900;
}
}
.date {
padding-left: 5px;
padding-right: 5px;
border-radius: 3px;
white-space: nowrap;
}
:deep(mark) {
border-radius: 2px;
background-color: var(--secondary-color);
animation: pops 200ms ease-out;
display: inline-block;
}
@keyframes pops {
0% {
transform: scale(1.5);
}
100% {
transform: scale(1.05);
}
}
</style>

View File

@@ -1,5 +1,10 @@
<template>
<span class="text" v-html="colorize(logEntry.message)"></span>
<div class="columns is-1 is-variable">
<div class="column is-narrow" v-if="showTimestamp">
<log-date :date="logEntry.date"></log-date>
</div>
<div class="text column" v-html="colorize(logEntry.message)"></div>
</div>
</template>
<script lang="ts" setup>
import { SimpleLogEntry } from "@/models/LogEntry";
@@ -23,8 +28,5 @@ const colorize = (value: string) => markSearch(ansiConvertor.toHtml(value));
.text {
white-space: pre-wrap;
&::before {
content: " ";
}
}
</style>

View File

@@ -1,5 +1,10 @@
<template>
<span class="text">{{ $t("error.logs-skipped", { total: logEntry.totalSkipped }) }}</span>
<div class="is-flex-grow-1 has-text-centered my-4">
<div class="is-relative">
<zig-zag class="is-overlay mt-2"></zig-zag>
<span class="text is-relative py-2 px-4">{{ $t("error.logs-skipped", { total: logEntry.totalSkipped }) }}</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { SkippedLogsEntry } from "@/models/LogEntry";
@@ -10,15 +15,9 @@ defineProps<{
</script>
<style lang="scss" scoped>
span {
&.text {
flex-grow: 1;
text-align: center;
font-weight: bold;
white-space: pre-wrap;
&::before {
content: " ";
}
}
.text {
font-weight: bold;
white-space: pre-wrap;
background-color: var(--body-background-color);
}
</style>

View File

@@ -0,0 +1,56 @@
<template>
<div class="has-text-centered is-relative host" @mouseenter="mouseOver = true" @mouseleave="mouseOver = false">
<div class="has-border has-boxshadow">
<stat-sparkline :data="data" @selected-point="onSelectedPoint"></stat-sparkline>
</div>
<div class="has-background-body-color is-top-left">
<span class="has-text-weight-light has-spacer">{{ label }}</span>
<span class="has-text-weight-bold">
{{ mouseOver ? selectedPoint?.value ?? selectedPoint?.y ?? statValue : statValue }}
</span>
</div>
</div>
</template>
<script lang="ts" setup>
const { data, label, statValue } = defineProps<{ data: Point<unknown>[]; label: string; statValue: string | number }>();
let selectedPoint: Point<unknown> = $ref();
function onSelectedPoint(point: Point<unknown>) {
selectedPoint = point;
}
let mouseOver = $ref(false);
</script>
<style lang="scss" scoped>
.has-spacer {
&::after {
content: " ";
}
}
.has-border {
border: 1px solid var(--primary-color);
border-radius: 3px;
padding: 1px 1px 0 1px;
display: flex;
overflow: hidden;
padding-top: 0.25em;
}
.has-background-body-color {
background-color: var(--body-background-color);
}
.host:hover span {
color: var(--secondary-color);
}
.is-top-left {
position: absolute;
top: 0;
left: 0.75em;
}
</style>

View File

@@ -0,0 +1,62 @@
<template>
<svg :width="width" :height="height" @mousemove="onMove">
<path :d="path" class="area" />
<line :x1="lineX" y1="0" :x2="lineX" :y2="height" class="line" />
</svg>
</template>
<script lang="ts" setup>
import { extent } from "d3-array";
import { scaleLinear } from "d3-scale";
import { area, curveStep } from "d3-shape";
const d3 = { extent, scaleLinear, area, curveStep };
const { data, width = 150, height = 30 } = defineProps<{ data: Point<unknown>[]; width?: number; height?: number }>();
const x = d3.scaleLinear().range([width, 0]);
const y = d3.scaleLinear().range([height, 0]);
const emit = defineEmits<{
(event: "selected-point", value: Point<unknown>): void;
}>();
const shape = d3
.area<Point<unknown>>()
.curve(d3.curveStep)
.x((d) => x(d.x))
.y0(height)
.y1((d) => y(d.y));
const path = computed(() => {
x.domain(d3.extent(data, (d) => d.x) as [number, number]);
y.domain(d3.extent(data, (d) => d.y) as [number, number]);
return shape(data) ?? "";
});
let lineX = $ref(0);
function onMove(e: MouseEvent) {
const { offsetX } = e;
const xValue = x.invert(offsetX);
const index = Math.round(xValue);
lineX = x(index);
const point = data[index];
emit("selected-point", point);
}
</script>
<style scoped>
:deep(.area) {
fill: var(--primary-color);
}
:deep(.line) {
stroke: var(--secondary-color);
stroke-width: 2;
display: none;
}
svg:hover :deep(.line) {
display: unset;
}
</style>

View File

@@ -0,0 +1,19 @@
<template>
<svg width="100%" height="8" class="zigzag">
<defs>
<pattern id="zigzag" x="0" y="0" width="30" height="8" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="15" y2="8" class="line" />
<line x1="15" y1="8" x2="30" y2="0" class="line" />
</pattern>
</defs>
<rect x="0" y="0" width="100%" height="100%" fill="url(#zigzag)"></rect>
</svg>
</template>
<style lang="scss" scoped>
.line {
stroke: var(--primary-color);
stroke-width: 1;
stroke-linecap: round;
}
</style>

View File

@@ -5,12 +5,12 @@ exports[`<LogEventSource /> > render html correctly > should render dates with 1
<li data-key=\\"1\\" class=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"line-options\\" data-v-2e92daca=\\"\\" style=\\"display: none;\\">
<div class=\\"dropdown is-hoverable is-last is-top minimal\\" data-v-539164cb=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"dropdown-trigger\\" data-v-539164cb=\\"\\"><button class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\" data-v-539164cb=\\"\\"><span class=\\"icon\\" data-v-539164cb=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\" data-v-539164cb=\\"\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div class=\\"dropdown-trigger\\" data-v-539164cb=\\"\\"><button class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\" data-v-539164cb=\\"\\"><span class=\\"icon\\" data-v-539164cb=\\"\\"><svg viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\" data-v-539164cb=\\"\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div class=\\"dropdown-menu\\" id=\\"dropdown-menu\\" role=\\"menu\\" data-v-539164cb=\\"\\">
<div class=\\"dropdown-content\\" data-v-539164cb=\\"\\"><a class=\\"dropdown-item\\" href=\\"#1\\" data-v-2e92daca=\\"\\">
<div class=\\"level is-justify-content-start\\" data-v-2e92daca=\\"\\">
<div class=\\"level-left\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\"><svg viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-2e92daca=\\"\\">
<path fill=\\"currentColor\\" d=\\"M334.627 16H48v480h424V153.373ZM440 464H80V48h241.373L440 166.627Z\\"></path>
<path fill=\\"currentColor\\" d=\\"M239.861 152a95.861 95.861 0 1 0 53.624 175.284l68.03 68.029l22.627-22.626l-67.5-67.5A95.816 95.816 0 0 0 239.861 152ZM176 247.861a63.862 63.862 0 1 1 63.861 63.861A63.933 63.933 0 0 1 176 247.861Z\\"></path>
</svg></div>
@@ -23,7 +23,10 @@ exports[`<LogEventSource /> > render html correctly > should render dates with 1
</div>
</div>
</div>
<div class=\\"line\\" data-v-2e92daca=\\"\\"><span class=\\"date\\" data-v-2e92daca=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" data-v-2e92daca=\\"\\">06/12/2019 10:55:42 AM</time></span><span class=\\"text\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">&lt;test&gt;foo bar&lt;/test&gt;</span></div>
<div class=\\"columns is-1 is-variable\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\">06/12/2019 10:55:42 AM</time></div>
<div class=\\"text column\\" data-v-a49e52d4=\\"\\">&lt;test&gt;foo bar&lt;/test&gt;</div>
</div>
</li>
</ul>"
`;
@@ -33,12 +36,12 @@ exports[`<LogEventSource /> > render html correctly > should render dates with 2
<li data-key=\\"1\\" class=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"line-options\\" data-v-2e92daca=\\"\\" style=\\"display: none;\\">
<div class=\\"dropdown is-hoverable is-last is-top minimal\\" data-v-539164cb=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"dropdown-trigger\\" data-v-539164cb=\\"\\"><button class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\" data-v-539164cb=\\"\\"><span class=\\"icon\\" data-v-539164cb=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\" data-v-539164cb=\\"\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div class=\\"dropdown-trigger\\" data-v-539164cb=\\"\\"><button class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\" data-v-539164cb=\\"\\"><span class=\\"icon\\" data-v-539164cb=\\"\\"><svg viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\" data-v-539164cb=\\"\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div class=\\"dropdown-menu\\" id=\\"dropdown-menu\\" role=\\"menu\\" data-v-539164cb=\\"\\">
<div class=\\"dropdown-content\\" data-v-539164cb=\\"\\"><a class=\\"dropdown-item\\" href=\\"#1\\" data-v-2e92daca=\\"\\">
<div class=\\"level is-justify-content-start\\" data-v-2e92daca=\\"\\">
<div class=\\"level-left\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\"><svg viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-2e92daca=\\"\\">
<path fill=\\"currentColor\\" d=\\"M334.627 16H48v480h424V153.373ZM440 464H80V48h241.373L440 166.627Z\\"></path>
<path fill=\\"currentColor\\" d=\\"M239.861 152a95.861 95.861 0 1 0 53.624 175.284l68.03 68.029l22.627-22.626l-67.5-67.5A95.816 95.816 0 0 0 239.861 152ZM176 247.861a63.862 63.862 0 1 1 63.861 63.861A63.933 63.933 0 0 1 176 247.861Z\\"></path>
</svg></div>
@@ -51,7 +54,10 @@ exports[`<LogEventSource /> > render html correctly > should render dates with 2
</div>
</div>
</div>
<div class=\\"line\\" data-v-2e92daca=\\"\\"><span class=\\"date\\" data-v-2e92daca=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" data-v-2e92daca=\\"\\">06/12/2019 10:55:42</time></span><span class=\\"text\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">&lt;test&gt;foo bar&lt;/test&gt;</span></div>
<div class=\\"columns is-1 is-variable\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\">06/12/2019 10:55:42</time></div>
<div class=\\"text column\\" data-v-a49e52d4=\\"\\">&lt;test&gt;foo bar&lt;/test&gt;</div>
</div>
</li>
</ul>"
`;
@@ -61,12 +67,12 @@ exports[`<LogEventSource /> > render html correctly > should render messages 1`]
<li data-key=\\"1\\" class=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"line-options\\" data-v-2e92daca=\\"\\" style=\\"display: none;\\">
<div class=\\"dropdown is-hoverable is-last is-top minimal\\" data-v-539164cb=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"dropdown-trigger\\" data-v-539164cb=\\"\\"><button class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\" data-v-539164cb=\\"\\"><span class=\\"icon\\" data-v-539164cb=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\" data-v-539164cb=\\"\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div class=\\"dropdown-trigger\\" data-v-539164cb=\\"\\"><button class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\" data-v-539164cb=\\"\\"><span class=\\"icon\\" data-v-539164cb=\\"\\"><svg viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\" data-v-539164cb=\\"\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div class=\\"dropdown-menu\\" id=\\"dropdown-menu\\" role=\\"menu\\" data-v-539164cb=\\"\\">
<div class=\\"dropdown-content\\" data-v-539164cb=\\"\\"><a class=\\"dropdown-item\\" href=\\"#1\\" data-v-2e92daca=\\"\\">
<div class=\\"level is-justify-content-start\\" data-v-2e92daca=\\"\\">
<div class=\\"level-left\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\"><svg viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-2e92daca=\\"\\">
<path fill=\\"currentColor\\" d=\\"M334.627 16H48v480h424V153.373ZM440 464H80V48h241.373L440 166.627Z\\"></path>
<path fill=\\"currentColor\\" d=\\"M239.861 152a95.861 95.861 0 1 0 53.624 175.284l68.03 68.029l22.627-22.626l-67.5-67.5A95.816 95.816 0 0 0 239.861 152ZM176 247.861a63.862 63.862 0 1 1 63.861 63.861A63.933 63.933 0 0 1 176 247.861Z\\"></path>
</svg></div>
@@ -79,7 +85,10 @@ exports[`<LogEventSource /> > render html correctly > should render messages 1`]
</div>
</div>
</div>
<div class=\\"line\\" data-v-2e92daca=\\"\\"><span class=\\"date\\" data-v-2e92daca=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" data-v-2e92daca=\\"\\">06/12/2019 10:55:42 AM</time></span><span class=\\"text\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">This is a message.</span></div>
<div class=\\"columns is-1 is-variable\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\">06/12/2019 10:55:42 AM</time></div>
<div class=\\"text column\\" data-v-a49e52d4=\\"\\">This is a message.</div>
</div>
</li>
</ul>"
`;
@@ -89,12 +98,12 @@ exports[`<LogEventSource /> > render html correctly > should render messages wit
<li data-key=\\"1\\" class=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"line-options\\" data-v-2e92daca=\\"\\" style=\\"display: none;\\">
<div class=\\"dropdown is-hoverable is-last is-top minimal\\" data-v-539164cb=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"dropdown-trigger\\" data-v-539164cb=\\"\\"><button class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\" data-v-539164cb=\\"\\"><span class=\\"icon\\" data-v-539164cb=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\" data-v-539164cb=\\"\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div class=\\"dropdown-trigger\\" data-v-539164cb=\\"\\"><button class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\" data-v-539164cb=\\"\\"><span class=\\"icon\\" data-v-539164cb=\\"\\"><svg viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\" data-v-539164cb=\\"\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div class=\\"dropdown-menu\\" id=\\"dropdown-menu\\" role=\\"menu\\" data-v-539164cb=\\"\\">
<div class=\\"dropdown-content\\" data-v-539164cb=\\"\\"><a class=\\"dropdown-item\\" href=\\"#1\\" data-v-2e92daca=\\"\\">
<div class=\\"level is-justify-content-start\\" data-v-2e92daca=\\"\\">
<div class=\\"level-left\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\"><svg viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-2e92daca=\\"\\">
<path fill=\\"currentColor\\" d=\\"M334.627 16H48v480h424V153.373ZM440 464H80V48h241.373L440 166.627Z\\"></path>
<path fill=\\"currentColor\\" d=\\"M239.861 152a95.861 95.861 0 1 0 53.624 175.284l68.03 68.029l22.627-22.626l-67.5-67.5A95.816 95.816 0 0 0 239.861 152ZM176 247.861a63.862 63.862 0 1 1 63.861 63.861A63.933 63.933 0 0 1 176 247.861Z\\"></path>
</svg></div>
@@ -107,7 +116,10 @@ exports[`<LogEventSource /> > render html correctly > should render messages wit
</div>
</div>
</div>
<div class=\\"line\\" data-v-2e92daca=\\"\\"><span class=\\"date\\" data-v-2e92daca=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" data-v-2e92daca=\\"\\">06/12/2019 10:55:42 AM</time></span><span class=\\"text\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\"><span style=\\"color:#000\\">black<span style=\\"color:#AAA\\">white</span></span></span></div>
<div class=\\"columns is-1 is-variable\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\">06/12/2019 10:55:42 AM</time></div>
<div class=\\"text column\\" data-v-a49e52d4=\\"\\"><span style=\\"color:#000\\">black<span style=\\"color:#AAA\\">white</span></span></div>
</div>
</li>
</ul>"
`;
@@ -115,14 +127,14 @@ exports[`<LogEventSource /> > render html correctly > should render messages wit
exports[`<LogEventSource /> > render html correctly > should render messages with filter 1`] = `
"<ul class=\\"events medium\\" data-v-2e92daca=\\"\\">
<li data-key=\\"2\\" class=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"line-options\\" data-v-2e92daca=\\"\\" style=\\"display: none;\\">
<div class=\\"line-options\\" data-v-2e92daca=\\"\\">
<div class=\\"dropdown is-hoverable is-last is-top minimal\\" data-v-539164cb=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"dropdown-trigger\\" data-v-539164cb=\\"\\"><button class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\" data-v-539164cb=\\"\\"><span class=\\"icon\\" data-v-539164cb=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\" data-v-539164cb=\\"\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div class=\\"dropdown-trigger\\" data-v-539164cb=\\"\\"><button class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\" data-v-539164cb=\\"\\"><span class=\\"icon\\" data-v-539164cb=\\"\\"><svg viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\" data-v-539164cb=\\"\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div class=\\"dropdown-menu\\" id=\\"dropdown-menu\\" role=\\"menu\\" data-v-539164cb=\\"\\">
<div class=\\"dropdown-content\\" data-v-539164cb=\\"\\"><a class=\\"dropdown-item\\" href=\\"#2\\" data-v-2e92daca=\\"\\">
<div class=\\"level is-justify-content-start\\" data-v-2e92daca=\\"\\">
<div class=\\"level-left\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\"><svg viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-2e92daca=\\"\\">
<path fill=\\"currentColor\\" d=\\"M334.627 16H48v480h424V153.373ZM440 464H80V48h241.373L440 166.627Z\\"></path>
<path fill=\\"currentColor\\" d=\\"M239.861 152a95.861 95.861 0 1 0 53.624 175.284l68.03 68.029l22.627-22.626l-67.5-67.5A95.816 95.816 0 0 0 239.861 152ZM176 247.861a63.862 63.862 0 1 1 63.861 63.861A63.933 63.933 0 0 1 176 247.861Z\\"></path>
</svg></div>
@@ -135,7 +147,10 @@ exports[`<LogEventSource /> > render html correctly > should render messages wit
</div>
</div>
</div>
<div class=\\"line\\" data-v-2e92daca=\\"\\"><span class=\\"date\\" data-v-2e92daca=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" data-v-2e92daca=\\"\\">06/12/2019 10:55:42 AM</time></span><span class=\\"text\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\"><mark>test</mark> bar</span></div>
<div class=\\"columns is-1 is-variable\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\">06/12/2019 10:55:42 AM</time></div>
<div class=\\"text column\\" data-v-a49e52d4=\\"\\"><mark>test</mark> bar</div>
</div>
</li>
</ul>"
`;
@@ -145,12 +160,12 @@ exports[`<LogEventSource /> > render html correctly > should render messages wit
<li data-key=\\"1\\" class=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"line-options\\" data-v-2e92daca=\\"\\" style=\\"display: none;\\">
<div class=\\"dropdown is-hoverable is-last is-top minimal\\" data-v-539164cb=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"dropdown-trigger\\" data-v-539164cb=\\"\\"><button class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\" data-v-539164cb=\\"\\"><span class=\\"icon\\" data-v-539164cb=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\" data-v-539164cb=\\"\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div class=\\"dropdown-trigger\\" data-v-539164cb=\\"\\"><button class=\\"button\\" aria-haspopup=\\"true\\" aria-controls=\\"dropdown-menu\\" data-v-539164cb=\\"\\"><span class=\\"icon\\" data-v-539164cb=\\"\\"><svg viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\" data-v-539164cb=\\"\\"><path fill=\\"currentColor\\" d=\\"M12 16a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2m0-6a2 2 0 0 1 2 2a2 2 0 0 1-2 2a2 2 0 0 1-2-2a2 2 0 0 1 2-2Z\\"></path></svg></span></button></div>
<div class=\\"dropdown-menu\\" id=\\"dropdown-menu\\" role=\\"menu\\" data-v-539164cb=\\"\\">
<div class=\\"dropdown-content\\" data-v-539164cb=\\"\\"><a class=\\"dropdown-item\\" href=\\"#1\\" data-v-2e92daca=\\"\\">
<div class=\\"level is-justify-content-start\\" data-v-2e92daca=\\"\\">
<div class=\\"level-left\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\"><svg preserveAspectRatio=\\"xMidYMid meet\\" viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-2e92daca=\\"\\">
<div class=\\"level-item\\" data-v-2e92daca=\\"\\"><svg viewBox=\\"0 0 512 512\\" width=\\"1.2em\\" height=\\"1.2em\\" class=\\"mr-4\\" data-v-2e92daca=\\"\\">
<path fill=\\"currentColor\\" d=\\"M334.627 16H48v480h424V153.373ZM440 464H80V48h241.373L440 166.627Z\\"></path>
<path fill=\\"currentColor\\" d=\\"M239.861 152a95.861 95.861 0 1 0 53.624 175.284l68.03 68.029l22.627-22.626l-67.5-67.5A95.816 95.816 0 0 0 239.861 152ZM176 247.861a63.862 63.862 0 1 1 63.861 63.861A63.933 63.933 0 0 1 176 247.861Z\\"></path>
</svg></div>
@@ -163,7 +178,10 @@ exports[`<LogEventSource /> > render html correctly > should render messages wit
</div>
</div>
</div>
<div class=\\"line\\" data-v-2e92daca=\\"\\"><span class=\\"date\\" data-v-2e92daca=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" data-v-2e92daca=\\"\\">06/12/2019 10:55:42 AM</time></span><span class=\\"text\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">&lt;test&gt;foo bar&lt;/test&gt;</span></div>
<div class=\\"columns is-1 is-variable\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\">06/12/2019 10:55:42 AM</time></div>
<div class=\\"text column\\" data-v-a49e52d4=\\"\\">&lt;test&gt;foo bar&lt;/test&gt;</div>
</div>
</li>
</ul>"
`;

View File

@@ -46,10 +46,10 @@ const store = useContainerStore();
const route = useRoute();
const { visibleContainers, allContainersById } = storeToRefs(store);
const showNav = ref(false);
let showNav = $ref(false);
watch(route, () => {
showNav.value = false;
showNav = false;
});
</script>
<style scoped lang="scss">

View File

@@ -16,7 +16,12 @@
<div class="is-scrollbar-notification">
<transition name="fade">
<button class="button" :class="hasMore ? 'has-more' : ''" @click="scrollToBottom()" v-show="paused">
<button
class="button has-boxshadow"
:class="hasMore ? 'has-more' : ''"
@click="scrollToBottom()"
v-show="paused"
>
<mdi-light-chevron-double-down />
</button>
</transition>
@@ -109,18 +114,18 @@ section {
button {
position: fixed;
bottom: 30px;
background-color: var(--secondary-color);
transition: background-color 1s ease-out;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
background-color: var(--primary-color);
transition: background-color 0.24s ease-out;
border: none !important;
color: #222;
color: #eee;
&.has-more {
background-color: var(--primary-color);
background-color: var(--secondary-color);
animation-name: bounce;
animation-duration: 1000ms;
animation-fill-mode: both;
color: #fff;
color: #222;
}
}
}

View File

@@ -22,26 +22,18 @@
</template>
<script lang="ts" setup>
import hotkeys from "hotkeys-js";
const input = ref<HTMLInputElement>();
const { searchFilter, showSearch, resetSearch } = useSearchFilter();
onMounted(() => {
hotkeys("command+f, ctrl+f", (event, handler) => {
onKeyStroke("f", (e) => {
if (e.ctrlKey || e.metaKey) {
showSearch.value = true;
nextTick(() => input.value?.focus() || input.value?.select());
event.preventDefault();
});
hotkeys("esc", () => resetSearch());
e.preventDefault();
}
});
onUnmounted(() => {
searchFilter.value = "";
showSearch.value = false;
hotkeys.unbind("command+f, ctrl+f");
hotkeys.unbind("esc");
});
onUnmounted(() => resetSearch());
</script>
<style lang="scss" scoped>

View File

@@ -9,7 +9,7 @@
</router-link>
</div>
<div class="column is-narrow has-text-right px-1">
<button class="button is-rounded" @click="$emit('search')" title="$t('tooltip.search')">
<button class="button is-rounded" @click="$emit('search')" :title="$t('tooltip.search')">
<span class="icon">
<mdi-light-magnify />
</span>
@@ -40,7 +40,7 @@
class="icon is-small"
@click.stop.prevent="store.appendActiveContainer(item)"
v-show="!activeContainersById[item.id]"
title="$t('tooltip.pin-column')"
:title="$t('tooltip.pin-column')"
>
<cil-columns />
</span>

View File

@@ -8,7 +8,7 @@ import {
DockerEventLogEntry,
SkippedLogsEntry,
} from "@/models/LogEntry";
import { type Container } from "@/types/Container";
import { Container } from "@/models/Container";
function parseMessage(data: string): LogEntry<string | JSONObject> {
const e = JSON.parse(data) as LogEvent;
@@ -16,8 +16,8 @@ function parseMessage(data: string): LogEntry<string | JSONObject> {
}
export function useLogStream(container: ComputedRef<Container>) {
let messages = $ref<LogEntry<string | JSONObject>[]>([]);
let buffer = $ref<LogEntry<string | JSONObject>[]>([]);
let messages: LogEntry<string | JSONObject>[] = $ref([]);
let buffer: LogEntry<string | JSONObject>[] = $ref([]);
const scrollingPaused = $ref(inject("scrollingPaused") as Ref<boolean>);
function flushNow() {
@@ -37,7 +37,7 @@ export function useLogStream(container: ComputedRef<Container>) {
} else {
messages.push(...buffer);
buffer = [];
messages.splice(0, messages.length - config.maxLogs);
messages = messages.slice(-config.maxLogs);
}
} else {
messages.push(...buffer);
@@ -117,8 +117,9 @@ export function useLogStream(container: ComputedRef<Container>) {
watch(
() => container.value.id,
() => connect()
() => connect(),
{ immediate: true }
);
return $$({ connect, messages, loadOlderLogs });
return { ...$$({ messages }), loadOlderLogs };
}

View File

@@ -26,7 +26,7 @@ export function useSearchFilter() {
function filteredMessages(messages: Ref<LogEntry<string | JSONObject>[]>) {
return computed(() => {
if (debouncedSearchFilter.value) {
if (debouncedSearchFilter.value && showSearch.value) {
try {
return messages.value.filter((d) => {
if (d instanceof SimpleLogEntry) {
@@ -34,7 +34,7 @@ export function useSearchFilter() {
} else if (d instanceof ComplexLogEntry) {
return matchRecord(d.message, regex.value);
}
throw new Error("Unknown message type");
return false;
});
} catch (e) {
if (e instanceof SyntaxError) {
@@ -49,9 +49,9 @@ export function useSearchFilter() {
});
}
function markSearch(log: string): string;
function markSearch(log: { toString(): string }): string;
function markSearch(log: string[]): string[];
function markSearch(log: string | string[]) {
function markSearch(log: { toString(): string } | string[]) {
if (!debouncedSearchFilter.value) {
return log;
}

View File

@@ -42,10 +42,9 @@
</template>
<script lang="ts" setup>
// @ts-ignore - splitpanes types are not available
import { Splitpanes, Pane } from "splitpanes";
import { useProgrammatic } from "@oruga-ui/oruga-next";
import hotkeys from "hotkeys-js";
import FuzzySearchModal from "@/components/FuzzySearchModal.vue";
const collapseNav = ref(false);
@@ -53,20 +52,19 @@ const { oruga } = useProgrammatic();
const { authorizationNeeded } = config;
const containerStore = useContainerStore();
const { activeContainers, visibleContainers } = storeToRefs(containerStore);
onMounted(() => {
hotkeys("command+k, ctrl+k", (event, handler) => {
event.preventDefault();
showFuzzySearch();
});
});
watchEffect(() => {
setTitle(`${visibleContainers.value.length} containers`);
});
onKeyStroke("k", (e) => {
if (e.ctrlKey || e.metaKey) {
showFuzzySearch();
e.preventDefault();
}
});
function showFuzzySearch() {
oruga.modal.open({
// parent: this,

View File

@@ -0,0 +1,31 @@
import type { ContainerStat, ContainerState } from "@/types/Container";
import type { UseThrottledRefHistoryReturn } from "@vueuse/core";
import { Ref } from "vue";
type Stat = Omit<ContainerStat, "id">;
export class Container {
public stat: Ref<Stat>;
private readonly throttledStatHistory: UseThrottledRefHistoryReturn<Stat, Stat>;
constructor(
public readonly id: string,
public readonly created: number,
public readonly image: string,
public readonly name: string,
public readonly command: string,
public status: string,
public state: ContainerState
) {
this.stat = ref({ cpu: 0, memory: 0, memoryUsage: 0 });
this.throttledStatHistory = useThrottledRefHistory(this.stat, { capacity: 300, deep: true, throttle: 1000 });
}
public getStatHistory() {
return unref(this.throttledStatHistory.history);
}
public getLastStat() {
return unref(this.throttledStatHistory.last);
}
}

View File

@@ -54,8 +54,8 @@
class="input"
type="text"
:placeholder="$t('placeholder.search-containers')"
v-model="search"
@keyup.esc="search = null"
v-model="query"
@keyup.esc="query = ''"
@keyup.enter="onEnter()"
/>
<span class="icon is-left">
@@ -63,13 +63,13 @@
</span>
</p>
</div>
<p class="panel-tabs" v-if="!search">
<p class="panel-tabs" v-if="query === ''">
<a :class="{ 'is-active': sort === 'running' }" @click="sort = 'running'">{{ $t("label.running") }}</a>
<a :class="{ 'is-active': sort === 'all' }" @click="sort = 'all'">{{ $t("label.all") }}</a>
</p>
<router-link
:to="{ name: 'container-id', params: { id: item.id } }"
v-for="item in results.slice(0, 10)"
v-for="item in data.slice(0, 10)"
:key="item.id"
class="panel-block"
>
@@ -86,54 +86,59 @@
</template>
<script lang="ts" setup>
import fuzzysort from "fuzzysort";
import SearchIcon from "~icons/mdi-light/magnify";
import { useFuse } from "@vueuse/integrations/useFuse";
const { base, version, secured } = config;
const containerStore = useContainerStore();
const { containers } = storeToRefs(containerStore);
const router = useRouter();
const sort = ref("running");
const search = ref();
const sort = $ref("running");
const query = ref("");
const results = computed(() => {
if (search.value) {
return fuzzysort.go(search.value, containers.value, { key: "name" }).map((i) => i.obj);
const mostRecentContainers = $computed(() => [...containers.value].sort((a, b) => b.created - a.created));
const runningContainers = $computed(() => mostRecentContainers.filter((c) => c.state === "running"));
const { results } = useFuse(query, containers, {
fuseOptions: { keys: ["name"] },
matchAllWhenSearchEmpty: false,
});
const data = computed(() => {
if (results.value.length) {
return results.value.map(({ item }) => item);
}
switch (sort.value) {
switch (sort) {
case "all":
return mostRecentContainers.value;
return mostRecentContainers;
case "running":
return runningContainers.value;
return runningContainers;
default:
throw `Invalid sort order: ${sort.value}`;
throw `Invalid sort order: ${sort}`;
}
});
const mostRecentContainers = computed(() => [...containers.value].sort((a, b) => b.created - a.created));
const runningContainers = computed(() => mostRecentContainers.value.filter((c) => c.state === "running"));
const totalCpu = ref(0);
let totalCpu = $ref(0);
useIntervalFn(
() => {
totalCpu.value = runningContainers.value.reduce((acc, c) => acc + (c.stat?.cpu ?? 0), 0);
totalCpu = runningContainers.reduce((acc, c) => acc + (c.stat?.cpu ?? 0), 0);
},
1000,
{ immediate: true }
);
const totalMem = ref(0);
let totalMem = $ref(0);
useIntervalFn(
() => {
totalMem.value = runningContainers.value.reduce((acc, c) => acc + (c.stat?.memoryUsage ?? 0), 0);
totalMem = runningContainers.reduce((acc, c) => acc + (c.stat?.memoryUsage ?? 0), 0);
},
1000,
{ immediate: true }
);
function onEnter() {
if (results.value.length == 1) {
const [item] = results.value;
if (data.value.length > 0) {
const item = data.value[0];
router.push({ name: "container-id", params: { id: item.id } });
}
}

View File

@@ -57,7 +57,7 @@ setTitle(t("title.login"));
let error = $ref(false);
let username = $ref("");
let password = $ref("");
let form = $ref();
let form: HTMLFormElement = $ref();
async function onLogin() {
const response = await fetch(`${config.base}/api/validateCredentials`, {

View File

@@ -54,8 +54,8 @@
class="input"
type="text"
:placeholder="$t('placeholder.search-containers')"
v-model="search"
@keyup.esc="search = null"
v-model="query"
@keyup.esc="query = ''"
@keyup.enter="onEnter()"
/>
<span class="icon is-left">
@@ -63,13 +63,13 @@
</span>
</p>
</div>
<p class="panel-tabs" v-if="!search">
<p class="panel-tabs" v-if="query === ''">
<a :class="{ 'is-active': sort === 'running' }" @click="sort = 'running'">{{ $t("label.running") }}</a>
<a :class="{ 'is-active': sort === 'all' }" @click="sort = 'all'">{{ $t("label.all") }}</a>
</p>
<router-link
:to="{ name: 'container-id', params: { id: item.id } }"
v-for="item in results.slice(0, 10)"
v-for="item in data.slice(0, 10)"
:key="item.id"
class="panel-block"
>
@@ -86,54 +86,59 @@
</template>
<script lang="ts" setup>
import fuzzysort from "fuzzysort";
import SearchIcon from "~icons/mdi-light/magnify";
import { useFuse } from "@vueuse/integrations/useFuse";
const { base, version, secured } = config;
const containerStore = useContainerStore();
const { containers } = storeToRefs(containerStore);
const router = useRouter();
const sort = ref("running");
const search = ref();
const sort = $ref("running");
const query = ref("");
const results = computed(() => {
if (search.value) {
return fuzzysort.go(search.value, containers.value, { key: "name" }).map((i) => i.obj);
const mostRecentContainers = $computed(() => [...containers.value].sort((a, b) => b.created - a.created));
const runningContainers = $computed(() => mostRecentContainers.filter((c) => c.state === "running"));
const { results } = useFuse(query, containers, {
fuseOptions: { keys: ["name"] },
matchAllWhenSearchEmpty: false,
});
const data = computed(() => {
if (results.value.length) {
return results.value.map(({ item }) => item);
}
switch (sort.value) {
switch (sort) {
case "all":
return mostRecentContainers.value;
return mostRecentContainers;
case "running":
return runningContainers.value;
return runningContainers;
default:
throw `Invalid sort order: ${sort.value}`;
throw `Invalid sort order: ${sort}`;
}
});
const mostRecentContainers = computed(() => [...containers.value].sort((a, b) => b.created - a.created));
const runningContainers = computed(() => mostRecentContainers.value.filter((c) => c.state === "running"));
const totalCpu = ref(0);
let totalCpu = $ref(0);
useIntervalFn(
() => {
totalCpu.value = runningContainers.value.reduce((acc, c) => acc + (c.stat?.cpu ?? 0), 0);
totalCpu = runningContainers.reduce((acc, c) => acc + (c.stat?.cpu ?? 0), 0);
},
1000,
{ immediate: true }
);
const totalMem = ref(0);
let totalMem = $ref(0);
useIntervalFn(
() => {
totalMem.value = runningContainers.value.reduce((acc, c) => acc + (c.stat?.memoryUsage ?? 0), 0);
totalMem = runningContainers.reduce((acc, c) => acc + (c.stat?.memoryUsage ?? 0), 0);
},
1000,
{ immediate: true }
);
function onEnter() {
if (results.value.length == 1) {
const [item] = results.value;
if (data.value.length > 0) {
const item = data.value[0];
router.push({ name: "container-id", params: { id: item.id } });
}
}

View File

@@ -57,7 +57,7 @@ setTitle(t("title.login"));
let error = $ref(false);
let username = $ref("");
let password = $ref("");
let form = $ref();
let form: HTMLFormElement = $ref();
async function onLogin() {
const response = await fetch(`${config.base}/api/validateCredentials`, {

View File

@@ -1,14 +1,11 @@
import { acceptHMRUpdate, defineStore } from "pinia";
import { ref, Ref, computed } from "vue";
import { showAllContainers } from "@/composables/settings";
import config from "@/stores/config";
import type { Container, ContainerStat } from "@/types/Container";
import { watchOnce } from "@vueuse/core";
import { Ref, UnwrapNestedRefs } from "vue";
import type { ContainerJson, ContainerStat } from "@/types/Container";
import { Container } from "@/models/Container";
export const useContainerStore = defineStore("container", () => {
const containers = ref<Container[]>([]);
const activeContainerIds = ref<string[]>([]);
const containers: Ref<Container[]> = ref([]);
const activeContainerIds: Ref<string[]> = ref([]);
const allContainersById = computed(() =>
containers.value.reduce((acc, container) => {
@@ -25,33 +22,36 @@ export const useContainerStore = defineStore("container", () => {
const activeContainers = computed(() => activeContainerIds.value.map((id) => allContainersById.value[id]));
const es = new EventSource(`${config.base}/api/events/stream`);
es.addEventListener(
"containers-changed",
(e: Event) => (containers.value = JSON.parse((e as MessageEvent).data)),
false
es.addEventListener("containers-changed", (e: Event) =>
setContainers(JSON.parse((e as MessageEvent).data) as ContainerJson[])
);
es.addEventListener(
"container-stat",
(e) => {
const stat = JSON.parse((e as MessageEvent).data) as ContainerStat;
const container = allContainersById.value[stat.id];
if (container) {
container.stat = stat;
es.addEventListener("container-stat", (e) => {
const stat = JSON.parse((e as MessageEvent).data) as ContainerStat;
const container = allContainersById.value[stat.id] as unknown as UnwrapNestedRefs<Container>;
if (container) {
const { id, ...rest } = stat;
container.stat = rest;
}
});
es.addEventListener("container-die", (e) => {
const event = JSON.parse((e as MessageEvent).data) as { actorId: string };
const container = allContainersById.value[event.actorId];
if (container) {
container.state = "dead";
}
});
const setContainers = (newContainers: ContainerJson[]) => {
containers.value = newContainers.map((c) => {
const existing = allContainersById.value[c.id];
if (existing) {
existing.status = c.status;
existing.state = c.state;
return existing;
}
},
false
);
es.addEventListener(
"container-die",
(e) => {
const event = JSON.parse((e as MessageEvent).data) as { actorId: string };
const container = allContainersById.value[event.actorId];
if (container) {
container.state = "dead";
}
},
false
);
return new Container(c.id, c.created, c.image, c.name, c.command, c.status, c.state);
});
};
const currentContainer = (id: Ref<string>) => computed(() => allContainersById.value[id.value]);
const appendActiveContainer = ({ id }: Container) => activeContainerIds.value.push(id);

View File

@@ -75,10 +75,10 @@ $light-toolbar-color: rgba($grey-darker, 0.7);
--border-color: #{$grey-lighter};
--border-hover-color: var(--secondary-color);
--logo-color: #{$grey-darker};
--logo-color: var(--secondary-color);
--primary-color: #{$turquoise};
--secondary-color: #d8f0ca;
--secondary-color: rgb(249 115 22);
--body-background-color: #{$white-bis};
--action-toolbar-background-color: #{$light-toolbar-color};
@@ -159,6 +159,12 @@ html.has-custom-scrollbars {
}
}
@media screen and (max-device-width: 480px) {
body {
-webkit-text-size-adjust: 100%;
}
}
.splitpanes__splitter {
z-index: 99;
}
@@ -176,3 +182,27 @@ html.has-custom-scrollbars {
.button .button-wrapper > span {
display: contents;
}
.has-dropshadow {
filter: drop-shadow(0 1px 2px rgb(0 0 0 / 0.1)) drop-shadow(0 1px 1px rgb(0 0 0 / 0.06));
}
.has-boxshadow {
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
}
mark {
border-radius: 2px;
background-color: var(--secondary-color);
animation: pops 200ms ease-out;
display: inline-block;
}
@keyframes pops {
0% {
transform: scale(1.5);
}
100% {
transform: scale(1.05);
}
}

View File

@@ -1,17 +1,18 @@
export interface Container {
readonly id: string;
readonly created: number;
readonly image: string;
readonly name: string;
readonly status: string;
readonly command: string;
state: "created" | "running" | "exited" | "dead" | "paused" | "restarting";
stat?: ContainerStat;
}
export interface ContainerStat {
readonly id: string;
readonly cpu: number;
readonly memory: number;
readonly memoryUsage: number;
}
export type ContainerJson = {
readonly id: string;
readonly created: number;
readonly image: string;
readonly name: string;
readonly command: string;
readonly status: string;
readonly state: ContainerState;
};
export type ContainerState = "created" | "running" | "exited" | "dead" | "paused" | "restarting";

1
assets/types/Point.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
type Point<T> = { x: number; y: number; value?: T };

View File

@@ -1,4 +1,4 @@
import { Container } from "@/types/Container";
import { Container } from "@/models/Container";
import { useStorage } from "@vueuse/core";
import { computed, ComputedRef } from "vue";

17
docker/calculation.go Normal file
View File

@@ -0,0 +1,17 @@
package docker
import "github.com/docker/docker/api/types"
func calculateMemUsageUnixNoCache(mem types.MemoryStats) float64 {
// re implementation of the docker calculation
// https://github.com/docker/cli/blob/53f8ed4bec07084db4208f55987a2ea94b7f01d6/cli/command/container/stats_helpers.go#L227-L249
// cgroup v1
if v, isCGroup := mem.Stats["total_inactive_file"]; isCGroup && v < mem.Usage {
return float64(mem.Usage - v)
}
// cgroup v2
if v := mem.Stats["inactive_file"]; v < mem.Usage {
return float64(mem.Usage - v)
}
return float64(mem.Usage)
}

View File

@@ -0,0 +1,57 @@
package docker
import (
"github.com/docker/docker/api/types"
"github.com/stretchr/testify/assert"
"testing"
)
func Test_calculateMemUsageUnixNoCache(t *testing.T) {
type args struct {
mem types.MemoryStats
}
tests := []struct {
name string
args args
want float64
}{
{
name: "with cgroup v1",
args: args{
mem: types.MemoryStats{
Usage: 100,
Stats: map[string]uint64{
"total_inactive_file": 1,
},
},
},
want: 99,
},
{
name: "with cgroup v2",
args: args{
mem: types.MemoryStats{
Usage: 100,
Stats: map[string]uint64{
"inactive_file": 2,
},
},
},
want: 98,
},
{
name: "without cgroup",
args: args{
mem: types.MemoryStats{
Usage: 100,
},
},
want: 100,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, calculateMemUsageUnixNoCache(tt.args.mem), "calculateMemUsageUnixNoCache(%v)", tt.args.mem)
})
}
}

View File

@@ -147,7 +147,7 @@ func (d *dockerClient) ContainerStats(ctx context.Context, id string, stats chan
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(ncpus) * 100)
memUsage = int64(v.MemoryStats.Usage - v.MemoryStats.Stats["cache"])
memUsage = int64(calculateMemUsageUnixNoCache(v.MemoryStats))
memPercent = int64(float64(memUsage) / float64(v.MemoryStats.Limit) * 100)
)

View File

@@ -1,4 +1,4 @@
FROM cypress/included:10.8.0
FROM cypress/included:10.10.0
RUN apt install curl && curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -9,7 +9,7 @@ context("Dozzle default mode", { baseUrl: Cypress.env("DOZZLE_DEFAULT") }, () =>
cy.get("li.running", { timeout: 10000 }).removeDates().replaceSkippedElements().matchImage();
});
it("correct title", () => {
it("correct title is shown", () => {
cy.title().should("eq", "1 containers - Dozzle");
cy.get("li.running:first a").click();
@@ -17,9 +17,15 @@ context("Dozzle default mode", { baseUrl: Cypress.env("DOZZLE_DEFAULT") }, () =>
cy.title().should("include", "- Dozzle");
});
it("settings page", () => {
it("navigating to setting page works ", () => {
cy.get("a[href='/settings']").click();
cy.contains("About");
});
it("shortcut for fuzzy search works", () => {
cy.get("body").type("{ctrl}k");
cy.get("input[placeholder='Search containers (⌘ + k, ⌃k)']").should("be.visible");
});
});

View File

@@ -6,8 +6,8 @@
},
"license": "ISC",
"dependencies": {
"@frsource/cypress-plugin-visual-regression-diff": "^1.9.18",
"cypress": "^10.7.0",
"@frsource/cypress-plugin-visual-regression-diff": "^2.0.0",
"cypress": "^10.8.0",
"typescript": "^4.8.3"
}
}

42
e2e/pnpm-lock.yaml generated
View File

@@ -1,13 +1,13 @@
lockfileVersion: 5.4
specifiers:
'@frsource/cypress-plugin-visual-regression-diff': ^1.9.18
cypress: ^10.7.0
'@frsource/cypress-plugin-visual-regression-diff': ^2.0.0
cypress: ^10.8.0
typescript: ^4.8.3
dependencies:
'@frsource/cypress-plugin-visual-regression-diff': 1.9.18_cypress@10.7.0
cypress: 10.7.0
'@frsource/cypress-plugin-visual-regression-diff': 2.0.0_cypress@10.8.0
cypress: 10.8.0
typescript: 4.8.3
packages:
@@ -52,21 +52,21 @@ packages:
- supports-color
dev: false
/@frsource/cypress-plugin-visual-regression-diff/1.9.18_cypress@10.7.0:
resolution: {integrity: sha512-QS52M0V4UFqQHW85cKRoTKVtzYWQ0BA88+rDco3BMkzSpOQokwgZ7iYlnKJbLuePNLakbaoHWNWSDjNrG9OP8w==}
/@frsource/cypress-plugin-visual-regression-diff/2.0.0_cypress@10.8.0:
resolution: {integrity: sha512-hwMEZzD0tFbNNNbEjdF8q2ELpxhby25VQ665QUJy1soGPiGwY+rBDdktFg3EXVGs/Euq0Nj8hq/K18BWYoX10g==}
engines: {node: '>=10'}
peerDependencies:
cypress: '>=4.5.0'
dependencies:
cypress: 10.7.0
cypress: 10.8.0
move-file: 2.1.0
pixelmatch: 5.3.0
pngjs: 6.0.0
sharp: 0.30.7
dev: false
/@types/node/14.18.26:
resolution: {integrity: sha512-0b+utRBSYj8L7XAp0d+DX7lI4cSmowNaaTkk6/1SKzbKkG+doLuPusB9EOvzLJ8ahJSk03bTLIL6cWaEd4dBKA==}
/@types/node/14.18.29:
resolution: {integrity: sha512-LhF+9fbIX4iPzhsRLpK5H7iPdvW8L4IwGciXQIOEcuF62+9nw/VQVsOViAOOGxY3OlOKGLFv0sWwJXdwQeTn6A==}
dev: false
/@types/sinonjs__fake-timers/8.1.1:
@@ -81,7 +81,7 @@ packages:
resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==}
requiresBuild: true
dependencies:
'@types/node': 14.18.26
'@types/node': 14.18.29
dev: false
optional: true
@@ -232,8 +232,8 @@ packages:
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
dev: false
/ci-info/3.3.2:
resolution: {integrity: sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==}
/ci-info/3.4.0:
resolution: {integrity: sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug==}
dev: false
/clean-stack/2.2.0:
@@ -248,8 +248,8 @@ packages:
restore-cursor: 3.1.0
dev: false
/cli-table3/0.6.2:
resolution: {integrity: sha512-QyavHCaIC80cMivimWu4aWHilIpiDpfm3hGmqAmXVL1UsnbLuBSMd21hTX6VY4ZSDSM73ESLeF8TOYId3rBTbw==}
/cli-table3/0.6.3:
resolution: {integrity: sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==}
engines: {node: 10.* || >= 12.*}
dependencies:
string-width: 4.2.3
@@ -313,7 +313,7 @@ packages:
dev: false
/concat-map/0.0.1:
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
dev: false
/core-util-is/1.0.2:
@@ -329,15 +329,15 @@ packages:
which: 2.0.2
dev: false
/cypress/10.7.0:
resolution: {integrity: sha512-gTFvjrUoBnqPPOu9Vl5SBHuFlzx/Wxg/ZXIz2H4lzoOLFelKeF7mbwYUOzgzgF0oieU2WhJAestQdkgwJMMTvQ==}
/cypress/10.8.0:
resolution: {integrity: sha512-QVse0dnLm018hgti2enKMVZR9qbIO488YGX06nH5j3Dg1isL38DwrBtyrax02CANU6y8F4EJUuyW6HJKw1jsFA==}
engines: {node: '>=12.0.0'}
hasBin: true
requiresBuild: true
dependencies:
'@cypress/request': 2.88.10
'@cypress/xvfb': 1.2.4_supports-color@8.1.1
'@types/node': 14.18.26
'@types/node': 14.18.29
'@types/sinonjs__fake-timers': 8.1.1
'@types/sizzle': 2.3.3
arch: 2.2.0
@@ -348,7 +348,7 @@ packages:
chalk: 4.1.2
check-more-types: 2.24.0
cli-cursor: 3.1.0
cli-table3: 0.6.2
cli-table3: 0.6.3
commander: 5.1.0
common-tags: 1.8.2
dayjs: 1.11.5
@@ -665,7 +665,7 @@ packages:
resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==}
hasBin: true
dependencies:
ci-info: 3.3.2
ci-info: 3.4.0
dev: false
/is-fullwidth-code-point/3.0.0:
@@ -1269,7 +1269,7 @@ packages:
dev: false
/verror/1.10.0:
resolution: {integrity: sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=}
resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==}
engines: {'0': node >=0.6.0}
dependencies:
assert-plus: 1.0.0

2
go.mod
View File

@@ -5,7 +5,7 @@ require (
github.com/alexflint/go-arg v1.4.3
github.com/beme/abide v0.0.0-20190723115211-635a09831760
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v20.10.18+incompatible
github.com/docker/docker v20.10.20+incompatible
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/dustin/go-humanize v1.0.0

4
go.sum
View File

@@ -62,8 +62,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v20.10.18+incompatible h1:SN84VYXTBNGn92T/QwIRPlum9zfemfitN7pbsp26WSc=
github.com/docker/docker v20.10.18+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v20.10.20+incompatible h1:kH9tx6XO+359d+iAkumyKDc5Q1kOwPuAUaeri48nD6E=
github.com/docker/docker v20.10.20+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=

View File

@@ -27,7 +27,7 @@ button:
logout: Logout
login: Login
placeholder:
search-containers: Search Containers
search-containers: Search containers (⌘ + k, ⌃k)
settings:
display: Display
small-scrollbars: Use smaller scrollbars

View File

@@ -27,7 +27,7 @@ button:
logout: Cerrar la sesión
login: Iniciar sesión
placeholder:
search-containers: Buscar Contenedores
search-containers: Buscar contenedores (⌘ + K, CTRL + K)
settings:
display: Vista
small-scrollbars: Utilizar barras de desplazamiento más pequeñas

View File

@@ -27,7 +27,7 @@ button:
logout: Terminar sessão
login: Iniciar sessão
placeholder:
search-containers: Pesquisar Contentores
search-containers: Pesquisar contentores (⌘ + K, CTRL + K)
settings:
display: Visão
small-scrollbars: Usar barras de rolagem mais pequenas

View File

@@ -1,6 +1,6 @@
{
"name": "dozzle",
"version": "4.1.2",
"version": "4.2.2",
"description": "Realtime log viewer for docker containers. ",
"homepage": "https://github.com/amir20/dozzle#readme",
"bugs": {
@@ -22,57 +22,69 @@
"postinstall": "husky install"
},
"dependencies": {
"@iconify-json/carbon": "^1.1.7",
"@iconify-json/carbon": "^1.1.9",
"@iconify-json/cil": "^1.1.2",
"@iconify-json/mdi": "^1.1.32",
"@iconify-json/mdi": "^1.1.34",
"@iconify-json/mdi-light": "^1.1.2",
"@iconify-json/octicon": "^1.1.17",
"@intlify/vite-plugin-vue-i18n": "^6.0.1",
"@iconify-json/octicon": "^1.1.20",
"@oruga-ui/oruga-next": "^0.5.6",
"@oruga-ui/theme-bulma": "^0.2.7",
"@vitejs/plugin-vue": "3.1.0",
"@vue/compiler-sfc": "^3.2.39",
"@vueuse/core": "^9.2.0",
"@vueuse/router": "^9.2.0",
"@vueuse/core": "^9.3.1",
"@vueuse/integrations": "^9.3.1",
"@vueuse/router": "^9.3.1",
"ansi-to-html": "^0.7.2",
"bulma": "^0.9.4",
"d3-array": "^3.2.0",
"d3-ease": "^3.0.1",
"d3-scale": "^4.0.2",
"d3-selection": "^3.0.0",
"d3-shape": "^3.1.0",
"d3-transition": "^3.0.1",
"date-fns": "^2.29.3",
"fuzzysort": "^2.0.1",
"hotkeys-js": "^3.10.0",
"fuse.js": "^6.6.2",
"lodash.debounce": "^4.0.8",
"pinia": "^2.0.22",
"sass": "^1.54.9",
"semver": "^7.3.7",
"pinia": "^2.0.23",
"semver": "^7.3.8",
"splitpanes": "^3.1.1",
"typescript": "^4.8.3",
"unplugin-auto-import": "^0.11.2",
"unplugin-icons": "^0.14.9",
"unplugin-vue-components": "^0.22.7",
"vite": "3.1.1",
"vite-plugin-pages": "^0.26.0",
"vite-plugin-vue-layouts": "^0.7.0",
"vue": "^3.2.39",
"vue": "^3.2.41",
"vue-i18n": "^9.2.2",
"vue-router": "^4.1.5"
},
"devDependencies": {
"@intlify/vite-plugin-vue-i18n": "^6.0.3",
"@pinia/testing": "^0.0.14",
"@types/d3-array": "^3.0.3",
"@types/d3-ease": "^3.0.0",
"@types/d3-scale": "^4.0.2",
"@types/d3-selection": "^3.0.3",
"@types/d3-shape": "^3.1.0",
"@types/d3-transition": "^3.0.2",
"@types/lodash.debounce": "^4.0.7",
"@types/node": "^18.7.18",
"@types/node": "^18.11.2",
"@types/semver": "^7.3.12",
"@vue/test-utils": "^2.0.2",
"@vitejs/plugin-vue": "3.1.2",
"@vue/compiler-sfc": "^3.2.41",
"@vue/test-utils": "^2.1.0",
"c8": "^7.12.0",
"eventsourcemock": "^2.0.0",
"husky": "^8.0.1",
"jest-serializer-vue": "^2.0.2",
"jsdom": "^20.0.0",
"jsdom": "^20.0.1",
"lint-staged": "^13.0.3",
"npm-run-all": "^4.1.5",
"prettier": "^2.7.1",
"release-it": "^15.4.2",
"release-it": "^15.5.0",
"sass": "^1.55.0",
"ts-node": "^10.9.1",
"vitest": "^0.23.2",
"vue-tsc": "^0.40.13"
"typescript": "^4.8.4",
"unplugin-auto-import": "^0.11.2",
"unplugin-icons": "^0.14.12",
"unplugin-vue-components": "^0.22.8",
"vite": "3.1.8",
"vite-plugin-pages": "^0.27.0",
"vite-plugin-vue-layouts": "^0.7.0",
"vitest": "^0.24.3",
"vue-tsc": "^1.0.8"
},
"lint-staged": {
"*.{js,vue,css}": [

1610
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff