Compare commits

...

125 Commits

Author SHA1 Message Date
Amir Raminfar b066433933 chore: release v5.4.2 2023-11-14 13:06:04 -08:00
Amir Raminfar 52dc1ff2d1 fix: fixes collapse button to not cover the menu on left. see #2500 (#2501) 2023-11-14 13:05:01 -08:00
Suresh Ramakrishnaiah 10ea7cfb51 docs: update authentication.md (#2492) 2023-11-13 18:41:25 -08:00
renovate[bot] eea9813f84 fix(deps): update all non-major dependencies (#2498)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-14 00:52:30 +00:00
Amir Raminfar 8ac79c707c chore: updates go version for CI (#2499) 2023-11-14 00:48:28 +00:00
Amir Raminfar 4637b49075 chore: fixes golang version 2023-11-13 14:29:21 -08:00
renovate[bot] 147902f191 fix(deps): update all non-major dependencies (#2493)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-13 14:28:59 -08:00
Amir Raminfar f83b5c948e fix: converts HSL colors OKLCH with DaisyUI v4 (#2497) 2023-11-13 14:00:35 -08:00
Amir Raminfar 542c895812 chore: fixes padding 2023-11-11 14:08:11 -08:00
Amir Raminfar d14d140cb6 chore(test): adds test for simple mode (#2491) 2023-11-11 22:04:45 +00:00
Amir Raminfar be74d48de7 fix: improves the alignment of drop downs 2023-11-11 13:48:14 -08:00
Amir Raminfar f8a9396f6e chore(test): adds test for proxy mode (#2490) 2023-11-11 13:22:06 -08:00
Amir Raminfar 009529f20e chore: release v5.4.1 2023-11-11 06:41:45 -08:00
Amir Raminfar 63f16ef575 fix: fixes proxy pass not finding user (#2488) 2023-11-11 06:37:06 -08:00
Amir Raminfar 9de964dac4 chore: release v5.4.0 2023-11-10 15:20:41 -08:00
Amir Raminfar 1ff0b5541d chore: fixes incorrect version in doc 2023-11-10 13:47:30 -08:00
Amir Raminfar 55326cdd41 docs: adds sponsor section (#2487) 2023-11-10 21:08:32 +00:00
Amir Raminfar 9fa4911aec feat: list of releases can now be seen at the top (#2480) 2023-11-10 19:45:47 +00:00
Amir Raminfar 7dae54df26 chore: fixes golang version 2023-11-10 09:04:30 -08:00
renovate[bot] 1b101e62da chore(deps): update all non-major dependencies (#2485)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-10 09:04:07 -08:00
Amir Raminfar 5c5cd17fb6 chore: removes netlify files 2023-11-10 08:46:27 -08:00
Amir Raminfar c697be659b docs: adds docs for custom content (#2483) 2023-11-08 14:58:48 -08:00
Amir Raminfar d469066c07 feat: moves user's name and email to dropdown (#2479) 2023-11-07 11:07:34 -08:00
Amir Raminfar 4380681812 feat: supports default avatar when gravatar is missing (#2478) 2023-11-07 09:26:48 -08:00
Amir Raminfar 75cf2f045a chore: release v5.3.1 2023-11-06 15:29:56 -08:00
Amir Raminfar ab5807b3ae chore: fixes go.mod 2023-11-06 15:24:50 -08:00
renovate[bot] fe0f163090 chore(deps): update all non-major dependencies (#2477)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-06 15:23:04 -08:00
Amir Raminfar 38d1f99387 fix: reduces padding on mobile (#2476) 2023-11-06 21:59:53 +00:00
Amir Raminfar 11298e83b0 chore: release v5.3.0 2023-11-06 12:39:29 -08:00
Amir Raminfar 32de313c27 feat: supports having custom content with markdown (#2466) 2023-11-06 12:39:11 -08:00
renovate[bot] 1e1c64d6e7 fix(deps): update all non-major dependencies (#2474)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-06 01:13:20 +00:00
Amir Raminfar 0f02de2070 chore: release v5.2.3 2023-11-05 12:30:54 -08:00
Amir Raminfar bb085affb2 fix: removes fetch request when user does not exist (#2473) 2023-11-05 12:29:44 -08:00
Amir Raminfar 1d022b51b5 fix: fixes bad icon (#2472) 2023-11-04 20:53:24 +00:00
Amir Raminfar eb31b46e6f fix: fixes a potential rare edge case that log entry has no payload. see #2451. (#2471) 2023-11-04 13:51:29 -07:00
Amir Raminfar 066541288a docs: updates readme (#2470) 2023-11-03 21:10:21 +00:00
renovate[bot] eba7d92ba6 fix(deps): update all non-major dependencies (#2468)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-03 17:44:40 +00:00
Jacek Dobosz 579ae12920 docs: installation on podman (#2467) 2023-11-03 05:41:48 -07:00
Amir Raminfar ba9531dc59 chore: release v5.2.2 2023-11-01 12:36:12 -07:00
Amir Raminfar c4cc233720 chore: updates go.mod 2023-11-01 12:13:47 -07:00
Amir Raminfar 6c1f59c7e3 feat: removes request analytics on page load (#2464) 2023-11-01 12:13:17 -07:00
Amir Raminfar ebc02900b6 feat: prints local docker engine error 2023-10-31 16:02:58 -07:00
Amir Raminfar 4e5f7ae1cf chore: release v5.2.1 2023-10-31 15:11:58 -07:00
Amir Raminfar 55f076382e fix: fixes not being able to create /data dir (#2463) 2023-10-31 15:11:15 -07:00
Amir Raminfar cae8f2740d feat: warns against using username and password 2023-10-31 12:45:30 -07:00
Amir Raminfar c75b5a598d chore: fixes typo 2023-10-31 12:33:43 -07:00
Amir Raminfar 701f16fcc2 chore: release v5.2.0 2023-10-31 12:33:12 -07:00
Amir Raminfar 49d53ce255 chore(docs): updates gh pages to have simple auth 2023-10-31 12:30:39 -07:00
Amir Raminfar e57d038221 fix: changes mime type for fetching between to application/x-jsonl. see #2458 (#2461) 2023-10-31 11:22:27 -07:00
Amir Raminfar c74384d012 fix: fixes blank name for proxy mode 2023-10-31 11:15:50 -07:00
Amir Raminfar 5a068133bf fix: fixes forward proxy bug not working (#2460) 2023-10-30 22:42:40 +00:00
Amir Raminfar e85a4aefa2 feat: adds support for user.yml from disk (#2455) 2023-10-30 12:20:00 -07:00
Amir Raminfar b0e43333d2 chore: release v5.1.2 2023-10-30 08:20:25 -07:00
renovate[bot] d2c71e2dc9 fix(deps): update dependency vue-i18n to ^9.6.2 (#2459)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-30 03:56:24 +00:00
renovate[bot] 7fdaa3e4f0 chore(deps): update actions/setup-node action to v4 (#2457)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-29 18:50:14 -07:00
renovate[bot] 0344e2adb4 chore(deps): update actions/checkout action to v4 (#2456)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-29 18:49:54 -07:00
Amir Raminfar 7431c3455f chore(docs): enables gh pages 2023-10-27 13:00:51 -07:00
Amir Raminfar 1fe46e605a feat: syncs settings to disk for authenticated users (#2445) 2023-10-27 12:53:42 -07:00
renovate[bot] e4d1a18d37 fix(deps): update all non-major dependencies (#2453)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-27 19:44:15 +00:00
Amir Raminfar a0a2b7175c chore: release v5.1.1 2023-10-27 11:22:01 -07:00
Amir Raminfar b88fc3c5d1 feat: makes hour time format always 2 digits (#2452) 2023-10-27 09:46:04 -07:00
Amir Raminfar a8bc1dd4c2 chore: reverts netlify changes 2023-10-26 13:45:09 -07:00
Amir Raminfar 29fcb53fac chore: uses removes store 2023-10-26 13:34:38 -07:00
Amir Raminfar 4dfd57612d chore: rollback pnpm changes 2023-10-26 11:28:43 -07:00
Amir Raminfar 51b9ad5f00 chore: tries to clear pnpm store prune again 2023-10-26 10:29:21 -07:00
Amir Raminfar f4b53fd09d chore: tries to clear pnpm store prune 2023-10-26 10:28:49 -07:00
renovate[bot] 5d5628530b fix(deps): update dependency fuse.js to v7 (#2447)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-26 09:47:36 -07:00
Amir Raminfar 4d0904bb2c chore: cleans pnpm 2023-10-26 09:41:27 -07:00
renovate[bot] 13ebc06812 fix(deps): update all non-major dependencies (#2446)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-25 22:09:14 +00:00
Amir Raminfar 7fbf760e9a chore: release v5.1.0 2023-10-25 14:01:40 -07:00
Amir Raminfar 9a2935d3d4 chore: fixes typos 2023-10-25 13:56:56 -07:00
Amir Raminfar fd24659903 chore(docs): adds docks for forward-proxy 2023-10-25 13:48:11 -07:00
renovate[bot] d2a43b7e6f chore(deps): update actions/setup-node action to v4 (#2444) 2023-10-24 06:43:12 -07:00
Amir Raminfar 2a07ca39db chore(refactor): refactors go code (#2443) 2023-10-24 06:28:16 -07:00
Amir Raminfar 74e44bd5f2 fix: fixes log levels that are not in the start of the line 2023-10-23 19:06:04 -07:00
Amir Raminfar be11c32139 feat: adds forward-proxy (#2442) 2023-10-23 15:31:39 -07:00
Amir Raminfar e55df82023 chore: release v5.0.10 2023-10-23 09:01:18 -07:00
Amir Raminfar 78c1d72f3d feat: adds more levels parsing like [ error] with spaces (#2441) 2023-10-23 09:00:29 -07:00
renovate[bot] caa3bb14ab chore(deps): update dependency vitepress to v1.0.0-rc.23 (#2440)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-23 00:58:43 +00:00
renovate[bot] 2881f25a1a chore(deps): update all non-major dependencies (#2435)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-21 16:39:44 +00:00
Amir Raminfar 11a972b048 fix: fixes broken icons (#2438) 2023-10-21 09:36:11 -07:00
Amir Raminfar 8fc308d294 chore: updates vue 2023-10-20 12:32:32 -07:00
renovate[bot] 33e7b84061 chore(deps): update node.js to v21 (#2436)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-20 12:28:45 -07:00
Amir Raminfar 470d6ed0d9 chore: release v5.0.9 2023-10-20 11:57:56 -07:00
Amir Raminfar 01f3c5ee15 fix: fixes reading incorrect number of bytes. see #2431 (#2434) 2023-10-20 11:51:06 -07:00
Amir Raminfar 79f8efd909 feat: supports lower case level like [info] (#2433) 2023-10-20 18:12:22 +00:00
Amir Raminfar 021d3f7a99 chore: updates node modules 2023-10-18 14:12:28 -07:00
Amir Raminfar bf79b213c6 chore(docs): update security readme. (#2430) 2023-10-18 12:31:03 -07:00
Amir Raminfar f216587c9b chore: release v5.0.8 2023-10-16 11:46:10 -07:00
Amir Raminfar 803b66188e chore: code clean up (#2428) 2023-10-16 11:45:43 -07:00
Amir Raminfar 37d2b07720 fix: reverts broken changes to event source (#2427) 2023-10-16 11:37:23 -07:00
Amir Raminfar 9879004ae3 feat: adds workflow for semantic messages 2023-10-16 10:11:14 -07:00
Amir Raminfar 0c5c08deeb chore: release v5.0.7 2023-10-16 10:01:06 -07:00
Peter Dave Hello 89a6fbfab2 Add zh-tw locale (#2425) 2023-10-16 09:59:29 -07:00
Amir Raminfar 5b1f3892a7 chore: release v5.0.6 2023-10-16 09:26:50 -07:00
Amir Raminfar 37c6033371 fix: cleans up reading messages when headers are invalid (#2424) 2023-10-16 09:26:27 -07:00
Amir Raminfar 4f748bf9e1 fix: fixes swallowed error when bad headers are found and fixing #2416 (#2423)
* fix: fixes swallowed error when bad headers are found and fixing #2416

* minor clean up

* fixes broken on first connect

* fixes on reconnect
2023-10-16 03:35:10 +00:00
renovate[bot] c0b7f960f4 chore(deps): update dependency lint-staged to v15 (#2421)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-15 20:11:28 -07:00
Amir Raminfar f35e2c0ace chore: code clean up 2023-10-15 19:24:38 -07:00
renovate[bot] 69e434f14b chore(deps): update pnpm to v8.9.2 2023-10-16 01:25:56 +00:00
Amir Raminfar f47f60e51c chore: code clean up 2023-10-15 16:47:16 -07:00
Peter Dave Hello 55775f651f Improve i18n support, close #2417 (#2418) 2023-10-15 16:45:53 -07:00
renovate[bot] 59c72f91ce chore(deps): update all non-major dependencies 2023-10-15 22:15:26 +00:00
Amir Raminfar 732df996bf fix(docs): fixes bad url in docs 2023-10-08 14:17:21 -07:00
Amir Raminfar a7f602449b chore: updates docs to have base 2023-10-08 13:52:34 -07:00
renovate[bot] 84c062c13d chore(deps): update all non-major dependencies 2023-10-08 20:24:12 +00:00
Amir Raminfar 194fcd0dea fix: clears all containers on reconnect and removes stale old ones (#2414) 2023-10-07 21:35:23 +00:00
Amir Raminfar ae1c0207de chore: cleans up v-model for toggle 2023-10-05 20:35:20 -07:00
renovate[bot] 3ca2308be8 chore(deps): update all non-major dependencies 2023-10-06 02:27:53 +00:00
Amir Raminfar bf000a1028 chore: release v5.0.5 2023-10-05 13:51:40 -07:00
Amir Raminfar 9b2cf1254e chore(docs): adds docs for hostname label 2023-10-05 13:50:41 -07:00
Amir Raminfar f4bc3bdc21 feat: uses --hostname as the label for localhost. see #2394 (#2412)
* feat: uses --hostname as the label for localhost. see #2394

* fixes tests
2023-10-05 13:36:33 -07:00
Amir Raminfar 0e135d0eac fix(docs): fixes broken docs page 2023-10-03 15:40:00 -07:00
Amir Raminfar 8f314f5e6f chore: release v5.0.4 2023-10-03 10:28:50 -07:00
Amir Raminfar f1dad96b86 fix: clears logs when an exception is thrown in logs events source (#2406)
* feat: adds more logging for eventsource

* feat: removes error from event streams

* removes unused code

* adds errors back

* moves logs to debug

* removes lastEventId

* fixes tests
2023-10-03 16:58:46 +00:00
renovate[bot] 32bc9a407b chore(deps): update all non-major dependencies to ^0.56.5 2023-10-03 15:12:27 +00:00
renovate[bot] a0e3baabbf chore(deps): update all non-major dependencies 2023-10-03 02:48:37 +00:00
Amir Raminfar b40b23b76f chore: updates node modules 2023-10-02 13:13:13 -07:00
renovate[bot] aa2a9dc7a0 chore(deps): update docker/metadata-action action to v5 (#2404)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-02 08:25:12 -07:00
renovate[bot] 6fe883f4be chore(deps): update docker/setup-buildx-action action to v3 (#2405)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-02 08:25:02 -07:00
Amir Raminfar 10721f5d07 feat: adds linux/arm/v6 to docker builds (#2401) 2023-09-30 13:46:46 -07:00
Amir Raminfar f6ef1f7381 feat: adds touch icon to index.html (#2402) 2023-09-30 13:46:34 -07:00
Amir Raminfar 32f9364f2b chore: cleans up package.json (#2397) 2023-09-29 21:06:52 +00:00
Amir Raminfar 233c546f92 chore: release v5.0.3 2023-09-28 11:05:50 -07:00
Amir Raminfar 666ad7ba05 fix: fixes negative CPU values when container stops 2023-09-28 09:46:45 -07:00
123 changed files with 3891 additions and 2356 deletions
+11 -10
View File
@@ -9,13 +9,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
name: Install Node
with:
node-version: latest
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: latest
cache: "pnpm"
@@ -31,7 +31,8 @@ jobs:
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: 1.20.x
go-version: 1.21.x
check-latest: true
- name: Checkout code
uses: actions/checkout@v4
- name: Run Go Tests with Coverage
@@ -42,13 +43,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
name: Install Node
with:
node-version: latest
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: latest
cache: "pnpm"
@@ -56,7 +57,7 @@ jobs:
- name: Install dependencies
run: pnpm install
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Build
uses: docker/bake-action@v4
with:
@@ -79,11 +80,11 @@ jobs:
steps:
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
uses: docker/metadata-action@v5
with:
images: amir20/dozzle
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
- name: Login to DockerHub
uses: docker/login-action@v3.0.0
with:
@@ -93,7 +94,7 @@ jobs:
uses: docker/build-push-action@v5.0.0
with:
push: true
platforms: linux/amd64,linux/arm/v7,linux/arm64/v8
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8
tags: ${{ steps.meta.outputs.tags }}
build-args: TAG=${{ steps.meta.outputs.version }}
labels: ${{ steps.meta.outputs.labels }}
@@ -109,7 +110,7 @@ jobs:
with:
fetch-depth: 0
- name: Install Node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
- name: Release to Github
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+3 -3
View File
@@ -14,11 +14,11 @@ jobs:
steps:
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
uses: docker/metadata-action@v5
with:
images: amir20/dozzle
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@885d1462b80bc1c1c7f0b00334ad271f09369c55 # v2.10.0
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
- name: Login to DockerHub
uses: docker/login-action@v3.0.0
with:
@@ -28,7 +28,7 @@ jobs:
uses: docker/build-push-action@v5.0.0
with:
push: true
platforms: linux/amd64,linux/arm/v7,linux/arm64/v8
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8
tags: ${{ steps.meta.outputs.tags }}
build-args: TAG=${{ steps.meta.outputs.version }}
labels: ${{ steps.meta.outputs.labels }}
+62
View File
@@ -0,0 +1,62 @@
# Sample workflow for building and deploying a VitePress site to GitHub Pages
#
name: Deploy VitePress site to Pages
on:
push:
branches: [master]
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: pages
cancel-in-progress: false
jobs:
# Build job
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # Not needed if lastUpdated is not enabled
- uses: pnpm/action-setup@v2
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: latest
cache: pnpm # or pnpm / yarn
- name: Setup Pages
uses: actions/configure-pages@v3
- name: Install dependencies
run: pnpm install
- name: Build with VitePress
run: |
pnpm docs:build
touch docs/.vitepress/dist/.nojekyll
- name: Upload artifact
uses: actions/upload-pages-artifact@v2
with:
path: docs/.vitepress/dist
# Deployment job
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
needs: build
runs-on: ubuntu-latest
name: Deploy
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v2
+20
View File
@@ -0,0 +1,20 @@
name: "Lint PR"
on:
pull_request_target:
types:
- opened
- edited
- synchronize
permissions:
pull-requests: read
jobs:
main:
name: Validate PR title
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+9 -8
View File
@@ -12,13 +12,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
name: Install Node
with:
node-version: latest
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: latest
cache: "pnpm"
@@ -32,13 +32,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
name: Install Node
with:
node-version: latest
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: latest
cache: "pnpm"
@@ -54,7 +54,8 @@ jobs:
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: 1.20.x
go-version: 1.21.x
check-latest: true
- name: Checkout code
uses: actions/checkout@v4
- name: Run Go Tests with Coverage
@@ -65,13 +66,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
name: Install Node
with:
node-version: latest
- run: corepack enable
- run: pnpm --version
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: latest
cache: "pnpm"
@@ -79,7 +80,7 @@ jobs:
- name: Install dependencies
run: pnpm install
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Build
uses: docker/bake-action@v4
with:
+1
View File
@@ -1,4 +1,5 @@
certs
data
dist
node_modules
.cache
+5 -7
View File
@@ -1,5 +1,5 @@
# Build assets
FROM --platform=$BUILDPLATFORM node:20-alpine as node
FROM --platform=$BUILDPLATFORM node:21-alpine as node
RUN corepack enable
@@ -22,7 +22,7 @@ COPY public ./public
# Build assets
RUN pnpm build
FROM --platform=$BUILDPLATFORM golang:1.21.1-alpine AS builder
FROM --platform=$BUILDPLATFORM golang:1.21.4-alpine AS builder
RUN apk add --no-cache ca-certificates && mkdir /dozzle
@@ -36,10 +36,7 @@ RUN go mod download
COPY --from=node /build/dist ./dist
# Copy all other files
COPY analytics ./analytics
COPY healthcheck ./healthcheck
COPY docker ./docker
COPY web ./web
COPY internal ./internal
COPY main.go ./
# Args
@@ -49,11 +46,12 @@ ARG TARGETOS TARGETARCH
# Build binary
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -ldflags "-s -w -X main.version=$TAG" -o dozzle
RUN mkdir /data
FROM scratch
ENV PATH /bin
COPY --from=builder /data /data
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=builder /dozzle/dozzle /dozzle
+40 -145
View File
@@ -15,71 +15,27 @@ https://user-images.githubusercontent.com/260667/227634771-9ebbe381-16a8-465a-b2
- Search logs using regex 🔦
- Small memory footprint 🏎
- Split screen for viewing multiple logs
- Download logs easy
- Download logs easily
- Live stats with memory and CPU usage
- Authentication with username and password 🚨
- Multi-user authentication with support for proxy forward authorization 🚨
Dozzle should work for most. It has been tested with hundreds of containers. However, it doesn't support offline searching. Products like [Loggly](https://www.loggly.com), [Papertrail](https://papertrailapp.com) or [Kibana](https://www.elastic.co/products/kibana) are more suited for full search capabilities.
Dozzle has been tested with hundreds of containers. However, it doesn't support offline searching. Products like [Loggly](https://www.loggly.com), [Papertrail](https://papertrailapp.com) or [Kibana](https://www.elastic.co/products/kibana) are more suited for full search capabilities.
Dozzle doesn't cost any money and aims to focus on real-time debugging.
## Getting Started
## Getting Dozzle
Dozzle is a very small Docker container (4 MB compressed). Pull the latest release from the index:
Dozzle is a small container (4 MB compressed). Pull the latest release with:
$ docker pull amir20/dozzle:latest
## Using Dozzle
### Running Dozzle
The simplest way to use dozzle is to run the docker container. Also, mount the Docker Unix socket with `--volume` to `/var/run/docker.sock`:
$ docker run --name dozzle -d --volume=/var/run/docker.sock:/var/run/docker.sock -p 8888:8080 amir20/dozzle:latest
Dozzle will be available at [http://localhost:8888/](http://localhost:8888/). You can change `-p 8888:8080` to any port. For example, if you want to view dozzle over port 4040 then you would do `-p 4040:8080`.
Dozzle will be available at [http://localhost:8888/](http://localhost:8888/).
### Connecting with Docker compose
version: "3"
services:
dozzle:
container_name: dozzle
image: amir20/dozzle:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
ports:
- 9999:8080
### Connecting to remote hosts
Dozzle supports connecting to multiple remote hosts via `tcp://` using TLS or without. Appropriate certs need to be mounted for Dozzle to be able to successfully connect. At this point, `ssh://` is not supported because Dozzle docker image does not ship with any ssh clients.
To configure remote hosts, `--remote-host` or `DOZZLE_REMOTE_HOST` need to provided and the `pem` files need to be mounted to `/cert` directory. The `/cert` directory expects to have `/certs/{ca,cert,key}.pem` or `/certs/{host}/{ca,cert,key}.pem` in case of multiple hosts.
Below are examples of using `--remote-host` via CLI:
$ docker run -v /var/run/docker.sock:/var/run/docker.sock -v /path/to/certs:/certs -p 8080:8080 amir20/dozzle --remote-host tcp://167.99.1.1:2376
Multiple `--remote-host` flags can be used to specify multiple hosts.
Or to use compose:
version: "3"
services:
dozzle:
image: amir20/dozzle:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /path/to/certs:/certs
ports:
- 8080:8080
environment:
DOZZLE_REMOTE_HOST: tcp://167.99.1.1:2376,tcp://167.99.1.2:2376
You need to make sure appropriate certs are provided in `/certs/167.99.1.1/{ca,cert,key}.pem` and `/certs/167.99.1.2/{ca,cert,key}.pem` for both hosts to work.
### Adding health check
Dozzle doesn't enable healthcheck by default as it adds extra CPU usage. `healthcheck` can be enabled manually.
Here is the Docker Compose file:
version: "3"
services:
@@ -90,49 +46,50 @@ Dozzle doesn't enable healthcheck by default as it adds extra CPU usage. `health
- /var/run/docker.sock:/var/run/docker.sock
ports:
- 8080:8080
environment:
DOZZLE_LEVEL: trace
healthcheck:
test: [ "CMD", "/dozzle", "healthcheck" ]
interval: 3s
timeout: 30s
retries: 5
start_period: 30s
#### Security
For advanced options like [authentication](https://dozzle.dev/guide/authentication), [remote hosts](https://dozzle.dev/guide/remote-hosts) or common [questions](https://dozzle.dev/guide/faq) see documentation at [dozzle.dev](https://dozzle.dev/guide/getting-started).
You can control the device Dozzle binds to by passing `--addr` parameter. For example,
## Technical Details
$ docker run --volume=/var/run/docker.sock:/var/run/docker.sock -p 8888:1224 amir20/dozzle:latest --addr localhost:1224
Dozzle users automatic API negotiation which works with most Docker configurations. Dozzle also works with [Colima](https://github.com/abiosoft/colima) and [Podman](https://podman.io/).
will bind to `localhost` on port `1224`. You can then use a reverse proxy to control who can see dozzle.
### Installation on podman
If you wish to restrict the containers shown you can pass the `--filter` parameter. For example,
By default Podman doesn't have a background process but you can enable this for Dozzle to work.
$ docker run --volume=/var/run/docker.sock:/var/run/docker.sock -p 8888:1224 amir20/dozzle:latest --filter name=foo
Verify first if your podman installation has enabled remote socket:
this would then only allow you to view containers with a name starting with "foo". You can use other filters like `status` as well, please check the official docker [command line docs](https://docs.docker.com/engine/reference/commandline/ps/#filtering) for available filters. Multiple `--filter` arguments can be provided.
```
podman info
```
#### Authentication
When you get under the key remote socket output like this, its already enabled:
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`.
```
remoteSocket:
exists: true
path: /run/user/1000/podman/podman.sock
```
#### Changing base URL
If it's not enabled please follow [this tutorial](https://github.com/containers/podman/blob/main/docs/tutorials/socket_activation.md) to enable it.
Dozzle by default mounts to "/". If you want to control the base path you can use the `--base` option. For example, if you want to mount at "/foobar",
then you can override by using `--base /foobar`. See env variables below for using `DOZZLE_BASE` to change this.
Once you have the podman remote socket you can run Dozzle on podman.
$ docker run --volume=/var/run/docker.sock:/var/run/docker.sock -p 8080:8080 amir20/dozzle:latest --base /foobar
```
podman run --volume=/run/user/1000/podman/podman.sock:/var/run/docker.sock -d -p 8080:8080 amir20/dozzle:latest
```
Dozzle will be available at [http://localhost:8080/foobar/](http://localhost:8080/foobar/).
## Security
#### Analytics collected
Dozzle supports file based authentication and forward proxy like [Authelia](https://www.authelia.com/). These are documented at https://dozzle.dev/guide/authentication.
## Analytics collected
Dozzle collects anonymous user configurations using Google Analytics. Why? Dozzle is an open source project with no funding. As a result, there is no time to do user studies of Dozzle. Analytics is collected to prioritize features and fixes based on how people use Dozzle. This data is completely public and can be viewed live using [ Data Studio dashboard](https://datastudio.google.com/s/naeIu0MiWsY).
If you do not want to be tracked at all, see the `--no-analytics` flag below.
#### Environment variables and configuration
## Environment variables and configuration
Dozzle follows the [12-factor](https://12factor.net/) model. Configurations can use the CLI flags or environment variables. The table below outlines all supported options and their respective env vars.
@@ -150,77 +107,15 @@ Dozzle follows the [12-factor](https://12factor.net/) model. Configurations can
| `--no-analytics` | `DOZZLE_NO_ANALYTICS` | false |
| `--remote-host` | `DOZZLE_REMOTE_HOST` | |
## Troubleshooting and FAQs
## Support
<details>
<summary>I installed Dozzle, but logs are slow or they never load. Help!</summary>
There are many ways you can support Dozzle:
Dozzle uses Server Sent Events (SSE) which connects to a server using a HTTP stream without closing the connection. If any proxy tries to buffer this connection, then Dozzle never receives the data and hangs forever waiting for the reverse proxy to flush the buffer. Since version `1.23.0`, Dozzle sends the `X-Accel-Buffering: no` header which should stop reverse proxies buffering. However, some proxies may ignore this header. In those cases, you need to explicitly disable any buffering.
- Use it! Write about it! Star it! If you love Dozzle, drop me a line and tell me what you love.
- Blog about Dozzle to spread the word. If you are good at writing send PRs to improve the documentation at [dozzle.dev](https://dozzle.dev/)
- Sponsor my work at https://www.buymeacoffee.com/amirraminfar
Below is an example with nginx and using `proxy_pass` to disable buffering.
```
server {
...
location / {
proxy_pass http://<dozzle.container.ip.address>:8080;
}
location /api {
proxy_pass http://<dozzle.container.ip.address>:8080;
proxy_buffering off;
proxy_cache off;
}
}
```
</details>
<details>
<summary>What data does Dozzle collect?</summary>
Dozzle does collect some analytics. Analytics is anonymous usage tracking of the features which are used the most. See the section above on how to disable any analytic collection.
In the browser, Dozzle has a [strict](https://github.com/amir20/dozzle/blob/master/web/csp.go#L9) Content Security Policy which only allows the following policies:
- Allow connect to `api.github.com` to fetch most recent version.
- Only allow `<script>` and `<style>` files from `self`
Dozzle opens all links with `rel="noopener"`.
</details>
<details>
<summary>We have tools that uses Dozzle when a new container is created. How can I get a direct link to a container by name?</summary>
Dozzle has a [special route](https://github.com/amir20/dozzle/blob/master/assets/pages/Show.vue) that can be used to search containers by name and then forward to that container. For example, if you have a container with name `"foo.bar"` and id `abc123`, you can send your users to `/show?name=foo.bar` which will be forwarded to `/container/abc123`.
</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>
<a href="https://www.buymeacoffee.com/amirraminfar" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
## License
@@ -232,6 +127,6 @@ To Build and test locally:
1. Install [NodeJs](https://nodejs.org/en/download/) and [pnpm](https://pnpm.io/installation).
2. Install [Go](https://go.dev/doc/install).
3. Install [reflex](https://github.com/cespare/reflex) with `go get -u github.com/cespare/reflex` outside of dozzle.
3. Install [reflex](https://github.com/cespare/reflex) with `go install github.com/cespare/reflex@latest`.
4. Install node modules `pnpm install`.
5. Do `pnpm dev`
+5
View File
@@ -0,0 +1,5 @@
# Security Policy
## Reporting a Vulnerability
Dozzle is checked daily with https://docs.docker.com/scout/ and its dependencies are updated regularly. If you find a security bug, please update to the latest. For vulnerability on latest images please file an issue on Github.
+98 -82
View File
@@ -12,21 +12,21 @@ declare global {
const $ref: typeof import('vue/macros')['$ref']
const $shallowRef: typeof import('vue/macros')['$shallowRef']
const $toRef: typeof import('vue/macros')['$toRef']
const DEFAULT_SETTINGS: typeof import('./composables/settings')['DEFAULT_SETTINGS']
const DEFAULT_SETTINGS: typeof import('./stores/settings')['DEFAULT_SETTINGS']
const EffectScope: typeof import('vue')['EffectScope']
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
const arrayEquals: typeof import('./utils/index')['arrayEquals']
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
const automaticRedirect: typeof import('./composables/settings')['automaticRedirect']
const collapseNav: typeof import('./composables/settings')['collapseNav']
const automaticRedirect: typeof import('./stores/settings')['automaticRedirect']
const collapseNav: typeof import('./stores/settings')['collapseNav']
const computed: typeof import('vue')['computed']
const computedAsync: typeof import('@vueuse/core')['computedAsync']
const computedEager: typeof import('@vueuse/core')['computedEager']
const computedInject: typeof import('@vueuse/core')['computedInject']
const computedWithControl: typeof import('@vueuse/core')['computedWithControl']
const config: typeof import('./stores/config')['default']
const containerContext: typeof import('./composables/containerContext')['containerContext']
const containerContext: typeof import('./composable/containerContext')['containerContext']
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
const controlledRef: typeof import('@vueuse/core')['controlledRef']
const createApp: typeof import('vue')['createApp']
@@ -56,19 +56,20 @@ declare global {
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const getDeep: typeof import('./utils/index')['getDeep']
const globalShowPopup: typeof import('./composables/popup')['globalShowPopup']
const globalShowPopup: typeof import('./composable/popup')['globalShowPopup']
const h: typeof import('vue')['h']
const hourStyle: typeof import('./composables/settings')['hourStyle']
const hourStyle: typeof import('./stores/settings')['hourStyle']
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
const inject: typeof import('vue')['inject']
const injectLocal: typeof import('@vueuse/core')['injectLocal']
const isDefined: typeof import('@vueuse/core')['isDefined']
const isMobile: typeof import('./composables/media')['isMobile']
const isMobile: typeof import('./composable/media')['isMobile']
const isObject: typeof import('./utils/index')['isObject']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const lightTheme: typeof import('./composables/settings')['lightTheme']
const lightTheme: typeof import('./stores/settings')['lightTheme']
const logicAnd: typeof import('@vueuse/math')['logicAnd']
const logicNot: typeof import('@vueuse/math')['logicNot']
const logicOr: typeof import('@vueuse/math')['logicOr']
@@ -79,7 +80,7 @@ declare global {
const mapStores: typeof import('pinia')['mapStores']
const mapWritableState: typeof import('pinia')['mapWritableState']
const markRaw: typeof import('vue')['markRaw']
const menuWidth: typeof import('./composables/settings')['menuWidth']
const menuWidth: typeof import('./stores/settings')['menuWidth']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
@@ -101,10 +102,11 @@ declare global {
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
const persistentVisibleKeys: typeof import('./composables/storage')['persistentVisibleKeys']
const pinnedContainers: typeof import('./composables/storage')['pinnedContainers']
const persistentVisibleKeys: typeof import('./composable/storage')['persistentVisibleKeys']
const pinnedContainers: typeof import('./composable/storage')['pinnedContainers']
const provide: typeof import('vue')['provide']
const provideContainerContext: typeof import('./composables/containerContext')['provideContainerContext']
const provideContainerContext: typeof import('./composable/containerContext')['provideContainerContext']
const provideLocal: typeof import('@vueuse/core')['provideLocal']
const reactify: typeof import('@vueuse/core')['reactify']
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
const reactive: typeof import('vue')['reactive']
@@ -118,24 +120,25 @@ declare global {
const refDefault: typeof import('@vueuse/core')['refDefault']
const refThrottled: typeof import('@vueuse/core')['refThrottled']
const refWithControl: typeof import('@vueuse/core')['refWithControl']
const releases: typeof import('./stores/releases')['default']
const resolveComponent: typeof import('vue')['resolveComponent']
const resolveRef: typeof import('@vueuse/core')['resolveRef']
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
const search: typeof import('./composables/settings')['search']
const sessionHost: typeof import('./composables/storage')['sessionHost']
const search: typeof import('./stores/settings')['search']
const sessionHost: typeof import('./composable/storage')['sessionHost']
const setActivePinia: typeof import('pinia')['setActivePinia']
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
const setTitle: typeof import('./composables/title')['setTitle']
const settings: typeof import('./composables/settings')['settings']
const setTitle: typeof import('./composable/title')['setTitle']
const settings: typeof import('./stores/settings')['settings']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const showAllContainers: typeof import('./composables/settings')['showAllContainers']
const showStd: typeof import('./composables/settings')['showStd']
const showTimestamp: typeof import('./composables/settings')['showTimestamp']
const size: typeof import('./composables/settings')['size']
const smallerScrollbars: typeof import('./composables/settings')['smallerScrollbars']
const softWrap: typeof import('./composables/settings')['softWrap']
const showAllContainers: typeof import('./stores/settings')['showAllContainers']
const showStd: typeof import('./stores/settings')['showStd']
const showTimestamp: typeof import('./stores/settings')['showTimestamp']
const size: typeof import('./stores/settings')['size']
const smallerScrollbars: typeof import('./stores/settings')['smallerScrollbars']
const softWrap: typeof import('./stores/settings')['softWrap']
const storeToRefs: typeof import('pinia')['storeToRefs']
const stripVersion: typeof import('./utils/index')['stripVersion']
const syncRef: typeof import('@vueuse/core')['syncRef']
@@ -186,10 +189,11 @@ declare global {
const useCeil: typeof import('@vueuse/math')['useCeil']
const useClamp: typeof import('@vueuse/math')['useClamp']
const useClipboard: typeof import('@vueuse/core')['useClipboard']
const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems']
const useCloned: typeof import('@vueuse/core')['useCloned']
const useColorMode: typeof import('@vueuse/core')['useColorMode']
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
const useContainerContext: typeof import('./composables/containerContext')['useContainerContext']
const useContainerContext: typeof import('./composable/containerContext')['useContainerContext']
const useContainerStore: typeof import('./stores/container')['useContainerStore']
const useCounter: typeof import('@vueuse/core')['useCounter']
const useCssModule: typeof import('vue')['useCssModule']
@@ -243,7 +247,7 @@ declare global {
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
const useLink: typeof import('vue-router')['useLink']
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
const useLogStream: typeof import('./composables/eventsource')['useLogStream']
const useLogStream: typeof import('./composable/eventsource')['useLogStream']
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory']
const useMath: typeof import('@vueuse/math')['useMath']
@@ -282,6 +286,7 @@ declare global {
const useProjection: typeof import('@vueuse/math')['useProjection']
const useRafFn: typeof import('@vueuse/core')['useRafFn']
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
const useReleases: typeof import('./stores/releases')['useReleases']
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
const useRound: typeof import('@vueuse/math')['useRound']
const useRoute: typeof import('vue-router')['useRoute']
@@ -291,7 +296,7 @@ declare global {
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
const useScroll: typeof import('@vueuse/core')['useScroll']
const useScrollLock: typeof import('@vueuse/core')['useScrollLock']
const useSearchFilter: typeof import('./composables/search')['useSearchFilter']
const useSearchFilter: typeof import('./composable/search')['useSearchFilter']
const useSeoMeta: typeof import('@vueuse/head')['useSeoMeta']
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
const useShare: typeof import('@vueuse/core')['useShare']
@@ -321,7 +326,7 @@ declare global {
const useTitle: typeof import('@vueuse/core')['useTitle']
const useToNumber: typeof import('@vueuse/core')['useToNumber']
const useToString: typeof import('@vueuse/core')['useToString']
const useToast: typeof import('./composables/toast')['useToast']
const useToast: typeof import('./composable/toast')['useToast']
const useToggle: typeof import('@vueuse/core')['useToggle']
const useTransition: typeof import('@vueuse/core')['useTransition']
const useTrunc: typeof import('@vueuse/math')['useTrunc']
@@ -331,7 +336,7 @@ declare global {
const useVModels: typeof import('@vueuse/core')['useVModels']
const useVibrate: typeof import('@vueuse/core')['useVibrate']
const useVirtualList: typeof import('@vueuse/core')['useVirtualList']
const useVisibleFilter: typeof import('./composables/visible')['useVisibleFilter']
const useVisibleFilter: typeof import('./composable/visible')['useVisibleFilter']
const useWakeLock: typeof import('@vueuse/core')['useWakeLock']
const useWebNotification: typeof import('@vueuse/core')['useWebNotification']
const useWebSocket: typeof import('@vueuse/core')['useWebSocket']
@@ -356,11 +361,12 @@ declare global {
const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable']
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
const whenever: typeof import('@vueuse/core')['whenever']
const withBase: typeof import('./stores/config')['withBase']
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode } from 'vue'
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
}
// for vue template auto import
import { UnwrapRef } from 'vue'
@@ -373,21 +379,21 @@ declare module 'vue' {
readonly $ref: UnwrapRef<typeof import('vue/macros')['$ref']>
readonly $shallowRef: UnwrapRef<typeof import('vue/macros')['$shallowRef']>
readonly $toRef: UnwrapRef<typeof import('vue/macros')['$toRef']>
readonly DEFAULT_SETTINGS: UnwrapRef<typeof import('./composables/settings')['DEFAULT_SETTINGS']>
readonly DEFAULT_SETTINGS: UnwrapRef<typeof import('./stores/settings')['DEFAULT_SETTINGS']>
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
readonly arrayEquals: UnwrapRef<typeof import('./utils/index')['arrayEquals']>
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
readonly automaticRedirect: UnwrapRef<typeof import('./composables/settings')['automaticRedirect']>
readonly collapseNav: UnwrapRef<typeof import('./composables/settings')['collapseNav']>
readonly automaticRedirect: UnwrapRef<typeof import('./stores/settings')['automaticRedirect']>
readonly collapseNav: UnwrapRef<typeof import('./stores/settings')['collapseNav']>
readonly computed: UnwrapRef<typeof import('vue')['computed']>
readonly computedAsync: UnwrapRef<typeof import('@vueuse/core')['computedAsync']>
readonly computedEager: UnwrapRef<typeof import('@vueuse/core')['computedEager']>
readonly computedInject: UnwrapRef<typeof import('@vueuse/core')['computedInject']>
readonly computedWithControl: UnwrapRef<typeof import('@vueuse/core')['computedWithControl']>
readonly config: UnwrapRef<typeof import('./stores/config')['default']>
readonly containerContext: UnwrapRef<typeof import('./composables/containerContext')['containerContext']>
readonly containerContext: UnwrapRef<typeof import('./composable/containerContext')['containerContext']>
readonly controlledComputed: UnwrapRef<typeof import('@vueuse/core')['controlledComputed']>
readonly controlledRef: UnwrapRef<typeof import('@vueuse/core')['controlledRef']>
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
@@ -415,19 +421,20 @@ declare module 'vue' {
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
readonly getDeep: UnwrapRef<typeof import('./utils/index')['getDeep']>
readonly globalShowPopup: UnwrapRef<typeof import('./composables/popup')['globalShowPopup']>
readonly globalShowPopup: UnwrapRef<typeof import('./composable/popup')['globalShowPopup']>
readonly h: UnwrapRef<typeof import('vue')['h']>
readonly hourStyle: UnwrapRef<typeof import('./composables/settings')['hourStyle']>
readonly hourStyle: UnwrapRef<typeof import('./stores/settings')['hourStyle']>
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
readonly inject: UnwrapRef<typeof import('vue')['inject']>
readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
readonly isMobile: UnwrapRef<typeof import('./composables/media')['isMobile']>
readonly isMobile: UnwrapRef<typeof import('./composable/media')['isMobile']>
readonly isObject: UnwrapRef<typeof import('./utils/index')['isObject']>
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
readonly lightTheme: UnwrapRef<typeof import('./composables/settings')['lightTheme']>
readonly lightTheme: UnwrapRef<typeof import('./stores/settings')['lightTheme']>
readonly makeDestructurable: UnwrapRef<typeof import('@vueuse/core')['makeDestructurable']>
readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>
@@ -435,7 +442,7 @@ declare module 'vue' {
readonly mapStores: UnwrapRef<typeof import('pinia')['mapStores']>
readonly mapWritableState: UnwrapRef<typeof import('pinia')['mapWritableState']>
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
readonly menuWidth: UnwrapRef<typeof import('./composables/settings')['menuWidth']>
readonly menuWidth: UnwrapRef<typeof import('./stores/settings')['menuWidth']>
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
@@ -457,10 +464,11 @@ declare module 'vue' {
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
readonly persistentVisibleKeys: UnwrapRef<typeof import('./composables/storage')['persistentVisibleKeys']>
readonly pinnedContainers: UnwrapRef<typeof import('./composables/storage')['pinnedContainers']>
readonly persistentVisibleKeys: UnwrapRef<typeof import('./composable/storage')['persistentVisibleKeys']>
readonly pinnedContainers: UnwrapRef<typeof import('./composable/storage')['pinnedContainers']>
readonly provide: UnwrapRef<typeof import('vue')['provide']>
readonly provideContainerContext: UnwrapRef<typeof import('./composables/containerContext')['provideContainerContext']>
readonly provideContainerContext: UnwrapRef<typeof import('./composable/containerContext')['provideContainerContext']>
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
@@ -477,21 +485,21 @@ declare module 'vue' {
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
readonly resolveUnref: UnwrapRef<typeof import('@vueuse/core')['resolveUnref']>
readonly search: UnwrapRef<typeof import('./composables/settings')['search']>
readonly sessionHost: UnwrapRef<typeof import('./composables/storage')['sessionHost']>
readonly search: UnwrapRef<typeof import('./stores/settings')['search']>
readonly sessionHost: UnwrapRef<typeof import('./composable/storage')['sessionHost']>
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
readonly setTitle: UnwrapRef<typeof import('./composables/title')['setTitle']>
readonly settings: UnwrapRef<typeof import('./composables/settings')['settings']>
readonly setTitle: UnwrapRef<typeof import('./composable/title')['setTitle']>
readonly settings: UnwrapRef<typeof import('./stores/settings')['settings']>
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
readonly showAllContainers: UnwrapRef<typeof import('./composables/settings')['showAllContainers']>
readonly showStd: UnwrapRef<typeof import('./composables/settings')['showStd']>
readonly showTimestamp: UnwrapRef<typeof import('./composables/settings')['showTimestamp']>
readonly size: UnwrapRef<typeof import('./composables/settings')['size']>
readonly smallerScrollbars: UnwrapRef<typeof import('./composables/settings')['smallerScrollbars']>
readonly softWrap: UnwrapRef<typeof import('./composables/settings')['softWrap']>
readonly showAllContainers: UnwrapRef<typeof import('./stores/settings')['showAllContainers']>
readonly showStd: UnwrapRef<typeof import('./stores/settings')['showStd']>
readonly showTimestamp: UnwrapRef<typeof import('./stores/settings')['showTimestamp']>
readonly size: UnwrapRef<typeof import('./stores/settings')['size']>
readonly smallerScrollbars: UnwrapRef<typeof import('./stores/settings')['smallerScrollbars']>
readonly softWrap: UnwrapRef<typeof import('./stores/settings')['softWrap']>
readonly storeToRefs: UnwrapRef<typeof import('pinia')['storeToRefs']>
readonly stripVersion: UnwrapRef<typeof import('./utils/index')['stripVersion']>
readonly syncRef: UnwrapRef<typeof import('@vueuse/core')['syncRef']>
@@ -538,10 +546,11 @@ declare module 'vue' {
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
readonly useClipboardItems: UnwrapRef<typeof import('@vueuse/core')['useClipboardItems']>
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
readonly useContainerContext: UnwrapRef<typeof import('./composables/containerContext')['useContainerContext']>
readonly useContainerContext: UnwrapRef<typeof import('./composable/containerContext')['useContainerContext']>
readonly useContainerStore: UnwrapRef<typeof import('./stores/container')['useContainerStore']>
readonly useCounter: UnwrapRef<typeof import('@vueuse/core')['useCounter']>
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
@@ -594,7 +603,7 @@ declare module 'vue' {
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 useLogStream: UnwrapRef<typeof import('./composables/eventsource')['useLogStream']>
readonly useLogStream: UnwrapRef<typeof import('./composable/eventsource')['useLogStream']>
readonly useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']>
readonly useManualRefHistory: UnwrapRef<typeof import('@vueuse/core')['useManualRefHistory']>
readonly useMediaControls: UnwrapRef<typeof import('@vueuse/core')['useMediaControls']>
@@ -628,6 +637,7 @@ declare module 'vue' {
readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
readonly useReleases: UnwrapRef<typeof import('./stores/releases')['useReleases']>
readonly useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
@@ -636,7 +646,7 @@ declare module 'vue' {
readonly useScriptTag: UnwrapRef<typeof import('@vueuse/core')['useScriptTag']>
readonly useScroll: UnwrapRef<typeof import('@vueuse/core')['useScroll']>
readonly useScrollLock: UnwrapRef<typeof import('@vueuse/core')['useScrollLock']>
readonly useSearchFilter: UnwrapRef<typeof import('./composables/search')['useSearchFilter']>
readonly useSearchFilter: UnwrapRef<typeof import('./composable/search')['useSearchFilter']>
readonly useSeoMeta: UnwrapRef<typeof import('@vueuse/head')['useSeoMeta']>
readonly useSessionStorage: UnwrapRef<typeof import('@vueuse/core')['useSessionStorage']>
readonly useShare: UnwrapRef<typeof import('@vueuse/core')['useShare']>
@@ -665,7 +675,7 @@ declare module 'vue' {
readonly useTitle: UnwrapRef<typeof import('@vueuse/core')['useTitle']>
readonly useToNumber: UnwrapRef<typeof import('@vueuse/core')['useToNumber']>
readonly useToString: UnwrapRef<typeof import('@vueuse/core')['useToString']>
readonly useToast: UnwrapRef<typeof import('./composables/toast')['useToast']>
readonly useToast: UnwrapRef<typeof import('./composable/toast')['useToast']>
readonly useToggle: UnwrapRef<typeof import('@vueuse/core')['useToggle']>
readonly useTransition: UnwrapRef<typeof import('@vueuse/core')['useTransition']>
readonly useUrlSearchParams: UnwrapRef<typeof import('@vueuse/core')['useUrlSearchParams']>
@@ -674,7 +684,7 @@ declare module 'vue' {
readonly useVModels: UnwrapRef<typeof import('@vueuse/core')['useVModels']>
readonly useVibrate: UnwrapRef<typeof import('@vueuse/core')['useVibrate']>
readonly useVirtualList: UnwrapRef<typeof import('@vueuse/core')['useVirtualList']>
readonly useVisibleFilter: UnwrapRef<typeof import('./composables/visible')['useVisibleFilter']>
readonly useVisibleFilter: UnwrapRef<typeof import('./composable/visible')['useVisibleFilter']>
readonly useWakeLock: UnwrapRef<typeof import('@vueuse/core')['useWakeLock']>
readonly useWebNotification: UnwrapRef<typeof import('@vueuse/core')['useWebNotification']>
readonly useWebSocket: UnwrapRef<typeof import('@vueuse/core')['useWebSocket']>
@@ -699,6 +709,7 @@ declare module 'vue' {
readonly watchTriggerable: UnwrapRef<typeof import('@vueuse/core')['watchTriggerable']>
readonly watchWithFilter: UnwrapRef<typeof import('@vueuse/core')['watchWithFilter']>
readonly whenever: UnwrapRef<typeof import('@vueuse/core')['whenever']>
readonly withBase: UnwrapRef<typeof import('./stores/config')['withBase']>
}
}
declare module '@vue/runtime-core' {
@@ -710,21 +721,21 @@ declare module '@vue/runtime-core' {
readonly $ref: UnwrapRef<typeof import('vue/macros')['$ref']>
readonly $shallowRef: UnwrapRef<typeof import('vue/macros')['$shallowRef']>
readonly $toRef: UnwrapRef<typeof import('vue/macros')['$toRef']>
readonly DEFAULT_SETTINGS: UnwrapRef<typeof import('./composables/settings')['DEFAULT_SETTINGS']>
readonly DEFAULT_SETTINGS: UnwrapRef<typeof import('./stores/settings')['DEFAULT_SETTINGS']>
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
readonly arrayEquals: UnwrapRef<typeof import('./utils/index')['arrayEquals']>
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
readonly automaticRedirect: UnwrapRef<typeof import('./composables/settings')['automaticRedirect']>
readonly collapseNav: UnwrapRef<typeof import('./composables/settings')['collapseNav']>
readonly automaticRedirect: UnwrapRef<typeof import('./stores/settings')['automaticRedirect']>
readonly collapseNav: UnwrapRef<typeof import('./stores/settings')['collapseNav']>
readonly computed: UnwrapRef<typeof import('vue')['computed']>
readonly computedAsync: UnwrapRef<typeof import('@vueuse/core')['computedAsync']>
readonly computedEager: UnwrapRef<typeof import('@vueuse/core')['computedEager']>
readonly computedInject: UnwrapRef<typeof import('@vueuse/core')['computedInject']>
readonly computedWithControl: UnwrapRef<typeof import('@vueuse/core')['computedWithControl']>
readonly config: UnwrapRef<typeof import('./stores/config')['default']>
readonly containerContext: UnwrapRef<typeof import('./composables/containerContext')['containerContext']>
readonly containerContext: UnwrapRef<typeof import('./composable/containerContext')['containerContext']>
readonly controlledComputed: UnwrapRef<typeof import('@vueuse/core')['controlledComputed']>
readonly controlledRef: UnwrapRef<typeof import('@vueuse/core')['controlledRef']>
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
@@ -752,19 +763,20 @@ declare module '@vue/runtime-core' {
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
readonly getDeep: UnwrapRef<typeof import('./utils/index')['getDeep']>
readonly globalShowPopup: UnwrapRef<typeof import('./composables/popup')['globalShowPopup']>
readonly globalShowPopup: UnwrapRef<typeof import('./composable/popup')['globalShowPopup']>
readonly h: UnwrapRef<typeof import('vue')['h']>
readonly hourStyle: UnwrapRef<typeof import('./composables/settings')['hourStyle']>
readonly hourStyle: UnwrapRef<typeof import('./stores/settings')['hourStyle']>
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
readonly inject: UnwrapRef<typeof import('vue')['inject']>
readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
readonly isMobile: UnwrapRef<typeof import('./composables/media')['isMobile']>
readonly isMobile: UnwrapRef<typeof import('./composable/media')['isMobile']>
readonly isObject: UnwrapRef<typeof import('./utils/index')['isObject']>
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
readonly lightTheme: UnwrapRef<typeof import('./composables/settings')['lightTheme']>
readonly lightTheme: UnwrapRef<typeof import('./stores/settings')['lightTheme']>
readonly makeDestructurable: UnwrapRef<typeof import('@vueuse/core')['makeDestructurable']>
readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>
@@ -772,7 +784,7 @@ declare module '@vue/runtime-core' {
readonly mapStores: UnwrapRef<typeof import('pinia')['mapStores']>
readonly mapWritableState: UnwrapRef<typeof import('pinia')['mapWritableState']>
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
readonly menuWidth: UnwrapRef<typeof import('./composables/settings')['menuWidth']>
readonly menuWidth: UnwrapRef<typeof import('./stores/settings')['menuWidth']>
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
@@ -794,10 +806,11 @@ declare module '@vue/runtime-core' {
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
readonly persistentVisibleKeys: UnwrapRef<typeof import('./composables/storage')['persistentVisibleKeys']>
readonly pinnedContainers: UnwrapRef<typeof import('./composables/storage')['pinnedContainers']>
readonly persistentVisibleKeys: UnwrapRef<typeof import('./composable/storage')['persistentVisibleKeys']>
readonly pinnedContainers: UnwrapRef<typeof import('./composable/storage')['pinnedContainers']>
readonly provide: UnwrapRef<typeof import('vue')['provide']>
readonly provideContainerContext: UnwrapRef<typeof import('./composables/containerContext')['provideContainerContext']>
readonly provideContainerContext: UnwrapRef<typeof import('./composable/containerContext')['provideContainerContext']>
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
@@ -814,21 +827,21 @@ declare module '@vue/runtime-core' {
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
readonly resolveUnref: UnwrapRef<typeof import('@vueuse/core')['resolveUnref']>
readonly search: UnwrapRef<typeof import('./composables/settings')['search']>
readonly sessionHost: UnwrapRef<typeof import('./composables/storage')['sessionHost']>
readonly search: UnwrapRef<typeof import('./stores/settings')['search']>
readonly sessionHost: UnwrapRef<typeof import('./composable/storage')['sessionHost']>
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
readonly setTitle: UnwrapRef<typeof import('./composables/title')['setTitle']>
readonly settings: UnwrapRef<typeof import('./composables/settings')['settings']>
readonly setTitle: UnwrapRef<typeof import('./composable/title')['setTitle']>
readonly settings: UnwrapRef<typeof import('./stores/settings')['settings']>
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
readonly showAllContainers: UnwrapRef<typeof import('./composables/settings')['showAllContainers']>
readonly showStd: UnwrapRef<typeof import('./composables/settings')['showStd']>
readonly showTimestamp: UnwrapRef<typeof import('./composables/settings')['showTimestamp']>
readonly size: UnwrapRef<typeof import('./composables/settings')['size']>
readonly smallerScrollbars: UnwrapRef<typeof import('./composables/settings')['smallerScrollbars']>
readonly softWrap: UnwrapRef<typeof import('./composables/settings')['softWrap']>
readonly showAllContainers: UnwrapRef<typeof import('./stores/settings')['showAllContainers']>
readonly showStd: UnwrapRef<typeof import('./stores/settings')['showStd']>
readonly showTimestamp: UnwrapRef<typeof import('./stores/settings')['showTimestamp']>
readonly size: UnwrapRef<typeof import('./stores/settings')['size']>
readonly smallerScrollbars: UnwrapRef<typeof import('./stores/settings')['smallerScrollbars']>
readonly softWrap: UnwrapRef<typeof import('./stores/settings')['softWrap']>
readonly storeToRefs: UnwrapRef<typeof import('pinia')['storeToRefs']>
readonly stripVersion: UnwrapRef<typeof import('./utils/index')['stripVersion']>
readonly syncRef: UnwrapRef<typeof import('@vueuse/core')['syncRef']>
@@ -875,10 +888,11 @@ declare module '@vue/runtime-core' {
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
readonly useClipboardItems: UnwrapRef<typeof import('@vueuse/core')['useClipboardItems']>
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
readonly useContainerContext: UnwrapRef<typeof import('./composables/containerContext')['useContainerContext']>
readonly useContainerContext: UnwrapRef<typeof import('./composable/containerContext')['useContainerContext']>
readonly useContainerStore: UnwrapRef<typeof import('./stores/container')['useContainerStore']>
readonly useCounter: UnwrapRef<typeof import('@vueuse/core')['useCounter']>
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
@@ -931,7 +945,7 @@ declare module '@vue/runtime-core' {
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 useLogStream: UnwrapRef<typeof import('./composables/eventsource')['useLogStream']>
readonly useLogStream: UnwrapRef<typeof import('./composable/eventsource')['useLogStream']>
readonly useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']>
readonly useManualRefHistory: UnwrapRef<typeof import('@vueuse/core')['useManualRefHistory']>
readonly useMediaControls: UnwrapRef<typeof import('@vueuse/core')['useMediaControls']>
@@ -965,6 +979,7 @@ declare module '@vue/runtime-core' {
readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
readonly useReleases: UnwrapRef<typeof import('./stores/releases')['useReleases']>
readonly useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
@@ -973,7 +988,7 @@ declare module '@vue/runtime-core' {
readonly useScriptTag: UnwrapRef<typeof import('@vueuse/core')['useScriptTag']>
readonly useScroll: UnwrapRef<typeof import('@vueuse/core')['useScroll']>
readonly useScrollLock: UnwrapRef<typeof import('@vueuse/core')['useScrollLock']>
readonly useSearchFilter: UnwrapRef<typeof import('./composables/search')['useSearchFilter']>
readonly useSearchFilter: UnwrapRef<typeof import('./composable/search')['useSearchFilter']>
readonly useSeoMeta: UnwrapRef<typeof import('@vueuse/head')['useSeoMeta']>
readonly useSessionStorage: UnwrapRef<typeof import('@vueuse/core')['useSessionStorage']>
readonly useShare: UnwrapRef<typeof import('@vueuse/core')['useShare']>
@@ -1002,7 +1017,7 @@ declare module '@vue/runtime-core' {
readonly useTitle: UnwrapRef<typeof import('@vueuse/core')['useTitle']>
readonly useToNumber: UnwrapRef<typeof import('@vueuse/core')['useToNumber']>
readonly useToString: UnwrapRef<typeof import('@vueuse/core')['useToString']>
readonly useToast: UnwrapRef<typeof import('./composables/toast')['useToast']>
readonly useToast: UnwrapRef<typeof import('./composable/toast')['useToast']>
readonly useToggle: UnwrapRef<typeof import('@vueuse/core')['useToggle']>
readonly useTransition: UnwrapRef<typeof import('@vueuse/core')['useTransition']>
readonly useUrlSearchParams: UnwrapRef<typeof import('@vueuse/core')['useUrlSearchParams']>
@@ -1011,7 +1026,7 @@ declare module '@vue/runtime-core' {
readonly useVModels: UnwrapRef<typeof import('@vueuse/core')['useVModels']>
readonly useVibrate: UnwrapRef<typeof import('@vueuse/core')['useVibrate']>
readonly useVirtualList: UnwrapRef<typeof import('@vueuse/core')['useVirtualList']>
readonly useVisibleFilter: UnwrapRef<typeof import('./composables/visible')['useVisibleFilter']>
readonly useVisibleFilter: UnwrapRef<typeof import('./composable/visible')['useVisibleFilter']>
readonly useWakeLock: UnwrapRef<typeof import('@vueuse/core')['useWakeLock']>
readonly useWebNotification: UnwrapRef<typeof import('@vueuse/core')['useWebNotification']>
readonly useWebSocket: UnwrapRef<typeof import('@vueuse/core')['useWebSocket']>
@@ -1036,5 +1051,6 @@ declare module '@vue/runtime-core' {
readonly watchTriggerable: UnwrapRef<typeof import('@vueuse/core')['watchTriggerable']>
readonly watchWithFilter: UnwrapRef<typeof import('@vueuse/core')['watchWithFilter']>
readonly whenever: UnwrapRef<typeof import('@vueuse/core')['whenever']>
readonly withBase: UnwrapRef<typeof import('./stores/config')['withBase']>
}
}
+12 -7
View File
@@ -29,12 +29,14 @@ declare module 'vue' {
DistanceTime: typeof import('./components/common/DistanceTime.vue')['default']
DockerEventLogItem: typeof import('./components/LogViewer/DockerEventLogItem.vue')['default']
Dropdown: typeof import('./components/common/Dropdown.vue')['default']
DropdownMenu: typeof import('./components/common/DropdownMenu.vue')['default']
FieldList: typeof import('./components/LogViewer/FieldList.vue')['default']
FuzzySearchModal: typeof import('./components/FuzzySearchModal.vue')['default']
'Ic:sharpFindInPage': typeof import('~icons/ic/sharp-find-in-page')['default']
'Ic:sharpKeyboardReturn': typeof import('~icons/ic/sharp-keyboard-return')['default']
InfiniteLoader: typeof import('./components/InfiniteLoader.vue')['default']
KeyShortcut: typeof import('./components/common/KeyShortcut.vue')['default']
Links: typeof import('./components/Links.vue')['default']
LogActionsToolbar: typeof import('./components/LogViewer/LogActionsToolbar.vue')['default']
LogContainer: typeof import('./components/LogViewer/LogContainer.vue')['default']
LogDate: typeof import('./components/LogViewer/LogDate.vue')['default']
@@ -43,25 +45,28 @@ declare module 'vue' {
LogStd: typeof import('./components/LogViewer/LogStd.vue')['default']
LogViewer: typeof import('./components/LogViewer/LogViewer.vue')['default']
LogViewerWithSource: typeof import('./components/LogViewer/LogViewerWithSource.vue')['default']
'Mdi:announcement': typeof import('~icons/mdi/announcement')['default']
'Mdi:arrowUp': typeof import('~icons/mdi/arrow-up')['default']
'Mdi:check': typeof import('~icons/mdi/check')['default']
'Mdi:chevronDoubleDown': typeof import('~icons/mdi/chevron-double-down')['default']
'Mdi:chevronLeft': typeof import('~icons/mdi/chevron-left')['default']
'Mdi:chevronRight': typeof import('~icons/mdi/chevron-right')['default']
'Mdi:close': typeof import('~icons/mdi/close')['default']
'Mdi:cog': typeof import('~icons/mdi/cog')['default']
'Mdi:hamburgerMenu': typeof import('~icons/mdi/hamburger-menu')['default']
'Mdi:keyboardEsc': typeof import('~icons/mdi/keyboard-esc')['default']
'Mdi:lightChevronDoubleDown': typeof import('~icons/mdi-light/chevron-double-down')['default']
'Mdi:lightChevronLeft': typeof import('~icons/mdi-light/chevron-left')['default']
'Mdi:lightChevronRight': typeof import('~icons/mdi-light/chevron-right')['default']
'Mdi:lightCog': typeof import('~icons/mdi-light/cog')['default']
'Mdi:lightLogout': typeof import('~icons/mdi-light/logout')['default']
'Mdi:lightMagnify': typeof import('~icons/mdi-light/magnify')['default']
'MdiLight:check': typeof import('~icons/mdi-light/check')['default']
'Mdi:logout': typeof import('~icons/mdi/logout')['default']
'Mdi:magnify': typeof import('~icons/mdi/magnify')['default']
MobileMenu: typeof import('./components/common/MobileMenu.vue')['default']
'Octicon:container24': typeof import('~icons/octicon/container24')['default']
'Octicon:download24': typeof import('~icons/octicon/download24')['default']
'Octicon:trash24': typeof import('~icons/octicon/trash24')['default']
PageWithLinks: typeof import('./components/PageWithLinks.vue')['default']
'Ph:command': typeof import('~icons/ph/command')['default']
'Ph:computerTower': typeof import('~icons/ph/computer-tower')['default']
'Ph:controlBold': typeof import('~icons/ph/control-bold')['default']
Popup: typeof import('./components/Popup.vue')['default']
Releases: typeof import('./components/Releases.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
ScrollableView: typeof import('./components/ScrollableView.vue')['default']
+1 -1
View File
@@ -1,7 +1,7 @@
<template>
<div class="dropdown dropdown-open w-full">
<div class="input input-primary flex h-auto items-center">
<mdi:light-magnify class="flex h-8 w-8" />
<mdi:magnify class="flex h-8 w-8" />
<input
tabindex="0"
class="input input-ghost input-lg flex-1 px-1"
+63
View File
@@ -0,0 +1,63 @@
<template>
<div class="flex items-center justify-end gap-4">
<template v-if="config.pages">
<router-link
:to="{ name: 'content-id', params: { id: page.id } }"
:title="page.title"
v-for="page in config.pages"
:key="page.id"
class="link-primary"
>
{{ page.title }}
</router-link>
</template>
<dropdown class="dropdown-end" @closed="latestTag = latest?.tag ?? config.version">
<template #trigger>
<mdi:announcement class="h-6 w-6 -rotate-12" />
<span
class="absolute right-px top-0 h-2 w-2 rounded-full bg-red"
v-if="hasUpdate && latestTag != latest?.tag"
></span>
</template>
<template #content>
<div class="w-72">
<releases />
</div>
</template>
</dropdown>
<dropdown class="dropdown-end" v-if="config.user">
<template #trigger>
<img class="h-6 w-6 max-w-none rounded-full p-px ring-1 ring-base-content/60" :src="config.user.avatar" />
</template>
<template #content>
<div class="p-2">
<div class="font-bold">
{{ config.user.name }}
</div>
<div class="text-sm font-light">
{{ config.user.email }}
</div>
</div>
<ul class="menu mt-4 p-0">
<li v-if="config.authProvider === 'simple'">
<button @click.prevent="logout()" class="p-2 text-primary">{{ $t("button.logout") }}</button>
</li>
</ul>
</template>
</dropdown>
</div>
</template>
<script lang="ts" setup>
async function logout() {
await fetch(withBase("/api/token"), {
method: "DELETE",
});
location.reload();
}
const { hasUpdate, latest } = useReleases();
const latestTag = useStorage("DOZZLE_LATEST_TAG", config.version);
</script>
@@ -1,7 +1,7 @@
<template>
<div class="flex gap-4" v-if="container.stat">
<stat-monitor :data="memoryData" label="mem" :stat-value="formatBytes(unref(container.stat).memoryUsage)" />
<stat-monitor :data="cpuData" label="load" :stat-value="unref(container.stat).cpu + '%'" />
<stat-monitor :data="cpuData" label="load" :stat-value="Math.max(0, unref(container.stat).cpu) + '%'" />
</div>
</template>
@@ -18,7 +18,7 @@
</li>
<li>
<a @click.prevent="showSearch = true">
<mdi:light-magnify /> {{ $t("toolbar.search") }}
<mdi:magnify /> {{ $t("toolbar.search") }}
<key-shortcut char="f"></key-shortcut>
</a>
</li>
@@ -4,16 +4,17 @@ import { createTestingPinia } from "@pinia/testing";
import EventSource, { sources } from "eventsourcemock";
import LogEventSource from "./LogEventSource.vue";
import LogViewer from "./LogViewer.vue";
import { settings } from "@/composables/settings";
import { useSearchFilter } from "@/composables/search";
import { settings } from "@/stores/settings";
import { useSearchFilter } from "@/composable/search";
import { vi, describe, expect, beforeEach, test, afterEach } from "vitest";
import { computed, nextTick } from "vue";
import { createRouter, createWebHistory } from "vue-router";
import { containerContext } from "@/composables/containerContext";
import { containerContext } from "@/composable/containerContext";
vi.mock("@/stores/config", () => ({
__esModule: true,
default: { base: "", hosts: [{ name: "localhost", id: "localhost" }] },
withBase: (path: string) => path,
}));
/**
@@ -88,7 +89,7 @@ describe("<LogEventSource />", () => {
});
}
const sourceUrl = "/api/logs/stream/localhost/abc?lastEventId=&stdout=1&stderr=1";
const sourceUrl = "/api/logs/stream/localhost/abc?stdout=1&stderr=1";
test("renders correctly", async () => {
const wrapper = createLogEventSource();
@@ -6,8 +6,7 @@
<script lang="ts" setup>
const loadingMore = defineEmit<[value: boolean]>();
const { container, streamConfig } = useContainerContext();
const { messages, loadOlderLogs } = useLogStream(container, streamConfig);
const { messages, loadOlderLogs } = useLogStream();
const beforeLoading = () => loadingMore(true);
const afterLoading = () => loadingMore(false);
+2 -2
View File
@@ -7,7 +7,7 @@
:class="{ 'border border-secondary': toRaw(item) === toRaw(lastSelectedItem) }"
>
<a
class="jump-context tooltip-primary tooltip tooltip-right"
class="jump-context tooltip tooltip-right tooltip-primary"
v-if="isSearching()"
data-tip="Jump to Context"
@click="handleJumpLineSelected($event, item)"
@@ -78,7 +78,7 @@ watch(
}
.jump-context {
@apply mr-2 flex items-center font-sans text-secondary hover:text-secondary-focus;
@apply mr-2 flex items-center font-sans text-secondary;
}
}
@@ -66,7 +66,7 @@ exports[`<LogEventSource /> > render html correctly > should render messages wit
exports[`<LogEventSource /> > render html correctly > should render messages with filter 1`] = `
"<ul data-v-2e92daca=\\"\\" class=\\"events group py-4 medium\\">
<li data-v-2e92daca=\\"\\" data-key=\\"2\\" class=\\"\\"><a data-v-2e92daca=\\"\\" class=\\"jump-context tooltip-primary tooltip tooltip-right\\" data-tip=\\"Jump to Context\\" href=\\"#2\\"><svg data-v-2e92daca=\\"\\" viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\">
<li data-v-2e92daca=\\"\\" data-key=\\"2\\" class=\\"\\"><a data-v-2e92daca=\\"\\" class=\\"jump-context tooltip tooltip-right tooltip-primary\\" data-tip=\\"Jump to Context\\" href=\\"#2\\"><svg data-v-2e92daca=\\"\\" viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\">
<path fill=\\"currentColor\\" d=\\"M20 19.59V8l-6-6H4v20l15.57-.02l-4.81-4.81c-.8.52-1.74.83-2.76.83c-2.76 0-5-2.24-5-5s2.24-5 5-5s5 2.24 5 5c0 1.02-.31 1.96-.83 2.75L20 19.59zM9 13c0 1.66 1.34 3 3 3s3-1.34 3-3s-1.34-3-3-3s-3 1.34-3 3z\\"></path>
</svg></a>
<div data-v-2e92daca=\\"\\" class=\\"flex items-start gap-x-2\\" visible-keys=\\"\\">
+12
View File
@@ -0,0 +1,12 @@
<template>
<div class="flex flex-col gap-8 px-4 py-4 md:px-8">
<section>
<links />
</section>
<slot></slot>
</div>
</template>
<script setup lang="ts"></script>
<style scoped></style>
+1 -1
View File
@@ -14,7 +14,7 @@
</template>
<script lang="ts" setup>
import { globalShowPopup } from "@/composables/popup";
import { globalShowPopup } from "@/composable/popup";
let glopbalShow = globalShowPopup();
let show = ref(glopbalShow.value);
+74
View File
@@ -0,0 +1,74 @@
<template>
<ul class="space-y-4 p-2">
<li v-for="release in releases" v-if="releases?.length">
<div class="flex items-baseline gap-1">
<carbon:warning class="h-4.25 w-4.25 self-center stroke-orange" v-if="release.breaking > 0" />
<a :href="release.htmlUrl" class="link-primary text-lg font-bold" target="_blank" rel="noreferrer noopener">
{{ release.name }}
</a>
<span class="ml-1 text-xs"><distance-time :date="new Date(release.createdAt)" /></span>
<tag class="ml-auto bg-red px-1 py-1 text-xs" v-if="release.tag === latest?.tag">
{{ $t("releases.latest") }}
</tag>
</div>
<div class="text-sm text-base-content/80">
{{ summary(release) }}
</div>
</li>
<li v-else>
<div class="text-sm text-base-content/80">
{{ $t("releases.no_releases") }}
</div>
</li>
</ul>
</template>
<script setup lang="ts">
const { releases, latest } = useReleases();
const { t } = useI18n();
function summary(release: { features: number; bugFixes: number; breaking: number }) {
if (release.features > 0 && release.bugFixes > 0 && release.breaking > 0) {
return t("releases.three_parts", {
first: t("releases.breaking", { count: release.breaking }),
second: t("releases.features", { count: release.features }),
third: t("releases.bugFixes", { count: release.bugFixes }),
});
}
if (release.features > 0 && release.bugFixes > 0) {
return t("releases.two_parts", {
first: t("releases.features", { count: release.features }),
second: t("releases.bugFixes", { count: release.bugFixes }),
});
}
if (release.features > 0 && release.breaking > 0) {
return t("releases.two_parts", {
first: t("releases.features", { count: release.features }),
second: t("releases.breaking", { count: release.breaking }),
});
}
if (release.bugFixes > 0 && release.breaking > 0) {
return t("releases.two_parts", {
first: t("releases.bugFixes", { count: release.bugFixes }),
second: t("releases.breaking", { count: release.breaking }),
});
}
if (release.features > 0) {
return t("releases.features", { count: release.features });
}
if (release.bugFixes > 0) {
return t("releases.bugFixes", { count: release.bugFixes });
}
if (release.breaking > 0) {
return t("releases.breaking", { count: release.breaking });
}
}
</script>
<style scoped></style>
+3 -3
View File
@@ -20,12 +20,12 @@
<div class="mr-16 text-right">
<transition name="fade">
<button
class="fixed bottom-8 rounded bg-primary p-3 text-primary-content shadow transition-colors hover:bg-primary-focus"
:class="hasMore ? 'animate-bounce-fast bg-secondary text-secondary-content hover:bg-secondary-focus' : ''"
class="btn btn-primary fixed bottom-8 rounded p-3 text-primary-content shadow transition-colors"
:class="hasMore ? 'btn-secondary animate-bounce-fast text-secondary-content' : ''"
@click="scrollToBottom()"
v-show="paused"
>
<mdi:light-chevron-double-down />
<mdi:chevron-double-down />
</button>
</transition>
</div>
+2 -2
View File
@@ -5,9 +5,9 @@
v-if="search"
>
<div class="input input-primary flex h-auto items-center">
<mdi:light-magnify />
<mdi:magnify />
<input
class="input flex-1"
class="input input-ghost flex-1"
type="text"
placeholder="Find / RegEx"
ref="input"
+1 -1
View File
@@ -63,7 +63,7 @@
<script lang="ts" setup>
import { Container } from "@/models/Container";
import { sessionHost } from "@/composables/storage";
import { sessionHost } from "@/composable/storage";
const { t } = useI18n();
+3 -3
View File
@@ -19,10 +19,10 @@
data-testid="settings"
class="btn btn-circle btn-sm"
>
<mdi:light-cog />
<mdi:cog />
</router-link>
<a :href="`${base}/logout`" :title="$t('button.logout')" v-if="secured" class="btn btn-circle btn-sm">
<mdi:light-logout />
<mdi:logout />
</a>
</div>
<a
@@ -30,7 +30,7 @@
@click="$emit('search')"
:title="$t('tooltip.search')"
>
<mdi:light-magnify />
<mdi:magnify />
Search
<key-shortcut char="k"></key-shortcut>
</a>
+1 -1
View File
@@ -14,7 +14,7 @@ const dateFormatter = new Intl.DateTimeFormat(undefined, { day: "2-digit", month
const use12Hour = $computed(() => ({ auto: undefined, "12": true, "24": false })[hourStyle.value]);
const timeFormatter = $computed(
() =>
new Intl.DateTimeFormat(undefined, { hour: "numeric", minute: "2-digit", second: "2-digit", hour12: use12Hour }),
new Intl.DateTimeFormat(undefined, { hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: use12Hour }),
);
const dateStr = $computed(() => dateFormatter.format(props.date));
+21 -40
View File
@@ -1,44 +1,25 @@
<template>
<details class="dropdown" ref="details" v-on-click-outside="close">
<summary class="btn btn-primary flex-nowrap" v-bind="$attrs">
<slot name="trigger"> {{ values[modelValue] ?? defaultLabel }} <carbon:caret-down /></slot>
</summary>
<ul class="menu dropdown-content rounded-box z-50 w-52 bg-base p-2 shadow">
<slot>
<li v-for="item in options">
<a @click="modelValue = item.value">
<mdi-light:check class="w-4" v-if="modelValue == item.value" />
<div v-else class="w-4"></div>
{{ item.label }}
</a>
</li>
</slot>
</ul>
</details>
<div class="dropdown">
<label tabindex="0" class="btn btn-circle btn-sm" @mousedown="checkAndCloseDropDown($event)" @blur="closed()">
<slot name="trigger"></slot>
</label>
<div
tabindex="0"
class="min-w-52 dropdown-content rounded-box z-50 mt-1 border border-base-content/20 bg-base p-2 shadow"
>
<slot name="content"></slot>
</div>
</div>
</template>
<script lang="ts" setup>
import { vOnClickOutside } from "@vueuse/components";
type DropdownItem = {
label: string;
value: string;
};
const { options = [], defaultLabel = "" } = defineProps<{ options?: DropdownItem[]; defaultLabel?: string }>();
const { modelValue } = defineModels<{
modelValue: string;
}>();
const values = computed(() =>
options.reduce(
(acc, curr) => {
acc[curr.value] = curr.label;
return acc;
},
{} as Record<string, string>,
),
);
const details = ref<HTMLElement | null>(null);
const close = () => details.value?.removeAttribute("open");
watch(modelValue, () => close());
<script setup lang="ts">
const closed = defineEmit();
function checkAndCloseDropDown(e: MouseEvent) {
const target = e.currentTarget as HTMLElement;
if (target?.matches(":focus")) {
setTimeout(() => target.blur(), 0);
}
}
</script>
<style scoped></style>
+44
View File
@@ -0,0 +1,44 @@
<template>
<details class="dropdown" ref="details" v-on-click-outside="close">
<summary class="btn btn-primary flex-nowrap" v-bind="$attrs">
<slot name="trigger"> {{ values[modelValue] ?? defaultLabel }} <carbon:caret-down /></slot>
</summary>
<ul class="menu dropdown-content rounded-box z-50 mt-1 w-52 border border-base-content/20 bg-base p-2 shadow">
<slot>
<li v-for="item in options">
<a @click="modelValue = item.value">
<mdi:check class="w-4" v-if="modelValue == item.value" />
<div v-else class="w-4"></div>
{{ item.label }}
</a>
</li>
</slot>
</ul>
</details>
</template>
<script lang="ts" setup>
import { vOnClickOutside } from "@vueuse/components";
type DropdownItem = {
label: string;
value: string;
};
const { options = [], defaultLabel = "" } = defineProps<{ options?: DropdownItem[]; defaultLabel?: string }>();
const { modelValue } = defineModels<{
modelValue: string;
}>();
const values = computed(() =>
options.reduce(
(acc, curr) => {
acc[curr.value] = curr.label;
return acc;
},
{} as Record<string, string>,
),
);
const details = ref<HTMLElement | null>(null);
const close = () => details.value?.removeAttribute("open");
watch(modelValue, () => close());
</script>
+5 -5
View File
@@ -9,7 +9,7 @@
<div class="ml-auto flex items-center gap-2">
<a class="btn btn-circle flex" @click="$emit('search')" :title="$t('tooltip.search')">
<mdi:light-magnify class="h-5 w-5" />
<mdi:magnify class="h-5 w-5" />
</a>
<label class="btn btn-circle swap swap-rotate" data-testid="hamburger">
<input type="checkbox" v-model="show" />
@@ -22,7 +22,7 @@
<transition name="fade">
<div v-show="show">
<div class="mt-4 flex items-center justify-center gap-2">
<dropdown
<dropdown-menu
v-model="sessionHost"
:options="hosts"
defaultLabel="Hosts"
@@ -30,10 +30,10 @@
v-if="config.hosts.length > 1"
/>
<router-link :to="{ name: 'settings' }" class="btn btn-outline btn-sm">
<mdi:light-cog /> {{ $t("button.settings") }}
<mdi:cog /> {{ $t("button.settings") }}
</router-link>
<a class="btn btn-outline btn-sm" :href="`${base}/logout`" :title="$t('button.logout')" v-if="secured">
<mdi:light-logout /> {{ $t("button.logout") }}
<mdi:logout /> {{ $t("button.logout") }}
</a>
</div>
@@ -57,7 +57,7 @@
<script lang="ts" setup>
const { base, secured } = config;
import { sessionHost } from "@/composables/storage";
import { sessionHost } from "@/composable/storage";
const store = useContainerStore();
const route = useRoute();
const { visibleContainers } = storeToRefs(store);
+1 -8
View File
@@ -1,11 +1,6 @@
<template>
<label class="label inline-flex cursor-pointer gap-4 font-normal">
<input
type="checkbox"
class="toggle toggle-primary hover:bg-primary-focus"
:checked="modelValue"
@click="toggle()"
/>
<input type="checkbox" class="toggle toggle-primary" v-model="modelValue" />
<slot />
</label>
</template>
@@ -14,6 +9,4 @@
const { modelValue } = defineModels<{
modelValue: boolean;
}>();
const toggle = useToggle(modelValue);
</script>
@@ -9,7 +9,6 @@ import {
DockerEventLogEntry,
SkippedLogsEntry,
} from "@/models/LogEntry";
import { Container } from "@/models/Container";
function parseMessage(data: string): LogEntry<string | JSONObject> {
const e = JSON.parse(data, (key, value) => {
@@ -21,15 +20,12 @@ function parseMessage(data: string): LogEntry<string | JSONObject> {
return asLogEntry(e);
}
type LogStreamConfig = {
stdout: boolean;
stderr: boolean;
};
export function useLogStream(container: Ref<Container>, streamConfig: LogStreamConfig) {
export function useLogStream() {
const { container, streamConfig } = useContainerContext();
let messages: LogEntry<string | JSONObject>[] = $ref([]);
let buffer: LogEntry<string | JSONObject>[] = $ref([]);
const scrollingPaused = $ref(inject("scrollingPaused") as Ref<boolean>);
let containerId = container.value.id;
function flushNow() {
if (messages.length > config.maxLogs) {
@@ -57,50 +53,54 @@ export function useLogStream(container: Ref<Container>, streamConfig: LogStreamC
}
const flushBuffer = debounce(flushNow, 250, { maxWait: 1000 });
let es: EventSource | null = null;
let lastEventId = "";
function close() {
if (es) {
es.close();
console.debug(`EventSource closed for ${containerId}`);
es = null;
}
}
function clearMessages() {
flushBuffer.cancel();
messages = [];
buffer = [];
console.debug(`Clearing messages for ${containerId}`);
}
function connect({ clear } = { clear: true }) {
es?.close();
close();
if (clear) {
flushBuffer.cancel();
messages = [];
buffer = [];
lastEventId = "";
clearMessages();
}
const params = {
lastEventId,
} as { lastEventId: string; stdout?: string; stderr?: string };
const params = Object.entries(streamConfig)
.filter(([, value]) => value)
.reduce((acc, [key]) => ({ ...acc, [key]: "1" }), {});
if (streamConfig.stdout) {
params.stdout = "1";
}
if (streamConfig.stderr) {
params.stderr = "1";
}
containerId = container.value.id;
console.debug(`Connecting to ${containerId} with params`, params);
es = new EventSource(
`${config.base}/api/logs/stream/${container.value.host}/${container.value.id}?${new URLSearchParams(
params,
).toString()}`,
withBase(`/api/logs/stream/${container.value.host}/${containerId}?${new URLSearchParams(params).toString()}`),
);
es.addEventListener("container-stopped", () => {
es?.close();
es = null;
close();
buffer.push(new DockerEventLogEntry("Container stopped", new Date(), "container-stopped"));
flushBuffer();
flushBuffer.flush();
});
es.addEventListener("error", (e) => console.error("EventSource failed: " + JSON.stringify(e)));
es.onmessage = (e) => {
lastEventId = e.lastEventId;
if (e.data) {
buffer.push(parseMessage(e.data));
flushBuffer();
}
};
es.onerror = () => clearMessages();
}
async function loadOlderLogs({ beforeLoading, afterLoading } = { beforeLoading: () => {}, afterLoading: () => {} }) {
@@ -112,23 +112,13 @@ export function useLogStream(container: Ref<Container>, streamConfig: LogStreamC
const delta = to.getTime() - last.getTime();
const from = new Date(to.getTime() + delta);
const params = {
from: from.toISOString(),
to: to.toISOString(),
} as { from: string; to: string; stdout?: string; stderr?: string };
if (streamConfig.stdout) {
params.stdout = "1";
}
if (streamConfig.stderr) {
params.stderr = "1";
}
const params = Object.entries(streamConfig)
.filter(([, value]) => value)
.reduce((acc, [key]) => ({ ...acc, [key]: "1" }), { from: from.toISOString(), to: to.toISOString() });
const logs = await (
await fetch(
`${config.base}/api/logs/${container.value.host}/${container.value.id}?${new URLSearchParams(
params,
).toString()}`,
withBase(`/api/logs/${container.value.host}/${containerId}?${new URLSearchParams(params).toString()}`),
)
).text();
if (logs) {
@@ -152,11 +142,7 @@ export function useLogStream(container: Ref<Container>, streamConfig: LogStreamC
},
);
onUnmounted(() => {
if (es) {
es.close();
}
});
onUnmounted(() => close());
watch(
() => container.value.id,
+5 -5
View File
@@ -25,13 +25,13 @@
</pane>
</splitpanes>
<label
class="btn btn-circle swap swap-rotate fixed bottom-8 left-4"
:class="{ '!-left-3': collapseNav }"
class="btn btn-circle swap btn-neutral swap-rotate fixed -left-12 bottom-4 w-16 transition-all hover:-left-4"
:class="{ '!-left-6': collapseNav }"
v-if="!isMobile"
>
<input type="checkbox" v-model="collapseNav" />
<mdi:light-chevron-right class="swap-on text-secondary" />
<mdi:light-chevron-left class="swap-off" />
<mdi:chevron-right class="swap-on" />
<mdi:chevron-left class="swap-off" />
</label>
</div>
<dialog ref="modal" class="modal items-start bg-white/20 backdrop:backdrop-blur-sm" @close="open = false">
@@ -65,7 +65,7 @@
<script lang="ts" setup>
// @ts-ignore - splitpanes types are not available
import { Splitpanes, Pane } from "splitpanes";
import { collapseNav } from "@/composables/settings";
import { collapseNav } from "@/stores/settings";
const { authorizationNeeded } = config;
const containerStore = useContainerStore();
+27 -25
View File
@@ -1,25 +1,21 @@
@import "splitpanes/dist/splitpanes.css";
@define-mixin light {
--base-lighter-color: 0 0% 100%;
--base-color: 0 0% 97%;
--base-darker-color: 0 0% 90%;
--base-content-color: 0 0% 21%;
--primary-color: 171 100% 41%;
--primary-focus-color: 171 100% 31%;
--secondary-color: 34 95% 53%;
--secondary-focus-color: 34 95% 43%;
--base-lighter-color: 100% 0 0;
--base-color: 97% 0 0;
--base-darker-color: 90% 0 0;
--base-content-color: 33% 0 0;
--primary-color: 76.96% 0.143 176.65;
--secondary-color: 76.04% 0.168 64.07;
}
@define-mixin dark {
--base-lighter-color: 0 0% 14%;
--base-color: 0 0% 7%;
--base-darker-color: 0 0% 4%;
--base-content-color: 0 0% 86%;
--primary-color: 171 100% 41%;
--primary-focus-color: 171 100% 31%;
--secondary-color: 44 100% 77%;
--secondary-focus-color: 44 100% 67%;
--base-lighter-color: 25% 0 0;
--base-color: 18% 0 0;
--base-darker-color: 11% 0 0;
--base-content-color: 89.23% 0 0;
--primary-color: 76.96% 0.143 176.65;
--secondary-color: 91.36% 0.11098 90.1243;
}
@tailwind base;
@@ -29,19 +25,18 @@
@layer base {
:root {
--p: var(--primary-color);
--pf: var(--primary-focus-color);
--pc: 0 0% 100%;
--pc: 89.23% 0;
--s: var(--secondary-color);
--sf: var(--secondary-focus-color);
--sc: 0 0% 4%;
--sc: 14.57% 0 0;
--b1: var(--base-lighter-color);
--b2: var(--base-color);
--b3: var(--base-darker-color);
--bc: var(--base-content-color);
--in: 207 90% 54%;
--inc: 207 90% 94%;
--er: 4 90% 58%;
--erc: 4 90% 98%;
--in: 65% 0.171 249.5;
--inc: 99% 0.171 249.5;
--er: 64% 0.218 28.85;
--erc: 99% 0.218 28.85;
--n: var(--base-lighter-color);
}
html[data-theme="dark"] {
@mixin dark;
@@ -94,7 +89,10 @@
@layer components {
.input {
@apply focus:outline-none;
@apply !outline-none;
&.input-ghost {
@apply focus:border-transparent;
}
}
.btn {
@@ -118,4 +116,8 @@
--nc: var(--pc);
@apply active;
}
.link-primary {
@apply underline-offset-4 hover:underline;
}
}
-1
View File
@@ -1,4 +1,3 @@
// import "./styles.scss";
import "./main.css";
import { createApp, App as VueApp } from "vue";
import App from "./App.vue";
+9 -9
View File
@@ -144,7 +144,15 @@ export class SkippedLogsEntry extends LogEntry<string> {
}
export function asLogEntry(event: LogEvent): LogEntry<string | JSONObject> {
if (typeof event.m === "string") {
if (isObject(event.m)) {
return new ComplexLogEntry(
event.m,
event.id,
new Date(event.ts),
event.l,
event.s === "unknown" ? "stderr" : event.s ?? "stderr",
);
} else {
return new SimpleLogEntry(
event.m,
event.id,
@@ -153,13 +161,5 @@ export function asLogEntry(event: LogEvent): LogEntry<string | JSONObject> {
event.p,
event.s === "unknown" ? "stderr" : event.s ?? "stderr",
);
} else {
return new ComplexLogEntry(
event.m,
event.id,
new Date(event.ts),
event.l,
event.s === "unknown" ? "stderr" : event.s ?? "stderr",
);
}
}
+6 -7
View File
@@ -1,18 +1,17 @@
import { type App } from "vue";
import { createI18n } from "vue-i18n";
const messages = Object.fromEntries(
Object.entries(import.meta.glob<{ default: any }>("../../locales/*.y(a)?ml", { eager: true })).map(([key, value]) => {
const yaml = key.endsWith(".yaml");
return [key.slice(14, yaml ? -5 : -4), value.default];
}),
);
import messages from "@intlify/unplugin-vue-i18n/messages";
const locale = messages?.hasOwnProperty(navigator.language) ? navigator.language : navigator.language.slice(0, 2);
const i18n = createI18n({
legacy: false,
locale: navigator.language.slice(0, 2),
locale: locale,
fallbackLocale: "en",
messages,
});
export const install = (app: App) => {
app.use(i18n);
};
+1 -2
View File
@@ -2,13 +2,12 @@ import { type App } from "vue";
import { createRouter, createWebHistory } from "vue-router";
import pages from "~pages";
import { setupLayouts } from "virtual:generated-layouts";
import config from "@/stores/config";
export const install = (app: App) => {
const routes = setupLayouts(pages);
const router = createRouter({
history: createWebHistory(`${config.base}/`),
history: createWebHistory(withBase("/")),
routes,
});
+7 -5
View File
@@ -1,11 +1,13 @@
<template>
<div class="hero min-h-screen bg-base-200">
<div class="hero-content text-center">
<div class="max-w-md">
<p class="py-6 text-2xl font-bold">{{ $t("error.page-not-found") }}</p>
<page-with-links>
<div class="hero min-h-screen bg-base-200">
<div class="hero-content text-center">
<div class="max-w-md">
<p class="py-6 text-2xl font-bold">{{ $t("error.page-not-found") }}</p>
</div>
</div>
</div>
</div>
</page-with-links>
</template>
<script lang="ts" setup>
+18
View File
@@ -0,0 +1,18 @@
<template>
<page-with-links>
<section>
<article class="prose" v-html="data.content" v-if="data"></article>
</section>
</page-with-links>
</template>
<script lang="ts" setup>
const { id } = defineProps<{ id: string }>();
const { data } = useFetch(() => withBase("/api/content/" + id), {
refetch: true,
})
.get()
.json<{ title: string; content: string }>();
</script>
<style lang="postcss" scoped></style>
+2 -2
View File
@@ -1,5 +1,5 @@
<template>
<div class="flex flex-col gap-16 px-4 pt-8 md:px-8">
<page-with-links class="gap-16">
<section>
<div class="stats grid bg-base-lighter shadow">
<div class="stat">
@@ -25,7 +25,7 @@
<section>
<container-table :containers="runningContainers"></container-table>
</section>
</div>
</page-with-links>
</template>
<script lang="ts" setup>
+4 -3
View File
@@ -49,14 +49,15 @@ let password = $ref("");
let form: HTMLFormElement | undefined = $ref();
async function onLogin() {
const response = await fetch(`${config.base}/api/validateCredentials`, {
const url = config.authProvider === "simple" ? "/api/token" : "/api/validateCredentials";
const response = await fetch(withBase(url), {
body: new FormData(form),
method: "post",
method: "POST",
});
if (response.status == 200) {
error = false;
window.location.href = `${config.base}/`;
window.location.href = withBase("/");
} else {
error = true;
}
+18 -41
View File
@@ -1,15 +1,15 @@
<template>
<div class="mt-10 flex flex-col gap-8 px-4 md:px-8">
<page-with-links>
<section>
<div class="has-underline">
<h2>{{ $t("settings.about") }}</h2>
</div>
<div>
<span v-html="$t('settings.using-version', { version: currentVersion })"></span>
<span v-html="$t('settings.using-version', { version: config.version })"></span>
<div
v-if="hasUpdate"
v-html="$t('settings.update-available', { nextVersion: nextRelease.name, href: nextRelease.html_url })"
v-html="$t('settings.update-available', { nextVersion: latest?.name, href: latest?.htmlUrl })"
></div>
</div>
</section>
@@ -34,7 +34,7 @@
</div>
<div class="flex items-center gap-6">
<dropdown
<dropdown-menu
v-model="hourStyle"
:options="[
{ label: 'Auto', value: 'auto' },
@@ -45,7 +45,7 @@
{{ $t("settings.12-24-format") }}
</div>
<div class="flex items-center gap-6">
<dropdown
<dropdown-menu
v-model="size"
:options="[
{ label: 'Small', value: 'small' },
@@ -56,7 +56,7 @@
{{ $t("settings.font-size") }}
</div>
<div class="flex items-center gap-6">
<dropdown
<dropdown-menu
v-model="lightTheme"
:options="[
{ label: 'Auto', value: 'auto' },
@@ -85,50 +85,27 @@
<toggle v-model="automaticRedirect">{{ $t("settings.automatic-redirect") }}</toggle>
</div>
</section>
</div>
</page-with-links>
</template>
<script lang="ts" setup>
import {
search,
lightTheme,
smallerScrollbars,
showTimestamp,
showStd,
hourStyle,
showAllContainers,
size,
softWrap,
automaticRedirect,
} from "@/composables/settings";
hourStyle,
lightTheme,
search,
showAllContainers,
showStd,
showTimestamp,
size,
smallerScrollbars,
softWrap,
} from "@/stores/settings";
const { t } = useI18n();
setTitle(t("title.settings"));
const currentVersion = 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 =
release.tag_name.slice(1).localeCompare(currentVersion, undefined, { numeric: true, sensitivity: "base" }) > 0;
nextRelease = release;
}
} else {
hasUpdate = true;
nextRelease = {
html_url: "",
name: "master",
};
}
}
fetchNextRelease();
const { latest, hasUpdate } = useReleases();
</script>
<style lang="postcss" scoped>
.has-underline {
+14 -1
View File
@@ -1,3 +1,5 @@
import { type Settings } from "@/stores/settings";
const text = document.querySelector("script#config__json")?.textContent || "{}";
interface Config {
@@ -8,6 +10,15 @@ interface Config {
maxLogs: number;
hostname: string;
hosts: { name: string; id: string }[];
authProvider: "simple" | "none" | "forward-proxy";
user?: {
username: string;
email: string;
name: string;
avatar: string;
};
serverSettings?: Settings;
pages?: { id: string; title: string }[];
}
const pageConfig = JSON.parse(text);
@@ -19,4 +30,6 @@ const config: Config = {
config.version = config.version.replace(/^v/, "");
export default config;
export default Object.freeze(config);
export const withBase = (path: string) => `${config.base}${path}`;
+7 -1
View File
@@ -34,7 +34,7 @@ export const useContainerStore = defineStore("container", () => {
function connect() {
es?.close();
ready.value = false;
es = new EventSource(`${config.base}/api/events/stream`);
es = new EventSource(withBase("/api/events/stream"));
es.addEventListener("error", (e) => {
if (es?.readyState === EventSource.CLOSED) {
showToast(
@@ -76,6 +76,12 @@ export const useContainerStore = defineStore("container", () => {
}
});
es.onopen = () => {
if (containers.value.length > 0) {
containers.value = [];
}
};
watchOnce(containers, () => (ready.value = true));
}
+29
View File
@@ -0,0 +1,29 @@
const { data: releases } = useFetch(withBase("/api/releases")).get().json<
{
name: string;
mentionsCount: number;
createdAt: string;
body: string;
tag: string;
htmlUrl: string;
latest: boolean;
features: number;
bugFixes: number;
breaking: number;
}[]
>();
const hasUpdate = computed(() => {
if (!releases.value?.length) return false;
return releases.value[0].tag !== config.version;
});
const latest = computed(() => releases.value?.find((release) => release.latest));
export function useReleases() {
return {
hasUpdate,
latest,
releases,
};
}
@@ -1,7 +1,7 @@
import { toRefs } from "@vueuse/core";
const DOZZLE_SETTINGS_KEY = "DOZZLE_SETTINGS";
export const DEFAULT_SETTINGS: {
export type Settings = {
search: boolean;
size: "small" | "medium" | "large";
menuWidth: number;
@@ -14,7 +14,8 @@ export const DEFAULT_SETTINGS: {
softWrap: boolean;
collapseNav: boolean;
automaticRedirect: boolean;
} = {
};
export const DEFAULT_SETTINGS: Settings = {
search: true,
size: "medium",
menuWidth: 15,
@@ -30,7 +31,16 @@ export const DEFAULT_SETTINGS: {
};
export const settings = useStorage(DOZZLE_SETTINGS_KEY, DEFAULT_SETTINGS);
settings.value = { ...DEFAULT_SETTINGS, ...settings.value };
settings.value = { ...DEFAULT_SETTINGS, ...settings.value, ...config.serverSettings };
if (config.user) {
watch(settings, (value) => {
fetch(withBase("/api/profile/settings"), {
method: "PUT",
body: JSON.stringify(value),
});
});
}
export const {
collapseNav,
+1 -1
View File
@@ -61,7 +61,7 @@ services:
playwright:
container_name: playwright
image: mcr.microsoft.com/playwright:v1.38.0-jammy
image: mcr.microsoft.com/playwright:v1.39.0-jammy
working_dir: /app
volumes:
- .:/app
+3
View File
@@ -64,8 +64,11 @@ export default defineConfig({
text: "Advanced Configuration",
items: [
{ text: "Authentication", link: "/guide/authentication" },
{ text: "Changing Base", link: "/guide/changing-base" },
{ text: "Healthcheck", link: "/guide/healthcheck" },
{ text: "Hostname", link: "/guide/hostname" },
{ text: "Remote Hosts", link: "/guide/remote-hosts" },
{ text: "Custom Documentation", link: "/guide/custom-documentation" },
{ text: "Supported Env Vars", link: "/guide/supported-env-vars" },
{ text: "Logging Files on Disk", link: "/guide/log-files-on-disk" },
{ text: "Analytics", link: "/guide/analytics" },
+4 -2
View File
@@ -144,12 +144,14 @@
}
.VPHomeHero .name {
@apply font-playfair font-light text-7xl;
@apply text-7xl font-light;
font-family: "Playfair Display", serif;
}
.VPNavBarTitle .title {
@apply font-playfair font-light text-3xl;
@apply text-3xl font-light;
color: var(--bar-title-color);
font-family: "Playfair Display", serif;
}
.VPHero .container .image {
-30
View File
@@ -1,30 +0,0 @@
import { Handler, HandlerEvent, HandlerContext } from "@netlify/functions";
let cache;
let cacheTime = 0;
const handler: Handler = async (event: HandlerEvent, context: HandlerContext) => {
if (cache && Date.now() - cacheTime < 1000 * 60 * 10) {
const headers = {
"x-cache": "HIT",
};
return {
headers,
statusCode: 200,
body: JSON.stringify(cache),
};
}
const response = await fetch("https://hub.docker.com/v2/repositories/amir20/dozzle");
const data = await response.json();
const { full_description, ...rest } = data;
cache = rest;
cacheTime = Date.now();
return {
statusCode: 200,
body: JSON.stringify(rest),
};
};
export { handler };
+224
View File
@@ -4,6 +4,230 @@ title: Authentication
# Setting Up Authentication
Dozzle support two configurations for authentication. In the first configuration, you bring your own authentication method by protecting Dozzle through a proxy. Dozzle can read appropriate headers out of the box.
If you do not have an authentication solution then Dozzle has a simple file based user management solution. Authentication providers are setup using `--auth-provider` flag. In both of these configurations, Dozzle will try to save user settings to disk. This data is written to `/data`.
## Forward Proxy
Dozzle can be configured to read proxy headers by setting `--auth-provider` to `forward-proxy`.
::: code-group
```sh [cli]
$ docker run -v /var/run/docker.sock:/var/run/docker.sock -p 8080:8080 amir20/dozzle --auth-provider forward-proxy
```
```yaml [docker-compose.yml]
version: "3"
services:
dozzle:
image: amir20/dozzle:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
ports:
- 8080:8080
environment:
DOZZLE_AUTH_PROVIDER: forward-proxy
```
:::
In this mode, Dozzle expects the following headers:
- `Remote-User` to map to the username eg. `johndoe`
- `Remote-Email` to map to the user's email address. This email is also used to find the right [Gravatar](https://gravatar.com/) for the user.
- `Remote-Name` to be a display name like `John Doe`
### Setting up Dozzle with Authelia
[Authelia](https://www.authelia.com/) is an open-source authentication and authorization server and portal fulfilling the identity and access management. While setting up Authelia is out of scope for this section, the configuration can be shared as an example for setting up Dozzle with Authelia.
::: code-group
```yaml [docker-compose.yml]
version: "3.3"
networks:
net:
driver: bridge
services:
authelia:
image: authelia/authelia
container_name: authelia
volumes:
- ./authelia:/config
networks:
- net
labels:
- "traefik.enable=true"
- "traefik.http.routers.authelia.rule=Host(`authelia.example.com`)"
- "traefik.http.routers.authelia.entrypoints=https"
- "traefik.http.routers.authelia.tls=true"
- "traefik.http.routers.authelia.tls.options=default"
- "traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://authelia.example.com"
- "traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true"
- "traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email"
expose:
- 9091
restart: unless-stopped
traefik:
image: traefik:2.10.5
container_name: traefik
volumes:
- ./traefik:/etc/traefik
- /var/run/docker.sock:/var/run/docker.sock
networks:
- net
labels:
- "traefik.enable=true"
- "traefik.http.routers.api.rule=Host(`traefik.example.com`)"
- "traefik.http.routers.api.entrypoints=https"
- "traefik.http.routers.api.service=api@internal"
- "traefik.http.routers.api.tls=true"
- "traefik.http.routers.api.tls.options=default"
- "traefik.http.routers.api.middlewares=authelia@docker"
ports:
- "80:80"
- "443:443"
command:
- "--api"
- "--providers.docker=true"
- "--providers.docker.exposedByDefault=false"
- "--providers.file.filename=/etc/traefik/certificates.yml"
- "--entrypoints.http=true"
- "--entrypoints.http.address=:80"
- "--entrypoints.http.http.redirections.entrypoint.to=https"
- "--entrypoints.http.http.redirections.entrypoint.scheme=https"
- "--entrypoints.https=true"
- "--entrypoints.https.address=:443"
- "--log=true"
- "--log.level=DEBUG"
dozzle:
image: amir20/dozzle:latest
networks:
- net
environment:
DOZZLE_AUTH_PROVIDER: forward-proxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
labels:
- "traefik.enable=true"
- "traefik.http.routers.dozzle.rule=Host(`dozzle.example.com`)"
- "traefik.http.routers.dozzle.entrypoints=https"
- "traefik.http.routers.dozzle.tls=true"
- "traefik.http.routers.dozzle.tls.options=default"
- "traefik.http.routers.dozzle.middlewares=authelia@docker"
expose:
- 8080
restart: unless-stopped
```
```yaml [configuration.yml]
###############################################################
# Authelia configuration #
###############################################################
jwt_secret: a_very_important_secret
default_redirection_url: https://public.example.com
server:
host: 0.0.0.0
port: 9091
log:
level: info
totp:
issuer: authelia.com
authentication_backend:
file:
path: /config/users_database.yml
access_control:
default_policy: deny
rules:
- domain: traefik.example.com
policy: one_factor
- domain: dozzle.example.com
policy: one_factor
session:
secret: unsecure_session_secret
domain: example.com # Should match whatever your root protected domain is
regulation:
max_retries: 3
find_time: 120
ban_time: 300
storage:
encryption_key: you_must_generate_a_random_string_of_more_than_twenty_chars_and_configure_this
local:
path: /config/db.sqlite3
notifier:
filesystem:
filename: /config/notification.txt
```
:::
Valid SSL keys are required because Authelia only supports SSL.
## File Based User Management
Dozzle supports multi-user authentication by setting `--auth-provider` to `simple`. In this mode, Dozzle will try to read `/data/users.yml`. The content of the file looks like
```yml
users:
# "admin" here is username
admin:
name: "Admin"
# Just sha-256 which can be computed with "echo -n password | shasum -a 256"
password: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
email: me@email.net
```
Dozzle uses `email` to generate avatars using [Gravatar](https://gravatar.com/). It is optional.
The password is hashed using `sha256` which can be generated with `echo -n "secret-password" | shasum -a 256` or `echo -n "secret-password" | sha256sum` on linux.
You will need to mount this file for Dozzle to find it. Here is an example:
::: code-group
```sh [cli]
$ docker run -v /var/run/docker.sock:/var/run/docker.sock -v /path/to/dozzle/data:/data -p 8080:8080 amir20/dozzle --auth-provider simple
```
```yaml [docker-compose.yml]
version: "3"
services:
dozzle:
image: amir20/dozzle:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /path/to/dozzle/data:/data
ports:
- 8080:8080
environment:
DOZZLE_AUTH_PROVIDER: simple
```
:::
Dozzle uses [JWT](https://en.wikipedia.org/wiki/JSON_Web_Token) to generate tokens for authentication. This token is saved in a cookie.
## Single Username/Password
::: danger
`--username` and `--password` flags will be removed in v6.x in favor of `--auth-provider`.
:::
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 `--usernamefile` and `--passwordfile`.
::: code-group
+49
View File
@@ -0,0 +1,49 @@
---
title: Changing Application Base
---
# Changing Dozzle Base
Dozzle by default mounts to "/". This can be changed with the `--base` flag. For example, if you want to mount to "/foobar" then you can use `--base foobar` or the env variable `DOZZLE_BASE`.
::: code-group
```sh
docker run --volume=/var/run/docker.sock:/var/run/docker.sock -p 8080:8080 amir20/dozzle --base foobar
```
```yaml [docker-compose.yml]
version: "3"
services:
dozzle:
image: amir20/dozzle:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
ports:
- 8080:8080
environment:
DOZZLE_BASE: foobar
```
:::
Dozzle will be available at `http://localhost:8080/foobar/`. This option rewrites all assets to `/foobar/{file.path}` and automatically redirects `/foobar` to `/foobar/`.
## Example with proxy
Here is an example with Nginx and proxy Dozzle with a different base:
```conf
location ^~ /foobar/ {
include /config/nginx/proxy.conf;
include /config/nginx/resolver.conf;
set $upstream_app dozzle;
set $upstream_port 8080;
set $upstream_proto http;
proxy_pass $upstream_proto://$upstream_app:$upstream_port;
chunked_transfer_encoding off;
proxy_buffering off;
proxy_cache off;
}
```
+56
View File
@@ -0,0 +1,56 @@
---
title: Customizing Help Content
---
# Documentation and Content
::: tip Note
This feature was introduced in version `v5.1.x`
:::
Dozzle can be used by many users from different levels. Some teams what a central place to explain how to use Dozzle and what each of their containers do. Dozzle supports markdown content by reading `/data/content/*.md` files. Multiple files are supported. Each file will create a link at the top of Dozzle. Here is an example of what `/data/content/help.md` might look like:
```yml
---
title: Help
---
# This is help
Hello, from Dozzle.
Tables are also supported!
| foo | bar |
| --- | --- |
| baz | bim |
```
`title` is used to show a link on Dozzle's home screen to `/content/help`. Dozzle will need `/data` mounted. Here is an example:
::: code-group
```sh [cli]
$ docker run -v /var/run/docker.sock:/var/run/docker.sock -v /path/to/dozzle/data:/data -p 8080:8080 amir20/dozzle
```
```yaml [docker-compose.yml]
version: "3"
services:
dozzle:
image: amir20/dozzle:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /path/to/dozzle/data:/data
ports:
- 8080:8080
```
```yml [help.md]
---
title: Help
---
# This is help
Hello, from Dozzle.
```
:::
+30
View File
@@ -0,0 +1,30 @@
---
title: Hostname
---
# Changing Dozzle's Hostname
Dozzle's default connection is called localhost. Using `--hostname` flag Dozzle's name can be changed to anything. This value be will shown on the page title and under the Dozzle logo.
Changing the label for localhost also changes the label for the `localhost` connection which is displayed under the multi-host menu. Below is an example of using `--hostname` to change the name of Dozzle's subheader to `mywebsite.xyz`.
::: code-group
```sh
docker run --volume=/var/run/docker.sock:/var/run/docker.sock -p 8080:8080 amir20/dozzle --hostname mywebsite.xyz
```
```yaml [docker-compose.yml]
version: "3"
services:
dozzle:
image: amir20/dozzle:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
ports:
- 8080:8080
environment:
DOZZLE_HOSTNAME: mywebsite.xyz
```
:::
+4
View File
@@ -79,3 +79,7 @@ services:
```
:::
## Changing localhost label
`localhost` is a special connection and uses different configuration than `--remote-host`. Changing the label for localhost can be done using the `--hostname` or `DOZZLE_HOSTNAME` env variable. See [hostname](/guide/hostname) page for examples on how to use this flag.
+4 -5
View File
@@ -3,12 +3,11 @@
"type": "module",
"name": "docs",
"devDependencies": {
"@netlify/functions": "^2.1.0",
"@unocss/preset-typography": "^0.56.4",
"@unocss/reset": "^0.56.4",
"@unocss/transformer-directives": "^0.56.4",
"@unocss/preset-typography": "^0.57.3",
"@unocss/reset": "^0.57.3",
"@unocss/transformer-directives": "^0.57.3",
"dozzle": "workspace:*",
"sitemap": "^7.1.1",
"unocss": "^0.56.4"
"unocss": "^0.57.3"
}
}
+178 -212
View File
@@ -5,18 +5,15 @@ settings:
excludeLinksFromLockfile: false
devDependencies:
'@netlify/functions':
specifier: ^2.1.0
version: 2.1.0
'@unocss/preset-typography':
specifier: ^0.56.4
version: 0.56.4
specifier: ^0.57.3
version: 0.57.3
'@unocss/reset':
specifier: ^0.56.4
version: 0.56.4
specifier: ^0.57.3
version: 0.57.3
'@unocss/transformer-directives':
specifier: ^0.56.4
version: 0.56.4
specifier: ^0.57.3
version: 0.57.3
dozzle:
specifier: workspace:*
version: link:..
@@ -24,8 +21,8 @@ devDependencies:
specifier: ^7.1.1
version: 7.1.1
unocss:
specifier: ^0.56.4
version: 0.56.4(postcss@8.4.30)(vite@4.4.9)
specifier: ^0.57.3
version: 0.57.3(postcss@8.4.31)(vite@4.5.0)
packages:
@@ -34,7 +31,7 @@ packages:
engines: {node: '>=6.0.0'}
dependencies:
'@jridgewell/gen-mapping': 0.3.3
'@jridgewell/trace-mapping': 0.3.19
'@jridgewell/trace-mapping': 0.3.20
dev: true
/@antfu/install-pkg@0.1.1:
@@ -250,8 +247,8 @@ packages:
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
dev: true
/@iconify/utils@2.1.10:
resolution: {integrity: sha512-0/+5hxjzCZ9RoYpqxnOzbnpQyMdZRuHcMxPJeuX+x/aZkAAD/N4TajDjAPT7LpX+M0bfLExj/p0bbDkUfp0lrg==}
/@iconify/utils@2.1.11:
resolution: {integrity: sha512-M/w3PkN8zQYXi8N6qK/KhnYMfEbbb6Sk8RZVn8g+Pmmu5ybw177RpsaGwpziyHeUsu4etrexYSWq3rwnIqzYCg==}
dependencies:
'@antfu/install-pkg': 0.1.1
'@antfu/utils': 0.7.6
@@ -269,7 +266,7 @@ packages:
dependencies:
'@jridgewell/set-array': 1.1.2
'@jridgewell/sourcemap-codec': 1.4.15
'@jridgewell/trace-mapping': 0.3.19
'@jridgewell/trace-mapping': 0.3.20
dev: true
/@jridgewell/resolve-uri@3.1.1:
@@ -286,34 +283,13 @@ packages:
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
dev: true
/@jridgewell/trace-mapping@0.3.19:
resolution: {integrity: sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==}
/@jridgewell/trace-mapping@0.3.20:
resolution: {integrity: sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==}
dependencies:
'@jridgewell/resolve-uri': 3.1.1
'@jridgewell/sourcemap-codec': 1.4.15
dev: true
/@netlify/functions@2.1.0:
resolution: {integrity: sha512-QWuyj1Q6z6Cqcq3qCcOB75nxjYOxlNmv/d9/nnX4asSRQcWhM/ehXl/KDL6Sl9aufMRe6uQY10s6i9zCDNMRjg==}
engines: {node: '>=14.0.0'}
dependencies:
'@netlify/serverless-functions-api': 1.7.3
is-promise: 4.0.0
dev: true
/@netlify/node-cookies@0.1.0:
resolution: {integrity: sha512-OAs1xG+FfLX0LoRASpqzVntVV/RpYkgpI0VrUnw2u0Q1qiZUzcPffxRK8HF3gc4GjuhG5ahOEMJ9bswBiZPq0g==}
engines: {node: ^14.16.0 || >=16.0.0}
dev: true
/@netlify/serverless-functions-api@1.7.3:
resolution: {integrity: sha512-n6/7cJlSWvvbBlUOEAbkGyEld80S6KbG/ldQI9OhLfe1lTatgKmrTNIgqVNpaWpUdTgP2OHWFjmFBzkxxBWs5w==}
engines: {node: ^14.18.0 || >=16.0.0}
dependencies:
'@netlify/node-cookies': 0.1.0
urlpattern-polyfill: 8.0.2
dev: true
/@nodelib/fs.scandir@2.1.5:
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -339,241 +315,239 @@ packages:
resolution: {integrity: sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg==}
dev: true
/@rollup/pluginutils@5.0.4:
resolution: {integrity: sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g==}
/@rollup/pluginutils@5.0.5:
resolution: {integrity: sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
peerDependenciesMeta:
rollup:
optional: true
dependencies:
'@types/estree': 1.0.2
'@types/estree': 1.0.3
estree-walker: 2.0.2
picomatch: 2.3.1
dev: true
/@types/estree@1.0.2:
resolution: {integrity: sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==}
/@types/estree@1.0.3:
resolution: {integrity: sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==}
dev: true
/@types/node@17.0.45:
resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
dev: true
/@types/node@18.14.2:
resolution: {integrity: sha512-1uEQxww3DaghA0RxqHx0O0ppVlo43pJhepY51OxuQIKHpjbnYLA7vcdwioNPzIqmC2u3I/dmylcqjlh0e7AyUA==}
dev: true
/@types/sax@1.2.4:
resolution: {integrity: sha512-pSAff4IAxJjfAXUG6tFkO7dsSbTmf8CtUpfhhZ5VhkRpC4628tJhh3+V6H1E+/Gs9piSzYKT5yzHO5M4GG9jkw==}
/@types/sax@1.2.6:
resolution: {integrity: sha512-A1mpYCYu1aHFayy8XKN57ebXeAbh9oQIZ1wXcno6b1ESUAfMBDMx7mf/QGlYwcMRaFryh9YBuH03i/3FlPGDkQ==}
dependencies:
'@types/node': 18.14.2
'@types/node': 17.0.45
dev: true
/@unocss/astro@0.56.4(vite@4.4.9):
resolution: {integrity: sha512-2op/0y5UjiE0PRzKwHSEcOFtjDTkeIoCJR9FAYFC+xTx95Sp7+S4yClwaURZtohPnxQmGug0eGpOgsFzoJwmOQ==}
/@unocss/astro@0.57.3(vite@4.5.0):
resolution: {integrity: sha512-Kwu/k8iGNVrMtOuzJ7jKOvjYZFZz3recSxd7ceDp5Hi5SMsmjvXXHzkQ1Iypj1g0nczWcX4U+krROr2EH0GlnA==}
peerDependencies:
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0
peerDependenciesMeta:
vite:
optional: true
dependencies:
'@unocss/core': 0.56.4
'@unocss/reset': 0.56.4
'@unocss/vite': 0.56.4(vite@4.4.9)
vite: 4.4.9
'@unocss/core': 0.57.3
'@unocss/reset': 0.57.3
'@unocss/vite': 0.57.3(vite@4.5.0)
vite: 4.5.0
transitivePeerDependencies:
- rollup
dev: true
/@unocss/cli@0.56.4:
resolution: {integrity: sha512-WyqqJWfOVb7hvWU5nxtSFAGiDp/Peeohj1f/Xt0KlmgOPS8RA7kmLjRYruuuRuK5yR677IB+0C4kJDFkRpRlcA==}
/@unocss/cli@0.57.3:
resolution: {integrity: sha512-F5k0IjkbHFlZDcGAUr7UTa2xehxobfqWzooDL0tU9PtvAk6S4Edf5Iq0HymAcVK1k9yO17i7Pvg6dw7gOM0TIg==}
engines: {node: '>=14'}
hasBin: true
dependencies:
'@ampproject/remapping': 2.2.1
'@rollup/pluginutils': 5.0.4
'@unocss/config': 0.56.4
'@unocss/core': 0.56.4
'@unocss/preset-uno': 0.56.4
'@rollup/pluginutils': 5.0.5
'@unocss/config': 0.57.3
'@unocss/core': 0.57.3
'@unocss/preset-uno': 0.57.3
cac: 6.7.14
chokidar: 3.5.3
colorette: 2.0.20
consola: 3.2.3
fast-glob: 3.3.1
magic-string: 0.30.3
fast-glob: 3.3.2
magic-string: 0.30.5
pathe: 1.1.1
perfect-debounce: 1.0.0
transitivePeerDependencies:
- rollup
dev: true
/@unocss/config@0.56.4:
resolution: {integrity: sha512-e8pdkoTkKxqkimAttZ5ELiDXn3oYteNDOxNANygWUhw3h6DA1sKh831dQkhTu3Kigsy0/gQXmFXbekrlywMkKw==}
/@unocss/config@0.57.3:
resolution: {integrity: sha512-jrjvmcrrdiHHLqp6LBpHWs7VAnumFK3fEdMH7celRe+99CTOvRn73caUThyD2Ftt8rDIMejmteR1hqVBH51kug==}
engines: {node: '>=14'}
dependencies:
'@unocss/core': 0.56.4
unconfig: 0.3.10
'@unocss/core': 0.57.3
unconfig: 0.3.11
dev: true
/@unocss/core@0.56.4:
resolution: {integrity: sha512-9DL+2adfjWWnFWp2QOJTq2OcfKKo++agaXnCX7CzeD8KgfWCBqxs2RDoxP3HrDbTflhDHdbjz23B+vJoVykhZw==}
/@unocss/core@0.57.3:
resolution: {integrity: sha512-o6snDo5vwAenIqA+wjjI6BUsftJXXSqrPHYqplb+QX5bLfxW/OU1xhBRlnhiP0BOGGZXqgGEETU1ym8MM9bLwA==}
dev: true
/@unocss/extractor-arbitrary-variants@0.56.4:
resolution: {integrity: sha512-Q93Bu3wZld+Vj5dqfJYH6phcmjm7fy6mEul64RexLWF00As9WrRzWEXbLVYmmVwUm9e5xcxZO6s98gKq1nTH6Q==}
/@unocss/extractor-arbitrary-variants@0.57.3:
resolution: {integrity: sha512-OmF+2TjJ97i7KOCR8wPgZK/pkp8Rcfo4tzqT/4jBUIi7rfDGZx/eE3aZKFpZSZlUuTH5cdReaKxymXQmJ4dibA==}
dependencies:
'@unocss/core': 0.56.4
'@unocss/core': 0.57.3
dev: true
/@unocss/inspector@0.56.4:
resolution: {integrity: sha512-DY3kmX5nGTb8QsbqcGYDvyXXTPhGw7PoVN48wA8o0c+KkcfjjfsoZRTFDpj6P5JcW8Z68gIBOLyTnql6tIMesg==}
/@unocss/inspector@0.57.3:
resolution: {integrity: sha512-Oj5cUbuwx+4/rckW3mfpdKMWzhOOSehXChzuJ7x7tMDDB5ywdHwnDsxtK07Y+5UwKHC322T3I3VtLolOfsdlCA==}
dependencies:
'@unocss/rule-utils': 0.56.4
'@unocss/core': 0.57.3
'@unocss/rule-utils': 0.57.3
gzip-size: 6.0.0
sirv: 2.0.3
dev: true
/@unocss/postcss@0.56.4(postcss@8.4.30):
resolution: {integrity: sha512-lc0+BjW1gY+86tvtUFppHg++Tw8I0V7hlldloUy7W4Fg2Mu8VAi3I/KaSSRvqMr5SNlfaOPv0XuB/BNkDE65Gw==}
/@unocss/postcss@0.57.3(postcss@8.4.31):
resolution: {integrity: sha512-rYXQ2/iXeF59/g8xbvoyYJ9EClQCBcWj2oeJCt85dykOYyQJCWJT+LoYF0s/kvg7m+x5ovdNQfXtAACLYBqh9g==}
engines: {node: '>=14'}
peerDependencies:
postcss: ^8.4.21
dependencies:
'@unocss/config': 0.56.4
'@unocss/core': 0.56.4
'@unocss/rule-utils': 0.56.4
'@unocss/config': 0.57.3
'@unocss/core': 0.57.3
'@unocss/rule-utils': 0.57.3
css-tree: 2.3.1
fast-glob: 3.3.1
magic-string: 0.30.3
postcss: 8.4.30
fast-glob: 3.3.2
magic-string: 0.30.5
postcss: 8.4.31
dev: true
/@unocss/preset-attributify@0.56.4:
resolution: {integrity: sha512-mjs9NDniycAdipiLCHpGS75mItfFq3ruX6w4ypcvGxVAa8cGOhotWAAxqgHKSJobdDcoFTjnCaK1SA0svBplUQ==}
/@unocss/preset-attributify@0.57.3:
resolution: {integrity: sha512-leX9jxM2PnqvZn42thDb2rPdE0nq6WtIr98pvdnkRZKt5gLwtOJCANXH/gVP6tPdRRf6FiZstA8jvAxVGL1sIA==}
dependencies:
'@unocss/core': 0.56.4
'@unocss/core': 0.57.3
dev: true
/@unocss/preset-icons@0.56.4:
resolution: {integrity: sha512-N02I6ZYt2cAThE4pINTRY5tUeHNhcUQYeoqtQhQUk7YFfwRMosWA5JbtH7LTzQpYbE2GsbjXkKdII4bkTWNwhA==}
/@unocss/preset-icons@0.57.3:
resolution: {integrity: sha512-cG7gaFQzSidHS+nHPV9HEB3aaUVs/PjZywxMl5jwkJIWHuFMU/SQZXMorH6avU2jH8PoYkRZfjLdRWA+h/+fPA==}
dependencies:
'@iconify/utils': 2.1.10
'@unocss/core': 0.56.4
'@iconify/utils': 2.1.11
'@unocss/core': 0.57.3
ofetch: 1.3.3
transitivePeerDependencies:
- supports-color
dev: true
/@unocss/preset-mini@0.56.4:
resolution: {integrity: sha512-FCKs3sUUiHLXQ/ON/ZCQc2JZQox4tv0W8Du+8Y1mRZ6w1yazqqBhR2yDPaEZ51yKcHmDf6ndiTvcJ2tPIz/o8g==}
/@unocss/preset-mini@0.57.3:
resolution: {integrity: sha512-2KFxbbxRqhc+0fyWNYSiRGGr+3jp4jEQIRnjT8sv5uAMo1OaUmUTwz2qzYhSc3sCM8ZEofblZY2BOcqJwZ5yxA==}
dependencies:
'@unocss/core': 0.56.4
'@unocss/extractor-arbitrary-variants': 0.56.4
'@unocss/rule-utils': 0.56.4
'@unocss/core': 0.57.3
'@unocss/extractor-arbitrary-variants': 0.57.3
'@unocss/rule-utils': 0.57.3
dev: true
/@unocss/preset-tagify@0.56.4:
resolution: {integrity: sha512-CURDiJ+wzjzIARS2vF413hZ94u0EVK0+miY6l1eN6RrlW/YfU8LOaakbC/b0Cxl7hOhLrZuXFTgymcS9Ep4TMw==}
/@unocss/preset-tagify@0.57.3:
resolution: {integrity: sha512-GXs5Hu6XtHcIRLexPghHkfb6ekSodh4Xs8895xrvP5H7Tm/+wEIHALXkW762Rujl1Rtq+xzxAeCoeFxW+y9eLw==}
dependencies:
'@unocss/core': 0.56.4
'@unocss/core': 0.57.3
dev: true
/@unocss/preset-typography@0.56.4:
resolution: {integrity: sha512-/rwCZ0SHa8ht/Chb2rCoENL/S/CWspnTtvK4pPxALEcx/Mqi8Amk45YipU7gXmLsQ3eKwY/s0V1EgHF0DOccHg==}
/@unocss/preset-typography@0.57.3:
resolution: {integrity: sha512-C/pIfRY56wxBuV4bTIeZMZYMmYc0gD8DU+sJSPWiZJP1JHiLc3FzSnc51BYcT/Dqdx0fDWhJyP2qqo9000VFKQ==}
dependencies:
'@unocss/core': 0.56.4
'@unocss/preset-mini': 0.56.4
'@unocss/core': 0.57.3
'@unocss/preset-mini': 0.57.3
dev: true
/@unocss/preset-uno@0.56.4:
resolution: {integrity: sha512-E4VbUe1nIeJ7D1en84osGzwP79IBJ1P9v37Pcx2PvaRAQTKf6WRF7EXzmzEm6HPF+skyGOX9RWuXs9QFODsW8A==}
/@unocss/preset-uno@0.57.3:
resolution: {integrity: sha512-dLZrFc6GrE5J0zAZMFXk/c4WKq7fmU0jCgHvbDXLGdKdJ7zpByslhc2YTPqkLW40F6+73SCN7DlARInSh2fa4g==}
dependencies:
'@unocss/core': 0.56.4
'@unocss/preset-mini': 0.56.4
'@unocss/preset-wind': 0.56.4
'@unocss/rule-utils': 0.56.4
'@unocss/core': 0.57.3
'@unocss/preset-mini': 0.57.3
'@unocss/preset-wind': 0.57.3
'@unocss/rule-utils': 0.57.3
dev: true
/@unocss/preset-web-fonts@0.56.4:
resolution: {integrity: sha512-H9Vy4nYpx1jpQvSBHBTSiB83zU85CtehoK03EbzUrz/i69EjBWiw52+mP/vrtMK0M/5QghcpNf64i8pObFId+w==}
/@unocss/preset-web-fonts@0.57.3:
resolution: {integrity: sha512-W/voQjgo98oj/D/oGrhL4xAS0XsR6fF9yULu3xf4nWrUkdkZq/64/rOM5uLBgUFSmkulW524Dsjd1INYmPzz8w==}
dependencies:
'@unocss/core': 0.56.4
'@unocss/core': 0.57.3
ofetch: 1.3.3
dev: true
/@unocss/preset-wind@0.56.4:
resolution: {integrity: sha512-llQYQxrA531kl1juYMyaDEEl/IVdHoIPXaaJTdNHbGkSbMqrpZSRzLPsGE+CpaOBKTNqa93UyU1qlUGSr9ZB+A==}
/@unocss/preset-wind@0.57.3:
resolution: {integrity: sha512-LymBZtNK86qEpLpbH5eOAiHNFvkIAjfL+Jlok5xI/yO/GCqjnTiw1QAxu2vxLUnQlqlvu7IykOx+Hk1nNvkSaA==}
dependencies:
'@unocss/core': 0.56.4
'@unocss/preset-mini': 0.56.4
'@unocss/rule-utils': 0.56.4
'@unocss/core': 0.57.3
'@unocss/preset-mini': 0.57.3
'@unocss/rule-utils': 0.57.3
dev: true
/@unocss/reset@0.56.4:
resolution: {integrity: sha512-7o3Jpog49ORWW+Rpz7bzYY+CStfLMyBXAeDOzhM2PQjftuQl4ZQOvIdXi6zrXDEN2SJQsi279s2t50ERZN8YcA==}
/@unocss/reset@0.57.3:
resolution: {integrity: sha512-E6Q8jucQlVLOM+d+F5DKGi/8GVc8KDwAQnbcpbrGL/1iix4IM3emRkPmujgTLWS+HIRRcWcEvT6sNwnd9r6H2A==}
dev: true
/@unocss/rule-utils@0.56.4:
resolution: {integrity: sha512-pZmTPqi/Tb+vbwjNXu9+PwxEh7PVe+FwP+pzUbfNSUpE5Hg9CCbl8hpsXRE/r8kdaoKhmw2ryxvjocOgKXG/ag==}
/@unocss/rule-utils@0.57.3:
resolution: {integrity: sha512-koWXeRJHFt/SJdb3m7s/2+NrBCcUtl67SX7rSHJc99Z+dwZgIsPUfdfRKM4mZD43MayybvDrd1Wue2LNQg5R/g==}
engines: {node: '>=14'}
dependencies:
'@unocss/core': 0.56.4
'@unocss/core': 0.57.3
magic-string: 0.30.5
dev: true
/@unocss/scope@0.56.4:
resolution: {integrity: sha512-fS8HEWFUeuNhE3fSBwgSfcgC8JS80sYY692uvpwWUN29gFawQDyEmAkGbqDZoBuvfFtKN2IA+evVJ5iED1vtuA==}
/@unocss/scope@0.57.3:
resolution: {integrity: sha512-hL0Gjd5getA6ziiOvu1M2Jw5e+FnD9rzu+t+4SnxWcpP+bZtu+LBrt5FeqrizwUHfE/723iuEvg16W5hjhGLQA==}
dev: true
/@unocss/transformer-attributify-jsx-babel@0.56.4:
resolution: {integrity: sha512-ftkW+HX8tgtSG8bL3Ek9NeSHJfLLdIhokyP/31Kx+DZoOXcwGFN2oBfOs/tkPRdLXUDeEh/9+xLZyXSfrq2Kjw==}
/@unocss/transformer-attributify-jsx-babel@0.57.3:
resolution: {integrity: sha512-b5esljHAz274tv0sXe8GmHew7FXzwkRQrod6NdR9pyFlPQ9gn7gxi0MIsvIV0U8PdSz3HOHOuT0tU/zphjaJDA==}
dependencies:
'@unocss/core': 0.56.4
'@unocss/core': 0.57.3
dev: true
/@unocss/transformer-attributify-jsx@0.56.4:
resolution: {integrity: sha512-GWiiCMI4W5MaF5y7GejnvIjRzgpTj6xouQYXurMVyq8GaXX34oSzin09Vh7F2/9lMD582UV5/ISu54A78RtswA==}
/@unocss/transformer-attributify-jsx@0.57.3:
resolution: {integrity: sha512-OgejFNN7AcCgudh/HGe2BS00TbRv7Bi+siWeUS7AEGEG+p7cQZn92XljCFZGqIyvgfVWypb6/xve9H4oY3/E+w==}
dependencies:
'@unocss/core': 0.56.4
'@unocss/core': 0.57.3
dev: true
/@unocss/transformer-compile-class@0.56.4:
resolution: {integrity: sha512-/sPAamamhYBwg29ITrS9FOPF4Mt5r6H0ST0XdAaDVqt0JM3QmMHYUAn5YqvMS1z7c+A6FwsuZ5L0Sn55auXa+g==}
/@unocss/transformer-compile-class@0.57.3:
resolution: {integrity: sha512-DqmRTQujqAdk4uSrqy+t9xSVmKM9E3yW9PCwDxI1evva0/qTFexzjoR42glq8x7LSn0ZmFyflXcQoeXmwjsBrg==}
dependencies:
'@unocss/core': 0.56.4
'@unocss/core': 0.57.3
dev: true
/@unocss/transformer-directives@0.56.4:
resolution: {integrity: sha512-L+h0pPgukk12TuqGaZjvGI0y6i3tFfl0dXJnJ2W6eeK2Y2CMirfhCo9Cb2uF81gfIdFKJqXGY6qujK+AS0ji/Q==}
/@unocss/transformer-directives@0.57.3:
resolution: {integrity: sha512-GXedqnb0PB+XkjdfNEInuLxxLjPbaCQLCUhSvTcw67+kalGgX2Mn/hYwpSHmDMLe+Uld5n0vcJRu2a8chBdAwA==}
dependencies:
'@unocss/core': 0.56.4
'@unocss/rule-utils': 0.56.4
'@unocss/core': 0.57.3
'@unocss/rule-utils': 0.57.3
css-tree: 2.3.1
dev: true
/@unocss/transformer-variant-group@0.56.4:
resolution: {integrity: sha512-4ebNLWgspeoaHDLQ6qW8ZEWl7rzXURxBJLaw1hUqMwfbM/HEc5gPwUy7k7W+4TJyB426UpBb6kWLgO2WRrJXWw==}
/@unocss/transformer-variant-group@0.57.3:
resolution: {integrity: sha512-PpKtnwyb4ncjDhsTm/PtiL0RUdmaee+07W0AzSEz29IFFwyrueIP6WHmD6agKmgDPoaw5Ywebt6DdkSbnfYHzw==}
dependencies:
'@unocss/core': 0.56.4
'@unocss/core': 0.57.3
dev: true
/@unocss/vite@0.56.4(vite@4.4.9):
resolution: {integrity: sha512-hswOJk2C7scwQioLIGDnT+H/bbC1EEkq+MhN+VDiVlRUYwaIgLITmSp0q6y6JK2RKD7VhDVxLNMYw4LxxcJuzA==}
/@unocss/vite@0.57.3(vite@4.5.0):
resolution: {integrity: sha512-SX2wtxRFLka0CgMwqokKuhaBUptj8vcpmLObVRRgV+7dSdx6GMbZcjZfQfibMKhJY3d5iSAylcfyW2JqTX2F+g==}
peerDependencies:
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0
dependencies:
'@ampproject/remapping': 2.2.1
'@rollup/pluginutils': 5.0.4
'@unocss/config': 0.56.4
'@unocss/core': 0.56.4
'@unocss/inspector': 0.56.4
'@unocss/scope': 0.56.4
'@unocss/transformer-directives': 0.56.4
'@rollup/pluginutils': 5.0.5
'@unocss/config': 0.57.3
'@unocss/core': 0.57.3
'@unocss/inspector': 0.57.3
'@unocss/scope': 0.57.3
'@unocss/transformer-directives': 0.57.3
chokidar: 3.5.3
fast-glob: 3.3.1
magic-string: 0.30.3
vite: 4.4.9
fast-glob: 3.3.2
magic-string: 0.30.5
vite: 4.5.0
transitivePeerDependencies:
- rollup
dev: true
@@ -666,12 +640,12 @@ packages:
ms: 2.1.2
dev: true
/defu@6.1.2:
resolution: {integrity: sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==}
/defu@6.1.3:
resolution: {integrity: sha512-Vy2wmG3NTkmHNg/kzpuvHhkqeIx3ODWqasgCRbKtbXEN0G+HpEEv9BtJLp7ZG1CZloFaC41Ah3ZFbq7aqCqMeQ==}
dev: true
/destr@2.0.1:
resolution: {integrity: sha512-M1Ob1zPSIvlARiJUkKqvAZ3VAqQY6Jcuth/pBKQ2b1dX/Qx0OnJ8Vux6J2H5PTMQeRzWrrbTu70VxBfv/OPDJA==}
/destr@2.0.2:
resolution: {integrity: sha512-65AlobnZMiCET00KaFFjUefxDX0khFA/E4myqZ7a6Sq1yZtR8+FVIvilVX66vF2uobSumxooYZChiRPCKNqhmg==}
dev: true
/duplexer@0.1.2:
@@ -727,8 +701,8 @@ packages:
strip-final-newline: 2.0.0
dev: true
/fast-glob@3.3.1:
resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==}
/fast-glob@3.3.2:
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
engines: {node: '>=8.6.0'}
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -815,10 +789,6 @@ packages:
engines: {node: '>=0.12.0'}
dev: true
/is-promise@4.0.0:
resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
dev: true
/is-stream@2.0.1:
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
engines: {node: '>=8'}
@@ -853,8 +823,8 @@ packages:
p-locate: 5.0.0
dev: true
/magic-string@0.30.3:
resolution: {integrity: sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==}
/magic-string@0.30.5:
resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==}
engines: {node: '>=12'}
dependencies:
'@jridgewell/sourcemap-codec': 1.4.15
@@ -892,7 +862,7 @@ packages:
acorn: 8.10.0
pathe: 1.1.1
pkg-types: 1.0.3
ufo: 1.3.0
ufo: 1.3.1
dev: true
/mrmime@1.0.1:
@@ -929,9 +899,9 @@ packages:
/ofetch@1.3.3:
resolution: {integrity: sha512-s1ZCMmQWXy4b5K/TW9i/DtiN8Ku+xCiHcjQ6/J/nDdssirrQNOoB165Zu8EqLMA2lln1JUth9a0aW9Ap2ctrUg==}
dependencies:
destr: 2.0.1
destr: 2.0.2
node-fetch-native: 1.4.0
ufo: 1.3.0
ufo: 1.3.1
dev: true
/onetime@5.1.2:
@@ -990,8 +960,8 @@ packages:
pathe: 1.1.1
dev: true
/postcss@8.4.30:
resolution: {integrity: sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g==}
/postcss@8.4.31:
resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
nanoid: 3.3.6
@@ -1015,8 +985,8 @@ packages:
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
dev: true
/rollup@3.29.3:
resolution: {integrity: sha512-T7du6Hum8jOkSWetjRgbwpM6Sy0nECYrYRSmZjayFcOddtKJWU4d17AC3HNUk7HRuqy4p+G7aEZclSHytqUmEg==}
/rollup@3.29.4:
resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==}
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
hasBin: true
optionalDependencies:
@@ -1029,8 +999,8 @@ packages:
queue-microtask: 1.2.3
dev: true
/sax@1.2.4:
resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==}
/sax@1.3.0:
resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==}
dev: true
/shebang-command@2.0.0:
@@ -1064,9 +1034,9 @@ packages:
hasBin: true
dependencies:
'@types/node': 17.0.45
'@types/sax': 1.2.4
'@types/sax': 1.2.6
arg: 5.0.2
sax: 1.2.4
sax: 1.3.0
dev: true
/source-map-js@1.0.2:
@@ -1091,24 +1061,24 @@ packages:
engines: {node: '>=6'}
dev: true
/ufo@1.3.0:
resolution: {integrity: sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw==}
/ufo@1.3.1:
resolution: {integrity: sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==}
dev: true
/unconfig@0.3.10:
resolution: {integrity: sha512-tj317lhIq2iZF/NXrJnU1t2UaGUKKz1eL1sK2t63Oq66V9BxqvZV12m55fp/fpQJ+DDmVlLgo7cnLVOZkhlO/A==}
/unconfig@0.3.11:
resolution: {integrity: sha512-bV/nqePAKv71v3HdVUn6UefbsDKQWRX+bJIkiSm0+twIds6WiD2bJLWWT3i214+J/B4edufZpG2w7Y63Vbwxow==}
dependencies:
'@antfu/utils': 0.7.6
defu: 6.1.2
defu: 6.1.3
jiti: 1.20.0
mlly: 1.4.2
dev: true
/unocss@0.56.4(postcss@8.4.30)(vite@4.4.9):
resolution: {integrity: sha512-RcaZHLSlj7BAupx6Y9jJwOSRJ+Lu1pnLwRZuppMfD4GgWWNymDRJ7HF1uNqCCYhxJa1LgU1irOEfO8pzK8NYkw==}
/unocss@0.57.3(postcss@8.4.31)(vite@4.5.0):
resolution: {integrity: sha512-reIvKa1sG9bwRZ6oGwj8p2XZSmT5On/NEisqkxsk1vTV5ZHIagbilG3aNMb5vNcI7MhRb4dy0Z4cvyNGd3194Q==}
engines: {node: '>=14'}
peerDependencies:
'@unocss/webpack': 0.56.4
'@unocss/webpack': 0.57.3
vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0
peerDependenciesMeta:
'@unocss/webpack':
@@ -1116,39 +1086,35 @@ packages:
vite:
optional: true
dependencies:
'@unocss/astro': 0.56.4(vite@4.4.9)
'@unocss/cli': 0.56.4
'@unocss/core': 0.56.4
'@unocss/extractor-arbitrary-variants': 0.56.4
'@unocss/postcss': 0.56.4(postcss@8.4.30)
'@unocss/preset-attributify': 0.56.4
'@unocss/preset-icons': 0.56.4
'@unocss/preset-mini': 0.56.4
'@unocss/preset-tagify': 0.56.4
'@unocss/preset-typography': 0.56.4
'@unocss/preset-uno': 0.56.4
'@unocss/preset-web-fonts': 0.56.4
'@unocss/preset-wind': 0.56.4
'@unocss/reset': 0.56.4
'@unocss/transformer-attributify-jsx': 0.56.4
'@unocss/transformer-attributify-jsx-babel': 0.56.4
'@unocss/transformer-compile-class': 0.56.4
'@unocss/transformer-directives': 0.56.4
'@unocss/transformer-variant-group': 0.56.4
'@unocss/vite': 0.56.4(vite@4.4.9)
vite: 4.4.9
'@unocss/astro': 0.57.3(vite@4.5.0)
'@unocss/cli': 0.57.3
'@unocss/core': 0.57.3
'@unocss/extractor-arbitrary-variants': 0.57.3
'@unocss/postcss': 0.57.3(postcss@8.4.31)
'@unocss/preset-attributify': 0.57.3
'@unocss/preset-icons': 0.57.3
'@unocss/preset-mini': 0.57.3
'@unocss/preset-tagify': 0.57.3
'@unocss/preset-typography': 0.57.3
'@unocss/preset-uno': 0.57.3
'@unocss/preset-web-fonts': 0.57.3
'@unocss/preset-wind': 0.57.3
'@unocss/reset': 0.57.3
'@unocss/transformer-attributify-jsx': 0.57.3
'@unocss/transformer-attributify-jsx-babel': 0.57.3
'@unocss/transformer-compile-class': 0.57.3
'@unocss/transformer-directives': 0.57.3
'@unocss/transformer-variant-group': 0.57.3
'@unocss/vite': 0.57.3(vite@4.5.0)
vite: 4.5.0
transitivePeerDependencies:
- postcss
- rollup
- supports-color
dev: true
/urlpattern-polyfill@8.0.2:
resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==}
dev: true
/vite@4.4.9:
resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==}
/vite@4.5.0:
resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==}
engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true
peerDependencies:
@@ -1176,8 +1142,8 @@ packages:
optional: true
dependencies:
esbuild: 0.18.20
postcss: 8.4.30
rollup: 3.29.3
postcss: 8.4.31
rollup: 3.29.4
optionalDependencies:
fsevents: 2.3.3
dev: true
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

+25 -7
View File
@@ -5,12 +5,12 @@ require (
github.com/alexflint/go-arg v1.4.3
github.com/beme/abide v0.0.0-20190723115211-635a09831760
github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/docker v24.0.6+incompatible
github.com/docker/docker v24.0.7+incompatible
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1
github.com/gogo/protobuf v1.3.2 // indirect
github.com/gorilla/sessions v1.2.1
github.com/gorilla/sessions v1.2.2
github.com/magiconair/properties v1.8.7
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
github.com/morikuni/aec v1.0.0 // indirect
@@ -18,29 +18,47 @@ require (
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/sirupsen/logrus v1.9.3
github.com/spf13/afero v1.9.5
github.com/spf13/afero v1.10.0
github.com/stretchr/objx v0.5.0 // indirect
github.com/stretchr/testify v1.8.4
golang.org/x/net v0.14.0 // indirect
golang.org/x/sys v0.11.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gopkg.in/yaml.v3 v3.0.1
)
require github.com/go-chi/chi/v5 v5.0.10
require (
github.com/PuerkitoBio/goquery v1.8.1
github.com/go-chi/chi/v5 v5.0.10
github.com/go-chi/jwtauth/v5 v5.1.1
github.com/yuin/goldmark v1.6.0
github.com/yuin/goldmark-meta v1.1.0
)
require (
github.com/alexflint/go-scalar v1.2.0 // indirect
github.com/andybalholm/cascadia v1.3.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/google/go-cmp v0.5.5 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/kr/pretty v0.2.1 // indirect
github.com/lestrrat-go/blackmagic v1.0.1 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc v1.0.4 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/jwx/v2 v2.0.11 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect
golang.org/x/crypto v0.12.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/text v0.12.0 // indirect
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
golang.org/x/tools v0.12.0 // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
gotest.tools/v3 v3.0.3 // indirect
)
go 1.21
go 1.21.4
+67 -6
View File
@@ -42,11 +42,15 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
github.com/alexflint/go-arg v1.4.3 h1:9rwwEBpMXfKQKceuZfYcwuc/7YY7tWJbFsgG5cAU/uo=
github.com/alexflint/go-arg v1.4.3/go.mod h1:3PZ/wp/8HuqRZMUUgu7I+e1qcpUbvmS258mRXkFH4IA=
github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw=
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
github.com/beme/abide v0.0.0-20190723115211-635a09831760 h1:FvTM5NSN5HYvfKpgL+8x73U5v063vHsd7AX05eV1DnM=
github.com/beme/abide v0.0.0-20190723115211-635a09831760/go.mod h1:6+8gCKsZnxzhGTmKRh4BSkLos9CbWRJNcrp55We4SqQ=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -61,12 +65,13 @@ github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v24.0.5+incompatible h1:WmgcE4fxyI6EEXxBRxsHnZXrO1pQ3smi0k/jho4HLeY=
github.com/docker/docker v24.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE=
github.com/docker/docker v24.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@@ -81,9 +86,13 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/jwtauth/v5 v5.1.1 h1:Pjixqu5YkjE9sCLpzE01L0Q4sQzJIPdo7uz9r8ftp/c=
github.com/go-chi/jwtauth/v5 v5.1.1/go.mod h1:CYP1WSbzD4MPuKCr537EM3kfFhSQgpUEtMJFuYJjqWU=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -144,8 +153,12 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@@ -161,6 +174,19 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8=
github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
github.com/lestrrat-go/jwx/v2 v2.0.11 h1:ViHMnaMeaO0qV16RZWBHM7GTrAnX2aFLVKofc7FuKLQ=
github.com/lestrrat-go/jwx/v2 v2.0.11/go.mod h1:ZtPtMFlrfDrH2Y0iwfa3dRFn8VzwBrB+cyrm3IBWdDg=
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
@@ -179,12 +205,14 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY=
github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
@@ -193,6 +221,7 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE
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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/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.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@@ -202,6 +231,11 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
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.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc=
github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -214,7 +248,11 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -248,6 +286,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -281,7 +321,12 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -303,6 +348,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -341,10 +388,18 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -353,6 +408,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -410,6 +467,8 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -511,6 +570,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+47
View File
@@ -0,0 +1,47 @@
package auth
import (
"context"
"crypto/md5"
"encoding/hex"
"net/http"
"strings"
log "github.com/sirupsen/logrus"
)
type contextKey string
const remoteUser contextKey = "remoteUser"
type proxyAuthAuth struct {
}
func hashEmail(email string) string {
email = strings.TrimSpace(email)
email = strings.ToLower(email)
hash := md5.Sum([]byte(email))
return hex.EncodeToString(hash[:])
}
func NewForwardProxyAuth() *proxyAuthAuth {
return &proxyAuthAuth{}
}
func (p *proxyAuthAuth) AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Remote-Email") != "" {
user := newUser(r.Header.Get("Remote-User"), r.Header.Get("Remote-Email"), r.Header.Get("Remote-Name"))
ctx := context.WithValue(r.Context(), remoteUser, user)
next.ServeHTTP(w, r.WithContext(ctx))
} else {
next.ServeHTTP(w, r)
}
})
}
func (p *proxyAuthAuth) CreateToken(username, password string) (string, error) {
log.Fatalf("CreateToken not implemented for proxy auth")
return "", nil
}
+49
View File
@@ -0,0 +1,49 @@
package auth
import (
"crypto/sha256"
"errors"
"net/http"
"time"
"github.com/go-chi/jwtauth/v5"
)
type simpleAuthContext struct {
UserDatabase UserDatabase
tokenAuth *jwtauth.JWTAuth
}
var ErrInvalidCredentials = errors.New("invalid credentials")
func NewSimpleAuth(userDatabase UserDatabase) *simpleAuthContext {
h := sha256.New()
for _, user := range userDatabase.Users {
h.Write([]byte(user.Password))
}
tokenAuth := jwtauth.New("HS256", h.Sum(nil), nil)
return &simpleAuthContext{
UserDatabase: userDatabase,
tokenAuth: tokenAuth,
}
}
func (a *simpleAuthContext) CreateToken(username, password string) (string, error) {
user := a.UserDatabase.FindByPassword(username, password)
if user == nil {
return "", ErrInvalidCredentials
}
_, tokenString, err := a.tokenAuth.Encode(map[string]interface{}{"username": user.Username, "email": user.Email, "name": user.Name, "timestamp": time.Now()})
if err != nil {
return "", err
}
return tokenString, nil
}
func (a *simpleAuthContext) AuthMiddleware(next http.Handler) http.Handler {
return jwtauth.Verifier(a.tokenAuth)(next)
}
+115
View File
@@ -0,0 +1,115 @@
package auth
import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"net/http"
"os"
"github.com/go-chi/jwtauth/v5"
"gopkg.in/yaml.v3"
)
type User struct {
Username string `json:"username"`
Email string `json:"email" yaml:"email"`
Name string `json:"name" yaml:"name"`
Avatar string `json:"avatar,omitempty"`
Password string `json:"-" yaml:"password"`
}
func newUser(username, email, name string) User {
avatar := ""
if email != "" {
avatar = fmt.Sprintf("https://gravatar.com/avatar/%s?d=https%%3A%%2F%%2Fui-avatars.com%%2Fapi%%2F/%s/128", hashEmail(email), name)
}
return User{
Username: username,
Email: email,
Name: name,
Avatar: avatar,
}
}
type UserDatabase struct {
Users map[string]*User `yaml:"users"`
}
func ReadUsersFromFile(path string) (UserDatabase, error) {
users := UserDatabase{}
file, err := os.Open(path)
if err != nil {
return users, err
}
defer file.Close()
if err := yaml.NewDecoder(file).Decode(&users); err != nil {
return users, err
}
for username, user := range users.Users {
user.Username = username
}
return users, nil
}
func (u *UserDatabase) Find(username string) *User {
user, ok := u.Users[username]
if !ok {
return nil
}
return user
}
func (u *UserDatabase) FindByPassword(username, password string) *User {
user := u.Find(username)
if user == nil {
return nil
}
if user.Password != sha256sum(password) {
return nil
}
return user
}
func sha256sum(s string) string {
bytes := sha256.Sum256([]byte(s))
return hex.EncodeToString(bytes[:])
}
func UserFromContext(ctx context.Context) *User {
if user, ok := ctx.Value(remoteUser).(User); ok {
return &user
} else {
if _, claims, err := jwtauth.FromContext(ctx); err == nil {
username, ok := claims["username"].(string)
if !ok {
return nil
}
if username == "" {
return nil
}
email := claims["email"].(string)
name := claims["name"].(string)
user := newUser(username, email, name)
return &user
}
return nil
}
}
func RequireAuthentication(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user := UserFromContext(r.Context())
if user != nil {
next.ServeHTTP(w, r)
} else {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
})
}
+67
View File
@@ -0,0 +1,67 @@
package content
import (
"bytes"
"fmt"
"os"
"path/filepath"
"github.com/yuin/goldmark"
meta "github.com/yuin/goldmark-meta"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
)
type Page struct {
Id string `json:"id"`
Title string `json:"title"`
Content string `json:"content,omitempty"`
}
func ReadAll() ([]Page, error) {
var pages []Page
files, err := filepath.Glob("data/content/*.md")
if err != nil {
return nil, fmt.Errorf("error reading /data/content/*.md: %w", err)
}
for _, file := range files {
id := filepath.Base(file)
id = id[0 : len(id)-3]
page, err := Read(id)
if err != nil {
return nil, fmt.Errorf("error reading %s: %w", id, err)
}
pages = append(pages, page)
}
return pages, nil
}
func Read(id string) (Page, error) {
data, err := os.ReadFile("data/content/" + id + ".md")
if err != nil {
return Page{}, fmt.Errorf("error reading /data/content/%s.md: %w", id, err)
}
markdown := goldmark.New(
goldmark.WithExtensions(extension.GFM, meta.New()),
)
context := parser.NewContext()
var buf bytes.Buffer
if err := markdown.Convert(data, &buf, parser.WithContext(context)); err != nil {
return Page{}, fmt.Errorf("error converting markdown: %w", err)
}
metaData := meta.Get(context)
page := Page{
Content: buf.String(),
Id: id,
Title: id,
}
if title, ok := metaData["title"]; ok {
page.Title = title.(string)
}
return page, nil
}
@@ -31,7 +31,7 @@ var bufPool = sync.Pool{
},
}
var BadHeaderErr = fmt.Errorf("dozzle/docker: unable to read header")
var ErrBadHeader = fmt.Errorf("dozzle/docker: unable to read header")
func NewEventGenerator(reader io.Reader, tty bool) *EventGenerator {
generator := &EventGenerator{
@@ -83,11 +83,12 @@ func (g *EventGenerator) consumeReader() {
}
if readerError != nil {
if readerError != BadHeaderErr {
if readerError != ErrBadHeader {
log.Debugf("reader error: %v", readerError)
g.Errors <- readerError
close(g.buffer)
break
}
close(g.buffer)
break
}
}
g.wg.Done()
@@ -119,12 +120,14 @@ func readEvent(reader *bufio.Reader, tty bool) (string, StdType, error) {
}
return message, streamType, nil
} else {
n, err := reader.Read(header)
n, err := io.ReadFull(reader, header)
if err != nil {
return "", streamType, err
}
if n != 8 {
return "", streamType, BadHeaderErr
log.Warnf("unable to read header: %v", header)
message, _ := reader.ReadString('\n')
return message, streamType, ErrBadHeader
}
switch header[0] {
@@ -133,7 +136,7 @@ func readEvent(reader *bufio.Reader, tty bool) (string, StdType, error) {
case 2:
streamType = STDERR
default:
log.Warnf("unknown stream type %d", header[0])
log.Warnf("unknown stream type: %v", header[0])
}
count := binary.BigEndian.Uint32(header[4:])
@@ -7,18 +7,30 @@ import (
var KEY_VALUE_REGEX = regexp.MustCompile(`level=(\w+)`)
var ANSI_COLOR_REGEX = regexp.MustCompile(`\x1b\[[0-9;]*m`)
var LOG_LEVELS = []string{"error", "warn", "warning", "info", "debug", "trace", "fatal"}
var LOG_LEVELS_PLAIN = map[string]*regexp.Regexp{}
var LOG_LEVEL_BRACKET = map[string]*regexp.Regexp{}
func init() {
for _, level := range LOG_LEVELS {
LOG_LEVELS_PLAIN[level] = regexp.MustCompile("(?i)^" + level + "[^a-z]")
}
for _, level := range LOG_LEVELS {
LOG_LEVEL_BRACKET[level] = regexp.MustCompile("(?i)\\[ ?" + level + " ?\\]")
}
}
func guessLogLevel(logEvent *LogEvent) string {
switch value := logEvent.Message.(type) {
case string:
levels := []string{"error", "warn", "warning", "info", "debug", "trace", "fatal"}
stripped := ANSI_COLOR_REGEX.ReplaceAllString(value, "") // remove ansi color codes
for _, level := range levels {
if match, _ := regexp.MatchString("(?i)^"+level+"[^a-z]", stripped); match {
for _, level := range LOG_LEVELS {
if LOG_LEVELS_PLAIN[level].MatchString(stripped) {
return level
}
if strings.Contains(value, "["+strings.ToUpper(level)+"]") {
if LOG_LEVEL_BRACKET[level].MatchString(stripped) {
return level
}
+39
View File
@@ -0,0 +1,39 @@
package docker
import (
"testing"
)
func TestGuessLogLevel(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"ERROR: Something went wrong", "error"},
{"WARN: Something might be wrong", "warn"},
{"INFO: Something happened", "info"},
{"debug: Something happened", "debug"},
{"debug Something happened", "debug"},
{"TRACE: Something happened", "trace"},
{"FATAL: Something happened", "fatal"},
{"level=error Something went wrong", "error"},
{"[ERROR] Something went wrong", "error"},
{"[error] Something went wrong", "error"},
{"[ ERROR ] Something went wrong", "error"},
{"[error] Something went wrong", "error"},
{"[test] [error] Something went wrong", "error"},
{"[foo] [ ERROR] Something went wrong", "error"},
{"123 ERROR Something went wrong", "error"},
{"123 Something went wrong", ""},
}
for _, test := range tests {
logEvent := &LogEvent{
Message: test.input,
}
if level := guessLogLevel(logEvent); level != test.expected {
t.Errorf("guessLogLevel(%s) = %s, want %s", test.input, level, test.expected)
}
}
}
+99
View File
@@ -0,0 +1,99 @@
package profile
import (
"encoding/json"
"errors"
"os"
"path/filepath"
"github.com/amir20/dozzle/internal/auth"
log "github.com/sirupsen/logrus"
)
type Settings struct {
Search bool `json:"search"`
MenuWidth float32 `json:"menuWidth"`
SmallerScrollbars bool `json:"smallerScrollbars"`
ShowTimestamp bool `json:"showTimestamp"`
ShowStd bool `json:"showStd"`
ShowAllContainers bool `json:"showAllContainers"`
SoftWrap bool `json:"softWrap"`
CollapseNav bool `json:"collapseNav"`
AutomaticRedirect bool `json:"automaticRedirect"`
Size string `json:"size,omitempty"`
LightTheme string `json:"lightTheme,omitempty"`
HourStyle string `json:"hourStyle,omitempty"`
}
var data_path string
func init() {
path, err := filepath.Abs("./data")
if err != nil {
log.Fatalf("Unable to get absolute path for data directory: %s", err)
return
}
if _, err := os.Stat(path); os.IsNotExist(err) {
if err := os.Mkdir(path, 0755); err != nil {
log.Fatalf("Unable to create data directory: %s", err)
return
}
}
data_path = path
}
func SaveUserSettings(user auth.User, settings Settings) error {
path := filepath.Join(data_path, user.Username)
// Create user directory if it doesn't exist
if _, err := os.Stat(path); os.IsNotExist(err) {
if err := os.Mkdir(path, 0755); err != nil {
return err
}
}
settings_path := filepath.Join(path, "settings.json")
data, err := json.MarshalIndent(settings, "", " ")
if err != nil {
return err
}
f, err := os.Create(settings_path)
if err != nil {
return err
}
defer f.Close()
if _, err := f.Write(data); err != nil {
return err
}
log.Debugf("Saved settings for user %s", user.Username)
return f.Sync()
}
func LoadUserSettings(user auth.User) (Settings, error) {
path := filepath.Join(data_path, user.Username)
settings_path := filepath.Join(path, "settings.json")
if _, err := os.Stat(settings_path); os.IsNotExist(err) {
return Settings{}, errors.New("Settings file does not exist")
}
f, err := os.Open(settings_path)
if err != nil {
return Settings{}, err
}
defer f.Close()
var settings Settings
if err := json.NewDecoder(f).Decode(&settings); err != nil {
return Settings{}, err
}
return settings, nil
}
+90
View File
@@ -0,0 +1,90 @@
package releases
import (
"bytes"
"encoding/json"
"net/http"
"strings"
"time"
"github.com/PuerkitoBio/goquery"
"github.com/yuin/goldmark"
)
type githubRelease struct {
Name string `json:"name"`
MentionsCount int `json:"mentions_count"`
TagName string `json:"tag_name"`
Body string `json:"body"`
CreatedAt time.Time `json:"created_at"`
HtmlUrl string `json:"html_url"`
}
type Release struct {
Name string `json:"name"`
MentionsCount int `json:"mentionsCount"`
Tag string `json:"tag"`
Body string `json:"body"`
CreatedAt time.Time `json:"createdAt"`
HtmlUrl string `json:"htmlUrl"`
Latest bool `json:"latest"`
Features int `json:"features"`
BugFixes int `json:"bugFixes"`
Breaking int `json:"breaking"`
}
func Fetch(currentVersion string) ([]Release, error) {
response, err := http.Get("https://api.github.com/repos/amir20/dozzle/releases?per_page=9")
if err != nil {
return nil, err
}
defer response.Body.Close()
var githubReleases []githubRelease
if err := json.NewDecoder(response.Body).Decode(&githubReleases); err != nil {
return []Release{}, err
}
var releases []Release
for _, githubRelease := range githubReleases {
var buffer bytes.Buffer
goldmark.Convert([]byte(githubRelease.Body), &buffer)
html := buffer.String()
if githubRelease.TagName == currentVersion {
break
}
release := Release{
Name: githubRelease.Name,
MentionsCount: githubRelease.MentionsCount,
Tag: githubRelease.TagName,
Body: html,
CreatedAt: githubRelease.CreatedAt,
HtmlUrl: githubRelease.HtmlUrl,
}
doc, _ := goquery.NewDocumentFromReader(&buffer)
doc.Find("h3").Each(func(i int, s *goquery.Selection) {
if strings.Contains(s.Text(), "Features") {
release.Features = s.Next().Find("li").Length()
}
if strings.Contains(s.Text(), "Bug Fixes") {
release.BugFixes = s.Next().Find("li").Length()
}
if strings.Contains(s.Text(), "Breaking Changes") {
release.Breaking = s.Next().Find("li").Length()
}
})
releases = append(releases, release)
}
if len(releases) > 0 {
releases[0].Latest = true
}
return releases, nil
}
@@ -1,7 +1,7 @@
/* snapshot: Test_createRoutes_foobar */
HTTP/1.1 200 OK
Connection: close
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' gravatar.com data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Type: text/plain; charset=utf-8
foo page
@@ -9,7 +9,7 @@ foo page
/* snapshot: Test_createRoutes_index */
HTTP/1.1 200 OK
Connection: close
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' gravatar.com data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Type: text/plain; charset=utf-8
index page
@@ -17,7 +17,7 @@ index page
/* snapshot: Test_createRoutes_redirect */
HTTP/1.1 301 Moved Permanently
Connection: close
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' gravatar.com data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Type: text/html; charset=utf-8
Location: /foobar/
@@ -26,16 +26,25 @@ Location: /foobar/
/* snapshot: Test_createRoutes_redirect_with_auth */
HTTP/1.1 307 Temporary Redirect
Connection: close
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' gravatar.com data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Type: text/html; charset=utf-8
Location: /foobar/login
<a href="/foobar/login">Temporary Redirect</a>.
/* snapshot: Test_createRoutes_simple_redirect */
HTTP/1.1 307 Temporary Redirect
Connection: close
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' gravatar.com data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Type: text/html; charset=utf-8
Location: /login
<a href="/login">Temporary Redirect</a>.
/* snapshot: Test_createRoutes_username_password */
HTTP/1.1 307 Temporary Redirect
Connection: close
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' gravatar.com data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Type: text/html; charset=utf-8
Location: /login
@@ -44,7 +53,7 @@ Location: /login
/* snapshot: Test_createRoutes_username_password_invalid */
HTTP/1.1 401 Unauthorized
Connection: close
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' gravatar.com data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
@@ -56,7 +65,7 @@ Connection: close
Cache-Control: no-transform
Cache-Control: no-cache
Connection: keep-alive
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' gravatar.com data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Type: text/event-stream
X-Accel-Buffering: no
@@ -66,7 +75,7 @@ data: end of stream
/* snapshot: Test_createRoutes_version */
HTTP/1.1 200 OK
Connection: close
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' gravatar.com data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Type: text/html
<pre>dev</pre>
@@ -74,8 +83,8 @@ Content-Type: text/html
/* snapshot: Test_handler_between_dates */
HTTP/1.1 200 OK
Connection: close
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Type: application/ld+json; charset=UTF-8
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' gravatar.com data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Type: application/x-jsonl; charset=UTF-8
{"m":"INFO Testing stdout logs...","ts":1589396137772,"id":466600245,"l":"info","s":"stdout"}
{"m":"INFO Testing stderr logs...","ts":1589396197772,"id":1101501603,"l":"info","s":"stderr"}
@@ -101,7 +110,7 @@ Connection: close
Cache-Control: no-transform
Cache-Control: no-cache
Connection: keep-alive
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' gravatar.com data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Type: text/event-stream
X-Accel-Buffering: no
@@ -114,7 +123,7 @@ Connection: close
Cache-Control: no-transform
Cache-Control: no-cache
Connection: keep-alive
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' gravatar.com data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Type: text/event-stream
X-Accel-Buffering: no
@@ -132,7 +141,7 @@ data: {"actorId":"1234","name":"start","host":"localhost"}
/* snapshot: Test_handler_streamLogs_error_finding_container */
HTTP/1.1 404 Not Found
Connection: close
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' gravatar.com data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
@@ -144,7 +153,7 @@ Connection: close
Cache-Control: no-transform
Cache-Control: no-cache
Connection: keep-alive
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' gravatar.com data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Type: text/plain; charset=utf-8
X-Accel-Buffering: no
X-Content-Type-Options: nosniff
@@ -154,7 +163,7 @@ test error
/* snapshot: Test_handler_streamLogs_error_std */
HTTP/1.1 400 Bad Request
Connection: close
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' gravatar.com data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
@@ -166,7 +175,7 @@ Connection: close
Cache-Control: no-transform
Cache-Control: no-cache
Connection: keep-alive
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' gravatar.com data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Type: text/event-stream
X-Accel-Buffering: no
@@ -181,7 +190,7 @@ Connection: close
Cache-Control: no-transform
Cache-Control: no-cache
Connection: keep-alive
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' gravatar.com data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Type: text/event-stream
X-Accel-Buffering: no
@@ -194,7 +203,7 @@ Connection: close
Cache-Control: no-transform
Cache-Control: no-cache
Connection: keep-alive
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' gravatar.com data:; manifest-src 'self'; connect-src 'self' api.github.com;
Content-Type: text/event-stream
X-Accel-Buffering: no
+34
View File
@@ -101,6 +101,40 @@ func (h *handler) validateCredentials(w http.ResponseWriter, r *http.Request) {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
func (h *handler) createToken(w http.ResponseWriter, r *http.Request) {
user := r.PostFormValue("username")
pass := r.PostFormValue("password")
if token, err := h.config.Authorization.Authorizer.CreateToken(user, pass); err == nil {
http.SetCookie(w, &http.Cookie{
Name: "jwt",
Value: token,
HttpOnly: true,
Path: "/",
SameSite: http.SameSiteLaxMode,
})
log.Infof("Token created for user %s", user)
w.WriteHeader(http.StatusOK)
w.Write([]byte(http.StatusText(http.StatusOK)))
} else {
log.Errorf("Error while creating token: %v", err)
http.Error(w, err.Error(), http.StatusUnauthorized)
}
}
func (h *handler) deleteToken(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, &http.Cookie{
Name: "jwt",
Value: "",
HttpOnly: true,
Path: "/",
SameSite: http.SameSiteLaxMode,
Expires: time.Unix(0, 0),
})
w.WriteHeader(http.StatusOK)
w.Write([]byte(http.StatusText(http.StatusOK)))
}
func (h *handler) clearSession(w http.ResponseWriter, r *http.Request) {
if !secured {
log.Panic("Validating credentials with secured=false should not happen")
+25
View File
@@ -0,0 +1,25 @@
package web
import (
"encoding/json"
"net/http"
"github.com/amir20/dozzle/internal/content"
"github.com/go-chi/chi/v5"
log "github.com/sirupsen/logrus"
)
func (h *handler) staticContent(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
content, err := content.Read(id)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Warnf("error reading content: %v", err)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(content); err != nil {
log.Errorf("json encoding error while streaming %v", err.Error())
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ import (
func cspHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Security-Policy", "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; manifest-src 'self'; connect-src 'self' api.github.com;")
w.Header().Set("Content-Security-Policy", "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' gravatar.com data:; manifest-src 'self'; connect-src 'self' api.github.com;")
next.ServeHTTP(w, r)
})
}
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"net/http"
"sync"
"github.com/amir20/dozzle/docker"
"github.com/amir20/dozzle/internal/docker"
log "github.com/sirupsen/logrus"
)
+24
View File
@@ -0,0 +1,24 @@
package web
import (
"fmt"
"net/http"
log "github.com/sirupsen/logrus"
)
func (h *handler) healthcheck(w http.ResponseWriter, r *http.Request) {
log.Trace("Executing healthcheck request")
var client DockerClient
for _, v := range h.clients {
client = v
break
}
if ping, err := client.Ping(r.Context()); err != nil {
log.Error(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
} else {
fmt.Fprintf(w, "OK API Version %v", ping.APIVersion)
}
}
+144
View File
@@ -0,0 +1,144 @@
package web
import (
"encoding/json"
"html/template"
"io"
"sort"
"net/http"
"path"
"github.com/amir20/dozzle/internal/auth"
"github.com/amir20/dozzle/internal/content"
"github.com/amir20/dozzle/internal/docker"
"github.com/amir20/dozzle/internal/profile"
log "github.com/sirupsen/logrus"
)
func (h *handler) index(w http.ResponseWriter, req *http.Request) {
_, err := h.content.Open(req.URL.Path)
if err == nil && req.URL.Path != "" && req.URL.Path != "/" {
fileServer.ServeHTTP(w, req)
} else {
if !isAuthorized(req) && req.URL.Path != "login" {
http.Redirect(w, req, path.Clean(h.config.Base+"/login"), http.StatusTemporaryRedirect)
return
}
h.executeTemplate(w, req)
}
}
func (h *handler) executeTemplate(w http.ResponseWriter, req *http.Request) {
base := ""
if h.config.Base != "/" {
base = h.config.Base
}
hosts := make([]*docker.Host, 0, len(h.clients))
for _, v := range h.clients {
hosts = append(hosts, v.Host())
}
sort.Slice(hosts, func(i, j int) bool {
return hosts[i].Name < hosts[j].Name
})
config := map[string]interface{}{
"base": base,
"version": h.config.Version,
"authorizationNeeded": h.isAuthorizationNeeded(req),
"secured": secured,
"hostname": h.config.Hostname,
"hosts": hosts,
"authProvider": h.config.Authorization.Provider,
}
pages, err := content.ReadAll()
if err != nil {
log.Errorf("error reading content: %v", err)
} else if len(pages) > 0 {
for _, page := range pages {
page.Content = ""
}
config["pages"] = pages
}
user := auth.UserFromContext(req.Context())
if user != nil {
if settings, err := profile.LoadUserSettings(*user); err == nil {
config["serverSettings"] = settings
} else {
config["serverSettings"] = struct{}{}
}
config["user"] = user
} else if h.config.Authorization.Provider == FORWARD_PROXY {
log.Error("Unable to find remote user. Please check your proxy configuration. Expecting headers Remote-Email, Remote-User, Remote-Name.")
log.Debugf("Dumping all headers for url /%s", req.URL.String())
for k, v := range req.Header {
log.Debugf("%s: %s", k, v)
}
http.Error(w, "Unauthorized user", http.StatusUnauthorized)
return
} else if h.config.Authorization.Provider == SIMPLE && req.URL.Path != "login" {
log.Debugf("Redirecting to login page for url /%s", req.URL.String())
http.Redirect(w, req, path.Clean(h.config.Base+"/login"), http.StatusTemporaryRedirect)
return
}
data := map[string]interface{}{
"Config": config,
"Dev": h.config.Dev,
"Manifest": h.readManifest(),
"Base": base,
}
file, err := h.content.Open("index.html")
if err != nil {
log.Panic(err)
}
bytes, err := io.ReadAll(file)
if err != nil {
log.Panic(err)
}
tmpl, err := template.New("index.html").Funcs(template.FuncMap{
"marshal": func(v interface{}) template.JS {
var p []byte
if h.config.Dev {
p, _ = json.MarshalIndent(v, "", " ")
} else {
p, _ = json.Marshal(v)
}
return template.JS(p)
},
}).Parse(string(bytes))
if err != nil {
log.Panic(err)
}
err = tmpl.Execute(w, data)
if err != nil {
log.Panic(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func (h *handler) readManifest() map[string]interface{} {
if h.config.Dev {
return map[string]interface{}{}
} else {
file, err := h.content.Open("manifest.json")
if err != nil {
// this should only happen during test. In production, the file is embedded in the binary and checked in main.go
return map[string]interface{}{}
}
bytes, err := io.ReadAll(file)
if err != nil {
log.Fatalf("Could not read manifest.json: %v", err)
}
var manifest map[string]interface{}
err = json.Unmarshal(bytes, &manifest)
if err != nil {
log.Fatalf("Could not parse manifest.json: %v", err)
}
return manifest
}
}
+2 -2
View File
@@ -12,7 +12,7 @@ import (
"time"
"github.com/amir20/dozzle/docker"
"github.com/amir20/dozzle/internal/docker"
"github.com/docker/docker/pkg/stdcopy"
"github.com/dustin/go-humanize"
"github.com/go-chi/chi/v5"
@@ -51,7 +51,7 @@ func (h *handler) downloadLogs(w http.ResponseWriter, r *http.Request) {
}
func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/ld+json; charset=UTF-8")
w.Header().Set("Content-Type", "application/x-jsonl; charset=UTF-8")
from, _ := time.Parse(time.RFC3339, r.URL.Query().Get("from"))
to, _ := time.Parse(time.RFC3339, r.URL.Query().Get("to"))
+32
View File
@@ -0,0 +1,32 @@
package web
import (
"encoding/json"
"net/http"
"github.com/amir20/dozzle/internal/auth"
"github.com/amir20/dozzle/internal/profile"
log "github.com/sirupsen/logrus"
)
func (h *handler) saveSettings(w http.ResponseWriter, r *http.Request) {
var settings profile.Settings
if err := json.NewDecoder(r.Body).Decode(&settings); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
user := auth.UserFromContext(r.Context())
if user == nil {
http.Error(w, "Unable to find user", http.StatusInternalServerError)
return
}
if err := profile.SaveUserSettings(*user, settings); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
log.Errorf("Unable to save user settings: %s", err)
return
}
w.WriteHeader(http.StatusOK)
}

Some files were not shown because too many files have changed in this diff Show More