Compare commits
107 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae3283adda | ||
|
|
145158a925 | ||
|
|
e46e1a0814 | ||
|
|
7a624cd065 | ||
|
|
fe7c8743b9 | ||
|
|
ccc1aae78e | ||
|
|
935e17c200 | ||
|
|
63bb1f24a3 | ||
|
|
cdc8189ad1 | ||
|
|
f6daab2111 | ||
|
|
7ed461e6ef | ||
|
|
15636348b5 | ||
|
|
a370ad48d5 | ||
|
|
8900631b20 | ||
|
|
fa3ef24999 | ||
|
|
deaf58c47b | ||
|
|
1d4573a657 | ||
|
|
84300fc734 | ||
|
|
2b9873e76b | ||
|
|
c589c599fc | ||
|
|
2702e80314 | ||
|
|
bb1dda41ff | ||
|
|
ddafd95b69 | ||
|
|
d6e87a7137 | ||
|
|
54eca89c4e | ||
|
|
fbe34d7377 | ||
|
|
e2b497f158 | ||
|
|
ca9fcf8e1d | ||
|
|
4fb012c0e4 | ||
|
|
b77448f2a9 | ||
|
|
3bc921c8d7 | ||
|
|
54c08cf79b | ||
|
|
199c51722c | ||
|
|
379af164dd | ||
|
|
dc4ba652b4 | ||
|
|
0e8561a1c5 | ||
|
|
60ec928a8e | ||
|
|
26f3ab65a7 | ||
|
|
0cc209484d | ||
|
|
f09a7815af | ||
|
|
70b573cec0 | ||
|
|
0d79196eda | ||
|
|
8eb4e05055 | ||
|
|
cd793a9b53 | ||
|
|
fad75e2f5b | ||
|
|
d6710453e5 | ||
|
|
3a8c9cbcfa | ||
|
|
8586fee5ab | ||
|
|
8ccae473ed | ||
|
|
02434afef3 | ||
|
|
192ee98dd1 | ||
|
|
79e9dc7be6 | ||
|
|
19fe7e8d18 | ||
|
|
e4ecfca5be | ||
|
|
f37925b637 | ||
|
|
822b783063 | ||
|
|
7dcf1d4f15 | ||
|
|
d0af303d6f | ||
|
|
fd31d394a5 | ||
|
|
1c0af19c88 | ||
|
|
9d77613ee9 | ||
|
|
62f747797c | ||
|
|
fccb7fc2d4 | ||
|
|
7fecbdd000 | ||
|
|
46a2f2b810 | ||
|
|
e9dcdda64d | ||
|
|
67a0644c37 | ||
|
|
21fc2ce2fd | ||
|
|
20425bf6b1 | ||
|
|
828c288570 | ||
|
|
cde2589755 | ||
|
|
5fb2f452e2 | ||
|
|
6aea252d3e | ||
|
|
7337dcb5d4 | ||
|
|
deeb5fc100 | ||
|
|
5b15a0b29d | ||
|
|
e2ad2e0193 | ||
|
|
5b48426fc1 | ||
|
|
a24947ab3b | ||
|
|
8926b451d0 | ||
|
|
afd37d3455 | ||
|
|
4f84beb835 | ||
|
|
d54d894a66 | ||
|
|
7c854d31a7 | ||
|
|
4c2caad4a0 | ||
|
|
9684fd978b | ||
|
|
a79a3f680f | ||
|
|
1e18423b04 | ||
|
|
82b208c0bf | ||
|
|
8a3e9504a4 | ||
|
|
d8748c6e27 | ||
|
|
af8192fbc0 | ||
|
|
3492587d63 | ||
|
|
b8c522021d | ||
|
|
5850c319f2 | ||
|
|
afbed43185 | ||
|
|
d083430c73 | ||
|
|
79f553ff0c | ||
|
|
a02551f5ec | ||
|
|
7c486d57fc | ||
|
|
41acf28be9 | ||
|
|
21e5f4fc56 | ||
|
|
2d54cbba9c | ||
|
|
88844c895c | ||
|
|
7aa7f42c52 | ||
|
|
59f4b0da4f | ||
|
|
e99e6ebd49 |
2
.github/.kodiak.toml
vendored
2
.github/.kodiak.toml
vendored
@@ -1,2 +0,0 @@
|
|||||||
version = 1
|
|
||||||
merge.notify_on_conflict = false
|
|
||||||
47
.github/dependabot.yml
vendored
47
.github/dependabot.yml
vendored
@@ -1,47 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
directory: "/"
|
|
||||||
labels:
|
|
||||||
- "dependencies"
|
|
||||||
- "automerge"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
- package-ecosystem: "docker"
|
|
||||||
directory: "/"
|
|
||||||
labels:
|
|
||||||
- "dependencies"
|
|
||||||
- "automerge"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
- package-ecosystem: gomod
|
|
||||||
directory: "/"
|
|
||||||
labels:
|
|
||||||
- "gomod"
|
|
||||||
- "dependencies"
|
|
||||||
- "automerge"
|
|
||||||
schedule:
|
|
||||||
interval: daily
|
|
||||||
- package-ecosystem: npm
|
|
||||||
directory: "/"
|
|
||||||
labels:
|
|
||||||
- "npm"
|
|
||||||
- "dependencies"
|
|
||||||
- "automerge"
|
|
||||||
schedule:
|
|
||||||
interval: daily
|
|
||||||
- package-ecosystem: npm
|
|
||||||
directory: "/e2e"
|
|
||||||
labels:
|
|
||||||
- "npm"
|
|
||||||
- "dependencies"
|
|
||||||
- "automerge"
|
|
||||||
schedule:
|
|
||||||
interval: daily
|
|
||||||
- package-ecosystem: "docker"
|
|
||||||
directory: "/e2e"
|
|
||||||
labels:
|
|
||||||
- "dependencies"
|
|
||||||
- "automerge"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
12
.github/workflows/deploy.yml
vendored
12
.github/workflows/deploy.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v2.2.2
|
uses: pnpm/action-setup@v2.2.4
|
||||||
with:
|
with:
|
||||||
version: 6.20.1
|
version: 6.20.1
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -27,7 +27,7 @@ jobs:
|
|||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.18.x
|
go-version: 1.19.x
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
- name: Run Go Tests with Coverage
|
- name: Run Go Tests with Coverage
|
||||||
@@ -53,14 +53,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
images: amir20/dozzle
|
images: amir20/dozzle
|
||||||
- name: Set up Docker Buildx
|
- 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
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v2.0.0
|
uses: docker/login-action@v2.1.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v3.1.1
|
uses: docker/build-push-action@v3.2.0
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
platforms: linux/amd64,linux/arm/v7,linux/arm64/v8
|
platforms: linux/amd64,linux/arm/v7,linux/arm64/v8
|
||||||
@@ -81,7 +81,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v2.2.2
|
uses: pnpm/action-setup@v2.2.4
|
||||||
with:
|
with:
|
||||||
version: 6.20.1
|
version: 6.20.1
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
|||||||
8
.github/workflows/dev.yml
vendored
8
.github/workflows/dev.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
buildx:
|
buildx:
|
||||||
name: Push branches and PRs
|
name: Push branches and PRs
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == 'amir20/dozzle' }}
|
if: ${{ !github.event.repository.fork && !github.event.pull_request.head.repo.fork && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == 'amir20/dozzle') }}
|
||||||
steps:
|
steps:
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
@@ -18,14 +18,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
images: amir20/dozzle
|
images: amir20/dozzle
|
||||||
- name: Set up Docker Buildx
|
- 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
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v2.0.0
|
uses: docker/login-action@v2.1.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v3.1.1
|
uses: docker/build-push-action@v3.2.0
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
platforms: linux/amd64,linux/arm/v7,linux/arm64/v8
|
platforms: linux/amd64,linux/arm/v7,linux/arm64/v8
|
||||||
|
|||||||
10
.github/workflows/test.yml
vendored
10
.github/workflows/test.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v2.2.2
|
uses: pnpm/action-setup@v2.2.4
|
||||||
with:
|
with:
|
||||||
version: 6.20.1
|
version: 6.20.1
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
@@ -30,7 +30,7 @@ jobs:
|
|||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.18.x
|
go-version: 1.19.x
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
- name: Run Go Tests with Coverage
|
- name: Run Go Tests with Coverage
|
||||||
@@ -44,15 +44,17 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
- name: Set up Docker Buildx
|
- 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
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v2.0.0
|
if: ${{ !github.event.repository.fork && !github.event.pull_request.head.repo.fork }}
|
||||||
|
uses: docker/login-action@v2.1.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
- name: Build images
|
- name: Build images
|
||||||
run: COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f e2e/docker-compose.yml build --build-arg BUILDKIT_INLINE_CACHE=1
|
run: COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f e2e/docker-compose.yml build --build-arg BUILDKIT_INLINE_CACHE=1
|
||||||
- name: Push images
|
- name: Push images
|
||||||
|
if: ${{ !github.event.repository.fork && !github.event.pull_request.head.repo.fork }}
|
||||||
run: COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f e2e/docker-compose.yml push
|
run: COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f e2e/docker-compose.yml push
|
||||||
- name: Set commit message for push
|
- name: Set commit message for push
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
|
|||||||
16
Dockerfile
16
Dockerfile
@@ -1,5 +1,5 @@
|
|||||||
# Build assets
|
# Build assets
|
||||||
FROM --platform=$BUILDPLATFORM node:18-alpine as node
|
FROM --platform=$BUILDPLATFORM node:19-alpine as node
|
||||||
|
|
||||||
RUN npm install -g pnpm
|
RUN npm install -g pnpm
|
||||||
|
|
||||||
@@ -8,19 +8,21 @@ WORKDIR /build
|
|||||||
|
|
||||||
# Install dependencies from lock file
|
# Install dependencies from lock file
|
||||||
COPY pnpm-lock.yaml ./
|
COPY pnpm-lock.yaml ./
|
||||||
RUN pnpm fetch --prod
|
RUN pnpm fetch
|
||||||
|
|
||||||
# Copy files
|
# Copy package.json and install dependencies
|
||||||
COPY package.json .* vite.config.ts index.html ./
|
COPY package.json ./
|
||||||
|
RUN pnpm install -r --offline --ignore-scripts
|
||||||
|
|
||||||
# Copy assets and translations to build
|
# Copy assets and translations to build
|
||||||
|
COPY .* vite.config.ts index.html ./
|
||||||
COPY assets ./assets
|
COPY assets ./assets
|
||||||
COPY locales ./locales
|
COPY locales ./locales
|
||||||
|
|
||||||
# Install dependencies
|
# Build assets
|
||||||
RUN pnpm install -r --offline --prod --ignore-scripts && pnpm build
|
RUN pnpm build
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM golang:1.19.1-alpine AS builder
|
FROM --platform=$BUILDPLATFORM golang:1.19.3-alpine AS builder
|
||||||
|
|
||||||
RUN apk add --no-cache ca-certificates && mkdir /dozzle
|
RUN apk add --no-cache ca-certificates && mkdir /dozzle
|
||||||
|
|
||||||
|
|||||||
24
README.md
24
README.md
@@ -98,7 +98,7 @@ this would then only allow you to view containers with a name starting with "foo
|
|||||||
|
|
||||||
#### Authentication
|
#### Authentication
|
||||||
|
|
||||||
Dozzle supports a very simple authentication out of the box with just username and password. You should deploy using SSL to keep the credentials safe. See configuration to use `--username` and `--password`.
|
Dozzle supports a very simple authentication out of the box with just username and password. You should deploy using SSL to keep the credentials safe. See configuration to use `--username` and `--password`. You can also use [docker secrets](https://docs.docker.com/engine/swarm/secrets/) `--usernamefile` and `--passwordfile`.
|
||||||
|
|
||||||
#### Changing base URL
|
#### Changing base URL
|
||||||
|
|
||||||
@@ -129,6 +129,8 @@ Dozzle follows the [12-factor](https://12factor.net/) model. Configurations can
|
|||||||
| `--filter` | `DOZZLE_FILTER` | `""` |
|
| `--filter` | `DOZZLE_FILTER` | `""` |
|
||||||
| `--username` | `DOZZLE_USERNAME` | `""` |
|
| `--username` | `DOZZLE_USERNAME` | `""` |
|
||||||
| `--password` | `DOZZLE_PASSWORD` | `""` |
|
| `--password` | `DOZZLE_PASSWORD` | `""` |
|
||||||
|
| `--usernamefile` | `DOZZLE_USERNAME_FILE`| `""` |
|
||||||
|
| `--passwordfile` | `DOZZLE_PASSWORD_FILE`| `""` |
|
||||||
| `--no-analytics` | `DOZZLE_NO_ANALYTICS` | false |
|
| `--no-analytics` | `DOZZLE_NO_ANALYTICS` | false |
|
||||||
|
|
||||||
## Troubleshooting and FAQs
|
## Troubleshooting and FAQs
|
||||||
@@ -181,6 +183,26 @@ Dozzle has a [special route](https://github.com/amir20/dozzle/blob/master/assets
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>I installed Dozzle but memory consumption doesn't show up!</summary>
|
||||||
|
|
||||||
|
*This is an issue specific to ARM devices*
|
||||||
|
|
||||||
|
Dozzle uses the Docker API to gather information about the containers' memory usage. If the memory usage is not showing up, then it is likely that the Docker API is not returning the memory usage.
|
||||||
|
|
||||||
|
You can verify this by running `docker info`, and you should see the following:
|
||||||
|
```
|
||||||
|
WARNING: No memory limit support
|
||||||
|
WARNING: No swap limit support
|
||||||
|
```
|
||||||
|
|
||||||
|
In this case, you'll need to add the following line to your `/boot/cmdline.txt` file and reboot your device.
|
||||||
|
```
|
||||||
|
cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[MIT](LICENSE)
|
[MIT](LICENSE)
|
||||||
|
|||||||
14
assets/auto-imports.d.ts
vendored
14
assets/auto-imports.d.ts
vendored
@@ -14,6 +14,7 @@ declare global {
|
|||||||
const arrayEquals: typeof import('./utils/index')['arrayEquals']
|
const arrayEquals: typeof import('./utils/index')['arrayEquals']
|
||||||
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
||||||
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
||||||
|
const collapseNav: typeof import('./composables/settings')['collapseNav']
|
||||||
const computed: typeof import('vue')['computed']
|
const computed: typeof import('vue')['computed']
|
||||||
const computedAsync: typeof import('@vueuse/core')['computedAsync']
|
const computedAsync: typeof import('@vueuse/core')['computedAsync']
|
||||||
const computedEager: typeof import('@vueuse/core')['computedEager']
|
const computedEager: typeof import('@vueuse/core')['computedEager']
|
||||||
@@ -68,6 +69,8 @@ declare global {
|
|||||||
const nextTick: typeof import('vue')['nextTick']
|
const nextTick: typeof import('vue')['nextTick']
|
||||||
const onActivated: typeof import('vue')['onActivated']
|
const onActivated: typeof import('vue')['onActivated']
|
||||||
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||||
|
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
|
||||||
|
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
|
||||||
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||||
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
|
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
|
||||||
@@ -100,6 +103,7 @@ declare global {
|
|||||||
const refThrottled: typeof import('@vueuse/core')['refThrottled']
|
const refThrottled: typeof import('@vueuse/core')['refThrottled']
|
||||||
const refWithControl: typeof import('@vueuse/core')['refWithControl']
|
const refWithControl: typeof import('@vueuse/core')['refWithControl']
|
||||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||||
|
const resolveDirective: typeof import('vue')['resolveDirective']
|
||||||
const resolveRef: typeof import('@vueuse/core')['resolveRef']
|
const resolveRef: typeof import('@vueuse/core')['resolveRef']
|
||||||
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
|
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
|
||||||
const search: typeof import('./composables/settings')['search']
|
const search: typeof import('./composables/settings')['search']
|
||||||
@@ -207,6 +211,7 @@ declare global {
|
|||||||
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
|
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
|
||||||
const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
|
const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
|
||||||
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
|
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
|
||||||
|
const useLink: typeof import('vue-router')['useLink']
|
||||||
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
|
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
|
||||||
const useLogStream: typeof import('./composables/eventsource')['useLogStream']
|
const useLogStream: typeof import('./composables/eventsource')['useLogStream']
|
||||||
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
|
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
|
||||||
@@ -250,6 +255,7 @@ declare global {
|
|||||||
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
|
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
|
||||||
const useShare: typeof import('@vueuse/core')['useShare']
|
const useShare: typeof import('@vueuse/core')['useShare']
|
||||||
const useSlots: typeof import('vue')['useSlots']
|
const useSlots: typeof import('vue')['useSlots']
|
||||||
|
const useSorted: typeof import('@vueuse/core')['useSorted']
|
||||||
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
|
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
|
||||||
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
|
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
|
||||||
const useStepper: typeof import('@vueuse/core')['useStepper']
|
const useStepper: typeof import('@vueuse/core')['useStepper']
|
||||||
@@ -307,7 +313,7 @@ declare global {
|
|||||||
}
|
}
|
||||||
// for vue template auto import
|
// for vue template auto import
|
||||||
import { UnwrapRef } from 'vue'
|
import { UnwrapRef } from 'vue'
|
||||||
declare module '@vue/runtime-core' {
|
declare module 'vue' {
|
||||||
interface ComponentCustomProperties {
|
interface ComponentCustomProperties {
|
||||||
readonly $$: UnwrapRef<typeof import('vue/macros')['$$']>
|
readonly $$: UnwrapRef<typeof import('vue/macros')['$$']>
|
||||||
readonly $: UnwrapRef<typeof import('vue/macros')['$']>
|
readonly $: UnwrapRef<typeof import('vue/macros')['$']>
|
||||||
@@ -322,6 +328,7 @@ declare module '@vue/runtime-core' {
|
|||||||
readonly arrayEquals: UnwrapRef<typeof import('./utils/index')['arrayEquals']>
|
readonly arrayEquals: UnwrapRef<typeof import('./utils/index')['arrayEquals']>
|
||||||
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
|
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
|
||||||
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
|
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
|
||||||
|
readonly collapseNav: UnwrapRef<typeof import('./composables/settings')['collapseNav']>
|
||||||
readonly computed: UnwrapRef<typeof import('vue')['computed']>
|
readonly computed: UnwrapRef<typeof import('vue')['computed']>
|
||||||
readonly computedAsync: UnwrapRef<typeof import('@vueuse/core')['computedAsync']>
|
readonly computedAsync: UnwrapRef<typeof import('@vueuse/core')['computedAsync']>
|
||||||
readonly computedEager: UnwrapRef<typeof import('@vueuse/core')['computedEager']>
|
readonly computedEager: UnwrapRef<typeof import('@vueuse/core')['computedEager']>
|
||||||
@@ -376,6 +383,8 @@ declare module '@vue/runtime-core' {
|
|||||||
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
|
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
|
||||||
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
|
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
|
||||||
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
|
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
|
||||||
|
readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router')['onBeforeRouteLeave']>
|
||||||
|
readonly onBeforeRouteUpdate: UnwrapRef<typeof import('vue-router')['onBeforeRouteUpdate']>
|
||||||
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
|
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
|
||||||
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
|
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
|
||||||
readonly onClickOutside: UnwrapRef<typeof import('@vueuse/core')['onClickOutside']>
|
readonly onClickOutside: UnwrapRef<typeof import('@vueuse/core')['onClickOutside']>
|
||||||
@@ -408,6 +417,7 @@ declare module '@vue/runtime-core' {
|
|||||||
readonly refThrottled: UnwrapRef<typeof import('@vueuse/core')['refThrottled']>
|
readonly refThrottled: UnwrapRef<typeof import('@vueuse/core')['refThrottled']>
|
||||||
readonly refWithControl: UnwrapRef<typeof import('@vueuse/core')['refWithControl']>
|
readonly refWithControl: UnwrapRef<typeof import('@vueuse/core')['refWithControl']>
|
||||||
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
|
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
|
||||||
|
readonly resolveDirective: UnwrapRef<typeof import('vue')['resolveDirective']>
|
||||||
readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
|
readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
|
||||||
readonly resolveUnref: UnwrapRef<typeof import('@vueuse/core')['resolveUnref']>
|
readonly resolveUnref: UnwrapRef<typeof import('@vueuse/core')['resolveUnref']>
|
||||||
readonly search: UnwrapRef<typeof import('./composables/settings')['search']>
|
readonly search: UnwrapRef<typeof import('./composables/settings')['search']>
|
||||||
@@ -515,6 +525,7 @@ declare module '@vue/runtime-core' {
|
|||||||
readonly useIntervalFn: UnwrapRef<typeof import('@vueuse/core')['useIntervalFn']>
|
readonly useIntervalFn: UnwrapRef<typeof import('@vueuse/core')['useIntervalFn']>
|
||||||
readonly useKeyModifier: UnwrapRef<typeof import('@vueuse/core')['useKeyModifier']>
|
readonly useKeyModifier: UnwrapRef<typeof import('@vueuse/core')['useKeyModifier']>
|
||||||
readonly useLastChanged: UnwrapRef<typeof import('@vueuse/core')['useLastChanged']>
|
readonly useLastChanged: UnwrapRef<typeof import('@vueuse/core')['useLastChanged']>
|
||||||
|
readonly useLink: UnwrapRef<typeof import('vue-router')['useLink']>
|
||||||
readonly useLocalStorage: UnwrapRef<typeof import('@vueuse/core')['useLocalStorage']>
|
readonly useLocalStorage: UnwrapRef<typeof import('@vueuse/core')['useLocalStorage']>
|
||||||
readonly useLogStream: UnwrapRef<typeof import('./composables/eventsource')['useLogStream']>
|
readonly useLogStream: UnwrapRef<typeof import('./composables/eventsource')['useLogStream']>
|
||||||
readonly useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']>
|
readonly useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']>
|
||||||
@@ -558,6 +569,7 @@ declare module '@vue/runtime-core' {
|
|||||||
readonly useSessionStorage: UnwrapRef<typeof import('@vueuse/core')['useSessionStorage']>
|
readonly useSessionStorage: UnwrapRef<typeof import('@vueuse/core')['useSessionStorage']>
|
||||||
readonly useShare: UnwrapRef<typeof import('@vueuse/core')['useShare']>
|
readonly useShare: UnwrapRef<typeof import('@vueuse/core')['useShare']>
|
||||||
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
|
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
|
||||||
|
readonly useSorted: UnwrapRef<typeof import('@vueuse/core')['useSorted']>
|
||||||
readonly useSpeechRecognition: UnwrapRef<typeof import('@vueuse/core')['useSpeechRecognition']>
|
readonly useSpeechRecognition: UnwrapRef<typeof import('@vueuse/core')['useSpeechRecognition']>
|
||||||
readonly useSpeechSynthesis: UnwrapRef<typeof import('@vueuse/core')['useSpeechSynthesis']>
|
readonly useSpeechSynthesis: UnwrapRef<typeof import('@vueuse/core')['useSpeechSynthesis']>
|
||||||
readonly useStepper: UnwrapRef<typeof import('@vueuse/core')['useStepper']>
|
readonly useStepper: UnwrapRef<typeof import('@vueuse/core')['useStepper']>
|
||||||
|
|||||||
3
assets/components.d.ts
vendored
3
assets/components.d.ts
vendored
@@ -29,6 +29,7 @@ declare module '@vue/runtime-core' {
|
|||||||
MdiLightChevronLeft: typeof import('~icons/mdi-light/chevron-left')['default']
|
MdiLightChevronLeft: typeof import('~icons/mdi-light/chevron-left')['default']
|
||||||
MdiLightChevronRight: typeof import('~icons/mdi-light/chevron-right')['default']
|
MdiLightChevronRight: typeof import('~icons/mdi-light/chevron-right')['default']
|
||||||
MdiLightCog: typeof import('~icons/mdi-light/cog')['default']
|
MdiLightCog: typeof import('~icons/mdi-light/cog')['default']
|
||||||
|
MdiLightLogout: typeof import('~icons/mdi-light/logout')['default']
|
||||||
MdiLightMagnify: typeof import('~icons/mdi-light/magnify')['default']
|
MdiLightMagnify: typeof import('~icons/mdi-light/magnify')['default']
|
||||||
MobileMenu: typeof import('./components/MobileMenu.vue')['default']
|
MobileMenu: typeof import('./components/MobileMenu.vue')['default']
|
||||||
OcticonContainer24: typeof import('~icons/octicon/container24')['default']
|
OcticonContainer24: typeof import('~icons/octicon/container24')['default']
|
||||||
@@ -44,6 +45,8 @@ declare module '@vue/runtime-core' {
|
|||||||
SideMenu: typeof import('./components/SideMenu.vue')['default']
|
SideMenu: typeof import('./components/SideMenu.vue')['default']
|
||||||
SimpleLogItem: typeof import('./components/LogViewer/SimpleLogItem.vue')['default']
|
SimpleLogItem: typeof import('./components/LogViewer/SimpleLogItem.vue')['default']
|
||||||
SkippedEntriesLogItem: typeof import('./components/LogViewer/SkippedEntriesLogItem.vue')['default']
|
SkippedEntriesLogItem: typeof import('./components/LogViewer/SkippedEntriesLogItem.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']
|
ZigZag: typeof import('./components/LogViewer/ZigZag.vue')['default']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,26 +3,29 @@
|
|||||||
<o-autocomplete
|
<o-autocomplete
|
||||||
ref="autocomplete"
|
ref="autocomplete"
|
||||||
v-model="query"
|
v-model="query"
|
||||||
placeholder="Search containers using ⌘ + k or ctrl + k"
|
:placeholder="$t('placeholder.search-containers')"
|
||||||
field="name"
|
|
||||||
open-on-focus
|
open-on-focus
|
||||||
keep-first
|
keep-first
|
||||||
expanded
|
expanded
|
||||||
:data="results"
|
:data="data"
|
||||||
@select="selected"
|
@select="selected"
|
||||||
>
|
>
|
||||||
<template #default="props">
|
<template #default="{ option: item }">
|
||||||
<div class="media">
|
<div class="media">
|
||||||
<div class="media-left">
|
<div class="media-left">
|
||||||
<span class="icon is-small" :class="props.option.state">
|
<span class="icon is-small" :class="item.state">
|
||||||
<octicon-container-24 />
|
<octicon-container-24 />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="media-content">
|
<div class="media-content">
|
||||||
{{ props.option.name }}
|
{{ item.name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="media-right">
|
<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 />
|
<cil-columns />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -33,55 +36,61 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import fuzzysort from "fuzzysort";
|
import { Container } from "@/models/Container";
|
||||||
import { type Container } from "@/types/Container";
|
import { useFuse } from "@vueuse/integrations/useFuse";
|
||||||
|
|
||||||
const { maxResults = 20 } = defineProps<{
|
const { maxResults: resultLimit = 20 } = defineProps<{
|
||||||
maxResults?: number;
|
maxResults?: number;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits(["close"]);
|
const emit = defineEmits<{
|
||||||
|
(e: "close"): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
const query = ref("");
|
const query = ref("");
|
||||||
const autocomplete = ref<HTMLElement>();
|
const autocomplete = ref<HTMLElement>();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const store = useContainerStore();
|
const store = useContainerStore();
|
||||||
const { containers } = storeToRefs(store);
|
const { containers } = storeToRefs(store);
|
||||||
const preparedContainers = computed(() =>
|
|
||||||
containers.value.map(({ name, id, created, state }) =>
|
const list = computed(() => {
|
||||||
reactive({
|
return containers.value.map(({ id, created, name, state }) => {
|
||||||
name,
|
return {
|
||||||
id,
|
id,
|
||||||
created,
|
created,
|
||||||
|
name,
|
||||||
state,
|
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 }) {
|
const data = computed(() => {
|
||||||
router.push({ name: "container-id", params: { id: item.id } });
|
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");
|
emit("close");
|
||||||
}
|
}
|
||||||
function addColumn(container: Container) {
|
function addColumn(container: Container) {
|
||||||
@@ -96,6 +105,15 @@ function addColumn(container: Container) {
|
|||||||
width: 580px;
|
width: 580px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.panel {
|
||||||
|
min-height: 200px;
|
||||||
|
width: auto;
|
||||||
|
margin-left: 0.25rem!important;
|
||||||
|
margin-right: 0.25rem!important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.running {
|
.running {
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<log-date :date="logEntry.date"></log-date>
|
<log-date :date="logEntry.date"></log-date>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<ul class="fields" @click="expanded = !expanded">
|
<ul class="fields" :class="{ expanded }" @click="expanded = !expanded">
|
||||||
<li v-for="(value, name) in validValues(logEntry.message)">
|
<li v-for="(value, name) in validValues(logEntry.message)">
|
||||||
<span class="has-text-grey">{{ name }}=</span>
|
<span class="has-text-grey">{{ name }}=</span>
|
||||||
<span class="has-text-weight-bold" v-html="markSearch(value)"></span>
|
<span class="has-text-weight-bold" v-html="markSearch(value)"></span>
|
||||||
@@ -47,6 +47,12 @@ function validValues(obj: Record<string, any>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.expanded:hover {
|
||||||
|
&::after {
|
||||||
|
content: "collapse json";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
& + li {
|
& + li {
|
||||||
|
|||||||
@@ -1,27 +1,51 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="is-size-7 is-uppercase columns is-marginless is-mobile" v-if="container.stat">
|
<div class="is-size-7 is-uppercase columns is-marginless is-mobile is-vcentered" v-if="container.stat">
|
||||||
<div class="column is-narrow has-text-weight-bold">
|
<stat-monitor
|
||||||
{{ container.state }}
|
class="column is-narrow"
|
||||||
</div>
|
:data="memoryData"
|
||||||
<div class="column is-narrow" v-if="container.stat.memoryUsage !== null">
|
label="mem"
|
||||||
<span class="has-text-weight-light has-spacer">mem</span>
|
:stat-value="formatBytes(container.stat.memoryUsage)"
|
||||||
<span class="has-text-weight-bold">
|
></stat-monitor>
|
||||||
{{ formatBytes(container.stat.memoryUsage) }}
|
<stat-monitor
|
||||||
</span>
|
class="column is-narrow"
|
||||||
</div>
|
:data="cpuData"
|
||||||
|
label="load"
|
||||||
<div class="column is-narrow" v-if="container.stat.cpu !== null">
|
:stat-value="container.stat.cpu + '%'"
|
||||||
<span class="has-text-weight-light has-spacer">load</span>
|
></stat-monitor>
|
||||||
<span class="has-text-weight-bold"> {{ container.stat.cpu }}% </span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { type Container } from "@/types/Container";
|
import { Container } from "@/models/Container";
|
||||||
import { type ComputedRef } from "vue";
|
import { type ComputedRef } from "vue";
|
||||||
|
|
||||||
const container = inject("container") as ComputedRef<Container>;
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -30,4 +54,23 @@ const container = inject("container") as ComputedRef<Container>;
|
|||||||
content: " ";
|
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>
|
</style>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { type Container } from "@/types/Container";
|
import { Container } from "@/models/Container";
|
||||||
import { type ComputedRef } from "vue";
|
import { type ComputedRef } from "vue";
|
||||||
|
|
||||||
const container = inject("container") as ComputedRef<Container>;
|
const container = inject("container") as ComputedRef<Container>;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { type ComputedRef } from "vue";
|
import { type ComputedRef } from "vue";
|
||||||
import { type Container } from "@/types/Container";
|
import { Container } from "@/models/Container";
|
||||||
|
|
||||||
const { showSearch } = useSearchFilter();
|
const { showSearch } = useSearchFilter();
|
||||||
const { base } = config;
|
const { base } = config;
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<scrollable-view :scrollable="scrollable" v-if="container">
|
<scrollable-view :scrollable="scrollable" v-if="container">
|
||||||
<template #header v-if="showTitle">
|
<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">
|
<div class="column is-clipped is-paddingless">
|
||||||
<container-title @close="$emit('close')" />
|
<container-title @close="$emit('close')" />
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-narrow is-paddingless">
|
<div class="column is-narrow is-paddingless">
|
||||||
<container-stat v-if="container.stat" />
|
<container-stat />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mr-2 column is-narrow is-paddingless">
|
<div class="mr-2 column is-narrow is-paddingless">
|
||||||
|
|||||||
@@ -13,33 +13,7 @@ defineProps<{
|
|||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
background-color: var(--scheme-main-ter);
|
||||||
|
color: #258ccd;
|
||||||
@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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { type Container } from "@/types/Container";
|
import { Container } from "@/models/Container";
|
||||||
import { type ComputedRef } from "vue";
|
import { type ComputedRef } from "vue";
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { type ComputedRef, toRaw } from "vue";
|
import { type ComputedRef, toRaw } from "vue";
|
||||||
import { useRouteHash } from "@vueuse/router";
|
import { useRouteHash } from "@vueuse/router";
|
||||||
import { type Container } from "@/types/Container";
|
import { Container } from "@/models/Container";
|
||||||
import { type JSONObject, LogEntry } from "@/models/LogEntry";
|
import { type JSONObject, LogEntry } from "@/models/LogEntry";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|||||||
56
assets/components/LogViewer/StatMonitor.vue
Normal file
56
assets/components/LogViewer/StatMonitor.vue
Normal 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>
|
||||||
62
assets/components/LogViewer/StatSparkline.vue
Normal file
62
assets/components/LogViewer/StatSparkline.vue
Normal 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>
|
||||||
@@ -24,6 +24,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="menu-label level is-mobile is-hidden-mobile" :class="{ 'is-active': showNav }">
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<button class="button is-small is-rounded" @click="$emit('search')" :title="$t('tooltip.search')">
|
||||||
|
<span class="icon">
|
||||||
|
<mdi-light-magnify />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="level-item has-text-centered">
|
||||||
|
<div>
|
||||||
|
<router-link :to="{ name: 'settings' }" active-class="is-active" class="button is-small is-rounded">
|
||||||
|
<span class="icon">
|
||||||
|
<mdi-light-cog />
|
||||||
|
</span>
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="level-item has-text-centered" v-if="secured">
|
||||||
|
<div>
|
||||||
|
<a class="button is-small is-rounded" :href="`${base}/logout`" :title="$t('button.logout')">
|
||||||
|
<span class="icon">
|
||||||
|
<mdi-light-logout />
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p class="menu-label is-hidden-mobile" :class="{ 'is-active': showNav }">{{ $t("label.containers") }}</p>
|
<p class="menu-label is-hidden-mobile" :class="{ 'is-active': showNav }">{{ $t("label.containers") }}</p>
|
||||||
<ul class="menu-list is-hidden-mobile" :class="{ 'is-active': showNav }">
|
<ul class="menu-list is-hidden-mobile" :class="{ 'is-active': showNav }">
|
||||||
<li v-for="item in visibleContainers" :key="item.id">
|
<li v-for="item in visibleContainers" :key="item.id">
|
||||||
@@ -42,6 +72,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
const { base, secured } = config;
|
||||||
const store = useContainerStore();
|
const store = useContainerStore();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const { visibleContainers, allContainersById } = storeToRefs(store);
|
const { visibleContainers, allContainersById } = storeToRefs(store);
|
||||||
@@ -63,6 +94,10 @@ aside {
|
|||||||
max-height: 100vh;
|
max-height: 100vh;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
|
.level.is-hidden-mobile.is-active {
|
||||||
|
display: flex !important;
|
||||||
|
}
|
||||||
|
|
||||||
.menu-label {
|
.menu-label {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,12 @@
|
|||||||
|
|
||||||
<div class="is-scrollbar-notification">
|
<div class="is-scrollbar-notification">
|
||||||
<transition name="fade">
|
<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 />
|
<mdi-light-chevron-double-down />
|
||||||
</button>
|
</button>
|
||||||
</transition>
|
</transition>
|
||||||
@@ -109,18 +114,18 @@ section {
|
|||||||
button {
|
button {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 30px;
|
bottom: 30px;
|
||||||
background-color: var(--secondary-color);
|
background-color: var(--primary-color);
|
||||||
transition: background-color 1s ease-out;
|
transition: background-color 0.24s ease-out;
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
|
|
||||||
border: none !important;
|
border: none !important;
|
||||||
color: #222;
|
color: #eee;
|
||||||
|
|
||||||
&.has-more {
|
&.has-more {
|
||||||
background-color: var(--primary-color);
|
background-color: var(--secondary-color);
|
||||||
animation-name: bounce;
|
animation-name: bounce;
|
||||||
animation-duration: 1000ms;
|
animation-duration: 1000ms;
|
||||||
animation-fill-mode: both;
|
animation-fill-mode: both;
|
||||||
color: #fff;
|
|
||||||
|
color: #222;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,23 +22,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
const { Meta_F, Ctrl_F, esc } = useMagicKeys({
|
|
||||||
passive: false,
|
|
||||||
onEventFired(e) {
|
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key === "f" && e.type === "keydown") e.preventDefault();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const input = ref<HTMLInputElement>();
|
const input = ref<HTMLInputElement>();
|
||||||
const { searchFilter, showSearch, resetSearch } = useSearchFilter();
|
const { searchFilter, showSearch, resetSearch } = useSearchFilter();
|
||||||
|
|
||||||
whenever(
|
onKeyStroke("f", (e) => {
|
||||||
() => Meta_F.value || Ctrl_F.value,
|
if (e.ctrlKey || e.metaKey) {
|
||||||
() => {
|
|
||||||
showSearch.value = true;
|
showSearch.value = true;
|
||||||
nextTick(() => input.value?.focus() || input.value?.select());
|
nextTick(() => input.value?.focus() || input.value?.select());
|
||||||
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
whenever(esc, () => resetSearch());
|
|
||||||
onUnmounted(() => resetSearch());
|
onUnmounted(() => resetSearch());
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -8,20 +8,29 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-narrow has-text-right px-1">
|
</div>
|
||||||
<button class="button is-rounded" @click="$emit('search')" title="$t('tooltip.search')">
|
<div class="columns is-marginless">
|
||||||
|
<div class="column is-narrow py-0 pl-0 pr-1">
|
||||||
|
<button class="button is-rounded is-small" @click="$emit('search')" :title="$t('tooltip.search')">
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<mdi-light-magnify />
|
<mdi-light-magnify />
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-narrow has-text-right px-0">
|
<div class="column is-narrow py-0" :class="secured ? 'pl-0 pr-1' : 'px-0'">
|
||||||
<router-link :to="{ name: 'settings' }" active-class="is-active" class="button is-rounded">
|
<router-link :to="{ name: 'settings' }" active-class="is-active" class="button is-rounded is-small">
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<mdi-light-cog />
|
<mdi-light-cog />
|
||||||
</span>
|
</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="column is-narrow py-0 px-0" v-if="secured">
|
||||||
|
<a class="button is-rounded is-small" :href="`${base}/logout`" :title="$t('button.logout')">
|
||||||
|
<span class="icon">
|
||||||
|
<mdi-light-logout />
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="menu-label is-hidden-mobile">{{ $t("label.containers") }}</p>
|
<p class="menu-label is-hidden-mobile">{{ $t("label.containers") }}</p>
|
||||||
<ul class="menu-list is-hidden-mobile" v-if="ready">
|
<ul class="menu-list is-hidden-mobile" v-if="ready">
|
||||||
@@ -40,7 +49,7 @@
|
|||||||
class="icon is-small"
|
class="icon is-small"
|
||||||
@click.stop.prevent="store.appendActiveContainer(item)"
|
@click.stop.prevent="store.appendActiveContainer(item)"
|
||||||
v-show="!activeContainersById[item.id]"
|
v-show="!activeContainersById[item.id]"
|
||||||
title="$t('tooltip.pin-column')"
|
:title="$t('tooltip.pin-column')"
|
||||||
>
|
>
|
||||||
<cil-columns />
|
<cil-columns />
|
||||||
</span>
|
</span>
|
||||||
@@ -58,6 +67,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Container } from "@/types/Container";
|
import type { Container } from "@/types/Container";
|
||||||
|
|
||||||
|
const { base, secured } = config;
|
||||||
const store = useContainerStore();
|
const store = useContainerStore();
|
||||||
|
|
||||||
const { activeContainers, visibleContainers, ready } = storeToRefs(store);
|
const { activeContainers, visibleContainers, ready } = storeToRefs(store);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
DockerEventLogEntry,
|
DockerEventLogEntry,
|
||||||
SkippedLogsEntry,
|
SkippedLogsEntry,
|
||||||
} from "@/models/LogEntry";
|
} from "@/models/LogEntry";
|
||||||
import { type Container } from "@/types/Container";
|
import { Container } from "@/models/Container";
|
||||||
|
|
||||||
function parseMessage(data: string): LogEntry<string | JSONObject> {
|
function parseMessage(data: string): LogEntry<string | JSONObject> {
|
||||||
const e = JSON.parse(data) as LogEvent;
|
const e = JSON.parse(data) as LogEvent;
|
||||||
@@ -37,7 +37,7 @@ export function useLogStream(container: ComputedRef<Container>) {
|
|||||||
} else {
|
} else {
|
||||||
messages.push(...buffer);
|
messages.push(...buffer);
|
||||||
buffer = [];
|
buffer = [];
|
||||||
messages.splice(0, messages.length - config.maxLogs);
|
messages = messages.slice(-config.maxLogs);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
messages.push(...buffer);
|
messages.push(...buffer);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export const DEFAULT_SETTINGS: {
|
|||||||
lightTheme: "auto" | "dark" | "light";
|
lightTheme: "auto" | "dark" | "light";
|
||||||
hourStyle: "auto" | "24" | "12";
|
hourStyle: "auto" | "24" | "12";
|
||||||
softWrap: boolean;
|
softWrap: boolean;
|
||||||
|
collapseNav: boolean;
|
||||||
} = {
|
} = {
|
||||||
search: true,
|
search: true,
|
||||||
size: "medium",
|
size: "medium",
|
||||||
@@ -20,6 +21,7 @@ export const DEFAULT_SETTINGS: {
|
|||||||
lightTheme: "auto",
|
lightTheme: "auto",
|
||||||
hourStyle: "auto",
|
hourStyle: "auto",
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
|
collapseNav: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const settings = useStorage(DOZZLE_SETTINGS_KEY, DEFAULT_SETTINGS);
|
const settings = useStorage(DOZZLE_SETTINGS_KEY, DEFAULT_SETTINGS);
|
||||||
@@ -69,7 +71,13 @@ const softWrap = computed({
|
|||||||
set: (value) => (settings.value.softWrap = value),
|
set: (value) => (settings.value.softWrap = value),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const collapseNav = computed({
|
||||||
|
get: () => settings.value.collapseNav,
|
||||||
|
set: (value) => (settings.value.collapseNav = value),
|
||||||
|
});
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
collapseNav,
|
||||||
softWrap,
|
softWrap,
|
||||||
hourStyle,
|
hourStyle,
|
||||||
lightTheme,
|
lightTheme,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<main v-if="!authorizationNeeded">
|
<main v-if="!authorizationNeeded">
|
||||||
<mobile-menu v-if="isMobile"></mobile-menu>
|
<mobile-menu v-if="isMobile" @search="showFuzzySearch"></mobile-menu>
|
||||||
<splitpanes @resized="onResized($event)">
|
<splitpanes @resized="onResized($event)">
|
||||||
<pane min-size="10" :size="menuWidth" v-if="!isMobile && !collapseNav">
|
<pane min-size="10" :size="menuWidth" v-if="!isMobile && !collapseNav">
|
||||||
<side-menu @search="showFuzzySearch"></side-menu>
|
<side-menu @search="showFuzzySearch"></side-menu>
|
||||||
@@ -25,8 +25,8 @@
|
|||||||
</pane>
|
</pane>
|
||||||
</splitpanes>
|
</splitpanes>
|
||||||
<button
|
<button
|
||||||
@click="collapseNav = !collapseNav"
|
@click="collapse"
|
||||||
class="button is-rounded"
|
class="button is-small is-rounded"
|
||||||
:class="{ collapsed: collapseNav }"
|
:class="{ collapsed: collapseNav }"
|
||||||
id="hide-nav"
|
id="hide-nav"
|
||||||
v-if="!isMobile"
|
v-if="!isMobile"
|
||||||
@@ -47,22 +47,23 @@ import { Splitpanes, Pane } from "splitpanes";
|
|||||||
import { useProgrammatic } from "@oruga-ui/oruga-next";
|
import { useProgrammatic } from "@oruga-ui/oruga-next";
|
||||||
import FuzzySearchModal from "@/components/FuzzySearchModal.vue";
|
import FuzzySearchModal from "@/components/FuzzySearchModal.vue";
|
||||||
|
|
||||||
const collapseNav = ref(false);
|
|
||||||
const { oruga } = useProgrammatic();
|
const { oruga } = useProgrammatic();
|
||||||
const { authorizationNeeded } = config;
|
const { authorizationNeeded } = config;
|
||||||
const { Meta_K, Ctrl_K } = useMagicKeys();
|
|
||||||
const containerStore = useContainerStore();
|
const containerStore = useContainerStore();
|
||||||
const { activeContainers, visibleContainers } = storeToRefs(containerStore);
|
const { activeContainers, visibleContainers } = storeToRefs(containerStore);
|
||||||
|
|
||||||
whenever(
|
|
||||||
() => Meta_K.value || Ctrl_K.value,
|
|
||||||
() => showFuzzySearch()
|
|
||||||
);
|
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
setTitle(`${visibleContainers.value.length} containers`);
|
setTitle(`${visibleContainers.value.length} containers`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onKeyStroke("k", (e) => {
|
||||||
|
if (e.ctrlKey || e.metaKey) {
|
||||||
|
showFuzzySearch();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function showFuzzySearch() {
|
function showFuzzySearch() {
|
||||||
oruga.modal.open({
|
oruga.modal.open({
|
||||||
// parent: this,
|
// parent: this,
|
||||||
@@ -72,6 +73,9 @@ function showFuzzySearch() {
|
|||||||
active: true,
|
active: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function collapse() {
|
||||||
|
collapseNav.value = !collapseNav.value;
|
||||||
|
}
|
||||||
function onResized(e: any) {
|
function onResized(e: any) {
|
||||||
if (e.length == 2) {
|
if (e.length == 2) {
|
||||||
menuWidth.value = e[0].size;
|
menuWidth.value = e[0].size;
|
||||||
@@ -110,7 +114,8 @@ function onResized(e: any) {
|
|||||||
left: -40px;
|
left: -40px;
|
||||||
width: 60px;
|
width: 60px;
|
||||||
padding-left: 40px;
|
padding-left: 40px;
|
||||||
background: rgba(0, 0, 0, 0.95);
|
color: var(--text-strong-color);
|
||||||
|
background: var(--scheme-main);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
left: -25px;
|
left: -25px;
|
||||||
|
|||||||
31
assets/models/Container.ts
Normal file
31
assets/models/Container.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<section class="hero is-small mt-4">
|
|
||||||
<div class="hero-body">
|
|
||||||
<div class="container">
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column is-narrow" v-if="secured">
|
|
||||||
<a class="button is-primary is-small" :href="`${base}/logout`">{{ $t("button.logout") }}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section class="level section">
|
|
||||||
<div class="level-item has-text-centered">
|
|
||||||
<div>
|
|
||||||
<p class="title">{{ containers.length }}</p>
|
|
||||||
<p class="heading">{{ $t("label.total-containers") }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="level-item has-text-centered">
|
|
||||||
<div>
|
|
||||||
<p class="title">{{ runningContainers.length }}</p>
|
|
||||||
<p class="heading">{{ $t("label.running") }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="level-item has-text-centered">
|
|
||||||
<div>
|
|
||||||
<p class="title" data-ci-skip>{{ totalCpu }}%</p>
|
|
||||||
<p class="heading">{{ $t("label.total-cpu-usage") }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="level-item has-text-centered">
|
|
||||||
<div>
|
|
||||||
<p class="title" data-ci-skip>{{ formatBytes(totalMem) }}</p>
|
|
||||||
<p class="heading">{{ $t("label.total-mem-usage") }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="level-item has-text-centered">
|
|
||||||
<div>
|
|
||||||
<p class="title">{{ version }}</p>
|
|
||||||
<p class="heading">{{ $t("label.dozzle-version") }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="columns is-centered section is-marginless">
|
|
||||||
<div class="column is-4">
|
|
||||||
<div class="panel">
|
|
||||||
<p class="panel-heading">{{ $t("label.containers") }}</p>
|
|
||||||
<div class="panel-block">
|
|
||||||
<p class="control has-icons-left">
|
|
||||||
<input
|
|
||||||
class="input"
|
|
||||||
type="text"
|
|
||||||
:placeholder="$t('placeholder.search-containers')"
|
|
||||||
v-model="search"
|
|
||||||
@keyup.esc="search.value = null"
|
|
||||||
@keyup.enter="onEnter()"
|
|
||||||
/>
|
|
||||||
<span class="icon is-left">
|
|
||||||
<search-icon />
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<p class="panel-tabs" v-if="!search">
|
|
||||||
<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)"
|
|
||||||
:key="item.id"
|
|
||||||
class="panel-block"
|
|
||||||
>
|
|
||||||
<span class="name">{{ item.name }}</span>
|
|
||||||
|
|
||||||
<div class="subtitle is-7 status">
|
|
||||||
<past-time :date="new Date(item.created * 1000)"></past-time>
|
|
||||||
</div>
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import fuzzysort from "fuzzysort";
|
|
||||||
import SearchIcon from "~icons/mdi-light/magnify";
|
|
||||||
|
|
||||||
const { base, version, secured } = config;
|
|
||||||
const containerStore = useContainerStore();
|
|
||||||
const { containers } = storeToRefs(containerStore);
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const sort = ref("running");
|
|
||||||
const search = ref();
|
|
||||||
|
|
||||||
const results = computed(() => {
|
|
||||||
if (search.value) {
|
|
||||||
return fuzzysort.go(search.value, containers.value, { key: "name" }).map((i) => i.obj);
|
|
||||||
}
|
|
||||||
switch (sort.value) {
|
|
||||||
case "all":
|
|
||||||
return mostRecentContainers.value;
|
|
||||||
case "running":
|
|
||||||
return runningContainers.value;
|
|
||||||
default:
|
|
||||||
throw `Invalid sort order: ${sort.value}`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
useIntervalFn(
|
|
||||||
() => {
|
|
||||||
totalCpu.value = runningContainers.value.reduce((acc, c) => acc + (c.stat?.cpu ?? 0), 0);
|
|
||||||
},
|
|
||||||
1000,
|
|
||||||
{ immediate: true }
|
|
||||||
);
|
|
||||||
const totalMem = ref(0);
|
|
||||||
|
|
||||||
useIntervalFn(
|
|
||||||
() => {
|
|
||||||
totalMem.value = runningContainers.value.reduce((acc, c) => acc + (c.stat?.memoryUsage ?? 0), 0);
|
|
||||||
},
|
|
||||||
1000,
|
|
||||||
{ immediate: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
function onEnter() {
|
|
||||||
if (results.value.length == 1) {
|
|
||||||
const [item] = results.value;
|
|
||||||
router.push({ name: "container-id", params: { id: item.id } });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.panel {
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
.panel-block,
|
|
||||||
.panel-tabs {
|
|
||||||
border-color: var(--border-color);
|
|
||||||
.is-active {
|
|
||||||
border-color: var(--border-hover-color);
|
|
||||||
}
|
|
||||||
.name {
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.status {
|
|
||||||
margin-left: auto;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
padding: 10px 3px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="hero is-halfheight">
|
|
||||||
<div class="hero-body">
|
|
||||||
<div class="container">
|
|
||||||
<section class="columns is-centered section">
|
|
||||||
<div class="column is-4">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-content">
|
|
||||||
<form action="" method="post" @submit.prevent="onLogin" ref="form">
|
|
||||||
<div class="field">
|
|
||||||
<label class="label">{{ $t("label.username") }}</label>
|
|
||||||
<div class="control">
|
|
||||||
<input
|
|
||||||
class="input"
|
|
||||||
type="text"
|
|
||||||
name="username"
|
|
||||||
autocomplete="username"
|
|
||||||
v-model="username"
|
|
||||||
autofocus
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label class="label">{{ $t("label.password") }}</label>
|
|
||||||
<div class="control">
|
|
||||||
<input
|
|
||||||
class="input"
|
|
||||||
type="password"
|
|
||||||
name="password"
|
|
||||||
autocomplete="current-password"
|
|
||||||
v-model="password"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p class="help is-danger" v-if="error">{{ $t("error.invalid-auth") }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="field is-grouped is-grouped-centered mt-5">
|
|
||||||
<p class="control">
|
|
||||||
<button class="button is-primary" type="submit">{{ $t("button.login") }}</button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
setTitle(t("title.login"));
|
|
||||||
|
|
||||||
let error = $ref(false);
|
|
||||||
let username = $ref("");
|
|
||||||
let password = $ref("");
|
|
||||||
let form: HTMLFormElement = $ref();
|
|
||||||
|
|
||||||
async function onLogin() {
|
|
||||||
const response = await fetch(`${config.base}/api/validateCredentials`, {
|
|
||||||
body: new FormData(form),
|
|
||||||
method: "post",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.status == 200) {
|
|
||||||
error = false;
|
|
||||||
window.location.href = `${config.base}/`;
|
|
||||||
} else {
|
|
||||||
error = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<route lang="yaml">
|
|
||||||
meta:
|
|
||||||
layout: splash
|
|
||||||
</route>
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<section class="section">
|
|
||||||
<div class="has-underline">
|
|
||||||
<h2 class="title is-4">{{ $t("settings.about") }}</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<span v-html="$t('settings.using-version', { version: currentVersion })"></span>
|
|
||||||
<div
|
|
||||||
v-if="hasUpdate"
|
|
||||||
v-html="$t('settings.update-available', { nextVersion: nextRelease.name, href: nextRelease.html_url })"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="section">
|
|
||||||
<div class="has-underline">
|
|
||||||
<h2 class="title is-4">{{ $t("settings.display") }}</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item">
|
|
||||||
<o-switch v-model="smallerScrollbars"> {{ $t("settings.small-scrollbars") }} </o-switch>
|
|
||||||
</div>
|
|
||||||
<div class="item">
|
|
||||||
<o-switch v-model="showTimestamp"> {{ $t("settings.show-timesamps") }} </o-switch>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item">
|
|
||||||
<o-switch v-model="softWrap"> {{ $t("settings.soft-wrap") }}</o-switch>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item">
|
|
||||||
<div class="columns is-vcentered">
|
|
||||||
<div class="column is-narrow">
|
|
||||||
<o-field>
|
|
||||||
<o-dropdown v-model="hourStyle" aria-role="list">
|
|
||||||
<template #trigger>
|
|
||||||
<o-button variant="primary" type="button">
|
|
||||||
<span class="is-capitalized">{{ hourStyle }}</span>
|
|
||||||
<span class="icon">
|
|
||||||
<carbon-caret-down />
|
|
||||||
</span>
|
|
||||||
</o-button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<o-dropdown-item :value="value" aria-role="listitem" v-for="value in ['auto', '12', '24']" :key="value">
|
|
||||||
<span class="is-capitalized">{{ value }}</span>
|
|
||||||
</o-dropdown-item>
|
|
||||||
</o-dropdown>
|
|
||||||
</o-field>
|
|
||||||
</div>
|
|
||||||
<div class="column">
|
|
||||||
{{ $t("settings.12-24-format") }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="item">
|
|
||||||
<div class="columns is-vcentered">
|
|
||||||
<div class="column is-narrow">
|
|
||||||
<o-field>
|
|
||||||
<o-dropdown v-model="size" aria-role="list">
|
|
||||||
<template #trigger>
|
|
||||||
<o-button variant="primary" type="button">
|
|
||||||
<span class="is-capitalized">{{ size }}</span>
|
|
||||||
<span class="icon">
|
|
||||||
<carbon-caret-down />
|
|
||||||
</span>
|
|
||||||
</o-button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<o-dropdown-item
|
|
||||||
:value="value"
|
|
||||||
aria-role="listitem"
|
|
||||||
v-for="value in ['small', 'medium', 'large']"
|
|
||||||
:key="value"
|
|
||||||
>
|
|
||||||
<span class="is-capitalized">{{ value }}</span>
|
|
||||||
</o-dropdown-item>
|
|
||||||
</o-dropdown>
|
|
||||||
</o-field>
|
|
||||||
</div>
|
|
||||||
<div class="column">{{ $t("settings.font-size") }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="item">
|
|
||||||
<div class="columns is-vcentered">
|
|
||||||
<div class="column is-narrow">
|
|
||||||
<o-field>
|
|
||||||
<o-dropdown v-model="lightTheme" aria-role="list">
|
|
||||||
<template #trigger>
|
|
||||||
<o-button variant="primary" type="button">
|
|
||||||
<span class="is-capitalized">{{ lightTheme }}</span>
|
|
||||||
<span class="icon">
|
|
||||||
<carbon-caret-down />
|
|
||||||
</span>
|
|
||||||
</o-button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<o-dropdown-item
|
|
||||||
:value="value"
|
|
||||||
aria-role="listitem"
|
|
||||||
v-for="value in ['auto', 'dark', 'light']"
|
|
||||||
:key="value"
|
|
||||||
>
|
|
||||||
<span class="is-capitalized">{{ value }}</span>
|
|
||||||
</o-dropdown-item>
|
|
||||||
</o-dropdown>
|
|
||||||
</o-field>
|
|
||||||
</div>
|
|
||||||
<div class="column">{{ $t("settings.color-scheme") }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section class="section">
|
|
||||||
<div class="has-underline">
|
|
||||||
<h2 class="title is-4">{{ $t("settings.options") }}</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item">
|
|
||||||
<o-switch v-model="search">
|
|
||||||
<span v-html="$t('settings.search')"></span>
|
|
||||||
</o-switch>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item">
|
|
||||||
<o-switch v-model="showAllContainers"> {{ $t("settings.show-stopped-containers") }} </o-switch>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import gt from "semver/functions/gt";
|
|
||||||
import {
|
|
||||||
search,
|
|
||||||
lightTheme,
|
|
||||||
smallerScrollbars,
|
|
||||||
showTimestamp,
|
|
||||||
hourStyle,
|
|
||||||
showAllContainers,
|
|
||||||
size,
|
|
||||||
softWrap,
|
|
||||||
} from "@/composables/settings";
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
setTitle(t("title.settings"));
|
|
||||||
|
|
||||||
const currentVersion = $ref(config.version);
|
|
||||||
let nextRelease = $ref({ html_url: "", name: "" });
|
|
||||||
let hasUpdate = $ref(false);
|
|
||||||
|
|
||||||
async function fetchNextRelease() {
|
|
||||||
if (!["dev", "master"].includes(currentVersion)) {
|
|
||||||
const response = await fetch("https://api.github.com/repos/amir20/dozzle/releases/latest");
|
|
||||||
if (response.ok) {
|
|
||||||
const release = await response.json();
|
|
||||||
hasUpdate = gt(release.tag_name, currentVersion);
|
|
||||||
nextRelease = release;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
hasUpdate = true;
|
|
||||||
nextRelease = {
|
|
||||||
html_url: "",
|
|
||||||
name: "master",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchNextRelease();
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.title {
|
|
||||||
color: var(--title-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
a.next-release {
|
|
||||||
text-decoration: underline;
|
|
||||||
&:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.section {
|
|
||||||
padding: 1rem 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.has-underline {
|
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
padding: 1em 0px;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item {
|
|
||||||
padding: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: #444;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
const router = useRouter();
|
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
const store = useContainerStore();
|
|
||||||
const { visibleContainers } = storeToRefs(store);
|
|
||||||
|
|
||||||
watch(visibleContainers, (newValue) => {
|
|
||||||
if (newValue) {
|
|
||||||
if (route.query.name) {
|
|
||||||
const [container, _] = visibleContainers.value.filter((c) => c.name == route.query.name);
|
|
||||||
if (container) {
|
|
||||||
router.push({ name: "container-id", params: { id: container.id } });
|
|
||||||
} else {
|
|
||||||
console.error(`No containers found matching name=${route.query.name}. Redirecting to /`);
|
|
||||||
router.push({ name: "index" });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error(`Expection query parameter name to be set. Redirecting to /`);
|
|
||||||
router.push({ name: "index" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<template></template>
|
|
||||||
@@ -1,17 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<section class="hero is-small mt-4">
|
<section class="level section pb-0-is-mobile">
|
||||||
<div class="hero-body">
|
|
||||||
<div class="container">
|
|
||||||
<div class="columns">
|
|
||||||
<div class="column is-narrow" v-if="secured">
|
|
||||||
<a class="button is-primary is-small" :href="`${base}/logout`">{{ $t("button.logout") }}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section class="level section">
|
|
||||||
<div class="level-item has-text-centered">
|
<div class="level-item has-text-centered">
|
||||||
<div>
|
<div>
|
||||||
<p class="title">{{ containers.length }}</p>
|
<p class="title">{{ containers.length }}</p>
|
||||||
@@ -44,8 +33,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="columns is-centered section is-marginless">
|
<section class="columns is-centered section is-marginless pt-0-is-mobile">
|
||||||
<div class="column is-4">
|
<div class="column is-12-mobile is-6-tablet is-5-desktop is-4-fullhd">
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<p class="panel-heading">{{ $t("label.containers") }}</p>
|
<p class="panel-heading">{{ $t("label.containers") }}</p>
|
||||||
<div class="panel-block">
|
<div class="panel-block">
|
||||||
@@ -54,8 +43,8 @@
|
|||||||
class="input"
|
class="input"
|
||||||
type="text"
|
type="text"
|
||||||
:placeholder="$t('placeholder.search-containers')"
|
:placeholder="$t('placeholder.search-containers')"
|
||||||
v-model="search"
|
v-model="query"
|
||||||
@keyup.esc="search.value = null"
|
@keyup.esc="query = ''"
|
||||||
@keyup.enter="onEnter()"
|
@keyup.enter="onEnter()"
|
||||||
/>
|
/>
|
||||||
<span class="icon is-left">
|
<span class="icon is-left">
|
||||||
@@ -63,13 +52,13 @@
|
|||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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 === 'running' }" @click="sort = 'running'">{{ $t("label.running") }}</a>
|
||||||
<a :class="{ 'is-active': sort === 'all' }" @click="sort = 'all'">{{ $t("label.all") }}</a>
|
<a :class="{ 'is-active': sort === 'all' }" @click="sort = 'all'">{{ $t("label.all") }}</a>
|
||||||
</p>
|
</p>
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'container-id', params: { id: item.id } }"
|
: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"
|
:key="item.id"
|
||||||
class="panel-block"
|
class="panel-block"
|
||||||
>
|
>
|
||||||
@@ -86,54 +75,59 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import fuzzysort from "fuzzysort";
|
|
||||||
import SearchIcon from "~icons/mdi-light/magnify";
|
import SearchIcon from "~icons/mdi-light/magnify";
|
||||||
|
import { useFuse } from "@vueuse/integrations/useFuse";
|
||||||
|
|
||||||
const { base, version, secured } = config;
|
const { base, version, secured } = config;
|
||||||
const containerStore = useContainerStore();
|
const containerStore = useContainerStore();
|
||||||
const { containers } = storeToRefs(containerStore);
|
const { containers } = storeToRefs(containerStore);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const sort = ref("running");
|
const sort = $ref("running");
|
||||||
const search = ref();
|
const query = ref("");
|
||||||
|
|
||||||
const results = computed(() => {
|
const mostRecentContainers = $computed(() => [...containers.value].sort((a, b) => b.created - a.created));
|
||||||
if (search.value) {
|
const runningContainers = $computed(() => mostRecentContainers.filter((c) => c.state === "running"));
|
||||||
return fuzzysort.go(search.value, containers.value, { key: "name" }).map((i) => i.obj);
|
|
||||||
|
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":
|
case "all":
|
||||||
return mostRecentContainers.value;
|
return mostRecentContainers;
|
||||||
case "running":
|
case "running":
|
||||||
return runningContainers.value;
|
return runningContainers;
|
||||||
default:
|
default:
|
||||||
throw `Invalid sort order: ${sort.value}`;
|
throw `Invalid sort order: ${sort}`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const mostRecentContainers = computed(() => [...containers.value].sort((a, b) => b.created - a.created));
|
let totalCpu = $ref(0);
|
||||||
const runningContainers = computed(() => mostRecentContainers.value.filter((c) => c.state === "running"));
|
|
||||||
const totalCpu = ref(0);
|
|
||||||
useIntervalFn(
|
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,
|
1000,
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
const totalMem = ref(0);
|
|
||||||
|
|
||||||
|
let totalMem = $ref(0);
|
||||||
useIntervalFn(
|
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,
|
1000,
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
function onEnter() {
|
function onEnter() {
|
||||||
if (results.value.length == 1) {
|
if (data.value.length > 0) {
|
||||||
const [item] = results.value;
|
const item = data.value[0];
|
||||||
router.push({ name: "container-id", params: { id: item.id } });
|
router.push({ name: "container-id", params: { id: item.id } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,6 +153,16 @@ function onEnter() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.pb-0-is-mobile {
|
||||||
|
padding-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pt-0-is-mobile {
|
||||||
|
padding-top: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
padding: 10px 3px;
|
padding: 10px 3px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
import { acceptHMRUpdate, defineStore } from "pinia";
|
import { acceptHMRUpdate, defineStore } from "pinia";
|
||||||
import { ref, Ref, computed } from "vue";
|
import { Ref, UnwrapNestedRefs } from "vue";
|
||||||
|
import type { ContainerJson, ContainerStat } from "@/types/Container";
|
||||||
import { showAllContainers } from "@/composables/settings";
|
import { Container } from "@/models/Container";
|
||||||
import config from "@/stores/config";
|
|
||||||
import type { Container, ContainerStat } from "@/types/Container";
|
|
||||||
import { watchOnce } from "@vueuse/core";
|
|
||||||
|
|
||||||
export const useContainerStore = defineStore("container", () => {
|
export const useContainerStore = defineStore("container", () => {
|
||||||
const containers = ref<Container[]>([]);
|
const containers: Ref<Container[]> = ref([]);
|
||||||
const activeContainerIds = ref<string[]>([]);
|
const activeContainerIds: Ref<string[]> = ref([]);
|
||||||
|
|
||||||
const allContainersById = computed(() =>
|
const allContainersById = computed(() =>
|
||||||
containers.value.reduce((acc, container) => {
|
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 activeContainers = computed(() => activeContainerIds.value.map((id) => allContainersById.value[id]));
|
||||||
|
|
||||||
const es = new EventSource(`${config.base}/api/events/stream`);
|
const es = new EventSource(`${config.base}/api/events/stream`);
|
||||||
es.addEventListener(
|
es.addEventListener("containers-changed", (e: Event) =>
|
||||||
"containers-changed",
|
setContainers(JSON.parse((e as MessageEvent).data) as ContainerJson[])
|
||||||
(e: Event) => (containers.value = JSON.parse((e as MessageEvent).data)),
|
|
||||||
false
|
|
||||||
);
|
);
|
||||||
es.addEventListener(
|
es.addEventListener("container-stat", (e) => {
|
||||||
"container-stat",
|
const stat = JSON.parse((e as MessageEvent).data) as ContainerStat;
|
||||||
(e) => {
|
const container = allContainersById.value[stat.id] as unknown as UnwrapNestedRefs<Container>;
|
||||||
const stat = JSON.parse((e as MessageEvent).data) as ContainerStat;
|
if (container) {
|
||||||
const container = allContainersById.value[stat.id];
|
const { id, ...rest } = stat;
|
||||||
if (container) {
|
container.stat = rest;
|
||||||
container.stat = stat;
|
}
|
||||||
|
});
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
},
|
return new Container(c.id, c.created, c.image, c.name, c.command, c.status, c.state);
|
||||||
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
|
|
||||||
);
|
|
||||||
|
|
||||||
const currentContainer = (id: Ref<string>) => computed(() => allContainersById.value[id.value]);
|
const currentContainer = (id: Ref<string>) => computed(() => allContainersById.value[id.value]);
|
||||||
const appendActiveContainer = ({ id }: Container) => activeContainerIds.value.push(id);
|
const appendActiveContainer = ({ id }: Container) => activeContainerIds.value.push(id);
|
||||||
|
|||||||
@@ -75,10 +75,10 @@ $light-toolbar-color: rgba($grey-darker, 0.7);
|
|||||||
|
|
||||||
--border-color: #{$grey-lighter};
|
--border-color: #{$grey-lighter};
|
||||||
--border-hover-color: var(--secondary-color);
|
--border-hover-color: var(--secondary-color);
|
||||||
--logo-color: #{$grey-darker};
|
--logo-color: var(--secondary-color);
|
||||||
|
|
||||||
--primary-color: #{$turquoise};
|
--primary-color: #{$turquoise};
|
||||||
--secondary-color: #d8f0ca;
|
--secondary-color: rgb(249 115 22);
|
||||||
|
|
||||||
--body-background-color: #{$white-bis};
|
--body-background-color: #{$white-bis};
|
||||||
--action-toolbar-background-color: #{$light-toolbar-color};
|
--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 {
|
.splitpanes__splitter {
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
}
|
}
|
||||||
@@ -177,6 +183,14 @@ html.has-custom-scrollbars {
|
|||||||
display: contents;
|
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 {
|
mark {
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background-color: var(--secondary-color);
|
background-color: var(--secondary-color);
|
||||||
@@ -192,3 +206,8 @@ mark {
|
|||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button.is-rounded:hover {
|
||||||
|
color: var(--text-strong-color);
|
||||||
|
background: var(--scheme-main-ter);
|
||||||
|
}
|
||||||
|
|||||||
23
assets/types/Container.d.ts
vendored
23
assets/types/Container.d.ts
vendored
@@ -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 {
|
export interface ContainerStat {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
readonly cpu: number;
|
readonly cpu: number;
|
||||||
readonly memory: number;
|
readonly memory: number;
|
||||||
readonly memoryUsage: 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
1
assets/types/Point.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
type Point<T> = { x: number; y: number; value?: T };
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Container } from "@/types/Container";
|
import { Container } from "@/models/Container";
|
||||||
import { useStorage } from "@vueuse/core";
|
import { useStorage } from "@vueuse/core";
|
||||||
import { computed, ComputedRef } from "vue";
|
import { computed, ComputedRef } from "vue";
|
||||||
|
|
||||||
|
|||||||
17
docker/calculation.go
Normal file
17
docker/calculation.go
Normal 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)
|
||||||
|
}
|
||||||
57
docker/calculation_test.go
Normal file
57
docker/calculation_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(v.PreCPUStats.CPUUsage.TotalUsage)
|
||||||
systemDelta = float64(v.CPUStats.SystemUsage) - float64(v.PreCPUStats.SystemUsage)
|
systemDelta = float64(v.CPUStats.SystemUsage) - float64(v.PreCPUStats.SystemUsage)
|
||||||
cpuPercent = int64((cpuDelta / systemDelta) * float64(ncpus) * 100)
|
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)
|
memPercent = int64(float64(memUsage) / float64(v.MemoryStats.Limit) * 100)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM cypress/included:10.8.0
|
FROM cypress/included:11.2.0
|
||||||
|
|
||||||
RUN apt install curl && curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
|
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: 33 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 34 KiB |
@@ -9,7 +9,7 @@ context("Dozzle default mode", { baseUrl: Cypress.env("DOZZLE_DEFAULT") }, () =>
|
|||||||
cy.get("li.running", { timeout: 10000 }).removeDates().replaceSkippedElements().matchImage();
|
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.title().should("eq", "1 containers - Dozzle");
|
||||||
|
|
||||||
cy.get("li.running:first a").click();
|
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");
|
cy.title().should("include", "- Dozzle");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("settings page", () => {
|
it("navigating to setting page works ", () => {
|
||||||
cy.get("a[href='/settings']").click();
|
cy.get("a[href='/settings']").click();
|
||||||
|
|
||||||
cy.contains("About");
|
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");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
},
|
},
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@frsource/cypress-plugin-visual-regression-diff": "^2.0.0",
|
"@frsource/cypress-plugin-visual-regression-diff": "^3.1.2",
|
||||||
"cypress": "^10.8.0",
|
"cypress": "^11.0.0",
|
||||||
"typescript": "^4.8.3"
|
"typescript": "^4.8.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
127
e2e/pnpm-lock.yaml
generated
127
e2e/pnpm-lock.yaml
generated
@@ -1,14 +1,14 @@
|
|||||||
lockfileVersion: 5.4
|
lockfileVersion: 5.4
|
||||||
|
|
||||||
specifiers:
|
specifiers:
|
||||||
'@frsource/cypress-plugin-visual-regression-diff': ^2.0.0
|
'@frsource/cypress-plugin-visual-regression-diff': ^3.1.2
|
||||||
cypress: ^10.8.0
|
cypress: ^11.0.0
|
||||||
typescript: ^4.8.3
|
typescript: ^4.8.4
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@frsource/cypress-plugin-visual-regression-diff': 2.0.0_cypress@10.8.0
|
'@frsource/cypress-plugin-visual-regression-diff': 3.1.2_cypress@11.0.0
|
||||||
cypress: 10.8.0
|
cypress: 11.0.0
|
||||||
typescript: 4.8.3
|
typescript: 4.8.4
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -52,21 +52,28 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@frsource/cypress-plugin-visual-regression-diff/2.0.0_cypress@10.8.0:
|
/@frsource/base64/1.0.4:
|
||||||
resolution: {integrity: sha512-hwMEZzD0tFbNNNbEjdF8q2ELpxhby25VQ665QUJy1soGPiGwY+rBDdktFg3EXVGs/Euq0Nj8hq/K18BWYoX10g==}
|
resolution: {integrity: sha512-IphM1ro1cvV5CqJWzX/LvPJcUE26cwgt/zMtBdssvTrfSNhh9KjfS2VXTA8SivkvPHK1DsdPnoMoXitmx8c3Cg==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@frsource/cypress-plugin-visual-regression-diff/3.1.2_cypress@11.0.0:
|
||||||
|
resolution: {integrity: sha512-Tl04uSoyRyt/mUBEWnU49ZOYtQNRJIel6DwrBADGfohgQ81AGBTbO7yXOhVqIrUZ/PIJaAJG2J2vw6MDw1ObmA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
cypress: '>=4.5.0'
|
cypress: '>=4.5.0'
|
||||||
dependencies:
|
dependencies:
|
||||||
cypress: 10.8.0
|
'@frsource/base64': 1.0.4
|
||||||
|
cypress: 11.0.0
|
||||||
|
glob: 8.0.3
|
||||||
|
meta-png: 1.0.3
|
||||||
move-file: 2.1.0
|
move-file: 2.1.0
|
||||||
pixelmatch: 5.3.0
|
pixelmatch: 5.3.0
|
||||||
pngjs: 6.0.0
|
pngjs: 6.0.0
|
||||||
sharp: 0.30.7
|
sharp: 0.31.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@types/node/14.18.29:
|
/@types/node/14.18.33:
|
||||||
resolution: {integrity: sha512-LhF+9fbIX4iPzhsRLpK5H7iPdvW8L4IwGciXQIOEcuF62+9nw/VQVsOViAOOGxY3OlOKGLFv0sWwJXdwQeTn6A==}
|
resolution: {integrity: sha512-qelS/Ra6sacc4loe/3MSjXNL1dNQ/GjxNHVzuChwMfmk7HuycRLVQN2qNY3XahK+fZc5E2szqQSKUyAF0E+2bg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@types/sinonjs__fake-timers/8.1.1:
|
/@types/sinonjs__fake-timers/8.1.1:
|
||||||
@@ -81,7 +88,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==}
|
resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 14.18.29
|
'@types/node': 14.18.33
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -195,6 +202,12 @@ packages:
|
|||||||
concat-map: 0.0.1
|
concat-map: 0.0.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/brace-expansion/2.0.1:
|
||||||
|
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
|
||||||
|
dependencies:
|
||||||
|
balanced-match: 1.0.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/buffer-crc32/0.2.13:
|
/buffer-crc32/0.2.13:
|
||||||
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
|
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
|
||||||
dev: false
|
dev: false
|
||||||
@@ -232,8 +245,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
|
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/ci-info/3.4.0:
|
/ci-info/3.5.0:
|
||||||
resolution: {integrity: sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug==}
|
resolution: {integrity: sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/clean-stack/2.2.0:
|
/clean-stack/2.2.0:
|
||||||
@@ -329,15 +342,15 @@ packages:
|
|||||||
which: 2.0.2
|
which: 2.0.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/cypress/10.8.0:
|
/cypress/11.0.0:
|
||||||
resolution: {integrity: sha512-QVse0dnLm018hgti2enKMVZR9qbIO488YGX06nH5j3Dg1isL38DwrBtyrax02CANU6y8F4EJUuyW6HJKw1jsFA==}
|
resolution: {integrity: sha512-mYXGi2Wjmy9shRjAUDugSMOr4uuzE2nl7hXQi3oQkIQsnwwBx2HNB8Vbfsix3A0zyPXlL5jTcbb6rCVWKRaXbg==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@cypress/request': 2.88.10
|
'@cypress/request': 2.88.10
|
||||||
'@cypress/xvfb': 1.2.4_supports-color@8.1.1
|
'@cypress/xvfb': 1.2.4_supports-color@8.1.1
|
||||||
'@types/node': 14.18.29
|
'@types/node': 14.18.33
|
||||||
'@types/sinonjs__fake-timers': 8.1.1
|
'@types/sinonjs__fake-timers': 8.1.1
|
||||||
'@types/sizzle': 2.3.3
|
'@types/sizzle': 2.3.3
|
||||||
arch: 2.2.0
|
arch: 2.2.0
|
||||||
@@ -351,7 +364,7 @@ packages:
|
|||||||
cli-table3: 0.6.3
|
cli-table3: 0.6.3
|
||||||
commander: 5.1.0
|
commander: 5.1.0
|
||||||
common-tags: 1.8.2
|
common-tags: 1.8.2
|
||||||
dayjs: 1.11.5
|
dayjs: 1.11.6
|
||||||
debug: 4.3.4_supports-color@8.1.1
|
debug: 4.3.4_supports-color@8.1.1
|
||||||
enquirer: 2.3.6
|
enquirer: 2.3.6
|
||||||
eventemitter2: 6.4.7
|
eventemitter2: 6.4.7
|
||||||
@@ -367,12 +380,12 @@ packages:
|
|||||||
listr2: 3.14.0_enquirer@2.3.6
|
listr2: 3.14.0_enquirer@2.3.6
|
||||||
lodash: 4.17.21
|
lodash: 4.17.21
|
||||||
log-symbols: 4.1.0
|
log-symbols: 4.1.0
|
||||||
minimist: 1.2.6
|
minimist: 1.2.7
|
||||||
ospath: 1.2.2
|
ospath: 1.2.2
|
||||||
pretty-bytes: 5.6.0
|
pretty-bytes: 5.6.0
|
||||||
proxy-from-env: 1.0.0
|
proxy-from-env: 1.0.0
|
||||||
request-progress: 3.0.0
|
request-progress: 3.0.0
|
||||||
semver: 7.3.7
|
semver: 7.3.8
|
||||||
supports-color: 8.1.1
|
supports-color: 8.1.1
|
||||||
tmp: 0.2.1
|
tmp: 0.2.1
|
||||||
untildify: 4.0.0
|
untildify: 4.0.0
|
||||||
@@ -386,8 +399,8 @@ packages:
|
|||||||
assert-plus: 1.0.0
|
assert-plus: 1.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/dayjs/1.11.5:
|
/dayjs/1.11.6:
|
||||||
resolution: {integrity: sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==}
|
resolution: {integrity: sha512-zZbY5giJAinCG+7AGaw0wIhNZ6J8AhWuSXKvuc1KAyMiRsvGQWqh4L+MomvhdAYjN+lqvVCMq1I41e3YHvXkyQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/debug/3.2.7_supports-color@8.1.1:
|
/debug/3.2.7_supports-color@8.1.1:
|
||||||
@@ -598,6 +611,17 @@ packages:
|
|||||||
path-is-absolute: 1.0.1
|
path-is-absolute: 1.0.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/glob/8.0.3:
|
||||||
|
resolution: {integrity: sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
dependencies:
|
||||||
|
fs.realpath: 1.0.0
|
||||||
|
inflight: 1.0.6
|
||||||
|
inherits: 2.0.4
|
||||||
|
minimatch: 5.1.0
|
||||||
|
once: 1.4.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/global-dirs/3.0.0:
|
/global-dirs/3.0.0:
|
||||||
resolution: {integrity: sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==}
|
resolution: {integrity: sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -665,7 +689,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==}
|
resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
ci-info: 3.4.0
|
ci-info: 3.5.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/is-fullwidth-code-point/3.0.0:
|
/is-fullwidth-code-point/3.0.0:
|
||||||
@@ -758,7 +782,7 @@ packages:
|
|||||||
log-update: 4.0.0
|
log-update: 4.0.0
|
||||||
p-map: 4.0.0
|
p-map: 4.0.0
|
||||||
rfdc: 1.3.0
|
rfdc: 1.3.0
|
||||||
rxjs: 7.5.6
|
rxjs: 7.5.7
|
||||||
through: 2.3.8
|
through: 2.3.8
|
||||||
wrap-ansi: 7.0.0
|
wrap-ansi: 7.0.0
|
||||||
dev: false
|
dev: false
|
||||||
@@ -800,6 +824,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
|
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/meta-png/1.0.3:
|
||||||
|
resolution: {integrity: sha512-ts8SCT3qTvHBA723m1NYUKwzGDxYNCkMrHYrX0t7YaIch8giqpX+KyJEpCUFW7XfCWXiX5KSlQr4p3Ci9lrSug==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/mime-db/1.52.0:
|
/mime-db/1.52.0:
|
||||||
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
@@ -828,8 +856,15 @@ packages:
|
|||||||
brace-expansion: 1.1.11
|
brace-expansion: 1.1.11
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/minimist/1.2.6:
|
/minimatch/5.1.0:
|
||||||
resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==}
|
resolution: {integrity: sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
dependencies:
|
||||||
|
brace-expansion: 2.0.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/minimist/1.2.7:
|
||||||
|
resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/mkdirp-classic/0.5.3:
|
/mkdirp-classic/0.5.3:
|
||||||
@@ -855,11 +890,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
|
resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/node-abi/3.24.0:
|
/node-abi/3.28.0:
|
||||||
resolution: {integrity: sha512-YPG3Co0luSu6GwOBsmIdGW6Wx0NyNDLg/hriIyDllVsNwnI6UeqaWShxC3lbH4LtEQUgoLP3XR1ndXiDAWvmRw==}
|
resolution: {integrity: sha512-fRlDb4I0eLcQeUvGq7IY3xHrSb0c9ummdvDSYWfT9+LKP+3jCKw/tKoqaM7r1BAoiAC6GtwyjaGnOz6B3OtF+A==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dependencies:
|
dependencies:
|
||||||
semver: 7.3.7
|
semver: 7.3.8
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/node-addon-api/5.0.0:
|
/node-addon-api/5.0.0:
|
||||||
@@ -945,10 +980,10 @@ packages:
|
|||||||
detect-libc: 2.0.1
|
detect-libc: 2.0.1
|
||||||
expand-template: 2.0.3
|
expand-template: 2.0.3
|
||||||
github-from-package: 0.0.0
|
github-from-package: 0.0.0
|
||||||
minimist: 1.2.6
|
minimist: 1.2.7
|
||||||
mkdirp-classic: 0.5.3
|
mkdirp-classic: 0.5.3
|
||||||
napi-build-utils: 1.0.2
|
napi-build-utils: 1.0.2
|
||||||
node-abi: 3.24.0
|
node-abi: 3.28.0
|
||||||
pump: 3.0.0
|
pump: 3.0.0
|
||||||
rc: 1.2.8
|
rc: 1.2.8
|
||||||
simple-get: 4.0.1
|
simple-get: 4.0.1
|
||||||
@@ -992,7 +1027,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
deep-extend: 0.6.0
|
deep-extend: 0.6.0
|
||||||
ini: 1.3.8
|
ini: 1.3.8
|
||||||
minimist: 1.2.6
|
minimist: 1.2.7
|
||||||
strip-json-comments: 2.0.1
|
strip-json-comments: 2.0.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@@ -1030,10 +1065,10 @@ packages:
|
|||||||
glob: 7.2.3
|
glob: 7.2.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/rxjs/7.5.6:
|
/rxjs/7.5.7:
|
||||||
resolution: {integrity: sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==}
|
resolution: {integrity: sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.4.0
|
tslib: 2.4.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/safe-buffer/5.2.1:
|
/safe-buffer/5.2.1:
|
||||||
@@ -1044,24 +1079,24 @@ packages:
|
|||||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/semver/7.3.7:
|
/semver/7.3.8:
|
||||||
resolution: {integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==}
|
resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
lru-cache: 6.0.0
|
lru-cache: 6.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/sharp/0.30.7:
|
/sharp/0.31.2:
|
||||||
resolution: {integrity: sha512-G+MY2YW33jgflKPTXXptVO28HvNOo9G3j0MybYAHeEmby+QuD2U98dT6ueht9cv/XDqZspSpIhoSW+BAKJ7Hig==}
|
resolution: {integrity: sha512-DUdNVEXgS5A97cTagSLIIp8dUZ/lZtk78iNVZgHdHbx1qnQR7JAHY0BnXnwwH39Iw+VKhO08CTYhIg0p98vQ5Q==}
|
||||||
engines: {node: '>=12.13.0'}
|
engines: {node: '>=14.15.0'}
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dependencies:
|
dependencies:
|
||||||
color: 4.2.3
|
color: 4.2.3
|
||||||
detect-libc: 2.0.1
|
detect-libc: 2.0.1
|
||||||
node-addon-api: 5.0.0
|
node-addon-api: 5.0.0
|
||||||
prebuild-install: 7.1.1
|
prebuild-install: 7.1.1
|
||||||
semver: 7.3.7
|
semver: 7.3.8
|
||||||
simple-get: 4.0.1
|
simple-get: 4.0.1
|
||||||
tar-fs: 2.1.1
|
tar-fs: 2.1.1
|
||||||
tunnel-agent: 0.6.0
|
tunnel-agent: 0.6.0
|
||||||
@@ -1224,8 +1259,8 @@ packages:
|
|||||||
punycode: 2.1.1
|
punycode: 2.1.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/tslib/2.4.0:
|
/tslib/2.4.1:
|
||||||
resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
|
resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/tunnel-agent/0.6.0:
|
/tunnel-agent/0.6.0:
|
||||||
@@ -1243,8 +1278,8 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/typescript/4.8.3:
|
/typescript/4.8.4:
|
||||||
resolution: {integrity: sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==}
|
resolution: {integrity: sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==}
|
||||||
engines: {node: '>=4.2.0'}
|
engines: {node: '>=4.2.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: false
|
dev: false
|
||||||
|
|||||||
10
go.mod
10
go.mod
@@ -5,7 +5,7 @@ require (
|
|||||||
github.com/alexflint/go-arg v1.4.3
|
github.com/alexflint/go-arg v1.4.3
|
||||||
github.com/beme/abide v0.0.0-20190723115211-635a09831760
|
github.com/beme/abide v0.0.0-20190723115211-635a09831760
|
||||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||||
github.com/docker/docker v20.10.18+incompatible
|
github.com/docker/docker v20.10.21+incompatible
|
||||||
github.com/docker/go-connections v0.4.0 // indirect
|
github.com/docker/go-connections v0.4.0 // indirect
|
||||||
github.com/docker/go-units v0.4.0 // indirect
|
github.com/docker/go-units v0.4.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.0
|
github.com/dustin/go-humanize v1.0.0
|
||||||
@@ -19,9 +19,9 @@ require (
|
|||||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||||
github.com/sergi/go-diff v1.1.0 // indirect
|
github.com/sergi/go-diff v1.1.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.0
|
github.com/sirupsen/logrus v1.9.0
|
||||||
github.com/spf13/afero v1.9.2
|
github.com/spf13/afero v1.9.3
|
||||||
github.com/stretchr/objx v0.4.0 // indirect
|
github.com/stretchr/objx v0.5.0 // indirect
|
||||||
github.com/stretchr/testify v1.8.0
|
github.com/stretchr/testify v1.8.1
|
||||||
golang.org/x/net v0.0.0-20211104170005-ce137452f963 // indirect
|
golang.org/x/net v0.0.0-20211104170005-ce137452f963 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
@@ -40,4 +40,4 @@ require (
|
|||||||
gotest.tools/v3 v3.0.3 // indirect
|
gotest.tools/v3 v3.0.3 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.18
|
go 1.19
|
||||||
|
|||||||
12
go.sum
12
go.sum
@@ -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/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 h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
||||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
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.21+incompatible h1:UTLdBmHk3bEY+w8qeO5KttOhy6OmXWsl/FEet9Uswog=
|
||||||
github.com/docker/docker v20.10.18+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v20.10.21+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 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||||
@@ -183,17 +183,21 @@ github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0
|
|||||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw=
|
github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw=
|
||||||
github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
||||||
|
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
|
||||||
|
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
|
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ button:
|
|||||||
logout: Logout
|
logout: Logout
|
||||||
login: Login
|
login: Login
|
||||||
placeholder:
|
placeholder:
|
||||||
search-containers: Search Containers
|
search-containers: Search containers (⌘ + k, ⌃k)
|
||||||
settings:
|
settings:
|
||||||
display: Display
|
display: Display
|
||||||
small-scrollbars: Use smaller scrollbars
|
small-scrollbars: Use smaller scrollbars
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ button:
|
|||||||
logout: Cerrar la sesión
|
logout: Cerrar la sesión
|
||||||
login: Iniciar sesión
|
login: Iniciar sesión
|
||||||
placeholder:
|
placeholder:
|
||||||
search-containers: Buscar Contenedores
|
search-containers: Buscar contenedores (⌘ + K, CTRL + K)
|
||||||
settings:
|
settings:
|
||||||
display: Vista
|
display: Vista
|
||||||
small-scrollbars: Utilizar barras de desplazamiento más pequeñas
|
small-scrollbars: Utilizar barras de desplazamiento más pequeñas
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ button:
|
|||||||
logout: Terminar sessão
|
logout: Terminar sessão
|
||||||
login: Iniciar sessão
|
login: Iniciar sessão
|
||||||
placeholder:
|
placeholder:
|
||||||
search-containers: Pesquisar Contentores
|
search-containers: Pesquisar contentores (⌘ + K, CTRL + K)
|
||||||
settings:
|
settings:
|
||||||
display: Visão
|
display: Visão
|
||||||
small-scrollbars: Usar barras de rolagem mais pequenas
|
small-scrollbars: Usar barras de rolagem mais pequenas
|
||||||
|
|||||||
49
locales/ru.yml
Normal file
49
locales/ru.yml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
toolbar:
|
||||||
|
clear: Очистить
|
||||||
|
download: Скачать
|
||||||
|
search: Поиск
|
||||||
|
label:
|
||||||
|
containers: Контейнеры
|
||||||
|
total-containers: Всего Контейнеров
|
||||||
|
running: Запущенные
|
||||||
|
total-cpu-usage: Использование процессора
|
||||||
|
total-mem-usage: Использование памяти
|
||||||
|
dozzle-version: Версия Dozzle
|
||||||
|
all: Все
|
||||||
|
password: Пароль
|
||||||
|
username: Имя пользователя
|
||||||
|
tooltip:
|
||||||
|
search: Поиск контейнеров (⌘ + k, ⌃k)
|
||||||
|
pin-column: Закрепить столбец
|
||||||
|
error:
|
||||||
|
page-not-found: Эта страница не доступна.
|
||||||
|
invalid-auth: Имя пользователя или пароль неверны.
|
||||||
|
logs-skipped: Пропущено {total} записей
|
||||||
|
title:
|
||||||
|
page-not-found: Страница не найдена
|
||||||
|
login: Требуется авторизация
|
||||||
|
settings: Настройки
|
||||||
|
button:
|
||||||
|
logout: Выйти
|
||||||
|
login: Войти
|
||||||
|
placeholder:
|
||||||
|
search-containers: Поиск контейнеров (⌘ + k, ⌃k)
|
||||||
|
settings:
|
||||||
|
display: Вид
|
||||||
|
small-scrollbars: Уменьшенная полоса прокрутки
|
||||||
|
show-timesamps: Показывать временные метки
|
||||||
|
soft-wrap: Плавный перенос текста
|
||||||
|
12-24-format: >-
|
||||||
|
Формат времени. По умолчанию, Dozzle будет использовать локализацию вашего браузера.
|
||||||
|
font-size: Размер шрифта
|
||||||
|
color-scheme: Цветовая схема
|
||||||
|
options: Опции
|
||||||
|
show-stopped-containers: Показывать остановленные контейнеры
|
||||||
|
about: Информация
|
||||||
|
search: >-
|
||||||
|
Включить поиск с помощью Dozzle, используя <code>command+f</code> или
|
||||||
|
<code>ctrl+f</code>
|
||||||
|
using-version: Вы используете версию Dozzle {version}.
|
||||||
|
update-available: >-
|
||||||
|
Доступна новая версия! Обновить до <a :href="{href}" class="next-release"
|
||||||
|
target="_blank" rel="noreferrer noopener">{nextVersion}</a>.
|
||||||
20
main.go
20
main.go
@@ -24,6 +24,16 @@ var (
|
|||||||
version = "head"
|
version = "head"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type DockerSecret struct {
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DockerSecret) UnmarshalText(b []byte) error {
|
||||||
|
v, err := os.ReadFile(string(b))
|
||||||
|
s.Value = strings.Trim(string(v), "\r\n")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
type args struct {
|
type args struct {
|
||||||
Addr string `arg:"env:DOZZLE_ADDR" default:":8080" help:"sets host:port to bind for server. This is rarely needed inside a docker container."`
|
Addr string `arg:"env:DOZZLE_ADDR" default:":8080" help:"sets host:port to bind for server. This is rarely needed inside a docker container."`
|
||||||
Base string `arg:"env:DOZZLE_BASE" default:"/" help:"sets the base for http router."`
|
Base string `arg:"env:DOZZLE_BASE" default:"/" help:"sets the base for http router."`
|
||||||
@@ -31,6 +41,8 @@ type args struct {
|
|||||||
TailSize int `arg:"env:DOZZLE_TAILSIZE" default:"300" help:"update the initial tail size when fetching logs."`
|
TailSize int `arg:"env:DOZZLE_TAILSIZE" default:"300" help:"update the initial tail size when fetching logs."`
|
||||||
Username string `arg:"env:DOZZLE_USERNAME" help:"sets the username for auth."`
|
Username string `arg:"env:DOZZLE_USERNAME" help:"sets the username for auth."`
|
||||||
Password string `arg:"env:DOZZLE_PASSWORD" help:"sets password for auth"`
|
Password string `arg:"env:DOZZLE_PASSWORD" help:"sets password for auth"`
|
||||||
|
UsernameFile *DockerSecret `arg:"env:DOZZLE_USERNAME_FILE" help:"sets the secret path read username for auth."`
|
||||||
|
PasswordFile *DockerSecret `arg:"env:DOZZLE_PASSWORD_FILE" help:"sets the secret path read password for auth"`
|
||||||
NoAnalytics bool `arg:"--no-analytics,env:DOZZLE_NO_ANALYTICS" help:"disables anonymous analytics"`
|
NoAnalytics bool `arg:"--no-analytics,env:DOZZLE_NO_ANALYTICS" help:"disables anonymous analytics"`
|
||||||
WaitForDockerSeconds int `arg:"--wait-for-docker-seconds,env:DOZZLE_WAIT_FOR_DOCKER_SECONDS" help:"wait for docker to be available for at most this many seconds before starting the server."`
|
WaitForDockerSeconds int `arg:"--wait-for-docker-seconds,env:DOZZLE_WAIT_FOR_DOCKER_SECONDS" help:"wait for docker to be available for at most this many seconds before starting the server."`
|
||||||
FilterStrings []string `arg:"env:DOZZLE_FILTER,--filter,separate" help:"filters docker containers using Docker syntax."`
|
FilterStrings []string `arg:"env:DOZZLE_FILTER,--filter,separate" help:"filters docker containers using Docker syntax."`
|
||||||
@@ -93,6 +105,14 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if args.Username == "" && args.UsernameFile != nil {
|
||||||
|
args.Username = args.UsernameFile.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.Password == "" && args.PasswordFile != nil {
|
||||||
|
args.Password = args.PasswordFile.Value
|
||||||
|
}
|
||||||
|
|
||||||
if args.Username != "" || args.Password != "" {
|
if args.Username != "" || args.Password != "" {
|
||||||
if args.Username == "" || args.Password == "" {
|
if args.Username == "" || args.Password == "" {
|
||||||
log.Fatalf("Username AND password are required for authentication")
|
log.Fatalf("Username AND password are required for authentication")
|
||||||
|
|||||||
79
package.json
79
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dozzle",
|
"name": "dozzle",
|
||||||
"version": "4.1.6",
|
"version": "4.4.1",
|
||||||
"description": "Realtime log viewer for docker containers. ",
|
"description": "Realtime log viewer for docker containers. ",
|
||||||
"homepage": "https://github.com/amir20/dozzle#readme",
|
"homepage": "https://github.com/amir20/dozzle#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
@@ -22,56 +22,69 @@
|
|||||||
"postinstall": "husky install"
|
"postinstall": "husky install"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iconify-json/carbon": "^1.1.8",
|
"@iconify-json/carbon": "^1.1.10",
|
||||||
"@iconify-json/cil": "^1.1.2",
|
"@iconify-json/cil": "^1.1.2",
|
||||||
"@iconify-json/mdi": "^1.1.33",
|
"@iconify-json/mdi": "^1.1.34",
|
||||||
"@iconify-json/mdi-light": "^1.1.2",
|
"@iconify-json/mdi-light": "^1.1.2",
|
||||||
"@iconify-json/octicon": "^1.1.17",
|
"@iconify-json/octicon": "^1.1.23",
|
||||||
"@intlify/vite-plugin-vue-i18n": "^6.0.1",
|
"@oruga-ui/oruga-next": "^0.5.8",
|
||||||
"@oruga-ui/oruga-next": "^0.5.6",
|
|
||||||
"@oruga-ui/theme-bulma": "^0.2.7",
|
"@oruga-ui/theme-bulma": "^0.2.7",
|
||||||
"@vitejs/plugin-vue": "3.1.0",
|
"@vueuse/core": "^9.5.0",
|
||||||
"@vue/compiler-sfc": "^3.2.39",
|
"@vueuse/integrations": "^9.5.0",
|
||||||
"@vueuse/core": "^9.2.0",
|
"@vueuse/router": "^9.5.0",
|
||||||
"@vueuse/router": "^9.2.0",
|
|
||||||
"ansi-to-html": "^0.7.2",
|
"ansi-to-html": "^0.7.2",
|
||||||
"bulma": "^0.9.4",
|
"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",
|
"date-fns": "^2.29.3",
|
||||||
"fuzzysort": "^2.0.1",
|
"fuse.js": "^6.6.2",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"pinia": "^2.0.22",
|
"pinia": "^2.0.23",
|
||||||
"sass": "^1.54.9",
|
"semver": "^7.3.8",
|
||||||
"semver": "^7.3.7",
|
"splitpanes": "^3.1.5",
|
||||||
"splitpanes": "^3.1.1",
|
"vue": "^3.2.45",
|
||||||
"typescript": "^4.8.3",
|
|
||||||
"unplugin-auto-import": "^0.11.2",
|
|
||||||
"unplugin-icons": "^0.14.10",
|
|
||||||
"unplugin-vue-components": "^0.22.7",
|
|
||||||
"vite": "3.1.3",
|
|
||||||
"vite-plugin-pages": "^0.26.0",
|
|
||||||
"vite-plugin-vue-layouts": "^0.7.0",
|
|
||||||
"vue": "^3.2.39",
|
|
||||||
"vue-i18n": "^9.2.2",
|
"vue-i18n": "^9.2.2",
|
||||||
"vue-router": "^4.1.5"
|
"vue-router": "^4.1.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@intlify/vite-plugin-vue-i18n": "^6.0.3",
|
||||||
"@pinia/testing": "^0.0.14",
|
"@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/lodash.debounce": "^4.0.7",
|
||||||
"@types/node": "^18.7.18",
|
"@types/node": "^18.11.9",
|
||||||
"@types/semver": "^7.3.12",
|
"@types/semver": "^7.3.13",
|
||||||
"@vue/test-utils": "^2.0.2",
|
"@vitejs/plugin-vue": "3.2.0",
|
||||||
|
"@vue/compiler-sfc": "^3.2.45",
|
||||||
|
"@vue/test-utils": "^2.2.2",
|
||||||
"c8": "^7.12.0",
|
"c8": "^7.12.0",
|
||||||
"eventsourcemock": "^2.0.0",
|
"eventsourcemock": "^2.0.0",
|
||||||
"husky": "^8.0.1",
|
"husky": "^8.0.2",
|
||||||
"jest-serializer-vue": "^2.0.2",
|
"jest-serializer-vue": "^3.0.0",
|
||||||
"jsdom": "^20.0.0",
|
"jsdom": "^20.0.2",
|
||||||
"lint-staged": "^13.0.3",
|
"lint-staged": "^13.0.3",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
"release-it": "^15.4.2",
|
"release-it": "^15.5.0",
|
||||||
|
"sass": "^1.56.1",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"vitest": "^0.23.4",
|
"typescript": "^4.8.4",
|
||||||
"vue-tsc": "^0.40.13"
|
"unplugin-auto-import": "^0.12.0",
|
||||||
|
"unplugin-icons": "^0.14.13",
|
||||||
|
"unplugin-vue-components": "^0.22.9",
|
||||||
|
"vite": "3.2.4",
|
||||||
|
"vite-plugin-pages": "^0.27.1",
|
||||||
|
"vite-plugin-vue-layouts": "^0.7.0",
|
||||||
|
"vitest": "^0.25.2",
|
||||||
|
"vue-tsc": "^1.0.9"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{js,vue,css}": [
|
"*.{js,vue,css}": [
|
||||||
|
|||||||
1894
pnpm-lock.yaml
generated
1894
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
6
renovate.json
Normal file
6
renovate.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"config:base"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user