Compare commits

..

124 Commits

Author SHA1 Message Date
Amir Raminfar 645b9a8311 Release 4.7.2 2023-01-30 11:17:29 -08:00
Amir Raminfar 862bd449fd Fixes int tests 2023-01-30 11:14:18 -08:00
Amir Raminfar 0a7ea0251a Uses docker compose instead docker-compose 2023-01-30 10:09:09 -08:00
renovate[bot] 5809a5478b Update docker/setup-buildx-action action to v2.4.0 (#2023)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-30 09:26:20 -08:00
Amir Raminfar 8bbdfb4a9e Uses gha cache 2023-01-30 09:16:57 -08:00
Amir Raminfar 74b5ff48fc Updates cypress 2023-01-30 09:16:39 -08:00
Amir Raminfar c6c24aaf9f Changes minimum ms to 10ms 2023-01-30 09:00:53 -08:00
Amir Raminfar e133b59969 Updates tests 2023-01-30 08:54:46 -08:00
Amir Raminfar 61fea7afaa Hide date when mobile 2023-01-29 13:39:53 -08:00
renovate[bot] 46aa4ab005 Update dependency unplugin-auto-import to ^0.13.0 (#2022) 2023-01-29 13:18:38 -08:00
renovate[bot] 8a8b2607a1 Update docker/setup-buildx-action action to v2.3.0 (#2020)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-28 06:42:39 -08:00
renovate[bot] 5df604e480 Update dependency unplugin-vue-components to ^0.23.0 (#2021)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-28 06:42:31 -08:00
Amir Raminfar 7a169dd555 Release 4.7.1 2023-01-27 13:30:55 -08:00
Amir Raminfar af49e6fd6f Does not center multiline 2023-01-27 12:40:05 -08:00
Amir Raminfar 36a03d91fc Fixes error position to use flex instead (#2019) 2023-01-27 11:03:22 -08:00
Amir Raminfar 143246237d Fixes prefix bug 2023-01-25 13:02:33 -08:00
Amir Raminfar a0dbfeaba5 Release 4.7.0 2023-01-25 11:04:13 -08:00
Amir Raminfar d19d6efa8b Release 4.6.5 2023-01-25 10:54:08 -08:00
Amir Raminfar fd97c50da8 Fixes release 2023-01-25 10:54:00 -08:00
Amir Raminfar de109abd76 Release 4.6.4 2023-01-25 10:52:56 -08:00
Amir Raminfar 0e5830df20 Makes changes to log streamer to guess the event level (#2013)
* Makes changes to log streamer to guess the event level

* Updates diff

* Adds log class for level

* Groups messages by level

* Fixes bugs for grouping

* Fixes tests

* Fixes json bug

* Updates logic to support other kind of levels

* Fixes mobile view
2023-01-25 10:39:21 -08:00
Amir Raminfar 20e158c7bd Uses latest 2023-01-25 10:26:10 -08:00
Amir Raminfar b77c908719 Updates node 2023-01-25 10:24:08 -08:00
Amir Raminfar 5f9dc2780c Adds -- 2023-01-25 10:09:02 -08:00
Amir Raminfar 9c905da702 Adds --outputTruncateLength=500 2023-01-25 10:07:26 -08:00
Amir Raminfar 4729e7f29e Fixes tests 2023-01-25 09:50:07 -08:00
renovate[bot] 10c5a2739c Update dependency vitest to ^0.28.0 (#2016)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-23 08:51:39 -08:00
renovate[bot] 8d75729bca Update module github.com/docker/docker to v20.10.23+incompatible (#2014)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-23 08:51:29 -08:00
Amir Raminfar 3f61b8cfdc Release 4.6.3 2023-01-19 11:37:53 -08:00
Amir Raminfar 3a55fdfff0 Updates modules 2023-01-19 11:37:46 -08:00
renovate[bot] 941fb60336 Update module github.com/dustin/go-humanize to v1.0.1 (#2010)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-17 11:46:46 -08:00
renovate[bot] 133261fba2 Update docker/build-push-action action to v3.3.0 (#2011) 2023-01-14 08:06:59 -08:00
renovate[bot] 9fc01567a2 Update golang Docker tag to v1.19.5 (#2009)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-11 10:03:11 -08:00
renovate[bot] 4118204948 Update dependency vitest to ^0.27.0 (#2007) 2023-01-09 07:11:38 -08:00
dependabot[bot] 12bb8d3f68 Bump github.com/docker/distribution (#2004)
Bumps [github.com/docker/distribution](https://github.com/docker/distribution) from 2.7.1+incompatible to 2.8.0+incompatible.
- [Release notes](https://github.com/docker/distribution/releases)
- [Commits](https://github.com/docker/distribution/compare/v2.7.1...v2.8.0)

---
updated-dependencies:
- dependency-name: github.com/docker/distribution
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-07 13:39:14 -08:00
dependabot[bot] 88517ff0f7 Bump github.com/opencontainers/image-spec from 1.0.1 to 1.0.2 (#2005) 2023-01-07 12:25:33 -08:00
renovate[bot] 6dd3758f98 Update dependency jsdom to v21 (#2003) 2023-01-07 09:58:42 -08:00
Amir Raminfar e3630465ed Updates deps 2023-01-06 09:59:26 -08:00
renovate[bot] 4715f18c07 Update cypress/included Docker tag to v12.3.0 (#2002)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-06 09:46:19 -08:00
Amir Raminfar b1d4520360 Updates deps 2023-01-03 10:04:13 -08:00
renovate[bot] 467b5e8e19 Update dependency unplugin-icons to ^0.15.0 (#2001)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-03 10:03:27 -08:00
Amir Raminfar ce39d3837f Release 4.6.2 2023-01-02 10:06:09 -08:00
Amir Raminfar 884dd94242 Adds analytics for requests made (#1999) 2023-01-02 09:28:27 -08:00
Amir Raminfar df2ff662dd Removes outdated i18n plugin (#1998) 2022-12-29 08:27:54 -08:00
renovate[bot] f602d4e69d Update dependency vite to v4.0.3 (#1996) 2022-12-25 08:02:53 -08:00
renovate[bot] 27f898b696 Update cypress/included Docker tag to v12.2.0 (#1995) 2022-12-25 08:02:46 -08:00
renovate[bot] 400afddc45 Update dependency vitest to ^0.26.0 (#1994) 2022-12-20 07:06:03 -08:00
Amir Raminfar edf7edced3 Release 4.6.1 2022-12-18 14:44:29 -08:00
Amir Raminfar 0ef5c36900 Fixes #1990 by only coming list when relative fields change (#1993) 2022-12-18 14:38:29 -08:00
renovate[bot] a008ede927 Update dependency vite to v4.0.2 (#1992)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-18 14:18:39 -08:00
renovate[bot] b4e88937cc Update module github.com/docker/docker to v20.10.22+incompatible (#1989)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-18 14:18:32 -08:00
renovate[bot] fd3992cabd Update cypress/included Docker tag to v12.1.0 (#1984)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-14 11:00:42 -08:00
renovate[bot] 47bed89fe2 Update dependency vite to v4.0.1 (#1979)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-12 13:31:39 -08:00
Amir Raminfar 361fff7803 Release 4.6.0 2022-12-12 09:38:17 -08:00
Amir Raminfar a6ba94e03f Adds hostname config for setting UI parameter (#1983)
* Adds hostname config to setting a hostname in title

* Also updates title for hostname
2022-12-12 09:37:54 -08:00
renovate[bot] d39e2514b2 Update dependency vite-plugin-pages to ^0.28.0 (#1982)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-12 09:14:35 -08:00
Amir Raminfar 155757dd74 Removes tail size (#1981)
* Removes tail size

* Removes tail from analytics
2022-12-12 09:14:29 -08:00
Amir Raminfar cc97e90a3e Release 4.5.0 2022-12-09 10:20:28 -08:00
Amir Raminfar 68160dd9c4 Updates vite to v4 (#1980) 2022-12-09 10:17:28 -08:00
renovate[bot] 7694c95e1a Update module github.com/magiconair/properties to v1.8.7 (#1977)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-08 09:56:48 -08:00
renovate[bot] 5b91a4a9e8 Update cypress/included Docker tag to v12.0.2 (#1976)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-08 09:56:41 -08:00
renovate[bot] 7c56449874 Update cypress/included Docker tag to v12.0.1 (#1975)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-07 09:50:21 -08:00
renovate[bot] 3dbe83fd3e Update dependency vite to v3.2.5 (#1971)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-06 15:01:01 -08:00
renovate[bot] 2a3bce8cd8 Update cypress/included Docker tag to v12 (#1972)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-06 15:00:50 -08:00
renovate[bot] d3303b2436 Update dependency cypress to v12 (#1973)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-06 15:00:43 -08:00
renovate[bot] 5d2717d584 Update golang Docker tag to v1.19.4 (#1974)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-12-06 15:00:35 -08:00
Amir Raminfar e22fd87d45 Fixes #1963 2022-11-30 08:49:21 -08:00
Amir Raminfar 3a6f1bf92c Updates modules 2022-11-29 14:06:19 -08:00
IrwenXYZ ae3283adda Update readme for memory consumption on ARM devices (#1967) 2022-11-25 20:19:39 -08:00
renovate[bot] 145158a925 Update dependency unplugin-auto-import to ^0.12.0 (#1966) 2022-11-25 09:56:19 -08:00
renovate[bot] e46e1a0814 Update cypress/included Docker tag to v11.2.0 (#1961) 2022-11-23 06:34:10 -08:00
renovate[bot] 7a624cd065 Update dependency jest-serializer-vue to v3 (#1959) 2022-11-23 06:33:56 -08:00
Amir Raminfar fe7c8743b9 Removes depedenbot 2022-11-15 15:40:58 -08:00
renovate[bot] ccc1aae78e Update cypress/included Docker tag to v11.1.0 (#1958)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-11-15 13:05:01 -08:00
renovate[bot] 935e17c200 Update dependency vite to v3.2.4 (#1957) 2022-11-15 07:11:03 -08:00
renovate[bot] 63bb1f24a3 Update module github.com/spf13/afero to v1.9.3 (#1954) 2022-11-14 20:11:08 -08:00
renovate[bot] cdc8189ad1 Update module go to 1.19 (#1955) 2022-11-14 20:09:59 -08:00
renovate[bot] f6daab2111 Add renovate.json (#1953) 2022-11-14 19:43:38 -08:00
Amir Raminfar 7ed461e6ef Removes npmrc and hositing is not needed anymore (#1952) 2022-11-14 09:42:01 -08:00
Amir Raminfar 15636348b5 Updates modules 2022-11-10 09:35:11 -08:00
kodiakhq[bot] a370ad48d5 Merge pull request #1950 from amir20/dependabot/docker/e2e/cypress/included-11.0.1
Bump cypress/included from 11.0.0 to 11.0.1 in /e2e
2022-11-10 09:11:52 +00:00
dependabot[bot] 8900631b20 Bump cypress/included from 11.0.0 to 11.0.1 in /e2e
Bumps cypress/included from 11.0.0 to 11.0.1.

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-10 09:06:19 +00:00
Amir Raminfar fa3ef24999 Updates cypress to 11.x (#1949) 2022-11-09 09:11:02 -08:00
kodiakhq[bot] deaf58c47b Merge pull request #1948 from amir20/dependabot/docker/e2e/cypress/included-11.0.0
Bump cypress/included from 10.11.0 to 11.0.0 in /e2e
2022-11-09 09:11:24 +00:00
dependabot[bot] 1d4573a657 Bump cypress/included from 10.11.0 to 11.0.0 in /e2e
Bumps cypress/included from 10.11.0 to 11.0.0.

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-09 09:07:24 +00:00
Amir Raminfar 84300fc734 Release 4.4.1 2022-11-08 15:10:14 -08:00
Dmitry Mazurov 2b9873e76b Update mobile menu (#1946) 2022-11-08 15:09:57 -08:00
Amir Raminfar c589c599fc Release 4.4.0 2022-11-08 12:48:48 -08:00
Amir Raminfar 2702e80314 Adds hover for buttons 2022-11-08 12:47:10 -08:00
Dmitry Mazurov bb1dda41ff Add save collapse to localstorage (#1945) 2022-11-08 12:40:13 -08:00
Amir Raminfar ddafd95b69 Fixes int tests 2022-11-08 11:54:37 -08:00
Amir Raminfar d6e87a7137 Fixes collapseNav 2022-11-08 11:49:40 -08:00
Dmitry Mazurov 54eca89c4e Moved logout button to sidebar and fix default width sidebar. (#1942)
* Moved logout button to sidebar and fix default width sidebar.

Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>

* Fix logout url. Save collapse to localstorage.

Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>

* Clear code.

Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>

* Update logout button if collapse

Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>

* Fix dark mode for collapse button. Update toolbar. Remove localstorage.

* Fix collapse auto-import

* Remove collapse from settings

Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>
2022-11-08 11:45:11 -08:00
Amir Raminfar fbe34d7377 Updates modules 2022-11-07 09:08:29 -08:00
Dmitry Mazurov e2b497f158 Fix search modal for mobile view (#1941)
Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>

Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>
2022-11-04 10:36:19 -07:00
Amir Raminfar ca9fcf8e1d Updates snapshots 2022-11-03 13:43:24 -07:00
Dmitry Mazurov 4fb012c0e4 Merge branch 'amir20:master' into responsible 2022-11-03 22:48:44 +03:00
Dmitry Mazurov b77448f2a9 Fix responsible list containers
Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>
2022-11-03 22:09:56 +03:00
Amir Raminfar 3bc921c8d7 Updates auto import 2022-11-03 09:51:17 -07:00
Amir Raminfar 54c08cf79b Removes duplicate case sensistive files (#1939) 2022-11-03 09:41:59 -07:00
Amir Raminfar 199c51722c Also skip docker login 2022-11-02 10:43:08 -07:00
Amir Raminfar 379af164dd Skips only the push int tests (#1937) 2022-11-02 09:16:45 -07:00
Dmitry Mazurov dc4ba652b4 Skip Integration Tests and Push branches and PRs from fork (#1936)
Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>

Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>
2022-11-02 07:24:39 -07:00
kodiakhq[bot] 0e8561a1c5 Merge pull request #1935 from amir20/dependabot/docker/golang-1.19.3-alpine
Bump golang from 1.19.2-alpine to 1.19.3-alpine
2022-11-02 09:29:57 +00:00
dependabot[bot] 60ec928a8e Bump golang from 1.19.2-alpine to 1.19.3-alpine
Bumps golang from 1.19.2-alpine to 1.19.3-alpine.

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-02 09:26:23 +00:00
Dmitry Mazurov 26f3ab65a7 Update ru locale (#1934) 2022-11-01 11:36:02 -07:00
Amir Raminfar 0cc209484d Use trim instead 2022-11-01 10:57:49 -07:00
Amir Raminfar f09a7815af Cleans up DockerSecret 2022-11-01 09:08:05 -07:00
Amir Raminfar 70b573cec0 Fixes spacing 2022-10-31 15:11:33 -07:00
Amir Raminfar 0d79196eda Updates cypress 2022-10-31 10:21:41 -07:00
Amir Raminfar 8eb4e05055 Release 4.3.0 2022-10-31 09:33:26 -07:00
Amir Raminfar cd793a9b53 Updates modules 2022-10-31 09:32:21 -07:00
Dmitry Mazurov fad75e2f5b Fix read username and password from file (#1930)
* Fix read username and password from file

* Update Readme

* Add ru locale

* Update read the file from UnmarshalText

* Fix if not set username and password

* Fix if content in file empty

* Update ru locale

* Fix errorMsg for auth

* Removed local ru

Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>

* Remove struct for username and password

Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>
2022-10-31 09:31:27 -07:00
Dmitry Mazurov d6710453e5 Add ru locale (#1932)
Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>

Signed-off-by: Dmitry Mazurov <dimabzz@gmail.com>
2022-10-31 09:03:47 -07:00
Amir Raminfar 3a8c9cbcfa Release 4.2.3 2022-10-28 14:42:46 -07:00
Dmitry Mazurov 8586fee5ab Add docker secrets env (#1929) 2022-10-28 14:31:48 -07:00
Amir Raminfar 8ccae473ed Updates modules 2022-10-28 12:24:42 -07:00
kodiakhq[bot] 02434afef3 Merge pull request #1923 from amir20/dependabot/docker/e2e/cypress/included-10.11.0
Bump cypress/included from 10.10.0 to 10.11.0 in /e2e
2022-10-26 09:45:31 +00:00
dependabot[bot] 192ee98dd1 Bump cypress/included from 10.10.0 to 10.11.0 in /e2e
Bumps cypress/included from 10.10.0 to 10.11.0.

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-26 09:41:20 +00:00
kodiakhq[bot] 79e9dc7be6 Merge pull request #1922 from amir20/dependabot/go_modules/github.com/docker/docker-20.10.21incompatible
Bump github.com/docker/docker from 20.10.20+incompatible to 20.10.21+incompatible
2022-10-26 09:15:01 +00:00
dependabot[bot] 19fe7e8d18 Bump github.com/docker/docker
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 20.10.20+incompatible to 20.10.21+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Changelog](https://github.com/moby/moby/blob/master/CHANGELOG.md)
- [Commits](https://github.com/docker/docker/compare/v20.10.20...v20.10.21)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-26 09:11:08 +00:00
Amir Raminfar e4ecfca5be Updats modules 2022-10-24 16:08:59 -07:00
kodiakhq[bot] f37925b637 Merge pull request #1920 from amir20/dependabot/go_modules/github.com/stretchr/testify-1.8.1
Bump github.com/stretchr/testify from 1.8.0 to 1.8.1
2022-10-24 09:15:06 +00:00
dependabot[bot] 822b783063 Bump github.com/stretchr/testify from 1.8.0 to 1.8.1
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.0...v1.8.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-24 09:11:05 +00:00
55 changed files with 2662 additions and 1849 deletions
-2
View File
@@ -1,2 +0,0 @@
version = 1
merge.notify_on_conflict = false
-47
View File
@@ -1,47 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
labels:
- "dependencies"
- "automerge"
schedule:
interval: "daily"
- package-ecosystem: "docker"
directory: "/"
labels:
- "dependencies"
- "automerge"
schedule:
interval: "daily"
- package-ecosystem: gomod
directory: "/"
labels:
- "gomod"
- "dependencies"
- "automerge"
schedule:
interval: daily
- package-ecosystem: npm
directory: "/"
labels:
- "npm"
- "dependencies"
- "automerge"
schedule:
interval: daily
- package-ecosystem: npm
directory: "/e2e"
labels:
- "npm"
- "dependencies"
- "automerge"
schedule:
interval: daily
- package-ecosystem: "docker"
directory: "/e2e"
labels:
- "dependencies"
- "automerge"
schedule:
interval: "daily"
+12 -8
View File
@@ -12,10 +12,12 @@ jobs:
uses: actions/checkout@v3
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: latest
- name: Install pnpm
uses: pnpm/action-setup@v2.2.4
with:
version: 6.20.1
version: 7.26.0
- name: Install dependencies
run: pnpm install
- name: Run Tests
@@ -27,7 +29,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: 1.18.x
go-version: 1.19.x
- name: Checkout code
uses: actions/checkout@v3
- name: Run Go Tests with Coverage
@@ -39,9 +41,9 @@ jobs:
- name: Checkout code
uses: actions/checkout@v3
- name: Build images
run: COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f e2e/docker-compose.yml build
run: COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker compose -f e2e/docker-compose.yml build
- name: Run tests
run: COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f e2e/docker-compose.yml up --build --force-recreate --exit-code-from cypress
run: COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker compose -f e2e/docker-compose.yml up --build --force-recreate --exit-code-from cypress
buildx:
needs: [go-test, npm-test, int-test]
name: Release
@@ -53,22 +55,22 @@ jobs:
with:
images: amir20/dozzle
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.2.1
uses: docker/setup-buildx-action@v2.4.0
- name: Login to DockerHub
uses: docker/login-action@v2.1.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v3.2.0
uses: docker/build-push-action@v3.3.0
with:
push: true
platforms: linux/amd64,linux/arm/v7,linux/arm64/v8
tags: ${{ steps.meta.outputs.tags }}
build-args: TAG=${{ steps.meta.outputs.version }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=amir20/dozzle:latest
cache-to: type=inline
cache-from: type=gha
cache-to: type=gha,mode=max
git-release:
needs: [buildx]
name: Github Release
@@ -80,6 +82,8 @@ jobs:
fetch-depth: 0
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: latest
- name: Install pnpm
uses: pnpm/action-setup@v2.2.4
with:
+5 -5
View File
@@ -10,7 +10,7 @@ jobs:
buildx:
name: Push branches and PRs
runs-on: ubuntu-latest
if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == 'amir20/dozzle' }}
if: ${{ !github.event.repository.fork && !github.event.pull_request.head.repo.fork && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == 'amir20/dozzle') }}
steps:
- name: Docker meta
id: meta
@@ -18,19 +18,19 @@ jobs:
with:
images: amir20/dozzle
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.2.1
uses: docker/setup-buildx-action@v2.4.0
- name: Login to DockerHub
uses: docker/login-action@v2.1.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v3.2.0
uses: docker/build-push-action@v3.3.0
with:
push: true
platforms: linux/amd64,linux/arm/v7,linux/arm64/v8
tags: ${{ steps.meta.outputs.tags }}
build-args: TAG=${{ steps.meta.outputs.version }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=amir20/dozzle:master
cache-to: type=inline
cache-from: type=gha
cache-to: type=gha,mode=max
+11 -7
View File
@@ -15,14 +15,16 @@ jobs:
uses: actions/checkout@v3
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: latest
- name: Install pnpm
uses: pnpm/action-setup@v2.2.4
with:
version: 6.20.1
version: 7.26.0
- name: Install dependencies
run: pnpm install
- name: Run Tests
run: pnpm run test
run: pnpm run test -- --outputTruncateLength=500
go-test:
name: Go Tests
runs-on: ubuntu-latest
@@ -30,7 +32,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: 1.18.x
go-version: 1.19.x
- name: Checkout code
uses: actions/checkout@v3
- name: Run Go Tests with Coverage
@@ -44,16 +46,18 @@ jobs:
with:
fetch-depth: 2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.2.1
uses: docker/setup-buildx-action@v2.4.0
- name: Login to DockerHub
if: ${{ !github.event.repository.fork && !github.event.pull_request.head.repo.fork }}
uses: docker/login-action@v2.1.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build images
run: COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f e2e/docker-compose.yml build --build-arg BUILDKIT_INLINE_CACHE=1
run: COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker compose -f e2e/docker-compose.yml build --build-arg BUILDKIT_INLINE_CACHE=1
- name: Push images
run: COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f e2e/docker-compose.yml push
if: ${{ !github.event.repository.fork && !github.event.pull_request.head.repo.fork }}
run: COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker compose -f e2e/docker-compose.yml push
- name: Set commit message for push
if: github.event_name == 'push'
run: |
@@ -67,4 +71,4 @@ jobs:
git log -1 --pretty=%B ${{github.event.pull_request.head.sha}} >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
- name: Run tests
run: COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f e2e/docker-compose.yml up --build --force-recreate --exit-code-from cypress
run: COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker compose -f e2e/docker-compose.yml up --build --force-recreate --exit-code-from cypress
-1
View File
@@ -1 +0,0 @@
shamefully-hoist=true
+1 -1
View File
@@ -22,7 +22,7 @@ COPY locales ./locales
# Build assets
RUN pnpm build
FROM --platform=$BUILDPLATFORM golang:1.19.2-alpine AS builder
FROM --platform=$BUILDPLATFORM golang:1.19.5-alpine AS builder
RUN apk add --no-cache ca-certificates && mkdir /dozzle
+35 -14
View File
@@ -58,7 +58,6 @@ Dozzle will be available at [http://localhost:8888/](http://localhost:8888/). Yo
ports:
- 9999:8080
### Enabling health check
Dozzle doesn't enable healthcheck by default as it adds extra CPU usage. `healthcheck` can be enabled manually.
@@ -81,7 +80,6 @@ Dozzle doesn't enable healthcheck by default as it adds extra CPU usage. `health
retries: 5
start_period: 30s
#### Security
You can control the device Dozzle binds to by passing `--addr` parameter. For example,
@@ -98,7 +96,7 @@ this would then only allow you to view containers with a name starting with "foo
#### Authentication
Dozzle supports a very simple authentication out of the box with just username and password. You should deploy using SSL to keep the credentials safe. See configuration to use `--username` and `--password`.
Dozzle supports a very simple authentication out of the box with just username and password. You should deploy using SSL to keep the credentials safe. See configuration to use `--username` and `--password`. You can also use [docker secrets](https://docs.docker.com/engine/swarm/secrets/) `--usernamefile` and `--passwordfile`.
#### Changing base URL
@@ -119,17 +117,18 @@ If you do not want to be tracked at all, see the `--no-analytics` flag below.
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.
| Flag | Env Variable | Default |
| ---------------- | --------------------- | ------- |
| `--addr` | `DOZZLE_ADDR` | `:8080` |
| `--base` | `DOZZLE_BASE` | `/` |
| `--level` | `DOZZLE_LEVEL` | `info` |
| n/a | `DOCKER_API_VERSION` | not set |
| `--tailSize` | `DOZZLE_TAILSIZE` | `300` |
| `--filter` | `DOZZLE_FILTER` | `""` |
| `--username` | `DOZZLE_USERNAME` | `""` |
| `--password` | `DOZZLE_PASSWORD` | `""` |
| `--no-analytics` | `DOZZLE_NO_ANALYTICS` | false |
| Flag | Env Variable | Default |
| ---------------- | ---------------------- | ------- |
| `--addr` | `DOZZLE_ADDR` | `:8080` |
| `--base` | `DOZZLE_BASE` | `/` |
| `--hostname` | `DOZZLE_HOSTNAME` | `""` |
| `--level` | `DOZZLE_LEVEL` | `info` |
| `--filter` | `DOZZLE_FILTER` | `""` |
| `--username` | `DOZZLE_USERNAME` | `""` |
| `--password` | `DOZZLE_PASSWORD` | `""` |
| `--usernamefile` | `DOZZLE_USERNAME_FILE` | `""` |
| `--passwordfile` | `DOZZLE_PASSWORD_FILE` | `""` |
| `--no-analytics` | `DOZZLE_NO_ANALYTICS` | false |
## Troubleshooting and FAQs
@@ -181,6 +180,28 @@ Dozzle has a [special route](https://github.com/amir20/dozzle/blob/master/assets
</details>
<details>
<summary>I installed Dozzle but memory consumption doesn't show up!</summary>
_This is an issue specific to ARM devices_
Dozzle uses the Docker API to gather information about the containers' memory usage. If the memory usage is not showing up, then it is likely that the Docker API is not returning the memory usage.
You can verify this by running `docker info`, and you should see the following:
```
WARNING: No memory limit support
WARNING: No swap limit support
```
In this case, you'll need to add the following line to your `/boot/cmdline.txt` file and reboot your device.
```
cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1
```
</details>
## License
[MIT](LICENSE)
+19 -1
View File
@@ -21,7 +21,25 @@ func SendStartEvent(se StartEvent) error {
},
}
jsonValue, err := json.Marshal(postBody)
return doRequest(postBody)
}
func SendRequestEvent(re RequestEvent) error {
postBody := map[string]interface{}{
"client_id": re.ClientId,
"events": []map[string]interface{}{
{
"name": "request",
"params": re,
},
},
}
return doRequest(postBody)
}
func doRequest(body map[string]interface{}) error {
jsonValue, err := json.Marshal(body)
if err != nil {
return err
}
+7 -1
View File
@@ -6,6 +6,12 @@ type StartEvent struct {
FilterLength int `json:"filterLength"`
CustomAddress bool `json:"customAddress"`
CustomBase bool `json:"customBase"`
TailSize int `json:"tailSize"`
Protected bool `json:"protected"`
HasHostname bool `json:"hasHostname"`
}
type RequestEvent struct {
ClientId string `json:"-"`
TotalContainers int `json:"totalContainers"`
RunningContainers int `json:"runningContainers"`
}
+17 -1
View File
@@ -14,6 +14,7 @@ declare global {
const arrayEquals: typeof import('./utils/index')['arrayEquals']
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
const collapseNav: typeof import('./composables/settings')['collapseNav']
const computed: typeof import('vue')['computed']
const computedAsync: typeof import('@vueuse/core')['computedAsync']
const computedEager: typeof import('@vueuse/core')['computedEager']
@@ -68,6 +69,8 @@ declare global {
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
@@ -100,6 +103,7 @@ declare global {
const refThrottled: typeof import('@vueuse/core')['refThrottled']
const refWithControl: typeof import('@vueuse/core')['refWithControl']
const resolveComponent: typeof import('vue')['resolveComponent']
const resolveDirective: typeof import('vue')['resolveDirective']
const resolveRef: typeof import('@vueuse/core')['resolveRef']
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
const search: typeof import('./composables/settings')['search']
@@ -144,6 +148,7 @@ declare global {
const useArrayMap: typeof import('@vueuse/core')['useArrayMap']
const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce']
const useArraySome: typeof import('@vueuse/core')['useArraySome']
const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique']
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
const useAttrs: typeof import('vue')['useAttrs']
@@ -207,6 +212,7 @@ declare global {
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
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 useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
@@ -230,12 +236,14 @@ declare global {
const useParallax: typeof import('@vueuse/core')['useParallax']
const usePermission: typeof import('@vueuse/core')['usePermission']
const usePointer: typeof import('@vueuse/core')['usePointer']
const usePointerLock: typeof import('@vueuse/core')['usePointerLock']
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast']
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
const usePrevious: typeof import('@vueuse/core')['usePrevious']
const useRafFn: typeof import('@vueuse/core')['useRafFn']
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
@@ -308,7 +316,7 @@ declare global {
}
// for vue template auto import
import { UnwrapRef } from 'vue'
declare module '@vue/runtime-core' {
declare module 'vue' {
interface ComponentCustomProperties {
readonly $$: UnwrapRef<typeof import('vue/macros')['$$']>
readonly $: UnwrapRef<typeof import('vue/macros')['$']>
@@ -323,6 +331,7 @@ declare module '@vue/runtime-core' {
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 collapseNav: UnwrapRef<typeof import('./composables/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']>
@@ -377,6 +386,8 @@ declare module '@vue/runtime-core' {
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router')['onBeforeRouteLeave']>
readonly onBeforeRouteUpdate: UnwrapRef<typeof import('vue-router')['onBeforeRouteUpdate']>
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
readonly onClickOutside: UnwrapRef<typeof import('@vueuse/core')['onClickOutside']>
@@ -409,6 +420,7 @@ declare module '@vue/runtime-core' {
readonly refThrottled: UnwrapRef<typeof import('@vueuse/core')['refThrottled']>
readonly refWithControl: UnwrapRef<typeof import('@vueuse/core')['refWithControl']>
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
readonly resolveDirective: UnwrapRef<typeof import('vue')['resolveDirective']>
readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
readonly resolveUnref: UnwrapRef<typeof import('@vueuse/core')['resolveUnref']>
readonly search: UnwrapRef<typeof import('./composables/settings')['search']>
@@ -453,6 +465,7 @@ declare module '@vue/runtime-core' {
readonly useArrayMap: UnwrapRef<typeof import('@vueuse/core')['useArrayMap']>
readonly useArrayReduce: UnwrapRef<typeof import('@vueuse/core')['useArrayReduce']>
readonly useArraySome: UnwrapRef<typeof import('@vueuse/core')['useArraySome']>
readonly useArrayUnique: UnwrapRef<typeof import('@vueuse/core')['useArrayUnique']>
readonly useAsyncQueue: UnwrapRef<typeof import('@vueuse/core')['useAsyncQueue']>
readonly useAsyncState: UnwrapRef<typeof import('@vueuse/core')['useAsyncState']>
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
@@ -516,6 +529,7 @@ declare module '@vue/runtime-core' {
readonly useIntervalFn: UnwrapRef<typeof import('@vueuse/core')['useIntervalFn']>
readonly useKeyModifier: UnwrapRef<typeof import('@vueuse/core')['useKeyModifier']>
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 useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']>
@@ -539,12 +553,14 @@ declare module '@vue/runtime-core' {
readonly useParallax: UnwrapRef<typeof import('@vueuse/core')['useParallax']>
readonly usePermission: UnwrapRef<typeof import('@vueuse/core')['usePermission']>
readonly usePointer: UnwrapRef<typeof import('@vueuse/core')['usePointer']>
readonly usePointerLock: UnwrapRef<typeof import('@vueuse/core')['usePointerLock']>
readonly usePointerSwipe: UnwrapRef<typeof import('@vueuse/core')['usePointerSwipe']>
readonly usePreferredColorScheme: UnwrapRef<typeof import('@vueuse/core')['usePreferredColorScheme']>
readonly usePreferredContrast: UnwrapRef<typeof import('@vueuse/core')['usePreferredContrast']>
readonly usePreferredDark: UnwrapRef<typeof import('@vueuse/core')['usePreferredDark']>
readonly usePreferredLanguages: UnwrapRef<typeof import('@vueuse/core')['usePreferredLanguages']>
readonly usePreferredReducedMotion: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedMotion']>
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 useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
+2
View File
@@ -22,6 +22,7 @@ declare module '@vue/runtime-core' {
LogContainer: typeof import('./components/LogViewer/LogContainer.vue')['default']
LogDate: typeof import('./components/LogViewer/LogDate.vue')['default']
LogEventSource: typeof import('./components/LogViewer/LogEventSource.vue')['default']
LogLevel: typeof import('./components/LogViewer/LogLevel.vue')['default']
LogViewer: typeof import('./components/LogViewer/LogViewer.vue')['default']
LogViewerWithSource: typeof import('./components/LogViewer/LogViewerWithSource.vue')['default']
MdiDotsVertical: typeof import('~icons/mdi/dots-vertical')['default']
@@ -29,6 +30,7 @@ declare module '@vue/runtime-core' {
MdiLightChevronLeft: typeof import('~icons/mdi-light/chevron-left')['default']
MdiLightChevronRight: typeof import('~icons/mdi-light/chevron-right')['default']
MdiLightCog: typeof import('~icons/mdi-light/cog')['default']
MdiLightLogout: typeof import('~icons/mdi-light/logout')['default']
MdiLightMagnify: typeof import('~icons/mdi-light/magnify')['default']
MobileMenu: typeof import('./components/MobileMenu.vue')['default']
OcticonContainer24: typeof import('~icons/octicon/container24')['default']
+9
View File
@@ -105,6 +105,15 @@ function addColumn(container: Container) {
width: 580px;
}
@media screen and (max-width: 768px) {
.panel {
min-height: 200px;
width: auto;
margin-left: 0.25rem!important;
margin-right: 0.25rem!important;
}
}
.running {
color: var(--primary-color);
}
@@ -1,8 +1,11 @@
<template>
<div class="columns is-1 is-variable">
<div class="columns is-1 is-variable is-mobile">
<div class="column is-narrow" v-if="showTimestamp">
<log-date :date="logEntry.date"></log-date>
</div>
<div class="column is-narrow is-flex">
<log-level :level="logEntry.level"></log-level>
</div>
<div class="column">
<ul class="fields" :class="{ expanded }" @click="expanded = !expanded">
<li v-for="(value, name) in validValues(logEntry.message)">
@@ -49,12 +49,6 @@ const memoryData = computedWithControl(
</script>
<style lang="scss" scoped>
.has-spacer {
&::after {
content: " ";
}
}
.has-border {
border: 1px solid var(--primary-color);
border-radius: 3px;
+62
View File
@@ -0,0 +1,62 @@
<template>
<div :class="level" :data-position="position"></div>
</template>
<script lang="ts" setup>
import { Position } from "@/models/LogEntry";
defineProps<{
level?: string;
position?: Position;
}>();
</script>
<style lang="scss" scoped>
div {
display: inline-block;
width: 0.7em;
height: 0.7em;
border-radius: 0.5em;
align-self: auto;
margin-top: 0.4em;
&[data-position="start"] {
border-radius: 0.5em 0.5em 0 0;
height: 70%;
margin-bottom: -0.2em;
margin-top: auto;
align-self: flex-end;
}
&[data-position="middle"] {
border-radius: 0;
height: auto;
margin: -0.2em 0;
align-self: auto;
}
&[data-position="end"] {
border-radius: 0 0 0.5em 0.5em;
height: 70%;
margin-top: -0.2em;
align-self: flex-start;
}
&.debug,
&.trace {
background-color: #9c27b0;
}
&.info {
background-color: #00b5ad;
}
&.error,
&.fatal {
background-color: #f44336;
}
&.warn {
background-color: #ff9800;
}
}
</style>
@@ -1,8 +1,11 @@
<template>
<div class="columns is-1 is-variable">
<div class="columns is-1 is-variable is-mobile">
<div class="column is-narrow" v-if="showTimestamp">
<log-date :date="logEntry.date"></log-date>
</div>
<div class="column is-narrow is-flex">
<log-level :level="logEntry.level" :position="logEntry.position"></log-level>
</div>
<div class="text column" v-html="colorize(logEntry.message)"></div>
</div>
</template>
+1 -7
View File
@@ -4,7 +4,7 @@
<stat-sparkline :data="data" @selected-point="onSelectedPoint"></stat-sparkline>
</div>
<div class="has-background-body-color is-top-left">
<span class="has-text-weight-light has-spacer">{{ label }}</span>
<span class="has-text-weight-light">{{ label }}</span>
<span class="has-text-weight-bold">
{{ mouseOver ? selectedPoint?.value ?? selectedPoint?.y ?? statValue : statValue }}
</span>
@@ -25,12 +25,6 @@ let mouseOver = $ref(false);
</script>
<style lang="scss" scoped>
.has-spacer {
&::after {
content: " ";
}
}
.has-border {
border: 1px solid var(--primary-color);
border-radius: 3px;
@@ -23,8 +23,13 @@ exports[`<LogEventSource /> > render html correctly > should render dates with 1
</div>
</div>
</div>
<div class=\\"columns is-1 is-variable\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\">06/12/2019 10:55:42 AM</time></div>
<div class=\\"columns is-1 is-variable is-mobile\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\">
<div class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"is-hidden-mobile\\">06/12/2019</time> <time datetime=\\"2019-06-12T10:55:42.459Z\\">10:55:42AM</time></div>
</div>
<div class=\\"column is-narrow is-flex\\" data-v-a49e52d4=\\"\\">
<div class=\\"\\" data-v-e625cddd=\\"\\" data-v-a49e52d4=\\"\\"></div>
</div>
<div class=\\"text column\\" data-v-a49e52d4=\\"\\">&lt;test&gt;foo bar&lt;/test&gt;</div>
</div>
</li>
@@ -54,8 +59,13 @@ exports[`<LogEventSource /> > render html correctly > should render dates with 2
</div>
</div>
</div>
<div class=\\"columns is-1 is-variable\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\">06/12/2019 10:55:42</time></div>
<div class=\\"columns is-1 is-variable is-mobile\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\">
<div class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"is-hidden-mobile\\">06/12/2019</time> <time datetime=\\"2019-06-12T10:55:42.459Z\\">10:55:42</time></div>
</div>
<div class=\\"column is-narrow is-flex\\" data-v-a49e52d4=\\"\\">
<div class=\\"\\" data-v-e625cddd=\\"\\" data-v-a49e52d4=\\"\\"></div>
</div>
<div class=\\"text column\\" data-v-a49e52d4=\\"\\">&lt;test&gt;foo bar&lt;/test&gt;</div>
</div>
</li>
@@ -85,8 +95,13 @@ exports[`<LogEventSource /> > render html correctly > should render messages 1`]
</div>
</div>
</div>
<div class=\\"columns is-1 is-variable\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\">06/12/2019 10:55:42 AM</time></div>
<div class=\\"columns is-1 is-variable is-mobile\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\">
<div class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"is-hidden-mobile\\">06/12/2019</time> <time datetime=\\"2019-06-12T10:55:42.459Z\\">10:55:42AM</time></div>
</div>
<div class=\\"column is-narrow is-flex\\" data-v-a49e52d4=\\"\\">
<div class=\\"\\" data-v-e625cddd=\\"\\" data-v-a49e52d4=\\"\\"></div>
</div>
<div class=\\"text column\\" data-v-a49e52d4=\\"\\">This is a message.</div>
</div>
</li>
@@ -116,8 +131,13 @@ exports[`<LogEventSource /> > render html correctly > should render messages wit
</div>
</div>
</div>
<div class=\\"columns is-1 is-variable\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\">06/12/2019 10:55:42 AM</time></div>
<div class=\\"columns is-1 is-variable is-mobile\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\">
<div class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"is-hidden-mobile\\">06/12/2019</time> <time datetime=\\"2019-06-12T10:55:42.459Z\\">10:55:42AM</time></div>
</div>
<div class=\\"column is-narrow is-flex\\" data-v-a49e52d4=\\"\\">
<div class=\\"\\" data-v-e625cddd=\\"\\" data-v-a49e52d4=\\"\\"></div>
</div>
<div class=\\"text column\\" data-v-a49e52d4=\\"\\"><span style=\\"color:#000\\">black<span style=\\"color:#AAA\\">white</span></span></div>
</div>
</li>
@@ -147,8 +167,13 @@ exports[`<LogEventSource /> > render html correctly > should render messages wit
</div>
</div>
</div>
<div class=\\"columns is-1 is-variable\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\">06/12/2019 10:55:42 AM</time></div>
<div class=\\"columns is-1 is-variable is-mobile\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\">
<div class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"is-hidden-mobile\\">06/12/2019</time> <time datetime=\\"2019-06-12T10:55:42.459Z\\">10:55:42AM</time></div>
</div>
<div class=\\"column is-narrow is-flex\\" data-v-a49e52d4=\\"\\">
<div class=\\"\\" data-v-e625cddd=\\"\\" data-v-a49e52d4=\\"\\"></div>
</div>
<div class=\\"text column\\" data-v-a49e52d4=\\"\\"><mark>test</mark> bar</div>
</div>
</li>
@@ -178,8 +203,13 @@ exports[`<LogEventSource /> > render html correctly > should render messages wit
</div>
</div>
</div>
<div class=\\"columns is-1 is-variable\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\">06/12/2019 10:55:42 AM</time></div>
<div class=\\"columns is-1 is-variable is-mobile\\" visible-keys=\\"\\" data-v-a49e52d4=\\"\\" data-v-2e92daca=\\"\\">
<div class=\\"column is-narrow\\" data-v-a49e52d4=\\"\\">
<div class=\\"date\\" data-v-de513450=\\"\\" data-v-a49e52d4=\\"\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"is-hidden-mobile\\">06/12/2019</time> <time datetime=\\"2019-06-12T10:55:42.459Z\\">10:55:42AM</time></div>
</div>
<div class=\\"column is-narrow is-flex\\" data-v-a49e52d4=\\"\\">
<div class=\\"\\" data-v-e625cddd=\\"\\" data-v-a49e52d4=\\"\\"></div>
</div>
<div class=\\"text column\\" data-v-a49e52d4=\\"\\">&lt;test&gt;foo bar&lt;/test&gt;</div>
</div>
</li>
@@ -194,6 +224,7 @@ exports[`<LogEventSource /> > renders correctly 1`] = `
<div class=\\"bounce3\\" data-v-1cd63c6e=\\"\\"></div>
</div>
</div>
<ul class=\\"events medium\\" data-v-2e92daca=\\"\\"></ul>"
`;
@@ -202,5 +233,7 @@ SimpleLogEntry {
"_message": "This is a message.",
"date": 2019-06-12T10:55:42.459Z,
"id": 1,
"level": undefined,
"position": undefined,
}
`;
+35
View File
@@ -24,6 +24,36 @@
</div>
</div>
<div class="menu-label level is-mobile is-hidden-mobile" :class="{ 'is-active': showNav }">
<div class="level-item has-text-centered">
<div>
<button class="button is-small is-rounded" @click="$emit('search')" :title="$t('tooltip.search')">
<span class="icon">
<mdi-light-magnify />
</span>
</button>
</div>
</div>
<div class="level-item has-text-centered">
<div>
<router-link :to="{ name: 'settings' }" active-class="is-active" class="button is-small is-rounded">
<span class="icon">
<mdi-light-cog />
</span>
</router-link>
</div>
</div>
<div class="level-item has-text-centered" v-if="secured">
<div>
<a class="button is-small is-rounded" :href="`${base}/logout`" :title="$t('button.logout')">
<span class="icon">
<mdi-light-logout />
</span>
</a>
</div>
</div>
</div>
<p class="menu-label is-hidden-mobile" :class="{ 'is-active': showNav }">{{ $t("label.containers") }}</p>
<ul class="menu-list is-hidden-mobile" :class="{ 'is-active': showNav }">
<li v-for="item in visibleContainers" :key="item.id">
@@ -42,6 +72,7 @@
</template>
<script lang="ts" setup>
const { base, secured } = config;
const store = useContainerStore();
const route = useRoute();
const { visibleContainers, allContainersById } = storeToRefs(store);
@@ -63,6 +94,10 @@ aside {
max-height: 100vh;
overflow: auto;
.level.is-hidden-mobile.is-active {
display: flex !important;
}
.menu-label {
margin-top: 1em;
}
+7 -7
View File
@@ -1,9 +1,12 @@
<template>
<time :datetime="date.toISOString()">{{ format(date) }}</time>
<div>
<time :datetime="date.toISOString()" class="is-hidden-mobile">{{ dateStr }}</time>
<time :datetime="date.toISOString()">{{ timeStr }}</time>
</div>
</template>
<script lang="ts" setup>
defineProps<{
const props = defineProps<{
date: Date;
}>();
@@ -13,9 +16,6 @@ const timeFormatter = $computed(
() => new Intl.DateTimeFormat(undefined, { hour: "numeric", minute: "2-digit", second: "2-digit", hour12: use12Hour })
);
function format(date: Date) {
const dateStr = dateFormatter.format(date);
const timeStr = timeFormatter.format(date);
return `${dateStr} ${timeStr}`;
}
const dateStr = $computed(() => dateFormatter.format(props.date));
const timeStr = $computed(() => timeFormatter.format(props.date));
</script>
+25 -10
View File
@@ -2,26 +2,40 @@
<aside>
<div class="columns is-marginless">
<div class="column is-paddingless">
<router-link :to="{ name: 'index' }">
<svg class="logo">
<use href="#logo"></use>
</svg>
</router-link>
<h1>
<router-link :to="{ name: 'index' }">
<svg class="logo">
<use href="#logo"></use>
</svg>
</router-link>
<small class="subtitle is-6 is-block mb-4" v-if="hostname">
{{ hostname }}
</small>
</h1>
</div>
<div class="column is-narrow has-text-right px-1">
<button class="button is-rounded" @click="$emit('search')" :title="$t('tooltip.search')">
</div>
<div class="columns is-marginless">
<div class="column is-narrow py-0 pl-0 pr-1">
<button class="button is-rounded is-small" @click="$emit('search')" :title="$t('tooltip.search')">
<span class="icon">
<mdi-light-magnify />
</span>
</button>
</div>
<div class="column is-narrow has-text-right px-0">
<router-link :to="{ name: 'settings' }" active-class="is-active" class="button is-rounded">
<div class="column is-narrow py-0" :class="secured ? 'pl-0 pr-1' : 'px-0'">
<router-link :to="{ name: 'settings' }" active-class="is-active" class="button is-rounded is-small">
<span class="icon">
<mdi-light-cog />
</span>
</router-link>
</div>
<div class="column is-narrow py-0 px-0" v-if="secured">
<a class="button is-rounded is-small" :href="`${base}/logout`" :title="$t('button.logout')">
<span class="icon">
<mdi-light-logout />
</span>
</a>
</div>
</div>
<p class="menu-label is-hidden-mobile">{{ $t("label.containers") }}</p>
<ul class="menu-list is-hidden-mobile" v-if="ready">
@@ -56,8 +70,9 @@
</template>
<script lang="ts" setup>
import type { Container } from "@/types/Container";
import { Container } from "@/models/Container";
const { base, secured, hostname } = config;
const store = useContainerStore();
const { activeContainers, visibleContainers, ready } = storeToRefs(store);
+8
View File
@@ -10,6 +10,7 @@ export const DEFAULT_SETTINGS: {
lightTheme: "auto" | "dark" | "light";
hourStyle: "auto" | "24" | "12";
softWrap: boolean;
collapseNav: boolean;
} = {
search: true,
size: "medium",
@@ -20,6 +21,7 @@ export const DEFAULT_SETTINGS: {
lightTheme: "auto",
hourStyle: "auto",
softWrap: true,
collapseNav: false,
};
const settings = useStorage(DOZZLE_SETTINGS_KEY, DEFAULT_SETTINGS);
@@ -69,7 +71,13 @@ const softWrap = computed({
set: (value) => (settings.value.softWrap = value),
});
const collapseNav = computed({
get: () => settings.value.collapseNav,
set: (value) => (settings.value.collapseNav = value),
});
export {
collapseNav,
softWrap,
hourStyle,
lightTheme,
+2 -1
View File
@@ -1,5 +1,6 @@
const { hostname } = config;
let subtitle = $ref("");
const title = $computed(() => `${subtitle} - Dozzle`);
const title = $computed(() => `${subtitle} - Dozzle` + (hostname ? ` @ ${hostname}` : ""));
useTitle($$(title));
+8 -5
View File
@@ -1,6 +1,6 @@
<template>
<main v-if="!authorizationNeeded">
<mobile-menu v-if="isMobile"></mobile-menu>
<mobile-menu v-if="isMobile" @search="showFuzzySearch"></mobile-menu>
<splitpanes @resized="onResized($event)">
<pane min-size="10" :size="menuWidth" v-if="!isMobile && !collapseNav">
<side-menu @search="showFuzzySearch"></side-menu>
@@ -25,8 +25,8 @@
</pane>
</splitpanes>
<button
@click="collapseNav = !collapseNav"
class="button is-rounded"
@click="collapse"
class="button is-small is-rounded"
:class="{ collapsed: collapseNav }"
id="hide-nav"
v-if="!isMobile"
@@ -47,7 +47,6 @@ import { Splitpanes, Pane } from "splitpanes";
import { useProgrammatic } from "@oruga-ui/oruga-next";
import FuzzySearchModal from "@/components/FuzzySearchModal.vue";
const collapseNav = ref(false);
const { oruga } = useProgrammatic();
const { authorizationNeeded } = config;
@@ -74,6 +73,9 @@ function showFuzzySearch() {
active: true,
});
}
function collapse() {
collapseNav.value = !collapseNav.value;
}
function onResized(e: any) {
if (e.length == 2) {
menuWidth.value = e[0].size;
@@ -112,7 +114,8 @@ function onResized(e: any) {
left: -40px;
width: 60px;
padding-left: 40px;
background: rgba(0, 0, 0, 0.95);
color: var(--text-strong-color);
background: var(--scheme-main);
&:hover {
left: -25px;
+26 -9
View File
@@ -11,16 +11,18 @@ export interface HasComponent {
export type JSONValue = string | number | boolean | JSONObject | Array<JSONValue>;
export type JSONObject = { [x: string]: JSONValue };
export type Position = "start" | "end" | "middle" | undefined;
export interface LogEvent {
readonly m: string | JSONObject;
readonly ts: number;
readonly id: number;
readonly l: string;
readonly p: Position;
}
export abstract class LogEntry<T extends string | JSONObject> implements HasComponent {
protected readonly _message: T;
constructor(message: T, public readonly id: number, public readonly date: Date) {
constructor(message: T, public readonly id: number, public readonly date: Date, public readonly level?: string) {
this._message = message;
}
@@ -32,6 +34,15 @@ export abstract class LogEntry<T extends string | JSONObject> implements HasComp
}
export class SimpleLogEntry extends LogEntry<string> {
constructor(
message: string,
id: number,
date: Date,
public readonly level: string,
public readonly position: Position
) {
super(message, id, date, level);
}
getComponent(): Component {
return SimpleLogItem;
}
@@ -40,8 +51,14 @@ export class SimpleLogEntry extends LogEntry<string> {
export class ComplexLogEntry extends LogEntry<JSONObject> {
private readonly filteredMessage: ComputedRef<JSONObject>;
constructor(message: JSONObject, id: number, date: Date, visibleKeys?: Ref<string[][]>) {
super(message, id, date);
constructor(
message: JSONObject,
id: number,
date: Date,
public readonly level: string,
visibleKeys?: Ref<string[][]>
) {
super(message, id, date, level);
if (visibleKeys) {
this.filteredMessage = computed(() => {
if (!visibleKeys.value.length) {
@@ -67,13 +84,13 @@ export class ComplexLogEntry extends LogEntry<JSONObject> {
}
static fromLogEvent(event: ComplexLogEntry, visibleKeys: Ref<string[][]>): ComplexLogEntry {
return new ComplexLogEntry(event._message, event.id, event.date, visibleKeys);
return new ComplexLogEntry(event._message, event.id, event.date, event.level, visibleKeys);
}
}
export class DockerEventLogEntry extends LogEntry<string> {
constructor(message: string, date: Date, public readonly event: string) {
super(message, date.getTime(), date);
super(message, date.getTime(), date, "info");
}
getComponent(): Component {
return DockerEventLogItem;
@@ -90,7 +107,7 @@ export class SkippedLogsEntry extends LogEntry<string> {
public readonly firstSkipped: LogEntry<string | JSONObject>,
lastSkipped: LogEntry<string | JSONObject>
) {
super("", date.getTime(), date);
super("", date.getTime(), date, "info");
this._totalSkipped = totalSkipped;
this.lastSkipped = lastSkipped;
}
@@ -114,8 +131,8 @@ export class SkippedLogsEntry extends LogEntry<string> {
export function asLogEntry(event: LogEvent): LogEntry<string | JSONObject> {
if (typeof event.m === "string") {
return new SimpleLogEntry(event.m, event.id, new Date(event.ts));
return new SimpleLogEntry(event.m, event.id, new Date(event.ts), event.l, event.p);
} else {
return new ComplexLogEntry(event.m, event.id, new Date(event.ts));
return new ComplexLogEntry(event.m, event.id, new Date(event.ts), event.l);
}
}
-170
View File
@@ -1,170 +0,0 @@
<template>
<div>
<section class="hero is-small mt-4">
<div class="hero-body">
<div class="container">
<div class="columns">
<div class="column is-narrow" v-if="secured">
<a class="button is-primary is-small" :href="`${base}/logout`">{{ $t("button.logout") }}</a>
</div>
</div>
</div>
</div>
</section>
<section class="level section">
<div class="level-item has-text-centered">
<div>
<p class="title">{{ containers.length }}</p>
<p class="heading">{{ $t("label.total-containers") }}</p>
</div>
</div>
<div class="level-item has-text-centered">
<div>
<p class="title">{{ runningContainers.length }}</p>
<p class="heading">{{ $t("label.running") }}</p>
</div>
</div>
<div class="level-item has-text-centered">
<div>
<p class="title" data-ci-skip>{{ totalCpu }}%</p>
<p class="heading">{{ $t("label.total-cpu-usage") }}</p>
</div>
</div>
<div class="level-item has-text-centered">
<div>
<p class="title" data-ci-skip>{{ formatBytes(totalMem) }}</p>
<p class="heading">{{ $t("label.total-mem-usage") }}</p>
</div>
</div>
<div class="level-item has-text-centered">
<div>
<p class="title">{{ version }}</p>
<p class="heading">{{ $t("label.dozzle-version") }}</p>
</div>
</div>
</section>
<section class="columns is-centered section is-marginless">
<div class="column is-4">
<div class="panel">
<p class="panel-heading">{{ $t("label.containers") }}</p>
<div class="panel-block">
<p class="control has-icons-left">
<input
class="input"
type="text"
:placeholder="$t('placeholder.search-containers')"
v-model="query"
@keyup.esc="query = ''"
@keyup.enter="onEnter()"
/>
<span class="icon is-left">
<search-icon />
</span>
</p>
</div>
<p class="panel-tabs" v-if="query === ''">
<a :class="{ 'is-active': sort === 'running' }" @click="sort = 'running'">{{ $t("label.running") }}</a>
<a :class="{ 'is-active': sort === 'all' }" @click="sort = 'all'">{{ $t("label.all") }}</a>
</p>
<router-link
:to="{ name: 'container-id', params: { id: item.id } }"
v-for="item in data.slice(0, 10)"
:key="item.id"
class="panel-block"
>
<span class="name">{{ item.name }}</span>
<div class="subtitle is-7 status">
<past-time :date="new Date(item.created * 1000)"></past-time>
</div>
</router-link>
</div>
</div>
</section>
</div>
</template>
<script lang="ts" setup>
import SearchIcon from "~icons/mdi-light/magnify";
import { useFuse } from "@vueuse/integrations/useFuse";
const { base, version, secured } = config;
const containerStore = useContainerStore();
const { containers } = storeToRefs(containerStore);
const router = useRouter();
const sort = $ref("running");
const query = ref("");
const mostRecentContainers = $computed(() => [...containers.value].sort((a, b) => b.created - a.created));
const runningContainers = $computed(() => mostRecentContainers.filter((c) => c.state === "running"));
const { results } = useFuse(query, containers, {
fuseOptions: { keys: ["name"] },
matchAllWhenSearchEmpty: false,
});
const data = computed(() => {
if (results.value.length) {
return results.value.map(({ item }) => item);
}
switch (sort) {
case "all":
return mostRecentContainers;
case "running":
return runningContainers;
default:
throw `Invalid sort order: ${sort}`;
}
});
let totalCpu = $ref(0);
useIntervalFn(
() => {
totalCpu = runningContainers.reduce((acc, c) => acc + (c.stat?.cpu ?? 0), 0);
},
1000,
{ immediate: true }
);
let totalMem = $ref(0);
useIntervalFn(
() => {
totalMem = runningContainers.reduce((acc, c) => acc + (c.stat?.memoryUsage ?? 0), 0);
},
1000,
{ immediate: true }
);
function onEnter() {
if (data.value.length > 0) {
const item = data.value[0];
router.push({ name: "container-id", params: { id: item.id } });
}
}
</script>
<style lang="scss" scoped>
.panel {
border: 1px solid var(--border-color);
.panel-block,
.panel-tabs {
border-color: var(--border-color);
.is-active {
border-color: var(--border-hover-color);
}
.name {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.status {
margin-left: auto;
white-space: nowrap;
}
}
}
.icon {
padding: 10px 3px;
}
</style>
-79
View File
@@ -1,79 +0,0 @@
<template>
<div class="hero is-halfheight">
<div class="hero-body">
<div class="container">
<section class="columns is-centered section">
<div class="column is-4">
<div class="card">
<div class="card-content">
<form action="" method="post" @submit.prevent="onLogin" ref="form">
<div class="field">
<label class="label">{{ $t("label.username") }}</label>
<div class="control">
<input
class="input"
type="text"
name="username"
autocomplete="username"
v-model="username"
autofocus
/>
</div>
</div>
<div class="field">
<label class="label">{{ $t("label.password") }}</label>
<div class="control">
<input
class="input"
type="password"
name="password"
autocomplete="current-password"
v-model="password"
/>
</div>
<p class="help is-danger" v-if="error">{{ $t("error.invalid-auth") }}</p>
</div>
<div class="field is-grouped is-grouped-centered mt-5">
<p class="control">
<button class="button is-primary" type="submit">{{ $t("button.login") }}</button>
</p>
</div>
</form>
</div>
</div>
</div>
</section>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
const { t } = useI18n();
setTitle(t("title.login"));
let error = $ref(false);
let username = $ref("");
let password = $ref("");
let form: HTMLFormElement = $ref();
async function onLogin() {
const response = await fetch(`${config.base}/api/validateCredentials`, {
body: new FormData(form),
method: "post",
});
if (response.status == 200) {
error = false;
window.location.href = `${config.base}/`;
} else {
error = true;
}
}
</script>
<route lang="yaml">
meta:
layout: splash
</route>
-203
View File
@@ -1,203 +0,0 @@
<template>
<div>
<section class="section">
<div class="has-underline">
<h2 class="title is-4">{{ $t("settings.about") }}</h2>
</div>
<div>
<span v-html="$t('settings.using-version', { version: currentVersion })"></span>
<div
v-if="hasUpdate"
v-html="$t('settings.update-available', { nextVersion: nextRelease.name, href: nextRelease.html_url })"
></div>
</div>
</section>
<section class="section">
<div class="has-underline">
<h2 class="title is-4">{{ $t("settings.display") }}</h2>
</div>
<div class="item">
<o-switch v-model="smallerScrollbars"> {{ $t("settings.small-scrollbars") }} </o-switch>
</div>
<div class="item">
<o-switch v-model="showTimestamp"> {{ $t("settings.show-timesamps") }} </o-switch>
</div>
<div class="item">
<o-switch v-model="softWrap"> {{ $t("settings.soft-wrap") }}</o-switch>
</div>
<div class="item">
<div class="columns is-vcentered">
<div class="column is-narrow">
<o-field>
<o-dropdown v-model="hourStyle" aria-role="list">
<template #trigger>
<o-button variant="primary" type="button">
<span class="is-capitalized">{{ hourStyle }}</span>
<span class="icon">
<carbon-caret-down />
</span>
</o-button>
</template>
<o-dropdown-item :value="value" aria-role="listitem" v-for="value in ['auto', '12', '24']" :key="value">
<span class="is-capitalized">{{ value }}</span>
</o-dropdown-item>
</o-dropdown>
</o-field>
</div>
<div class="column">
{{ $t("settings.12-24-format") }}
</div>
</div>
</div>
<div class="item">
<div class="columns is-vcentered">
<div class="column is-narrow">
<o-field>
<o-dropdown v-model="size" aria-role="list">
<template #trigger>
<o-button variant="primary" type="button">
<span class="is-capitalized">{{ size }}</span>
<span class="icon">
<carbon-caret-down />
</span>
</o-button>
</template>
<o-dropdown-item
:value="value"
aria-role="listitem"
v-for="value in ['small', 'medium', 'large']"
:key="value"
>
<span class="is-capitalized">{{ value }}</span>
</o-dropdown-item>
</o-dropdown>
</o-field>
</div>
<div class="column">{{ $t("settings.font-size") }}</div>
</div>
</div>
<div class="item">
<div class="columns is-vcentered">
<div class="column is-narrow">
<o-field>
<o-dropdown v-model="lightTheme" aria-role="list">
<template #trigger>
<o-button variant="primary" type="button">
<span class="is-capitalized">{{ lightTheme }}</span>
<span class="icon">
<carbon-caret-down />
</span>
</o-button>
</template>
<o-dropdown-item
:value="value"
aria-role="listitem"
v-for="value in ['auto', 'dark', 'light']"
:key="value"
>
<span class="is-capitalized">{{ value }}</span>
</o-dropdown-item>
</o-dropdown>
</o-field>
</div>
<div class="column">{{ $t("settings.color-scheme") }}</div>
</div>
</div>
</section>
<section class="section">
<div class="has-underline">
<h2 class="title is-4">{{ $t("settings.options") }}</h2>
</div>
<div class="item">
<o-switch v-model="search">
<span v-html="$t('settings.search')"></span>
</o-switch>
</div>
<div class="item">
<o-switch v-model="showAllContainers"> {{ $t("settings.show-stopped-containers") }} </o-switch>
</div>
</section>
</div>
</template>
<script lang="ts" setup>
import gt from "semver/functions/gt";
import {
search,
lightTheme,
smallerScrollbars,
showTimestamp,
hourStyle,
showAllContainers,
size,
softWrap,
} from "@/composables/settings";
const { t } = useI18n();
setTitle(t("title.settings"));
const currentVersion = $ref(config.version);
let nextRelease = $ref({ html_url: "", name: "" });
let hasUpdate = $ref(false);
async function fetchNextRelease() {
if (!["dev", "master"].includes(currentVersion)) {
const response = await fetch("https://api.github.com/repos/amir20/dozzle/releases/latest");
if (response.ok) {
const release = await response.json();
hasUpdate = gt(release.tag_name, currentVersion);
nextRelease = release;
}
} else {
hasUpdate = true;
nextRelease = {
html_url: "",
name: "master",
};
}
}
fetchNextRelease();
</script>
<style lang="scss" scoped>
.title {
color: var(--title-color);
}
a.next-release {
text-decoration: underline;
&:hover {
text-decoration: none;
}
}
.section {
padding: 1rem 1.5rem;
}
.has-underline {
border-bottom: 1px solid var(--border-color);
padding: 1em 0px;
margin-bottom: 1em;
}
.item {
padding: 1em 0;
}
code {
border-radius: 4px;
background-color: #444;
}
</style>
-25
View File
@@ -1,25 +0,0 @@
<script lang="ts" setup>
const router = useRouter();
const route = useRoute();
const store = useContainerStore();
const { visibleContainers } = storeToRefs(store);
watch(visibleContainers, (newValue) => {
if (newValue) {
if (route.query.name) {
const [container, _] = visibleContainers.value.filter((c) => c.name == route.query.name);
if (container) {
router.push({ name: "container-id", params: { id: container.id } });
} else {
console.error(`No containers found matching name=${route.query.name}. Redirecting to /`);
router.push({ name: "index" });
}
} else {
console.error(`Expection query parameter name to be set. Redirecting to /`);
router.push({ name: "index" });
}
}
});
</script>
<template></template>
+27 -16
View File
@@ -1,17 +1,6 @@
<template>
<div>
<section class="hero is-small mt-4">
<div class="hero-body">
<div class="container">
<div class="columns">
<div class="column is-narrow" v-if="secured">
<a class="button is-primary is-small" :href="`${base}/logout`">{{ $t("button.logout") }}</a>
</div>
</div>
</div>
</div>
</section>
<section class="level section">
<section class="level section pb-0-is-mobile">
<div class="level-item has-text-centered">
<div>
<p class="title">{{ containers.length }}</p>
@@ -44,8 +33,8 @@
</div>
</section>
<section class="columns is-centered section is-marginless">
<div class="column is-4">
<section class="columns is-centered section is-marginless pt-0-is-mobile">
<div class="column is-12-mobile is-6-tablet is-5-desktop is-4-fullhd">
<div class="panel">
<p class="panel-heading">{{ $t("label.containers") }}</p>
<div class="panel-block">
@@ -89,7 +78,7 @@
import SearchIcon from "~icons/mdi-light/magnify";
import { useFuse } from "@vueuse/integrations/useFuse";
const { base, version, secured } = config;
const { version } = config;
const containerStore = useContainerStore();
const { containers } = storeToRefs(containerStore);
const router = useRouter();
@@ -100,10 +89,22 @@ const query = ref("");
const mostRecentContainers = $computed(() => [...containers.value].sort((a, b) => b.created - a.created));
const runningContainers = $computed(() => mostRecentContainers.filter((c) => c.state === "running"));
const { results } = useFuse(query, containers, {
const list = computed(() => {
return containers.value.map(({ id, created, name, state }) => {
return {
id,
created,
name,
state,
};
});
});
const { results } = useFuse(query, list, {
fuseOptions: { keys: ["name"] },
matchAllWhenSearchEmpty: false,
});
const data = computed(() => {
if (results.value.length) {
return results.value.map(({ item }) => item);
@@ -164,6 +165,16 @@ function onEnter() {
}
}
@media screen and (max-width: 768px) {
.pb-0-is-mobile {
padding-bottom: 0 !important;
}
.pt-0-is-mobile {
padding-top: 0 !important;
}
}
.icon {
padding: 10px 3px;
}
+2
View File
@@ -6,6 +6,7 @@ interface Config {
authorizationNeeded: boolean | "false" | "true";
secured: boolean | "false" | "true";
maxLogs: number;
hostname: string;
}
const pageConfig = JSON.parse(text);
@@ -20,6 +21,7 @@ if (config.version == "{{ .Version }}") {
config.base = "";
config.authorizationNeeded = false;
config.secured = false;
config.hostname = "localhost";
} else {
config.version = config.version.replace(/^v/, "");
config.authorizationNeeded = config.authorizationNeeded === "true";
+5
View File
@@ -206,3 +206,8 @@ mark {
transform: scale(1.05);
}
}
.button.is-rounded:hover {
color: var(--text-strong-color);
background: var(--scheme-main-ter);
}
+3 -3
View File
@@ -35,7 +35,7 @@ type dockerProxy interface {
type Client interface {
ListContainers() ([]Container, error)
FindContainer(string) (Container, error)
ContainerLogs(context.Context, string, int, string) (io.ReadCloser, error)
ContainerLogs(context.Context, string, string) (io.ReadCloser, error)
Events(context.Context) (<-chan ContainerEvent, <-chan error)
ContainerLogsBetweenDates(context.Context, string, time.Time, time.Time) (io.ReadCloser, error)
ContainerStats(context.Context, string, chan<- ContainerStat) error
@@ -169,7 +169,7 @@ func (d *dockerClient) ContainerStats(ctx context.Context, id string, stats chan
return nil
}
func (d *dockerClient) ContainerLogs(ctx context.Context, id string, tailSize int, since string) (io.ReadCloser, error) {
func (d *dockerClient) ContainerLogs(ctx context.Context, id string, since string) (io.ReadCloser, error) {
log.WithField("id", id).WithField("since", since).Debug("streaming logs for container")
if since != "" {
@@ -184,7 +184,7 @@ func (d *dockerClient) ContainerLogs(ctx context.Context, id string, tailSize in
ShowStdout: true,
ShowStderr: true,
Follow: true,
Tail: strconv.Itoa(tailSize),
Tail: "300",
Timestamps: true,
Since: since,
}
+3 -3
View File
@@ -133,7 +133,7 @@ func Test_dockerClient_ContainerLogs_happy(t *testing.T) {
proxy.On("ContainerInspect", mock.Anything, id).Return(json, nil)
client := &dockerClient{proxy, filters.NewArgs()}
logReader, _ := client.ContainerLogs(context.Background(), id, 300, "since")
logReader, _ := client.ContainerLogs(context.Background(), id, "since")
actual, _ := ioutil.ReadAll(logReader)
assert.Equal(t, expected, string(actual), "message doesn't match expected")
@@ -154,7 +154,7 @@ func Test_dockerClient_ContainerLogs_happy_with_tty(t *testing.T) {
proxy.On("ContainerInspect", mock.Anything, id).Return(json, nil)
client := &dockerClient{proxy, filters.NewArgs()}
logReader, _ := client.ContainerLogs(context.Background(), id, 300, "")
logReader, _ := client.ContainerLogs(context.Background(), id, "")
actual, _ := ioutil.ReadAll(logReader)
assert.Equal(t, expected, string(actual), "message doesn't match expected")
@@ -170,7 +170,7 @@ func Test_dockerClient_ContainerLogs_error(t *testing.T) {
client := &dockerClient{proxy, filters.NewArgs()}
reader, err := client.ContainerLogs(context.Background(), id, 300, "")
reader, err := client.ContainerLogs(context.Background(), id, "")
assert.Nil(t, reader, "reader should be nil")
assert.Error(t, err, "error should have been returned")
+153
View File
@@ -0,0 +1,153 @@
package docker
import (
"bufio"
"encoding/json"
"hash/fnv"
"regexp"
"strings"
"time"
log "github.com/sirupsen/logrus"
)
type eventGenerator struct {
reader *bufio.Reader
channel chan *LogEvent
next *LogEvent
error error
}
func NewEventIterator(reader *bufio.Reader) *eventGenerator {
generator := &eventGenerator{reader: reader, channel: make(chan *LogEvent, 100)}
go generator.consume()
return generator
}
func (g *eventGenerator) Next() (*LogEvent, error) {
var currentEvent *LogEvent
var nextEvent *LogEvent
if g.next != nil {
currentEvent = g.next
g.next = nil
nextEvent = g.Peek()
} else {
event, ok := <-g.channel
if !ok {
return nil, g.error
}
currentEvent = event
nextEvent = g.Peek()
}
currentLevel := guessLogLevel(currentEvent)
if nextEvent != nil {
if currentEvent.IsCloseToTime(nextEvent) && currentLevel != "" && !nextEvent.HasLevel() {
currentEvent.Position = START
nextEvent.Position = MIDDLE
}
// If next item is not close to current item or has level, set current item position to end
if currentEvent.Position == MIDDLE && (nextEvent.HasLevel() || !currentEvent.IsCloseToTime(nextEvent)) {
currentEvent.Position = END
}
// If next item is close to current item and has no level, set next item position to middle
if currentEvent.Position == MIDDLE && !nextEvent.HasLevel() && currentEvent.IsCloseToTime(nextEvent) {
nextEvent.Position = MIDDLE
}
// Set next item level to current item level
if currentEvent.Position == START || currentEvent.Position == MIDDLE {
nextEvent.Level = currentEvent.Level
}
} else if currentEvent.Position == MIDDLE {
currentEvent.Position = END
}
return currentEvent, nil
}
func (g *eventGenerator) LastError() error {
return g.error
}
func (g *eventGenerator) Peek() *LogEvent {
if g.next != nil {
return g.next
}
select {
case event := <-g.channel:
g.next = event
return g.next
case <-time.After(50 * time.Millisecond):
return nil
}
}
func (g *eventGenerator) consume() {
for {
message, readerError := g.reader.ReadString('\n')
h := fnv.New32a()
h.Write([]byte(message))
logEvent := &LogEvent{Id: h.Sum32(), Message: message}
if index := strings.IndexAny(message, " "); index != -1 {
logId := message[:index]
if timestamp, err := time.Parse(time.RFC3339Nano, logId); err == nil {
logEvent.Timestamp = timestamp.UnixMilli()
message = strings.TrimSuffix(message[index+1:], "\n")
logEvent.Message = message
if strings.HasPrefix(message, "{") && strings.HasSuffix(message, "}") {
var data map[string]interface{}
if err := json.Unmarshal([]byte(message), &data); err != nil {
log.Errorf("json unmarshal error while streaming %v", err.Error())
} else {
logEvent.Message = data
}
}
}
}
logEvent.Level = guessLogLevel(logEvent)
g.channel <- logEvent
if readerError != nil {
close(g.channel)
g.error = readerError
break
}
}
}
var NON_ASCII_REGEX = regexp.MustCompile("^[^a-z ]+[^ewidtf]?")
var KEY_VALUE_REGEX = regexp.MustCompile("level=([^ ]+)")
func guessLogLevel(logEvent *LogEvent) string {
switch value := logEvent.Message.(type) {
case string:
value = NON_ASCII_REGEX.ReplaceAllString(strings.ToLower(value), "")
levels := []string{"error", "warn", "info", "debug", "trace", "fatal"}
for _, level := range levels {
prefix := regexp.MustCompile("^" + level + "[^a-z]")
if prefix.MatchString(value) {
return level
}
}
if matches := KEY_VALUE_REGEX.FindStringSubmatch(value); matches != nil {
return matches[1]
}
case map[string]interface{}:
if value["level"] != nil {
return strings.ToLower(value["level"].(string))
}
}
return ""
}
+23 -3
View File
@@ -1,5 +1,7 @@
package docker
import "math"
// Container represents an internal representation of docker containers
type Container struct {
ID string `json:"id"`
@@ -27,8 +29,26 @@ type ContainerEvent struct {
Name string `json:"name"`
}
type LogPosition string
const (
START LogPosition = "start"
MIDDLE LogPosition = "middle"
END LogPosition = "end"
)
type LogEvent struct {
Message any `json:"m,omitempty"`
Timestamp int64 `json:"ts"`
Id uint32 `json:"id,omitempty"`
Message any `json:"m,omitempty"`
Timestamp int64 `json:"ts"`
Id uint32 `json:"id,omitempty"`
Level string `json:"l,omitempty"`
Position LogPosition `json:"p,omitempty"`
}
func (l *LogEvent) HasLevel() bool {
return l.Level != ""
}
func (l *LogEvent) IsCloseToTime(other *LogEvent) bool {
return math.Abs(float64(l.Timestamp-other.Timestamp)) < 10
}
+2 -2
View File
@@ -1,6 +1,6 @@
FROM cypress/included:10.10.0
FROM cypress/included:12.4.1
RUN apt install curl && curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
RUN apt update && apt install curl -y && curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
WORKDIR /e2e
Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 34 KiB

+3 -3
View File
@@ -6,8 +6,8 @@
},
"license": "ISC",
"dependencies": {
"@frsource/cypress-plugin-visual-regression-diff": "^2.0.0",
"cypress": "^10.8.0",
"typescript": "^4.8.3"
"@frsource/cypress-plugin-visual-regression-diff": "^3.2.8",
"cypress": "^12.4.1",
"typescript": "^4.9.4"
}
}
+143 -62
View File
@@ -1,14 +1,14 @@
lockfileVersion: 5.4
specifiers:
'@frsource/cypress-plugin-visual-regression-diff': ^2.0.0
cypress: ^10.8.0
typescript: ^4.8.3
'@frsource/cypress-plugin-visual-regression-diff': ^3.2.8
cypress: ^12.4.1
typescript: ^4.9.4
dependencies:
'@frsource/cypress-plugin-visual-regression-diff': 2.0.0_cypress@10.8.0
cypress: 10.8.0
typescript: 4.8.3
'@frsource/cypress-plugin-visual-regression-diff': 3.2.8_cypress@12.4.1
cypress: 12.4.1
typescript: 4.9.4
packages:
@@ -19,12 +19,12 @@ packages:
dev: false
optional: true
/@cypress/request/2.88.10:
resolution: {integrity: sha512-Zp7F+R93N0yZyG34GutyTNr+okam7s/Fzc1+i3kcqOP8vk6OuajuE9qZJ6Rs+10/1JFtXFYMdyarnU1rZuJesg==}
/@cypress/request/2.88.11:
resolution: {integrity: sha512-M83/wfQ1EkspjkE2lNWNV5ui2Cv7UCv1swW1DqljahbzLVWltcsexQh8jYtuS/vzFXP+HySntGM83ZXA9fn17w==}
engines: {node: '>= 6'}
dependencies:
aws-sign2: 0.7.0
aws4: 1.11.0
aws4: 1.12.0
caseless: 0.12.0
combined-stream: 1.0.8
extend: 3.0.2
@@ -36,7 +36,7 @@ packages:
json-stringify-safe: 5.0.1
mime-types: 2.1.35
performance-now: 2.1.0
qs: 6.5.3
qs: 6.10.4
safe-buffer: 5.2.1
tough-cookie: 2.5.0
tunnel-agent: 0.6.0
@@ -52,21 +52,28 @@ packages:
- supports-color
dev: false
/@frsource/cypress-plugin-visual-regression-diff/2.0.0_cypress@10.8.0:
resolution: {integrity: sha512-hwMEZzD0tFbNNNbEjdF8q2ELpxhby25VQ665QUJy1soGPiGwY+rBDdktFg3EXVGs/Euq0Nj8hq/K18BWYoX10g==}
/@frsource/base64/1.0.5:
resolution: {integrity: sha512-RIlQtKezeRhNuxIpIc2BuM4kNqAakIarKV/TCgx2EC+zgj1NxX3+Mc80AkJCCMO8dZUuGzbULG7295YeBQZlSA==}
dev: false
/@frsource/cypress-plugin-visual-regression-diff/3.2.8_cypress@12.4.1:
resolution: {integrity: sha512-B7TbwdAiql73FxRr2tiFmx8PvpK4a2oCBygPzT1v9IvCxh4XwoXwY47/klKtg2402PrhdXwcqI/NBSVqb4CJnQ==}
engines: {node: '>=10'}
peerDependencies:
cypress: '>=4.5.0'
dependencies:
cypress: 10.8.0
'@frsource/base64': 1.0.5
cypress: 12.4.1
glob: 8.0.3
meta-png: 1.0.5
move-file: 2.1.0
pixelmatch: 5.3.0
pngjs: 6.0.0
sharp: 0.30.7
sharp: 0.31.2
dev: false
/@types/node/14.18.29:
resolution: {integrity: sha512-LhF+9fbIX4iPzhsRLpK5H7iPdvW8L4IwGciXQIOEcuF62+9nw/VQVsOViAOOGxY3OlOKGLFv0sWwJXdwQeTn6A==}
/@types/node/14.18.36:
resolution: {integrity: sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ==}
dev: false
/@types/sinonjs__fake-timers/8.1.1:
@@ -81,7 +88,7 @@ packages:
resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==}
requiresBuild: true
dependencies:
'@types/node': 14.18.29
'@types/node': 14.18.36
dev: false
optional: true
@@ -154,8 +161,8 @@ packages:
resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==}
dev: false
/aws4/1.11.0:
resolution: {integrity: sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==}
/aws4/1.12.0:
resolution: {integrity: sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==}
dev: false
/balanced-match/1.0.2:
@@ -195,6 +202,12 @@ packages:
concat-map: 0.0.1
dev: false
/brace-expansion/2.0.1:
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
dependencies:
balanced-match: 1.0.2
dev: false
/buffer-crc32/0.2.13:
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
dev: false
@@ -211,6 +224,13 @@ packages:
engines: {node: '>=6'}
dev: false
/call-bind/1.0.2:
resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==}
dependencies:
function-bind: 1.1.1
get-intrinsic: 1.2.0
dev: false
/caseless/0.12.0:
resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==}
dev: false
@@ -232,8 +252,9 @@ packages:
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
dev: false
/ci-info/3.4.0:
resolution: {integrity: sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug==}
/ci-info/3.7.1:
resolution: {integrity: sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==}
engines: {node: '>=8'}
dev: false
/clean-stack/2.2.0:
@@ -329,15 +350,15 @@ packages:
which: 2.0.2
dev: false
/cypress/10.8.0:
resolution: {integrity: sha512-QVse0dnLm018hgti2enKMVZR9qbIO488YGX06nH5j3Dg1isL38DwrBtyrax02CANU6y8F4EJUuyW6HJKw1jsFA==}
engines: {node: '>=12.0.0'}
/cypress/12.4.1:
resolution: {integrity: sha512-IFMPzc1RJ22Fac+YbdqywtpcuqZXqQJh+JqZDXn+A15zG/dw99njfv8f0bTvD+WAHhP118+xWG/VwilkFg1rnA==}
engines: {node: ^14.0.0 || ^16.0.0 || >=18.0.0}
hasBin: true
requiresBuild: true
dependencies:
'@cypress/request': 2.88.10
'@cypress/request': 2.88.11
'@cypress/xvfb': 1.2.4_supports-color@8.1.1
'@types/node': 14.18.29
'@types/node': 14.18.36
'@types/sinonjs__fake-timers': 8.1.1
'@types/sizzle': 2.3.3
arch: 2.2.0
@@ -351,7 +372,7 @@ packages:
cli-table3: 0.6.3
commander: 5.1.0
common-tags: 1.8.2
dayjs: 1.11.5
dayjs: 1.11.7
debug: 4.3.4_supports-color@8.1.1
enquirer: 2.3.6
eventemitter2: 6.4.7
@@ -367,12 +388,12 @@ packages:
listr2: 3.14.0_enquirer@2.3.6
lodash: 4.17.21
log-symbols: 4.1.0
minimist: 1.2.6
minimist: 1.2.7
ospath: 1.2.2
pretty-bytes: 5.6.0
proxy-from-env: 1.0.0
request-progress: 3.0.0
semver: 7.3.7
semver: 7.3.8
supports-color: 8.1.1
tmp: 0.2.1
untildify: 4.0.0
@@ -386,8 +407,8 @@ packages:
assert-plus: 1.0.0
dev: false
/dayjs/1.11.5:
resolution: {integrity: sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==}
/dayjs/1.11.7:
resolution: {integrity: sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==}
dev: false
/debug/3.2.7_supports-color@8.1.1:
@@ -564,6 +585,18 @@ packages:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
dev: false
/function-bind/1.1.1:
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
dev: false
/get-intrinsic/1.2.0:
resolution: {integrity: sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==}
dependencies:
function-bind: 1.1.1
has: 1.0.3
has-symbols: 1.0.3
dev: false
/get-stream/5.2.0:
resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==}
engines: {node: '>=8'}
@@ -598,8 +631,19 @@ packages:
path-is-absolute: 1.0.1
dev: false
/global-dirs/3.0.0:
resolution: {integrity: sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==}
/glob/8.0.3:
resolution: {integrity: sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==}
engines: {node: '>=12'}
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 5.1.2
once: 1.4.0
dev: false
/global-dirs/3.0.1:
resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==}
engines: {node: '>=10'}
dependencies:
ini: 2.0.0
@@ -614,6 +658,18 @@ packages:
engines: {node: '>=8'}
dev: false
/has-symbols/1.0.3:
resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
engines: {node: '>= 0.4'}
dev: false
/has/1.0.3:
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
engines: {node: '>= 0.4.0'}
dependencies:
function-bind: 1.1.1
dev: false
/http-signature/1.3.6:
resolution: {integrity: sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==}
engines: {node: '>=0.10'}
@@ -665,7 +721,7 @@ packages:
resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==}
hasBin: true
dependencies:
ci-info: 3.4.0
ci-info: 3.7.1
dev: false
/is-fullwidth-code-point/3.0.0:
@@ -677,7 +733,7 @@ packages:
resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==}
engines: {node: '>=10'}
dependencies:
global-dirs: 3.0.0
global-dirs: 3.0.1
is-path-inside: 3.0.3
dev: false
@@ -758,7 +814,7 @@ packages:
log-update: 4.0.0
p-map: 4.0.0
rfdc: 1.3.0
rxjs: 7.5.6
rxjs: 7.8.0
through: 2.3.8
wrap-ansi: 7.0.0
dev: false
@@ -800,6 +856,10 @@ packages:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
dev: false
/meta-png/1.0.5:
resolution: {integrity: sha512-kR/npdqfnlB8HycaDImcpauYh/l1ldvuDoT/QhWazSD9jDHJ8v9tankLhjN5ea8OKV1Amp4v/xxNYG2WiQYEgg==}
dev: false
/mime-db/1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
@@ -828,8 +888,15 @@ packages:
brace-expansion: 1.1.11
dev: false
/minimist/1.2.6:
resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==}
/minimatch/5.1.2:
resolution: {integrity: sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==}
engines: {node: '>=10'}
dependencies:
brace-expansion: 2.0.1
dev: false
/minimist/1.2.7:
resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==}
dev: false
/mkdirp-classic/0.5.3:
@@ -855,11 +922,11 @@ packages:
resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
dev: false
/node-abi/3.24.0:
resolution: {integrity: sha512-YPG3Co0luSu6GwOBsmIdGW6Wx0NyNDLg/hriIyDllVsNwnI6UeqaWShxC3lbH4LtEQUgoLP3XR1ndXiDAWvmRw==}
/node-abi/3.30.0:
resolution: {integrity: sha512-qWO5l3SCqbwQavymOmtTVuCWZE23++S+rxyoHjXqUmPyzRcaoI4lA2gO55/drddGnedAyjA7sk76SfQ5lfUMnw==}
engines: {node: '>=10'}
dependencies:
semver: 7.3.7
semver: 7.3.8
dev: false
/node-addon-api/5.0.0:
@@ -873,6 +940,10 @@ packages:
path-key: 3.1.1
dev: false
/object-inspect/1.12.3:
resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==}
dev: false
/once/1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
@@ -945,10 +1016,10 @@ packages:
detect-libc: 2.0.1
expand-template: 2.0.3
github-from-package: 0.0.0
minimist: 1.2.6
minimist: 1.2.7
mkdirp-classic: 0.5.3
napi-build-utils: 1.0.2
node-abi: 3.24.0
node-abi: 3.30.0
pump: 3.0.0
rc: 1.2.8
simple-get: 4.0.1
@@ -976,14 +1047,16 @@ packages:
once: 1.4.0
dev: false
/punycode/2.1.1:
resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==}
/punycode/2.3.0:
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
engines: {node: '>=6'}
dev: false
/qs/6.5.3:
resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==}
/qs/6.10.4:
resolution: {integrity: sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==}
engines: {node: '>=0.6'}
dependencies:
side-channel: 1.0.4
dev: false
/rc/1.2.8:
@@ -992,7 +1065,7 @@ packages:
dependencies:
deep-extend: 0.6.0
ini: 1.3.8
minimist: 1.2.6
minimist: 1.2.7
strip-json-comments: 2.0.1
dev: false
@@ -1030,10 +1103,10 @@ packages:
glob: 7.2.3
dev: false
/rxjs/7.5.6:
resolution: {integrity: sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==}
/rxjs/7.8.0:
resolution: {integrity: sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==}
dependencies:
tslib: 2.4.0
tslib: 2.5.0
dev: false
/safe-buffer/5.2.1:
@@ -1044,24 +1117,24 @@ packages:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
dev: false
/semver/7.3.7:
resolution: {integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==}
/semver/7.3.8:
resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==}
engines: {node: '>=10'}
hasBin: true
dependencies:
lru-cache: 6.0.0
dev: false
/sharp/0.30.7:
resolution: {integrity: sha512-G+MY2YW33jgflKPTXXptVO28HvNOo9G3j0MybYAHeEmby+QuD2U98dT6ueht9cv/XDqZspSpIhoSW+BAKJ7Hig==}
engines: {node: '>=12.13.0'}
/sharp/0.31.2:
resolution: {integrity: sha512-DUdNVEXgS5A97cTagSLIIp8dUZ/lZtk78iNVZgHdHbx1qnQR7JAHY0BnXnwwH39Iw+VKhO08CTYhIg0p98vQ5Q==}
engines: {node: '>=14.15.0'}
requiresBuild: true
dependencies:
color: 4.2.3
detect-libc: 2.0.1
node-addon-api: 5.0.0
prebuild-install: 7.1.1
semver: 7.3.7
semver: 7.3.8
simple-get: 4.0.1
tar-fs: 2.1.1
tunnel-agent: 0.6.0
@@ -1079,6 +1152,14 @@ packages:
engines: {node: '>=8'}
dev: false
/side-channel/1.0.4:
resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
dependencies:
call-bind: 1.0.2
get-intrinsic: 1.2.0
object-inspect: 1.12.3
dev: false
/signal-exit/3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
dev: false
@@ -1221,11 +1302,11 @@ packages:
engines: {node: '>=0.8'}
dependencies:
psl: 1.9.0
punycode: 2.1.1
punycode: 2.3.0
dev: false
/tslib/2.4.0:
resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
/tslib/2.5.0:
resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==}
dev: false
/tunnel-agent/0.6.0:
@@ -1243,8 +1324,8 @@ packages:
engines: {node: '>=10'}
dev: false
/typescript/4.8.3:
resolution: {integrity: sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==}
/typescript/4.9.4:
resolution: {integrity: sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: false
+9 -9
View File
@@ -4,24 +4,24 @@ require (
github.com/Microsoft/go-winio v0.5.1 // indirect
github.com/alexflint/go-arg v1.4.3
github.com/beme/abide v0.0.0-20190723115211-635a09831760
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v20.10.20+incompatible
github.com/docker/distribution v2.8.0+incompatible // indirect
github.com/docker/docker v20.10.23+incompatible
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/dustin/go-humanize v1.0.0
github.com/dustin/go-humanize v1.0.1
github.com/gogo/protobuf v1.3.2 // indirect
github.com/gorilla/mux v1.8.0
github.com/gorilla/sessions v1.2.1
github.com/magiconair/properties v1.8.6
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
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/sirupsen/logrus v1.9.0
github.com/spf13/afero v1.9.2
github.com/stretchr/objx v0.4.0 // indirect
github.com/stretchr/testify v1.8.0
github.com/spf13/afero v1.9.3
github.com/stretchr/objx v0.5.0 // indirect
github.com/stretchr/testify v1.8.1
golang.org/x/net v0.0.0-20211104170005-ce137452f963 // indirect
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
@@ -40,4 +40,4 @@ require (
gotest.tools/v3 v3.0.3 // indirect
)
go 1.18
go 1.19
+18 -12
View File
@@ -60,16 +60,20 @@ 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/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v20.10.20+incompatible h1:kH9tx6XO+359d+iAkumyKDc5Q1kOwPuAUaeri48nD6E=
github.com/docker/docker v20.10.20+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/distribution v2.8.0+incompatible h1:l9EaZDICImO1ngI+uTifW+ZYvvz7fKISBAKpg+MbWbY=
github.com/docker/distribution v2.8.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v20.10.22+incompatible h1:6jX4yB+NtcbldT90k7vBSaWJDB3i+zkVJT9BEK8kQkk=
github.com/docker/docker v20.10.22+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v20.10.23+incompatible h1:1ZQUUYAdh+oylOT85aA2ZcfRp22jmLhoaEcVEfK8dyA=
github.com/docker/docker v20.10.23+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -158,16 +162,16 @@ 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/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
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=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -181,19 +185,21 @@ github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNX
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw=
github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.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.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 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+2 -1
View File
@@ -9,7 +9,8 @@
"base": "{{ .Base }}",
"version": "{{ .Version }}",
"authorizationNeeded": "{{ .AuthorizationNeeded }}",
"secured": "{{ .Secured }}"
"secured": "{{ .Secured }}",
"hostname": "{{ .Hostname }}"
}
</script>
<link
+49
View File
@@ -0,0 +1,49 @@
toolbar:
clear: Очистить
download: Скачать
search: Поиск
label:
containers: Контейнеры
total-containers: Всего Контейнеров
running: Запущенные
total-cpu-usage: Использование процессора
total-mem-usage: Использование памяти
dozzle-version: Версия Dozzle
all: Все
password: Пароль
username: Имя пользователя
tooltip:
search: Поиск контейнеров (⌘ + k, ⌃k)
pin-column: Закрепить столбец
error:
page-not-found: Эта страница не доступна.
invalid-auth: Имя пользователя или пароль неверны.
logs-skipped: Пропущено {total} записей
title:
page-not-found: Страница не найдена
login: Требуется авторизация
settings: Настройки
button:
logout: Выйти
login: Войти
placeholder:
search-containers: Поиск контейнеров (⌘ + k, ⌃k)
settings:
display: Вид
small-scrollbars: Уменьшенная полоса прокрутки
show-timesamps: Показывать временные метки
soft-wrap: Плавный перенос текста
12-24-format: >-
Формат времени. По умолчанию, Dozzle будет использовать локализацию вашего браузера.
font-size: Размер шрифта
color-scheme: Цветовая схема
options: Опции
show-stopped-containers: Показывать остановленные контейнеры
about: Информация
search: >-
Включить поиск с помощью Dozzle, используя <code>command+f</code> или
<code>ctrl+f</code>
using-version: Вы используете версию Dozzle {version}.
update-available: >-
Доступна новая версия! Обновить до <a :href="{href}" class="next-release"
target="_blank" rel="noreferrer noopener">{nextVersion}</a>.
+24 -3
View File
@@ -24,13 +24,25 @@ var (
version = "head"
)
type DockerSecret struct {
Value string
}
func (s *DockerSecret) UnmarshalText(b []byte) error {
v, err := os.ReadFile(string(b))
s.Value = strings.Trim(string(v), "\r\n")
return err
}
type args struct {
Addr string `arg:"env:DOZZLE_ADDR" default:":8080" help:"sets host:port to bind for server. This is rarely needed inside a docker container."`
Base string `arg:"env:DOZZLE_BASE" default:"/" help:"sets the base for http router."`
Hostname string `arg:"env:DOZZLE_HOSTNAME" help:"sets the hostname for display. This is useful with multiple Dozzle instances."`
Level string `arg:"env:DOZZLE_LEVEL" default:"info" help:"set Dozzle log level. Use debug for more logging."`
TailSize int `arg:"env:DOZZLE_TAILSIZE" default:"300" help:"update the initial tail size when fetching logs."`
Username string `arg:"env:DOZZLE_USERNAME" help:"sets the username for auth."`
Password string `arg:"env:DOZZLE_PASSWORD" help:"sets password for auth"`
UsernameFile *DockerSecret `arg:"env:DOZZLE_USERNAME_FILE" help:"sets the secret path read username for auth."`
PasswordFile *DockerSecret `arg:"env:DOZZLE_PASSWORD_FILE" help:"sets the secret path read password for auth"`
NoAnalytics bool `arg:"--no-analytics,env:DOZZLE_NO_ANALYTICS" help:"disables anonymous analytics"`
WaitForDockerSeconds int `arg:"--wait-for-docker-seconds,env:DOZZLE_WAIT_FOR_DOCKER_SECONDS" help:"wait for docker to be available for at most this many seconds before starting the server."`
FilterStrings []string `arg:"env:DOZZLE_FILTER,--filter,separate" help:"filters docker containers using Docker syntax."`
@@ -93,6 +105,14 @@ func main() {
}
}
if args.Username == "" && args.UsernameFile != nil {
args.Username = args.UsernameFile.Value
}
if args.Password == "" && args.PasswordFile != nil {
args.Password = args.PasswordFile.Value
}
if args.Username != "" || args.Password != "" {
if args.Username == "" || args.Password == "" {
log.Fatalf("Username AND password are required for authentication")
@@ -103,9 +123,10 @@ func main() {
Addr: args.Addr,
Base: args.Base,
Version: version,
TailSize: args.TailSize,
Username: args.Username,
Password: args.Password,
Hostname: args.Hostname,
NoAnalytics: args.NoAnalytics,
}
assets, err := fs.Sub(content, "dist")
@@ -155,8 +176,8 @@ func doStartEvent(arg args) {
FilterLength: len(arg.Filter),
CustomAddress: arg.Addr != ":8080",
CustomBase: arg.Base != "/",
TailSize: arg.TailSize,
Protected: arg.Username != "",
HasHostname: arg.Hostname != "",
}
if err := analytics.SendStartEvent(event); err != nil {
+43 -43
View File
@@ -1,6 +1,6 @@
{
"name": "dozzle",
"version": "4.2.2",
"version": "4.7.2",
"description": "Realtime log viewer for docker containers. ",
"homepage": "https://github.com/amir20/dozzle#readme",
"bugs": {
@@ -22,69 +22,69 @@
"postinstall": "husky install"
},
"dependencies": {
"@iconify-json/carbon": "^1.1.9",
"@iconify-json/cil": "^1.1.2",
"@iconify-json/mdi": "^1.1.34",
"@iconify-json/mdi-light": "^1.1.2",
"@iconify-json/octicon": "^1.1.20",
"@oruga-ui/oruga-next": "^0.5.6",
"@oruga-ui/theme-bulma": "^0.2.7",
"@vueuse/core": "^9.3.1",
"@vueuse/integrations": "^9.3.1",
"@vueuse/router": "^9.3.1",
"@iconify-json/carbon": "^1.1.14",
"@iconify-json/cil": "^1.1.3",
"@iconify-json/mdi": "^1.1.44",
"@iconify-json/mdi-light": "^1.1.5",
"@iconify-json/octicon": "^1.1.30",
"@intlify/unplugin-vue-i18n": "^0.8.1",
"@oruga-ui/oruga-next": "^0.5.10",
"@oruga-ui/theme-bulma": "^0.2.8",
"@vueuse/core": "^9.12.0",
"@vueuse/integrations": "^9.12.0",
"@vueuse/router": "^9.12.0",
"ansi-to-html": "^0.7.2",
"bulma": "^0.9.4",
"d3-array": "^3.2.0",
"d3-array": "^3.2.2",
"d3-ease": "^3.0.1",
"d3-scale": "^4.0.2",
"d3-selection": "^3.0.0",
"d3-shape": "^3.1.0",
"d3-shape": "^3.2.0",
"d3-transition": "^3.0.1",
"date-fns": "^2.29.3",
"fuse.js": "^6.6.2",
"lodash.debounce": "^4.0.8",
"pinia": "^2.0.23",
"pinia": "^2.0.29",
"semver": "^7.3.8",
"splitpanes": "^3.1.1",
"vue": "^3.2.41",
"splitpanes": "^3.1.5",
"vue": "^3.2.45",
"vue-i18n": "^9.2.2",
"vue-router": "^4.1.5"
"vue-router": "^4.1.6"
},
"devDependencies": {
"@intlify/vite-plugin-vue-i18n": "^6.0.3",
"@pinia/testing": "^0.0.14",
"@types/d3-array": "^3.0.3",
"@types/d3-array": "^3.0.4",
"@types/d3-ease": "^3.0.0",
"@types/d3-scale": "^4.0.2",
"@types/d3-selection": "^3.0.3",
"@types/d3-shape": "^3.1.0",
"@types/d3-transition": "^3.0.2",
"@types/d3-scale": "^4.0.3",
"@types/d3-selection": "^3.0.4",
"@types/d3-shape": "^3.1.1",
"@types/d3-transition": "^3.0.3",
"@types/lodash.debounce": "^4.0.7",
"@types/node": "^18.11.2",
"@types/semver": "^7.3.12",
"@vitejs/plugin-vue": "3.1.2",
"@vue/compiler-sfc": "^3.2.41",
"@vue/test-utils": "^2.1.0",
"@types/node": "^18.11.18",
"@types/semver": "^7.3.13",
"@vitejs/plugin-vue": "4.0.0",
"@vue/compiler-sfc": "^3.2.45",
"@vue/test-utils": "^2.2.8",
"c8": "^7.12.0",
"eventsourcemock": "^2.0.0",
"husky": "^8.0.1",
"jest-serializer-vue": "^2.0.2",
"jsdom": "^20.0.1",
"lint-staged": "^13.0.3",
"husky": "^8.0.3",
"jest-serializer-vue": "^3.0.0",
"jsdom": "^21.1.0",
"lint-staged": "^13.1.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.7.1",
"release-it": "^15.5.0",
"sass": "^1.55.0",
"prettier": "^2.8.3",
"release-it": "^15.6.0",
"sass": "^1.57.1",
"ts-node": "^10.9.1",
"typescript": "^4.8.4",
"unplugin-auto-import": "^0.11.2",
"unplugin-icons": "^0.14.12",
"unplugin-vue-components": "^0.22.8",
"vite": "3.1.8",
"vite-plugin-pages": "^0.27.0",
"typescript": "^4.9.4",
"unplugin-auto-import": "^0.13.0",
"unplugin-icons": "^0.15.2",
"unplugin-vue-components": "^0.23.0",
"vite": "4.0.4",
"vite-plugin-pages": "^0.28.0",
"vite-plugin-vue-layouts": "^0.7.0",
"vitest": "^0.24.3",
"vue-tsc": "^1.0.8"
"vitest": "^0.28.3",
"vue-tsc": "^1.0.24"
},
"lint-staged": {
"*.{js,vue,css}": [
+1723 -982
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -0,0 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
]
}
+16 -15
View File
@@ -7,18 +7,30 @@ import AutoImport from "unplugin-auto-import/vite";
import IconsResolver from "unplugin-icons/resolver";
import Pages from "vite-plugin-pages";
import Layouts from "vite-plugin-vue-layouts";
import VueI18n from "@intlify/vite-plugin-vue-i18n";
import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite";
export default defineConfig(({ mode }) => ({
export default defineConfig(() => ({
resolve: {
alias: {
"@/": `${path.resolve(__dirname, "assets")}/`,
},
},
base: mode === "production" ? "/{{ .Base }}/" : "/",
experimental: {
renderBuiltUrl(filename: string, { type }: { type: "public" | "asset" }) {
if (type === "asset") {
return `{{ .Base }}/${filename}`;
}
return filename;
},
},
plugins: [
vue({
reactivityTransform: true,
template: {
compilerOptions: {
whitespace: "preserve",
},
},
}),
Icons({
autoInstall: true,
@@ -46,12 +58,11 @@ export default defineConfig(({ mode }) => ({
dirs: ["assets/composables", "assets/stores", "assets/utils"],
vueTemplate: true,
}),
VueI18n({
VueI18nPlugin({
runtimeOnly: true,
compositionOnly: true,
include: [path.resolve(__dirname, "locales/**")],
}),
htmlPlugin(mode),
],
server: {
proxy: {
@@ -61,13 +72,3 @@ export default defineConfig(({ mode }) => ({
},
},
}));
const htmlPlugin = (mode) => {
return {
name: "html-transform",
enforce: "post" as const,
transformIndexHtml(html) {
return mode === "production" ? html.replaceAll("/{{ .Base }}/", "{{ .Base }}/") : html;
},
};
};
+2 -2
View File
@@ -144,7 +144,7 @@ Connection: keep-alive
Content-Type: text/event-stream
X-Accel-Buffering: no
data: {"m":"INFO Testing logs...","ts":0,"id":4256192898}
data: {"m":"INFO Testing logs...","ts":0,"id":4256192898,"l":"info"}
event: container-stopped
data: end of stream
@@ -170,7 +170,7 @@ Connection: keep-alive
Content-Type: text/event-stream
X-Accel-Buffering: no
data: {"m":"INFO Testing logs...","ts":1589396137772,"id":1469707724}
data: {"m":"INFO Testing logs...","ts":1589396137772,"id":1469707724,"l":"info"}
id: 1589396137772
event: container-stopped
+12 -44
View File
@@ -5,13 +5,11 @@ import (
"compress/gzip"
"context"
"encoding/json"
"hash/fnv"
"fmt"
"io"
"net/http"
"runtime"
"strings"
"time"
@@ -48,35 +46,6 @@ func (h *handler) downloadLogs(w http.ResponseWriter, r *http.Request) {
io.Copy(zw, reader)
}
func logEventIterator(reader *bufio.Reader) func() (docker.LogEvent, error) {
return func() (docker.LogEvent, error) {
message, readerError := reader.ReadString('\n')
h := fnv.New32a()
h.Write([]byte(message))
logEvent := docker.LogEvent{Id: h.Sum32(), Message: message}
if index := strings.IndexAny(message, " "); index != -1 {
logId := message[:index]
if timestamp, err := time.Parse(time.RFC3339Nano, logId); err == nil {
logEvent.Timestamp = timestamp.UnixMilli()
message = strings.TrimSuffix(message[index+1:], "\n")
logEvent.Message = message
if strings.HasPrefix(message, "{") && strings.HasSuffix(message, "}") {
var data map[string]interface{}
if err := json.Unmarshal([]byte(message), &data); err != nil {
log.Errorf("json unmarshal error while streaming %v", err.Error())
}
logEvent.Message = data
}
}
}
return logEvent, readerError
}
}
func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/ld+json; charset=UTF-8")
@@ -93,10 +62,10 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request)
}
buffered := bufio.NewReader(reader)
eventIterator := logEventIterator(buffered)
iterator := docker.NewEventIterator(buffered)
for {
logEvent, readerError := eventIterator()
logEvent, readerError := iterator.Next()
if readerError != nil {
break
}
@@ -136,7 +105,7 @@ func (h *handler) streamLogs(w http.ResponseWriter, r *http.Request) {
lastEventId = r.URL.Query().Get("lastEventId")
}
reader, err := h.client.ContainerLogs(r.Context(), container.ID, h.config.TailSize, lastEventId)
reader, err := h.client.ContainerLogs(r.Context(), container.ID, lastEventId)
if err != nil {
if err == io.EOF {
fmt.Fprintf(w, "event: container-stopped\ndata: end of stream\n\n")
@@ -164,13 +133,14 @@ func (h *handler) streamLogs(w http.ResponseWriter, r *http.Request) {
}()
buffered := bufio.NewReader(reader)
var readerError error
eventIterator := logEventIterator(buffered)
iterator := docker.NewEventIterator(buffered)
for {
var logEvent docker.LogEvent
logEvent, readerError = eventIterator()
logEvent, err := iterator.Next()
if err != nil {
break
}
if buf, err := json.Marshal(logEvent); err != nil {
log.Errorf("json encoding error while streaming %v", err.Error())
} else {
@@ -181,19 +151,17 @@ func (h *handler) streamLogs(w http.ResponseWriter, r *http.Request) {
}
fmt.Fprintf(w, "\n")
f.Flush()
if readerError != nil {
break
}
}
log.Debugf("streaming stopped: %v", container.ID)
if readerError == io.EOF {
if iterator.LastError() == io.EOF {
log.Debugf("container stopped: %v", container.ID)
fmt.Fprintf(w, "event: container-stopped\ndata: end of stream\n\n")
f.Flush()
} else if readerError != context.Canceled {
log.Errorf("unknown error while streaming %v", readerError.Error())
} else if iterator.LastError() != context.Canceled {
log.Errorf("unknown error while streaming %v", iterator.LastError().Error())
}
log.WithField("routines", runtime.NumGoroutine()).Debug("runtime goroutine stats")
+32 -6
View File
@@ -6,8 +6,10 @@ import (
"io/fs"
"io/ioutil"
"net/http"
"os"
"path"
"github.com/amir20/dozzle/analytics"
"github.com/amir20/dozzle/docker"
"github.com/gorilla/mux"
@@ -16,12 +18,13 @@ import (
// Config is a struct for configuring the web service
type Config struct {
Base string
Addr string
Version string
TailSize int
Username string
Password string
Base string
Addr string
Version string
Username string
Password string
Hostname string
NoAnalytics bool
}
type handler struct {
@@ -78,6 +81,27 @@ 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)
if !h.config.NoAnalytics {
go func() {
host, _ := os.Hostname()
if containers, err := h.client.ListContainers(); err == nil {
totalContainers := len(containers)
runningContainers := 0
for _, container := range containers {
if container.State == "running" {
runningContainers++
}
}
re := analytics.RequestEvent{
ClientId: host,
TotalContainers: totalContainers,
RunningContainers: runningContainers,
}
analytics.SendRequestEvent(re)
}
}()
}
} else {
if !isAuthorized(req) && req.URL.Path != "login" {
http.Redirect(w, req, path.Clean(h.config.Base+"/login"), http.StatusTemporaryRedirect)
@@ -111,11 +135,13 @@ func (h *handler) executeTemplate(w http.ResponseWriter, req *http.Request) {
Version string
AuthorizationNeeded bool
Secured bool
Hostname string
}{
path,
h.config.Version,
h.isAuthorizationNeeded(req),
secured,
h.config.Hostname,
}
err = tmpl.Execute(w, data)
if err != nil {
+17 -16
View File
@@ -41,8 +41,8 @@ func (m *MockedClient) ListContainers() ([]docker.Container, error) {
return args.Get(0).([]docker.Container), args.Error(1)
}
func (m *MockedClient) ContainerLogs(ctx context.Context, id string, tailSize int, since string) (io.ReadCloser, error) {
args := m.Called(ctx, id, tailSize)
func (m *MockedClient) ContainerLogs(ctx context.Context, id string, since string) (io.ReadCloser, error) {
args := m.Called(ctx, id, since)
return args.Get(0).(io.ReadCloser), args.Error(1)
}
@@ -75,9 +75,9 @@ func Test_handler_streamLogs_happy(t *testing.T) {
mockedClient := new(MockedClient)
reader := ioutil.NopCloser(strings.NewReader("INFO Testing logs..."))
mockedClient.On("FindContainer", id).Return(docker.Container{ID: id}, nil)
mockedClient.On("ContainerLogs", mock.Anything, mock.Anything, 300).Return(reader, nil)
mockedClient.On("ContainerLogs", mock.Anything, mock.Anything, "").Return(reader, nil)
h := handler{client: mockedClient, config: &Config{TailSize: 300}}
h := handler{client: mockedClient, config: &Config{}}
handler := http.HandlerFunc(h.streamLogs)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
@@ -96,9 +96,9 @@ func Test_handler_streamLogs_happy_with_id(t *testing.T) {
mockedClient := new(MockedClient)
reader := ioutil.NopCloser(strings.NewReader("2020-05-13T18:55:37.772853839Z INFO Testing logs..."))
mockedClient.On("FindContainer", id).Return(docker.Container{ID: id}, nil)
mockedClient.On("ContainerLogs", mock.Anything, mock.Anything, 300).Return(reader, nil)
mockedClient.On("ContainerLogs", mock.Anything, mock.Anything, "").Return(reader, nil)
h := handler{client: mockedClient, config: &Config{TailSize: 300}}
h := handler{client: mockedClient, config: &Config{}}
handler := http.HandlerFunc(h.streamLogs)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
@@ -116,9 +116,9 @@ func Test_handler_streamLogs_happy_container_stopped(t *testing.T) {
mockedClient := new(MockedClient)
mockedClient.On("FindContainer", id).Return(docker.Container{ID: id}, nil)
mockedClient.On("ContainerLogs", mock.Anything, id, 300).Return(ioutil.NopCloser(strings.NewReader("")), io.EOF)
mockedClient.On("ContainerLogs", mock.Anything, id, "").Return(ioutil.NopCloser(strings.NewReader("")), io.EOF)
h := handler{client: mockedClient, config: &Config{TailSize: 300}}
h := handler{client: mockedClient, config: &Config{}}
handler := http.HandlerFunc(h.streamLogs)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
@@ -137,7 +137,7 @@ func Test_handler_streamLogs_error_finding_container(t *testing.T) {
mockedClient := new(MockedClient)
mockedClient.On("FindContainer", id).Return(docker.Container{}, errors.New("error finding container"))
h := handler{client: mockedClient, config: &Config{TailSize: 300}}
h := handler{client: mockedClient, config: &Config{}}
handler := http.HandlerFunc(h.streamLogs)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
@@ -155,9 +155,9 @@ func Test_handler_streamLogs_error_reading(t *testing.T) {
mockedClient := new(MockedClient)
mockedClient.On("FindContainer", id).Return(docker.Container{ID: id}, nil)
mockedClient.On("ContainerLogs", mock.Anything, id, 300).Return(ioutil.NopCloser(strings.NewReader("")), errors.New("test error"))
mockedClient.On("ContainerLogs", mock.Anything, id, "").Return(ioutil.NopCloser(strings.NewReader("")), errors.New("test error"))
h := handler{client: mockedClient, config: &Config{TailSize: 300}}
h := handler{client: mockedClient, config: &Config{}}
handler := http.HandlerFunc(h.streamLogs)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
@@ -186,7 +186,7 @@ func Test_handler_streamEvents_happy(t *testing.T) {
close(messages)
}()
h := handler{client: mockedClient, config: &Config{TailSize: 300}}
h := handler{client: mockedClient, config: &Config{}}
handler := http.HandlerFunc(h.streamEvents)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
@@ -208,7 +208,7 @@ func Test_handler_streamEvents_error(t *testing.T) {
close(messages)
}()
h := handler{client: mockedClient, config: &Config{TailSize: 300}}
h := handler{client: mockedClient, config: &Config{}}
handler := http.HandlerFunc(h.streamEvents)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
@@ -234,7 +234,7 @@ func Test_handler_streamEvents_error_request(t *testing.T) {
cancel()
}()
h := handler{client: mockedClient, config: &Config{TailSize: 300}}
h := handler{client: mockedClient, config: &Config{}}
handler := http.HandlerFunc(h.streamEvents)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
@@ -397,7 +397,7 @@ func Test_createRoutes_username_password_login_failed(t *testing.T) {
func Test_createRoutes_username_password_valid_session(t *testing.T) {
mockedClient := new(MockedClient)
mockedClient.On("FindContainer", "123").Return(docker.Container{ID: "123"}, nil)
mockedClient.On("ContainerLogs", mock.Anything, "123", 0).Return(ioutil.NopCloser(strings.NewReader("test data")), io.EOF)
mockedClient.On("ContainerLogs", mock.Anything, "123", "").Return(ioutil.NopCloser(strings.NewReader("test data")), io.EOF)
handler := createHandler(mockedClient, nil, Config{Base: "/", Username: "amir", Password: "password"})
// Get cookie first
@@ -421,7 +421,7 @@ func Test_createRoutes_username_password_valid_session(t *testing.T) {
func Test_createRoutes_username_password_invalid_session(t *testing.T) {
mockedClient := new(MockedClient)
mockedClient.On("FindContainer", "123").Return(docker.Container{ID: "123"}, nil)
mockedClient.On("ContainerLogs", mock.Anything, "123", 0).Return(ioutil.NopCloser(strings.NewReader("test data")), io.EOF)
mockedClient.On("ContainerLogs", mock.Anything, "since").Return(ioutil.NopCloser(strings.NewReader("test data")), io.EOF)
handler := createHandler(mockedClient, nil, Config{Base: "/", Username: "amir", Password: "password"})
req, err := http.NewRequest("GET", "/api/logs/stream?id=123", nil)
require.NoError(t, err, "NewRequest should not return an error.")
@@ -434,6 +434,7 @@ func Test_createRoutes_username_password_invalid_session(t *testing.T) {
func createHandler(client docker.Client, content fs.FS, config Config) *mux.Router {
if client == nil {
client = new(MockedClient)
client.(*MockedClient).On("ListContainers").Return([]docker.Container{}, nil)
}
if content == nil {