Compare commits

...

97 Commits

Author SHA1 Message Date
Amir Raminfar
d57ea5233c Release 3.5.11 2021-04-13 18:51:27 -07:00
Amir Raminfar
90afa43cef Fixes TAG 2021-04-13 18:51:20 -07:00
Amir Raminfar
aac9905099 Release 3.5.10 2021-04-13 12:34:39 -07:00
Amir Raminfar
07723353d0 Adds labels 2021-04-13 12:25:06 -07:00
Amir Raminfar
c6a3b8f61e Release 3.5.9 2021-04-13 12:09:47 -07:00
Amir Raminfar
3db60e3d50 Adds QEMU 2021-04-13 12:09:40 -07:00
Amir Raminfar
b8d428f5b3 Release 3.5.8 2021-04-13 12:02:48 -07:00
Amir Raminfar
abca733e63 Removes context 2021-04-13 12:02:41 -07:00
Amir Raminfar
6bc133a643 Release 3.5.7 2021-04-13 11:55:36 -07:00
Amir Raminfar
280413c085 Uses local cache instead 2021-04-13 11:55:28 -07:00
Amir Raminfar
0bf265b001 Release 3.5.6 2021-04-13 11:48:29 -07:00
Amir Raminfar
c65d1f18ae Adds cache 2021-04-13 11:48:21 -07:00
Amir Raminfar
eb8f492ffe Release 3.5.5 2021-04-13 11:35:33 -07:00
Amir Raminfar
5358af5005 Adds setup buildx 2021-04-13 11:35:23 -07:00
Amir Raminfar
5569c7f5b4 Release 3.5.4 2021-04-13 11:29:48 -07:00
Amir Raminfar
5caf5eebb8 Uses docker/build-push-action (#1149) 2021-04-13 11:29:09 -07:00
kodiakhq[bot]
8ffe95b4ab Merge pull request #1148 from amir20/dependabot/go_modules/github.com/docker/docker-20.10.6incompatible
Bump github.com/docker/docker from 20.10.5+incompatible to 20.10.6+incompatible
2021-04-13 08:10:59 +00:00
dependabot[bot]
cca6b0971b Bump github.com/docker/docker
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 20.10.5+incompatible to 20.10.6+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.5...v20.10.6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-13 08:06:09 +00:00
kodiakhq[bot]
0b45972692 Merge pull request #1147 from amir20/dependabot/npm_and_yarn/date-fns-2.20.2
Bump date-fns from 2.20.1 to 2.20.2
2021-04-13 05:09:40 +00:00
kodiakhq[bot]
84d626c75b Merge pull request #1146 from amir20/dependabot/npm_and_yarn/webpack-5.32.0
Bump webpack from 5.31.2 to 5.32.0
2021-04-13 05:08:31 +00:00
dependabot[bot]
380e65641e Bump date-fns from 2.20.1 to 2.20.2
Bumps [date-fns](https://github.com/date-fns/date-fns) from 2.20.1 to 2.20.2.
- [Release notes](https://github.com/date-fns/date-fns/releases)
- [Changelog](https://github.com/date-fns/date-fns/blob/master/CHANGELOG.md)
- [Commits](https://github.com/date-fns/date-fns/compare/v2.20.1...v2.20.2)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-13 05:05:26 +00:00
dependabot[bot]
49a2bd918a Bump webpack from 5.31.2 to 5.32.0
Bumps [webpack](https://github.com/webpack/webpack) from 5.31.2 to 5.32.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.31.2...v5.32.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-13 05:04:36 +00:00
Amir Raminfar
e35734d73a Adds more tests 2021-04-12 14:45:46 -07:00
kodiakhq[bot]
9103124bfa Merge pull request #1144 from amir20/dependabot/npm_and_yarn/vue/test-utils-1.1.4
Bump @vue/test-utils from 1.1.3 to 1.1.4
2021-04-12 05:09:24 +00:00
kodiakhq[bot]
c440aaf1a1 Merge pull request #1143 from amir20/dependabot/npm_and_yarn/postcss-8.2.10
Bump postcss from 8.2.9 to 8.2.10
2021-04-12 05:09:17 +00:00
kodiakhq[bot]
aaf3cd84f0 Merge pull request #1142 from amir20/dependabot/npm_and_yarn/release-it-14.6.1
Bump release-it from 14.6.0 to 14.6.1
2021-04-12 05:09:05 +00:00
dependabot[bot]
cc1e7d0cbe Bump @vue/test-utils from 1.1.3 to 1.1.4
Bumps [@vue/test-utils](https://github.com/vuejs/vue-test-utils/tree/HEAD/packages/test-utils) from 1.1.3 to 1.1.4.
- [Release notes](https://github.com/vuejs/vue-test-utils/releases)
- [Changelog](https://github.com/vuejs/vue-test-utils/blob/dev/packages/test-utils/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vue-test-utils/commits/v1.1.4/packages/test-utils)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-12 05:05:26 +00:00
dependabot[bot]
4012145a3e Bump postcss from 8.2.9 to 8.2.10
Bumps [postcss](https://github.com/postcss/postcss) from 8.2.9 to 8.2.10.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.2.9...8.2.10)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-12 05:05:03 +00:00
dependabot[bot]
b3cfec8402 Bump release-it from 14.6.0 to 14.6.1
Bumps [release-it](https://github.com/release-it/release-it) from 14.6.0 to 14.6.1.
- [Release notes](https://github.com/release-it/release-it/releases)
- [Changelog](https://github.com/release-it/release-it/blob/master/CHANGELOG.md)
- [Commits](https://github.com/release-it/release-it/compare/14.6.0...14.6.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-12 05:04:39 +00:00
Amir Raminfar
3a555a8b04 Release 3.5.3 2021-04-11 14:57:21 -07:00
Amir Raminfar
99a023e3b1 Fixes int test snapshots 2021-04-11 14:52:46 -07:00
Amir Raminfar
f239d077a2 Adds more tests for auth 2021-04-11 14:46:59 -07:00
Amir Raminfar
f822bd5ccd Updates snapshot 2021-04-11 14:13:15 -07:00
Amir Raminfar
3c96572fb0 Fixes authrization bug 2021-04-11 14:12:37 -07:00
Amir Raminfar
4daccddab5 Adds more js tests 2021-04-11 14:02:56 -07:00
Amir Raminfar
41415e6731 Cleans up imports 2021-04-11 13:53:25 -07:00
Amir Raminfar
2bbbb5f7a5 Fixes security error with invalid session 2021-04-11 13:53:16 -07:00
Amir Raminfar
c89e70d697 Fixes #1137 by adding margin 0 2021-04-11 13:43:52 -07:00
kodiakhq[bot]
cbba0fa0ad Merge pull request #1138 from amir20/dependabot/npm_and_yarn/css-loader-5.2.1
Bump css-loader from 5.2.0 to 5.2.1
2021-04-11 20:35:24 +00:00
kodiakhq[bot]
a60634ab1b Merge pull request #1139 from amir20/dependabot/npm_and_yarn/release-it-14.6.0
Bump release-it from 14.5.1 to 14.6.0
2021-04-11 20:35:15 +00:00
kodiakhq[bot]
0a9072cf53 Merge pull request #1140 from amir20/dependabot/npm_and_yarn/date-fns-2.20.1
Bump date-fns from 2.20.0 to 2.20.1
2021-04-11 20:35:04 +00:00
kodiakhq[bot]
dfaea1c95a Merge pull request #1141 from amir20/dependabot/npm_and_yarn/webpack-5.31.2
Bump webpack from 5.31.0 to 5.31.2
2021-04-11 20:34:54 +00:00
Amir Raminfar
a331b6a20b Configures kodiak 2021-04-11 13:34:39 -07:00
Amir Raminfar
3d710118e1 Merge branch 'master' into dependabot/npm_and_yarn/css-loader-5.2.1 2021-04-10 21:18:18 -07:00
Amir Raminfar
be24567d74 Merge branch 'master' into dependabot/npm_and_yarn/release-it-14.6.0 2021-04-10 21:18:10 -07:00
Amir Raminfar
cf7594af38 Merge branch 'master' into dependabot/npm_and_yarn/date-fns-2.20.1 2021-04-10 21:18:03 -07:00
Amir Raminfar
0527d58959 Merge branch 'master' into dependabot/npm_and_yarn/webpack-5.31.2 2021-04-10 21:17:51 -07:00
dependabot[bot]
36984d0787 Bump webpack from 5.31.0 to 5.31.2
Bumps [webpack](https://github.com/webpack/webpack) from 5.31.0 to 5.31.2.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.31.0...v5.31.2)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-11 00:32:21 +00:00
dependabot[bot]
f6ec3126a4 Bump date-fns from 2.20.0 to 2.20.1
Bumps [date-fns](https://github.com/date-fns/date-fns) from 2.20.0 to 2.20.1.
- [Release notes](https://github.com/date-fns/date-fns/releases)
- [Changelog](https://github.com/date-fns/date-fns/blob/master/CHANGELOG.md)
- [Commits](https://github.com/date-fns/date-fns/compare/v2.20.0...v2.20.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-11 00:32:01 +00:00
dependabot[bot]
767fc4fde3 Bump release-it from 14.5.1 to 14.6.0
Bumps [release-it](https://github.com/release-it/release-it) from 14.5.1 to 14.6.0.
- [Release notes](https://github.com/release-it/release-it/releases)
- [Changelog](https://github.com/release-it/release-it/blob/master/CHANGELOG.md)
- [Commits](https://github.com/release-it/release-it/compare/14.5.1...14.6.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-11 00:31:48 +00:00
dependabot[bot]
137dc490ce Bump css-loader from 5.2.0 to 5.2.1
Bumps [css-loader](https://github.com/webpack-contrib/css-loader) from 5.2.0 to 5.2.1.
- [Release notes](https://github.com/webpack-contrib/css-loader/releases)
- [Changelog](https://github.com/webpack-contrib/css-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/css-loader/compare/v5.2.0...v5.2.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-11 00:31:36 +00:00
Amir Raminfar
4d7a682ecf Release 3.5.2 2021-04-10 17:29:59 -07:00
Amir Raminfar
7b4ca4e4fa Fixes tests for hourstyle 2021-04-10 17:14:28 -07:00
Amir Raminfar
232ec7d27a Adds seconds and fixes #1136 2021-04-10 17:07:30 -07:00
Amir Raminfar
bba34aa582 Minor clean up 2021-04-10 17:03:34 -07:00
github-actions[bot]
70a40cd7e4 Merge pull request #1133 from amir20/dependabot/npm_and_yarn/babel/plugin-transform-runtime-7.13.15 2021-04-09 21:42:28 +00:00
dependabot[bot]
b90f79449b Bump @babel/plugin-transform-runtime from 7.13.10 to 7.13.15
Bumps [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-transform-runtime) from 7.13.10 to 7.13.15.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.13.15/packages/babel-plugin-transform-runtime)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-09 21:34:34 +00:00
Amir Raminfar
8aea96d336 Release 3.5.1 2021-04-09 14:29:50 -07:00
Amir Raminfar
ed80bff954 Set maximum buffer size fixes #1135 2021-04-09 14:24:42 -07:00
github-actions[bot]
013aefa969 Merge pull request #1132 from amir20/dependabot/npm_and_yarn/babel/core-7.13.15 2021-04-09 17:09:57 +00:00
dependabot[bot]
986fe38254 Bump @babel/core from 7.13.14 to 7.13.15
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.13.14 to 7.13.15.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.13.15/packages/babel-core)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-09 17:05:29 +00:00
dependabot[bot]
9c9172a180 Bump caniuse-lite from 1.0.30001207 to 1.0.30001208 (#1134)
Bumps [caniuse-lite](https://github.com/ben-eb/caniuse-lite) from 1.0.30001207 to 1.0.30001208.
- [Release notes](https://github.com/ben-eb/caniuse-lite/releases)
- [Changelog](https://github.com/ben-eb/caniuse-lite/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ben-eb/caniuse-lite/compare/v1.0.30001207...v1.0.30001208)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-09 10:00:41 -07:00
Amir Raminfar
7f9fd2c80c Release 3.5.0 2021-04-08 16:32:40 -07:00
Amir Raminfar
c8202d1238 Fixes z-index bug #1091 2021-04-08 16:32:13 -07:00
Amir Raminfar
91b02bcfce Updates .reflex config 2021-04-08 16:17:54 -07:00
Amir Raminfar
46c4dde573 Adds auto focus to username 2021-04-08 16:14:50 -07:00
Amir Raminfar
52729fa980 Fixes mobile, colors and removes heading 2021-04-08 14:36:43 -07:00
dependabot[bot]
915cff92c6 Bump mini-css-extract-plugin from 1.4.0 to 1.4.1 (#1129)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-08 14:19:31 -07:00
dependabot[bot]
b591595285 Bump date-fns from 2.19.0 to 2.20.0 (#1130)
Bumps [date-fns](https://github.com/date-fns/date-fns) from 2.19.0 to 2.20.0.
- [Release notes](https://github.com/date-fns/date-fns/releases)
- [Changelog](https://github.com/date-fns/date-fns/blob/master/CHANGELOG.md)
- [Commits](https://github.com/date-fns/date-fns/compare/v2.19.0...v2.20.0)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-08 14:19:16 -07:00
dependabot[bot]
0486db9169 Bump buefy from 0.9.5 to 0.9.6 (#1131)
Bumps [buefy](https://github.com/buefy/buefy) from 0.9.5 to 0.9.6.
- [Release notes](https://github.com/buefy/buefy/releases)
- [Changelog](https://github.com/buefy/buefy/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/buefy/buefy/compare/v0.9.5...v0.9.6)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-08 14:19:05 -07:00
Amir Raminfar
4a1c699e93 Adds a new test 2021-04-08 10:22:34 -07:00
Amir Raminfar
83e4175fca Cleans up tests 2021-04-08 10:19:02 -07:00
Amir Raminfar
b8bec9d2f0 Updates makefile 2021-04-08 08:35:36 -07:00
Amir Raminfar
802744d132 Fixes relfex 2021-04-08 08:31:25 -07:00
Amir Raminfar
b4e0afdb2c Adds authentication as a feature with simple username and password configuration (#1127)
* Starting work for auth

* Adds flags

* Does redirect

* Refactors code

* Adds vue templates

* Completes logic

* Cleans up some of the error messages

* Refactors

* Updates readme

* Updates titles

* Adds logout

* Cleans up imports
2021-04-08 08:29:58 -07:00
github-actions[bot]
7b7fd11bc7 Merge pull request #1128 from amir20/dependabot/npm_and_yarn/webpack-5.31.0 2021-04-08 14:44:59 +00:00
dependabot[bot]
b8beb4eba7 Bump webpack from 5.30.0 to 5.31.0
Bumps [webpack](https://github.com/webpack/webpack) from 5.30.0 to 5.31.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.30.0...v5.31.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-08 14:34:22 +00:00
Amir Raminfar
e3e28e7e3d Delete codeql-analysis.yml 2021-04-08 07:29:47 -07:00
github-actions[bot]
509de3b89c Merge pull request #1125 from amir20/dependabot/github_actions/actions/setup-node-v2.1.5 2021-04-06 02:07:18 +00:00
github-actions[bot]
f3aef59740 Merge pull request #1126 from amir20/dependabot/github_actions/actions/setup-go-v2.1.3 2021-04-06 02:07:03 +00:00
dependabot[bot]
27d351f21c Bump actions/setup-go from v1 to v2.1.3
Bumps [actions/setup-go](https://github.com/actions/setup-go) from v1 to v2.1.3.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v1...37335c7bb261b353407cff977110895fa0b4f7d8)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-06 02:04:48 +00:00
dependabot[bot]
e98b482b73 Bump actions/setup-node from v1 to v2.1.5
Bumps [actions/setup-node](https://github.com/actions/setup-node) from v1 to v2.1.5.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v1...46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-06 02:04:45 +00:00
Amir Raminfar
70a4dc5734 Updates depndabot 2021-04-05 19:04:24 -07:00
github-actions[bot]
cda7318e45 Merge pull request #1124 from amir20/dependabot/go_modules/github.com/magiconair/properties-1.8.5 2021-04-05 19:02:49 +00:00
Amir Raminfar
6b098e1233 Adds automerge 2021-04-05 11:56:17 -07:00
dependabot[bot]
a6f28488ee Bump github.com/magiconair/properties from 1.8.4 to 1.8.5
Bumps [github.com/magiconair/properties](https://github.com/magiconair/properties) from 1.8.4 to 1.8.5.
- [Release notes](https://github.com/magiconair/properties/releases)
- [Changelog](https://github.com/magiconair/properties/blob/main/CHANGELOG.md)
- [Commits](https://github.com/magiconair/properties/compare/v1.8.4...v1.8.5)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-05 18:53:17 +00:00
Amir Raminfar
f4091ba95c Adds labels 2021-04-05 11:50:49 -07:00
dependabot-preview[bot]
d597f4d3f7 Create Dependabot config file (#1123)
Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2021-04-05 11:49:31 -07:00
dependabot-preview[bot]
f9bbf7081f Merge pull request #1122 from amir20/dependabot/npm_and_yarn/caniuse-lite-1.0.30001207 2021-04-05 05:49:31 +00:00
dependabot-preview[bot]
42f2028c29 Bump caniuse-lite from 1.0.30001205 to 1.0.30001207
Bumps [caniuse-lite](https://github.com/ben-eb/caniuse-lite) from 1.0.30001205 to 1.0.30001207.
- [Release notes](https://github.com/ben-eb/caniuse-lite/releases)
- [Changelog](https://github.com/ben-eb/caniuse-lite/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ben-eb/caniuse-lite/compare/v1.0.30001205...v1.0.30001207)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-04-05 05:44:30 +00:00
dependabot-preview[bot]
0b09d6c785 Merge pull request #1120 from amir20/dependabot/npm_and_yarn/integration/jest-image-snapshot-4.4.1 2021-04-02 13:23:26 +00:00
dependabot-preview[bot]
33430b2974 Merge pull request #1119 from amir20/dependabot/npm_and_yarn/release-it-14.5.1 2021-04-02 13:20:09 +00:00
dependabot-preview[bot]
ac64579bcd Bump jest-image-snapshot from 4.4.0 to 4.4.1 in /integration
Bumps [jest-image-snapshot](https://github.com/americanexpress/jest-image-snapshot) from 4.4.0 to 4.4.1.
- [Release notes](https://github.com/americanexpress/jest-image-snapshot/releases)
- [Changelog](https://github.com/americanexpress/jest-image-snapshot/blob/master/CHANGELOG.md)
- [Commits](https://github.com/americanexpress/jest-image-snapshot/compare/v4.4.0...v4.4.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-04-02 13:18:49 +00:00
dependabot-preview[bot]
dd7929ec59 Bump release-it from 14.5.0 to 14.5.1
Bumps [release-it](https://github.com/release-it/release-it) from 14.5.0 to 14.5.1.
- [Release notes](https://github.com/release-it/release-it/releases)
- [Changelog](https://github.com/release-it/release-it/blob/master/CHANGELOG.md)
- [Commits](https://github.com/release-it/release-it/compare/14.5.0...14.5.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-04-02 13:14:33 +00:00
Amir Raminfar
17fe73bd27 Updates readme 2021-04-01 19:40:42 -07:00
Amir Raminfar
a607c2d021 Release 3.4.8 2021-04-01 19:05:10 -07:00
Amir Raminfar
a840f19b20 Missed a font 2021-04-01 19:05:00 -07:00
46 changed files with 1123 additions and 620 deletions

40
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
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: "/integration"
labels:
- "npm"
- "dependencies"
- "automerge"
schedule:
interval: daily

View File

@@ -1,67 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '42 21 * * 0'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'go', 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -11,7 +11,7 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2
- name: Install Node
uses: actions/setup-node@v1
uses: actions/setup-node@v2.1.5
- name: Install dependencies
run: yarn
- name: Run Tests
@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v1
uses: actions/setup-go@v2.1.3
with:
go-version: 1.16.x
- name: Checkout code
@@ -43,19 +43,44 @@ jobs:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Docker meta
id: meta
uses: crazy-max/ghaction-docker-meta@v2
with:
images: amir20/dozzle
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
- name: Available platforms
run: echo ${{ steps.buildx.outputs.platforms }}
- name: Docker Login
run: docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
- name: Run Buildx
run: make publish
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Build and push
uses: docker/build-push-action@v2
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=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new
- # Temp fix
# https://github.com/docker/build-push-action/issues/252
# https://github.com/moby/buildkit/issues/1896
name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
git-release:
needs: [buildx]
name: Github Release
@@ -66,7 +91,7 @@ jobs:
with:
fetch-depth: 0
- name: Install Node
uses: actions/setup-node@v1
uses: actions/setup-node@v2.1.5
- name: Install dependencies
run: yarn
- name: Release to Github

View File

@@ -8,7 +8,7 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2
- name: Install Node
uses: actions/setup-node@v1
uses: actions/setup-node@v2.1.5
- name: Install dependencies
run: yarn
- name: Run Tests
@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v1
uses: actions/setup-go@v2.1.3
with:
go-version: 1.16.x
- name: Checkout code

1
.kodiak.toml Normal file
View File

@@ -0,0 +1 @@
version = 1

View File

@@ -1 +1 @@
-r '\.go$' -R 'node_modules' -R '^static/' -R '^.cache/' -G '*_test.go' -s -- go run main.go --level debug
-r '\.(go)$' -R 'node_modules' -G '*_test.go' -s -- go run main.go --level debug

View File

@@ -1,11 +1,3 @@
TAG := $(shell git describe --tags)
PLATFROMS := linux/amd64,linux/arm/v7,linux/arm64/v8
.PHONY: publish
publish:
docker buildx build --build-arg TAG=$(TAG) --platform $(PLATFROMS) -t amir20/dozzle:latest -t amir20/dozzle:$(TAG) --push .
.PHONY: clean
clean:
@rm -rf static
@@ -25,8 +17,18 @@ fake_static:
test: fake_static
go test -cover ./...
.PHONY: build
build: static
CGO_ENABLED=0 go build -ldflags "-s -w"
.PHONY: docker
docker:
@docker build -t amir20/dozzle .
.PHONY: dev
dev:
yarn dev
.PHONY: int
int:
docker-compose -f integration/docker-compose.test.yml up --build --force-recreate --exit-code-from integration

View File

@@ -1,32 +1,42 @@
# Dozzle - [dozzle.dev](https://dozzle.dev/)
Dozzle is a simple, lightweight application that provides you with a web based interface to monitor your Docker container logs live. It doesnt store log information, it is for live monitoring of your container logs only.
![Image](https://github.com/amir20/dozzle/blob/master/.github/demo.gif?raw=true)
[![Go Report Card](https://goreportcard.com/badge/github.com/amir20/dozzle)](https://goreportcard.com/report/github.com/amir20/dozzle)
[![Docker Pulls](https://img.shields.io/docker/pulls/amir20/dozzle.svg)](https://hub.docker.com/r/amir20/dozzle/)
[![Docker Size](https://images.microbadger.com/badges/image/amir20/dozzle.svg)](https://hub.docker.com/r/amir20/dozzle/)
[![Docker Version](https://images.microbadger.com/badges/version/amir20/dozzle.svg)](https://hub.docker.com/r/amir20/dozzle/)
![Test](https://github.com/amir20/dozzle/workflows/Test/badge.svg)
# Dozzle - [dozzle.dev](https://dozzle.dev/)
## Features
Dozzle is a simple, lightweight application that provides you with a web based interface to monitor your Docker container logs live. It doesnt store log information, it is for live monitoring of your container logs only.
- Intelligent fuzzy search for container names 🤖
- Search logs using regex 🔦
- Small memory footprint 🏎
- Split screen for viewing multiple logs
- Download logs easy
- Live stats with memory and CPU usage
- Authentication with username and password 🚨
While dozzle should work for most, it is not meant to be a full logging solution. For enterprise applications, products like [Loggly](https://www.loggly.com), [Papertrail](https://papertrailapp.com) or [Kibana](https://www.elastic.co/products/kibana) are more suited.
While Dozzle should work for most, it is not meant to be a full logging solution. For enterprise applications, products like [Loggly](https://www.loggly.com), [Papertrail](https://papertrailapp.com) or [Kibana](https://www.elastic.co/products/kibana) are more suited.
Dozzle doesn't cost any money. Dozzle aims to stay simple, small and free.
Dozzle won't cost any money and aims to focus only on real-time logs.
![Image](https://github.com/amir20/dozzle/blob/master/.github/demo.gif?raw=true)
## Getting dozzle
## Getting Dozzle
Dozzle is a very small Docker container (4 MB compressed). Pull the latest release from the index:
$ docker pull amir20/dozzle:latest
## Using dozzle
## Using Dozzle
The simplest way to use dozzle is to run the docker container. Also, mount the Docker Unix socket with `--volume` to `/var/run/docker.sock`:
$ docker run --name dozzle -d --volume=/var/run/docker.sock:/var/run/docker.sock -p 8888:8080 amir20/dozzle:latest
dozzle will be available at [http://localhost:8888/](http://localhost:8888/). You can change `-p 8888:8080` to any port. For example, if you want to view dozzle over port 4040 then you would do `-p 4040:8080`.
Dozzle will be available at [http://localhost:8888/](http://localhost:8888/). You can change `-p 8888:8080` to any port. For example, if you want to view dozzle over port 4040 then you would do `-p 4040:8080`.
### With Docker swarm
@@ -51,7 +61,7 @@ dozzle will be available at [http://localhost:8888/](http://localhost:8888/). Yo
#### Security
dozzle doesn't support authentication out of the box. You can control the device dozzle binds to by passing `--addr` parameter. For example,
You can control the device Dozzle binds to by passing `--addr` parameter. For example,
$ docker run --volume=/var/run/docker.sock:/var/run/docker.sock -p 8888:1224 amir20/dozzle:latest --addr localhost:1224
@@ -63,14 +73,16 @@ If you wish to restrict the containers shown you can pass the `--filter` paramet
this would then only allow you to view containers with a name starting with "foo". You can use other filters like `status` as well, please check the official docker [command line docs](https://docs.docker.com/engine/reference/commandline/ps/#filtering) for available filters.
Dozzle supports very simple authentication out of the box with username and password. You should deploy using SSL to keep the credentials safe.
#### Changing base URL
dozzle by default mounts to "/". If you want to control the base path you can use the `--base` option. For example, if you want to mount at "/foobar",
Dozzle by default mounts to "/". If you want to control the base path you can use the `--base` option. For example, if you want to mount at "/foobar",
then you can override by using `--base /foobar`. See env variables below for using `DOZZLE_BASE` to change this.
$ docker run --volume=/var/run/docker.sock:/var/run/docker.sock -p 8080:8080 amir20/dozzle:latest --base /foobar
dozzle will be available at [http://localhost:8080/foobar/](http://localhost:8080/foobar/).
Dozzle will be available at [http://localhost:8080/foobar/](http://localhost:8080/foobar/).
#### Environment variables and configuration
@@ -84,15 +96,20 @@ Dozzle follows the [12-factor](https://12factor.net/) model. Configurations can
| n/a | `DOCKER_API_VERSION` | not set |
| `--tailSize` | `DOZZLE_TAILSIZE` | `300` |
| `--filter` | `DOZZLE_FILTER` | `""` |
| `--username` | `DOZZLE_USERNAME` | `""` |
| `--password` | `DOZZLE_PASSWORD` | `""` |
| `--key` | `DOZZLE_KEY` | `""` |
Note: When using username and password `DOZZLE_KEY` is required for session management.
## Troubleshooting and FAQs
<details>
<summary>I installed Dozzle, but logs are slow or they never load. Help!</summary>
Dozzle uses Server Sent Events (SSE) which connects to a server using a HTTP stream without closing the connection. If any proxy tries to buffer this connection, then Dozzle never receives the data and hangs forever waiting for the reverse proxy to flush the buffer. Since version `1.23.0`, Dozzle sends the `X-Accel-Buffering: no` header which should stop reverse proxies buffering. However, some proxies may ignore this header. In those cases, you need to explicitly disable any buffering.
Dozzle uses Server Sent Events (SSE) which connects to a server using a HTTP stream without closing the connection. If any proxy tries to buffer this connection, then Dozzle never receives the data and hangs forever waiting for the reverse proxy to flush the buffer. Since version `1.23.0`, Dozzle sends the `X-Accel-Buffering: no` header which should stop reverse proxies buffering. However, some proxies may ignore this header. In those cases, you need to explicitly disable any buffering.
Below is an example with nginx and using `proxy_pass` to disable buffering.
Below is an example with nginx and using `proxy_pass` to disable buffering.
```
server {
@@ -111,20 +128,21 @@ Dozzle follows the [12-factor](https://12factor.net/) model. Configurations can
}
```
</details>
<details>
<summary>What data does Dozzle collect?</summary>
Dozzle does not collect any metrics or analytics. Dozzle has a [strict](https://github.com/amir20/dozzle/blob/master/routes.go#L33-L38) Content Security Policy which only allows the following policies:
Dozzle does not collect any metrics or analytics. Dozzle has a [strict](https://github.com/amir20/dozzle/blob/master/routes.go#L33-L38) Content Security Policy which only allows the following policies:
- Allow connect to `api.github.com` to fetch most recent version.
- Only allow `<script>` and `<style>` files from `self`
- Allow connect to `api.github.com` to fetch most recent version.
- Only allow `<script>` and `<style>` files from `self`
Dozzle opens all links with `rel="noopener"`.
Dozzle opens all links with `rel="noopener"`.
</details>
## License
[MIT](LICENSE)

View File

@@ -1,9 +1,9 @@
<template>
<main>
<mobile-menu v-if="isMobile"></mobile-menu>
<mobile-menu v-if="isMobile && !authorizationNeeded"></mobile-menu>
<splitpanes @resized="onResized($event)">
<pane min-size="10" :size="settings.menuWidth" v-if="!isMobile && !collapseNav">
<pane min-size="10" :size="settings.menuWidth" v-if="!authorizationNeeded && !isMobile && !collapseNav">
<side-menu @search="showFuzzySearch"></side-menu>
</pane>
<pane min-size="10">
@@ -11,15 +11,17 @@
<pane class="has-min-height router-view">
<router-view></router-view>
</pane>
<pane v-for="other in activeContainers" :key="other.id" v-if="!isMobile">
<log-container
:id="other.id"
show-title
scrollable
closable
@close="removeActiveContainer(other)"
></log-container>
</pane>
<template v-if="!isMobile">
<pane v-for="other in activeContainers" :key="other.id">
<log-container
:id="other.id"
show-title
scrollable
closable
@close="removeActiveContainer(other)"
></log-container>
</pane>
</template>
</splitpanes>
</pane>
</splitpanes>
@@ -28,7 +30,7 @@
class="button is-small is-rounded is-settings-control"
:class="{ collapsed: collapseNav }"
id="hide-nav"
v-if="!isMobile"
v-if="!isMobile && !authorizationNeeded"
>
<span class="icon">
<icon :name="collapseNav ? 'chevron-right' : 'chevron-left'"></icon>
@@ -106,7 +108,7 @@ export default {
},
},
computed: {
...mapState(["isMobile", "settings", "containers"]),
...mapState(["isMobile", "settings", "containers", "authorizationNeeded"]),
...mapGetters(["visibleContainers", "activeContainers"]),
hasSmallerScrollbars() {
return this.settings.smallerScrollbars;

View File

@@ -27,13 +27,13 @@ describe("<LogEventSource />", () => {
debounce.mockClear();
});
function createLogEventSource(searchFilter = null) {
function createLogEventSource({ hourStyle = "auto", searchFilter = null } = {}) {
const localVue = createLocalVue();
localVue.use(Vuex);
localVue.component("log-viewer", LogViewer);
const state = { searchFilter, settings: { size: "medium", showTimestamp: true } };
const state = { searchFilter, settings: { size: "medium", showTimestamp: true, hourStyle } };
const store = new Vuex.Store({
state,
@@ -144,7 +144,7 @@ describe("<LogEventSource />", () => {
await wrapper.vm.$nextTick();
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
<ul class="events medium">
<li class=""><span class="date"><time datetime="2019-06-12T10:55:42.459Z">today at 10:55 AM</time></span> <span class="text"> "This is a message."</span></li>
<li class=""><span class="date"><time datetime="2019-06-12T10:55:42.459Z">today at 10:55:42 AM</time></span> <span class="text"> "This is a message."</span></li>
</ul>
`);
});
@@ -159,7 +159,7 @@ describe("<LogEventSource />", () => {
await wrapper.vm.$nextTick();
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
<ul class="events medium">
<li class=""><span class="date"><time datetime="2019-06-12T10:55:42.459Z">today at 10:55 AM</time></span> <span class="text"> <span style="color:#000">black<span style="color:#AAA">white</span></span></span></li>
<li class=""><span class="date"><time datetime="2019-06-12T10:55:42.459Z">today at 10:55:42 AM</time></span> <span class="text"> <span style="color:#000">black<span style="color:#AAA">white</span></span></span></li>
</ul>
`);
});
@@ -174,13 +174,43 @@ describe("<LogEventSource />", () => {
await wrapper.vm.$nextTick();
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
<ul class="events medium">
<li class=""><span class="date"><time datetime="2019-06-12T10:55:42.459Z">today at 10:55 AM</time></span> <span class="text"> &lt;test&gt;foo bar&lt;/test&gt;</span></li>
<li class=""><span class="date"><time datetime="2019-06-12T10:55:42.459Z">today at 10:55:42 AM</time></span> <span class="text"> &lt;test&gt;foo bar&lt;/test&gt;</span></li>
</ul>
`);
});
test("should render dates with 12 hour style", async () => {
const wrapper = createLogEventSource({ hourStyle: "12" });
sources["/api/logs/stream?id=abc"].emitOpen();
sources["/api/logs/stream?id=abc"].emitMessage({
data: `2019-06-12T23:55:42.459034602Z <test>foo bar</test>`,
});
await wrapper.vm.$nextTick();
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
<ul class="events medium">
<li class=""><span class="date"><time datetime="2019-06-12T23:55:42.459Z">today at 11:55:42 PM</time></span> <span class="text"> &lt;test&gt;foo bar&lt;/test&gt;</span></li>
</ul>
`);
});
test("should render dates with 24 hour style", async () => {
const wrapper = createLogEventSource({ hourStyle: "24" });
sources["/api/logs/stream?id=abc"].emitOpen();
sources["/api/logs/stream?id=abc"].emitMessage({
data: `2019-06-12T23:55:42.459034602Z <test>foo bar</test>`,
});
await wrapper.vm.$nextTick();
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
<ul class="events medium">
<li class=""><span class="date"><time datetime="2019-06-12T23:55:42.459Z">today at 23:55:42</time></span> <span class="text"> &lt;test&gt;foo bar&lt;/test&gt;</span></li>
</ul>
`);
});
test("should render messages with filter", async () => {
const wrapper = createLogEventSource("test");
const wrapper = createLogEventSource({ searchFilter: "test" });
sources["/api/logs/stream?id=abc"].emitOpen();
sources["/api/logs/stream?id=abc"].emitMessage({
data: `2019-06-11T10:55:42.459034602Z Foo bar`,
@@ -192,7 +222,7 @@ describe("<LogEventSource />", () => {
await wrapper.vm.$nextTick();
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
<ul class="events medium">
<li class=""><span class="date"><time datetime="2019-06-12T10:55:42.459Z">today at 10:55 AM</time></span> <span class="text"> This is a <mark>test</mark> &lt;hi&gt;&lt;/hi&gt;</span></li>
<li class=""><span class="date"><time datetime="2019-06-12T10:55:42.459Z">today at 10:55:42 AM</time></span> <span class="text"> This is a <mark>test</mark> &lt;hi&gt;&lt;/hi&gt;</span></li>
</ul>
`);
});

View File

@@ -30,7 +30,7 @@ export default {
methods: {
loadLogs(id) {
this.reset();
this.es = new EventSource(`${config.base}/api/logs/stream?id=${this.id}`);
this.es = new EventSource(`${config.base}/api/logs/stream?id=${id}`);
this.es.addEventListener("container-stopped", (e) => {
this.es.close();
this.buffer.push({ event: "container-stopped", message: "Container stopped", date: new Date() });

View File

@@ -7,7 +7,7 @@
</ul>
</template>
<script>
import { mapActions, mapGetters, mapState } from "vuex";
import { mapState } from "vuex";
import AnsiConvertor from "ansi-to-html";
import DOMPurify from "dompurify";
import RelativeTime from "./RelativeTime";

View File

@@ -70,7 +70,7 @@ aside {
left: 0;
right: 0;
background: var(--scheme-main-ter);
z-index: 2;
z-index: 10;
max-height: 100vh;
overflow: auto;

View File

@@ -3,9 +3,10 @@
</template>
<script>
import { mapActions, mapState } from "vuex";
import { mapState } from "vuex";
import { formatRelative } from "date-fns";
import { enGB, enUS } from "date-fns/locale";
import enGB from "date-fns/locale/en-GB";
import enUS from "date-fns/locale/en-US";
const use24Hr =
new Intl.DateTimeFormat(undefined, {
@@ -30,7 +31,14 @@ export default {
computed: {
...mapState(["settings"]),
locale() {
return styles[this.settings.hourStyle];
const locale = styles[this.settings.hourStyle];
const oldFormatter = locale.formatRelative;
return {
...locale,
formatRelative(token) {
return oldFormatter(token) + "p";
},
};
},
},
filters: {

View File

@@ -7,7 +7,9 @@
<script type="application/json" id="config__json">
{
"base": "{{ .Base }}",
"version": "{{ .Version }}"
"version": "{{ .Version }}",
"authorizationNeeded": "{{ .AuthorizationNeeded }}",
"secured": "{{ .Secured }}"
}
</script>
</head>

View File

@@ -10,7 +10,7 @@ import Autocomplete from "buefy/dist/esm/autocomplete";
import store from "./store";
import config from "./store/config";
import App from "./App.vue";
import { Container, Settings, Index, Show, ContainerNotFound, PageNotFound } from "./pages";
import { Container, Settings, Index, Show, ContainerNotFound, PageNotFound, Login } from "./pages";
Vue.use(VueRouter);
Vue.use(Meta);
@@ -47,6 +47,11 @@ const routes = [
component: Show,
name: "show",
},
{
path: "/login",
component: Login,
name: "login",
},
{
path: "/*",
component: PageNotFound,

View File

@@ -6,7 +6,7 @@
</template>
<script>
import { mapActions, mapGetters, mapState } from "vuex";
import { mapGetters } from "vuex";
import Search from "../components/Search";
import LogContainer from "../components/LogContainer";

View File

@@ -14,5 +14,10 @@
<script>
export default {
name: "ContainerNotFound",
metaInfo() {
return {
title: "Not Found",
};
},
};
</script>

View File

@@ -3,7 +3,14 @@
<section class="hero is-small mt-4">
<div class="hero-body">
<div class="container">
<h1 class="title">Hello, there!</h1>
<div class="columns">
<div class="column">
<h1 class="title">Hello, there!</h1>
</div>
<div class="column is-narrow" v-if="secured">
<a class="button is-primary is-small" :href="`${base}/logout`">Logout</a>
</div>
</div>
</div>
</div>
</section>
@@ -28,7 +35,7 @@
</div>
</section>
<section class="columns is-centered section">
<section class="columns is-centered section is-marginless">
<div class="column is-4">
<div class="panel">
<p class="panel-heading">Containers</p>
@@ -84,6 +91,8 @@ export default {
version: config.version,
search: null,
sort: "running",
secured: config.secured,
base: config.base,
};
},
methods: {

84
assets/pages/Login.vue Normal file
View File

@@ -0,0 +1,84 @@
<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">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">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">Username and password are not valid.</p>
</div>
<div class="field is-grouped is-grouped-centered mt-5">
<p class="control">
<button class="button is-primary" type="submit">Login</button>
</p>
</div>
</form>
</div>
</div>
</div>
</section>
</div>
</div>
</div>
</template>
<script>
import config from "../store/config";
export default {
name: "Login",
data() {
return {
username: null,
password: null,
error: false,
};
},
metaInfo() {
return {
title: "Authentication Required",
};
},
methods: {
async onLogin() {
const response = await fetch(`${config.base}/api/validateCredentials`, {
body: new FormData(this.$refs.form),
method: "post",
});
if (response.status == 200) {
this.error = false;
window.location.href = `${config.base}/`;
} else {
this.error = true;
}
},
},
};
</script>

View File

@@ -14,5 +14,10 @@
<script>
export default {
name: "PageNotFound",
metaInfo() {
return {
title: "404 Error",
};
},
};
</script>

View File

@@ -91,7 +91,6 @@
<script>
import gt from "semver/functions/gt";
import valid from "semver/functions/valid";
import { mapActions, mapState } from "vuex";
import Icon from "../components/Icon";
import config from "../store/config";

View File

@@ -4,3 +4,4 @@ export { default as Show } from "./Show.vue";
export { default as Container } from "./Container.vue";
export { default as Settings } from "./Settings.vue";
export { default as PageNotFound } from "./PageNotFound.vue";
export { default as Login } from "./Login.vue";

View File

@@ -2,8 +2,12 @@ const config = JSON.parse(document.querySelector("script#config__json").textCont
if (config.version == "{{ .Version }}") {
config.version = "dev";
config.base = "";
config.authorizationNeeded = false;
config.secured = false;
} else {
config.version = config.version.replace(/^v/, "");
config.authorizationNeeded = config.authorizationNeeded === "true";
config.secured = config.secured === "true";
}
export default config;

View File

@@ -16,6 +16,7 @@ const state = {
searchFilter: null,
isMobile: mql.matches,
settings: storage.get(DOZZLE_SETTINGS_KEY),
authorizationNeeded: config.authorizationNeeded,
};
const mutations = {
@@ -101,10 +102,12 @@ const getters = {
},
};
const es = new EventSource(`${config.base}/api/events/stream`);
es.addEventListener("containers-changed", (e) => store.commit("SET_CONTAINERS", JSON.parse(e.data)), false);
es.addEventListener("container-stat", (e) => store.dispatch("UPDATE_STATS", JSON.parse(e.data)), false);
es.addEventListener("container-die", (e) => store.dispatch("UPDATE_CONTAINER", JSON.parse(e.data)), false);
if (!config.authorizationNeeded) {
const es = new EventSource(`${config.base}/api/events/stream`);
es.addEventListener("containers-changed", (e) => store.commit("SET_CONTAINERS", JSON.parse(e.data)), false);
es.addEventListener("container-stat", (e) => store.dispatch("UPDATE_STATS", JSON.parse(e.data)), false);
es.addEventListener("container-die", (e) => store.dispatch("UPDATE_CONTAINER", JSON.parse(e.data)), false);
}
mql.addEventListener("change", (e) => store.commit("SET_MOBILE_WIDTH", e.matches));

View File

@@ -1,7 +1,7 @@
@charset "utf-8";
@import "~bulma/sass/utilities/initial-variables.sass";
$body-family: "Roboto", sans-serif;
$body-background-color: var(--body-background-color);
$scheme-main: var(--scheme-main);

5
go.mod
View File

@@ -5,7 +5,7 @@ require (
github.com/beme/abide v0.0.0-20190723115211-635a09831760
github.com/containerd/containerd v1.4.4 // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v20.10.5+incompatible
github.com/docker/docker v20.10.6+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
@@ -13,7 +13,8 @@ require (
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.1 // indirect
github.com/gorilla/mux v1.8.0
github.com/magiconair/properties v1.8.4
github.com/gorilla/sessions v1.2.1
github.com/magiconair/properties v1.8.5
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
github.com/morikuni/aec v1.0.0 // indirect

12
go.sum
View File

@@ -48,8 +48,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
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.5+incompatible h1:o5WL5onN4awYGwrW7+oTn5x9AF2prw7V0Ox8ZEkoCdg=
github.com/docker/docker v20.10.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v20.10.6+incompatible h1:oXI3Vas8TI8Eu/EjH4srKHJBVqraSzJybhxY7Om9faQ=
github.com/docker/docker v20.10.6+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=
@@ -112,6 +112,10 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
@@ -155,8 +159,8 @@ 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.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.4 h1:8KGKTcQQGm0Kv7vEbKFErAoAOFyyacLStRtQSeYtvkY=
github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

@@ -2319,9 +2319,9 @@ jest-haste-map@^26.6.2:
fsevents "^2.1.2"
jest-image-snapshot@^4.0.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/jest-image-snapshot/-/jest-image-snapshot-4.4.0.tgz#7c5282cc62280086b6cbcda203d50b6ee0b0e71e"
integrity sha512-lGlHxoEHLYL0qyr6E7SA04/eMT/PFWxHRNmhEJZpw80iE1XH/wSHEKiK4oDGJI5FQ4hhWPXmcH5tDlTvsAHogQ==
version "4.4.1"
resolved "https://registry.yarnpkg.com/jest-image-snapshot/-/jest-image-snapshot-4.4.1.tgz#1ecc83ce55de9a92661bc488b795300719992808"
integrity sha512-Qdx9mGXMgmbw74YofHWny3J7yh08z+Hl+yzNt67RafpvE3bqboVCHdUDgesD5Jq83lQe0znbG7TzB3iL5DOx2A==
dependencies:
chalk "^1.1.3"
get-stdin "^5.0.1"

30
main.go
View File

@@ -4,6 +4,7 @@ import (
"context"
"embed"
"io/fs"
_ "net/http/pprof"
"net/url"
"os"
"os/signal"
@@ -25,6 +26,9 @@ var (
tailSize = 300
filters map[string]string
version = "dev"
key string
username string
password string
)
//go:embed static
@@ -40,6 +44,9 @@ func init() {
pflag.String("level", "info", "logging level")
pflag.Int("tailSize", 300, "Tail size to use for initial container logs")
pflag.StringToStringVar(&filters, "filter", map[string]string{}, "Container filters to use for showing logs")
pflag.String("key", "", "Dozzle secure key used for session encryption. Should be a random generated string. Use openssl rand -base64 32 to create one.")
pflag.String("username", "", "Dozzle username to use for authentication. Requires key and password.")
pflag.String("password", "", "Dozzle password for authentication. Requires username and key.")
pflag.Parse()
viper.AutomaticEnv()
@@ -50,6 +57,9 @@ func init() {
base = viper.GetString("base")
level = viper.GetString("level")
tailSize = viper.GetInt("tailSize")
key = viper.GetString("key")
username = viper.GetString("username")
password = viper.GetString("password")
// Until https://github.com/spf13/viper/issues/911 is fixed. We have to use this hacky way.
// filters = viper.GetStringMapString("filter")
@@ -83,11 +93,24 @@ func main() {
log.Fatalf("Could not connect to Docker Engine: %v", err)
}
if username != "" || password != "" {
if username == "" || password == "" {
log.Fatalf("Username AND password are required for authentication")
}
if key == "" {
log.Fatalf("Key is required for authentication")
}
}
config := web.Config{
Addr: addr,
Base: base,
Version: version,
TailSize: tailSize,
Key: key,
Username: username,
Password: password,
}
static, err := fs.Sub(content, "static")
@@ -95,6 +118,11 @@ func main() {
log.Fatalf("Could not open embedded static folder: %v", err)
}
if _, ok := os.LookupEnv("LIVE_FS"); ok {
log.Info("Using live filesystem at ./static")
static = os.DirFS("./static")
}
srv := web.CreateServer(dockerClient, static, config)
go func() {
@@ -108,7 +136,7 @@ func main() {
signal.Notify(c, os.Interrupt)
signal.Notify(c, os.Kill)
<-c
log.Infof("Shutting down...")
log.Info("Shutting down...")
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
srv.Shutdown(ctx)

View File

@@ -1,11 +1,11 @@
{
"name": "dozzle",
"version": "3.4.7",
"version": "3.5.11",
"description": "Realtime log viewer for docker containers. ",
"scripts": {
"watch": "npm-run-all -p watch:*",
"watch:assets": "webpack --mode=development --watch",
"watch:server": "reflex -c .reflex",
"watch:server": "LIVE_FS=true reflex -c .reflex",
"predev": "make fake_static",
"dev": "npm-run-all -p dev-server watch:server",
"dev-server": "webpack serve --mode=development",
@@ -28,9 +28,9 @@
"homepage": "https://github.com/amir20/dozzle#readme",
"dependencies": {
"ansi-to-html": "^0.6.14",
"buefy": "^0.9.5",
"buefy": "^0.9.6",
"bulma": "^0.9.2",
"date-fns": "^2.19.0",
"date-fns": "^2.20.2",
"dompurify": "^2.2.7",
"fuzzysort": "^1.1.4",
"hotkeys-js": "^3.8.3",
@@ -45,28 +45,28 @@
"vuex": "^3.6.2"
},
"devDependencies": {
"@babel/core": "^7.13.14",
"@babel/plugin-transform-runtime": "^7.13.10",
"@babel/core": "^7.13.15",
"@babel/plugin-transform-runtime": "^7.13.15",
"@vue/component-compiler-utils": "^3.2.0",
"@vue/test-utils": "^1.1.3",
"@vue/test-utils": "^1.1.4",
"autoprefixer": "^10.2.5",
"babel-core": "^7.0.0-bridge.0",
"babel-jest": "^26.6.3",
"babel-preset-env": "^1.7.0",
"caniuse-lite": "^1.0.30001205",
"css-loader": "^5.2.0",
"caniuse-lite": "^1.0.30001208",
"css-loader": "^5.2.1",
"eventsourcemock": "^2.0.0",
"html-webpack-plugin": "^5.3.1",
"husky": "^6.0.0",
"jest": "^26.6.3",
"jest-serializer-vue": "^2.0.2",
"lint-staged": "^10.5.4",
"mini-css-extract-plugin": "^1.4.0",
"mini-css-extract-plugin": "^1.4.1",
"npm-run-all": "^4.1.5",
"postcss": "^8.2.9",
"postcss": "^8.2.10",
"postcss-loader": "^5.2.0",
"prettier": "^2.2.1",
"release-it": "^14.5.0",
"release-it": "^14.6.1",
"sass": "^1.32.8",
"sass-loader": "^11.0.1",
"vue-hot-reload-api": "^2.3.4",
@@ -74,7 +74,7 @@
"vue-loader": "^15.9.6",
"vue-style-loader": "^4.1.3",
"vue-template-compiler": "^2.6.12",
"webpack": "^5.30.0",
"webpack": "^5.32.0",
"webpack-cli": "^4.6.0",
"webpack-dev-server": "^3.11.2",
"webpack-pwa-manifest": "^4.3.0"

View File

@@ -23,6 +23,36 @@ Location: /foobar/
<a href="/foobar/">Moved Permanently</a>.
/* snapshot: Test_createRoutes_username_password */
HTTP/1.1 307 Temporary Redirect
Connection: close
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self'; manifest-src 'self'; connect-src 'self' api.github.com; require-trusted-types-for 'script'
Content-Type: text/html; charset=utf-8
Location: /login
<a href="/login">Temporary Redirect</a>.
/* snapshot: Test_createRoutes_username_password_invalid */
HTTP/1.1 401 Unauthorized
Connection: close
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self'; manifest-src 'self'; connect-src 'self' api.github.com; require-trusted-types-for 'script'
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Unauthorized
/* snapshot: Test_createRoutes_username_password_valid_session */
HTTP/1.1 200 OK
Connection: close
Cache-Control: no-cache
Connection: keep-alive
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self'; manifest-src 'self'; connect-src 'self' api.github.com; require-trusted-types-for 'script'
Content-Type: text/event-stream
X-Accel-Buffering: no
event: container-stopped
data: end of stream
/* snapshot: Test_createRoutes_version */
HTTP/1.1 200 OK
Connection: close

116
web/auth.go Normal file
View File

@@ -0,0 +1,116 @@
package web
import (
"net/http"
"time"
"github.com/gorilla/sessions"
log "github.com/sirupsen/logrus"
)
var secured = false
var store *sessions.CookieStore
const authorityKey = "AUTH_TIMESTAMP"
const sessionName = "session"
func initializeAuth(h *handler) {
if h.config.Username != "" && h.config.Password != "" {
store = sessions.NewCookieStore([]byte(h.config.Key))
store.Options.HttpOnly = true
store.Options.SameSite = http.SameSiteLaxMode
store.Options.MaxAge = 0
secured = true
}
}
func authorizationRequired(f http.HandlerFunc) http.Handler {
if secured {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if isAuthorized(r) {
f(w, r)
} else {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
})
} else {
return http.HandlerFunc(f)
}
}
func isAuthorized(r *http.Request) bool {
if !secured {
return true
}
session, _ := store.Get(r, sessionName)
if session.IsNew {
return false
}
if _, ok := session.Values[authorityKey]; ok {
// TODO check for timestamp
return true
}
return false
}
func (h *handler) isAuthorizationNeeded(r *http.Request) bool {
return secured && !isAuthorized(r)
}
func (h *handler) validateCredentials(w http.ResponseWriter, r *http.Request) {
if !secured {
log.Panic("Validating credentials without username and password should not happen")
}
if r.Method != "POST" {
log.Fatal("Expecting credential validation method to be POST")
http.Error(w, http.StatusText(http.StatusNotAcceptable), http.StatusNotAcceptable)
return
}
if err := r.ParseMultipartForm(4 * 1024); err != nil {
log.Fatalf("Error while parsing form data: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
user := r.PostFormValue("username")
pass := r.PostFormValue("password")
session, _ := store.Get(r, sessionName)
if user == h.config.Username && pass == h.config.Password {
session.Values[authorityKey] = time.Now().Unix()
if err := session.Save(r, w); err != nil {
log.Fatalf("Error while parsing saving session: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte(http.StatusText(http.StatusOK)))
return
}
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
func (h *handler) clearSession(w http.ResponseWriter, r *http.Request) {
if !secured {
log.Panic("Validating credentials with secured=false should not happen")
}
session, _ := store.Get(r, sessionName)
delete(session.Values, authorityKey)
if err := session.Save(r, w); err != nil {
log.Fatalf("Error while parsing saving session: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, h.config.Base, http.StatusTemporaryRedirect)
}

12
web/csp.go Normal file
View File

@@ -0,0 +1,12 @@
package web
import (
"net/http"
)
func cspHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Security-Policy", "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self'; manifest-src 'self'; connect-src 'self' api.github.com; require-trusted-types-for 'script'")
next.ServeHTTP(w, r)
})
}

110
web/events.go Normal file
View File

@@ -0,0 +1,110 @@
package web
import (
"encoding/json"
"fmt"
"net/http"
"github.com/amir20/dozzle/docker"
log "github.com/sirupsen/logrus"
)
func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
f, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
w.Header().Set("X-Accel-Buffering", "no")
ctx := r.Context()
events, err := h.client.Events(ctx)
stats := make(chan docker.ContainerStat)
if containers, err := h.client.ListContainers(); err == nil {
for _, c := range containers {
if c.State == "running" {
if err := h.client.ContainerStats(ctx, c.ID, stats); err != nil {
log.Errorf("error while streaming container stats: %v", err)
}
}
}
}
if err := sendContainersJSON(h.client, w); err != nil {
log.Errorf("error while encoding containers to stream: %v", err)
}
f.Flush()
for {
select {
case stat := <-stats:
bytes, _ := json.Marshal(stat)
if _, err := fmt.Fprintf(w, "event: container-stat\ndata: %s\n\n", string(bytes)); err != nil {
log.Errorf("error writing stat to event stream: %v", err)
return
}
f.Flush()
case event, ok := <-events:
if !ok {
return
}
switch event.Name {
case "start", "die":
log.Debugf("triggering docker event: %v", event.Name)
if event.Name == "start" {
log.Debugf("found new container with id: %v", event.ActorID)
if err := h.client.ContainerStats(ctx, event.ActorID, stats); err != nil {
log.Errorf("error when streaming new container stats: %v", err)
}
if err := sendContainersJSON(h.client, w); err != nil {
log.Errorf("error encoding containers to stream: %v", err)
return
}
}
bytes, _ := json.Marshal(event)
if _, err := fmt.Fprintf(w, "event: container-%s\ndata: %s\n\n", event.Name, string(bytes)); err != nil {
log.Errorf("error writing event to event stream: %v", err)
return
}
f.Flush()
default:
// do nothing
}
case <-ctx.Done():
return
case <-err:
return
}
}
}
func sendContainersJSON(client docker.Client, w http.ResponseWriter) error {
containers, err := client.ListContainers()
if err != nil {
return err
}
if _, err := fmt.Fprint(w, "event: containers-changed\ndata: "); err != nil {
return err
}
if err := json.NewEncoder(w).Encode(containers); err != nil {
return err
}
if _, err := fmt.Fprint(w, "\n\n"); err != nil {
return err
}
return nil
}

145
web/logs.go Normal file
View File

@@ -0,0 +1,145 @@
package web
import (
"bufio"
"compress/gzip"
"context"
"fmt"
"io"
"net/http"
"runtime"
"strings"
"time"
"github.com/dustin/go-humanize"
log "github.com/sirupsen/logrus"
)
func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
from, _ := time.Parse(time.RFC3339, r.URL.Query().Get("from"))
to, _ := time.Parse(time.RFC3339, r.URL.Query().Get("to"))
id := r.URL.Query().Get("id")
reader, err := h.client.ContainerLogsBetweenDates(r.Context(), id, from, to)
defer reader.Close()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
io.Copy(w, reader)
}
func (h *handler) downloadLogs(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
container, err := h.client.FindContainer(id)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
now := time.Now()
from := time.Unix(container.Created, 0)
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%v.log.gz", container.ID))
w.Header().Set("Content-Type", "application/gzip")
zw := gzip.NewWriter(w)
defer zw.Close()
zw.Name = fmt.Sprintf("%v.log", container.ID)
zw.Comment = "Logs generated by Dozzle"
zw.ModTime = now
reader, err := h.client.ContainerLogsBetweenDates(r.Context(), container.ID, from, now)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
io.Copy(zw, reader)
}
func (h *handler) streamLogs(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
if id == "" {
http.Error(w, "id is required", http.StatusBadRequest)
return
}
f, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
return
}
container, err := h.client.FindContainer(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
w.Header().Set("X-Accel-Buffering", "no")
reader, err := h.client.ContainerLogs(r.Context(), container.ID, h.config.TailSize, r.Header.Get("Last-Event-ID"))
if err != nil {
if err == io.EOF {
fmt.Fprintf(w, "event: container-stopped\ndata: end of stream\n\n")
f.Flush()
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}
defer reader.Close()
buffered := bufio.NewReader(reader)
var readerError error
var message string
for {
message, readerError = buffered.ReadString('\n')
fmt.Fprintf(w, "data: %s\n", message)
if index := strings.IndexAny(message, " "); index != -1 {
id := message[:index]
if _, err := time.Parse(time.RFC3339Nano, id); err == nil {
fmt.Fprintf(w, "id: %s\n", id)
}
}
fmt.Fprintf(w, "\n")
f.Flush()
if readerError != nil {
break
}
}
log.Debugf("streaming stopped: %v", container.ID)
if readerError == 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())
}
log.WithField("routines", runtime.NumGoroutine()).Debug("runtime goroutine stats")
if log.IsLevelEnabled(log.DebugLevel) {
var m runtime.MemStats
runtime.ReadMemStats(&m)
// For info on each, see: https://golang.org/pkg/runtime/#MemStats
log.WithFields(log.Fields{
"allocated": humanize.Bytes(m.Alloc),
"totalAllocated": humanize.Bytes(m.TotalAlloc),
"system": humanize.Bytes(m.Sys),
}).Debug("runtime mem stats")
}
}

View File

@@ -1,25 +1,13 @@
package web
import (
"bufio"
"compress/gzip"
"context"
"encoding/json"
"fmt"
"html/template"
"io"
"io/fs"
"io/ioutil"
"net/http"
_ "net/http/pprof"
"runtime"
"strings"
"time"
"github.com/amir20/dozzle/docker"
"github.com/dustin/go-humanize"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
@@ -31,13 +19,15 @@ type Config struct {
Addr string
Version string
TailSize int
Key string
Username string
Password string
}
type handler struct {
client docker.Client
content fs.FS
config *Config
fileServer http.Handler
client docker.Client
content fs.FS
config *Config
}
// CreateServer creates a service for http handler
@@ -50,21 +40,27 @@ func CreateServer(c docker.Client, content fs.FS, config Config) *http.Server {
return &http.Server{Addr: config.Addr, Handler: createRouter(handler)}
}
var fileServer http.Handler
func createRouter(h *handler) *mux.Router {
initializeAuth(h)
base := h.config.Base
r := mux.NewRouter()
r.Use(setCSPHeaders)
r.Use(cspHeaders)
if base != "/" {
r.HandleFunc(base, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
http.Redirect(w, req, base+"/", http.StatusMovedPermanently)
}))
}
s := r.PathPrefix(base).Subrouter()
s.HandleFunc("/api/logs/stream", h.streamLogs)
s.HandleFunc("/api/logs/download", h.downloadLogs)
s.HandleFunc("/api/logs", h.fetchLogsBetweenDates)
s.HandleFunc("/api/events/stream", h.streamEvents)
s.HandleFunc("/version", h.version)
s.Handle("/api/logs/stream", authorizationRequired(h.streamLogs))
s.Handle("/api/logs/download", authorizationRequired(h.downloadLogs))
s.Handle("/api/logs", authorizationRequired(h.fetchLogsBetweenDates))
s.Handle("/api/events/stream", authorizationRequired(h.streamEvents))
s.HandleFunc("/api/validateCredentials", h.validateCredentials)
s.Handle("/logout", authorizationRequired(h.clearSession))
s.Handle("/version", authorizationRequired(h.version))
if log.IsLevelEnabled(log.DebugLevel) {
s.PathPrefix("/debug/pprof/").Handler(http.DefaultServeMux)
@@ -76,270 +72,61 @@ func createRouter(h *handler) *mux.Router {
s.PathPrefix("/").Handler(http.StripPrefix(base, http.HandlerFunc(h.index)))
}
h.fileServer = http.FileServer(http.FS(h.content))
fileServer = http.FileServer(http.FS(h.content))
return r
}
func setCSPHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Security-Policy", "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self'; manifest-src 'self'; connect-src 'self' api.github.com; require-trusted-types-for 'script'")
next.ServeHTTP(w, r)
})
}
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 != "/" {
h.fileServer.ServeHTTP(w, req)
fileServer.ServeHTTP(w, req)
} else {
file, err := h.content.Open("index.html")
if err != nil {
panic(err)
}
bytes, err := ioutil.ReadAll(file)
if err != nil {
panic(err)
}
tmpl, err := template.New("index.html").Parse(string(bytes))
if err != nil {
panic(err)
}
path := ""
if h.config.Base != "/" {
path = h.config.Base
}
data := struct {
Base string
Version string
}{path, h.config.Version}
err = tmpl.Execute(w, data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
}
func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
from, _ := time.Parse(time.RFC3339, r.URL.Query().Get("from"))
to, _ := time.Parse(time.RFC3339, r.URL.Query().Get("to"))
id := r.URL.Query().Get("id")
reader, err := h.client.ContainerLogsBetweenDates(r.Context(), id, from, to)
defer reader.Close()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
io.Copy(w, reader)
}
func (h *handler) downloadLogs(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
container, err := h.client.FindContainer(id)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
now := time.Now()
from := time.Unix(container.Created, 0)
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%v.log.gz", container.ID))
w.Header().Set("Content-Type", "application/gzip")
zw := gzip.NewWriter(w)
defer zw.Close()
zw.Name = fmt.Sprintf("%v.log", container.ID)
zw.Comment = "Logs generated by Dozzle"
zw.ModTime = now
reader, err := h.client.ContainerLogsBetweenDates(r.Context(), container.ID, from, now)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
io.Copy(zw, reader)
}
func (h *handler) streamLogs(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
if id == "" {
http.Error(w, "id is required", http.StatusBadRequest)
return
}
f, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
return
}
container, err := h.client.FindContainer(id)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
w.Header().Set("X-Accel-Buffering", "no")
reader, err := h.client.ContainerLogs(r.Context(), container.ID, h.config.TailSize, r.Header.Get("Last-Event-ID"))
if err != nil {
if err == io.EOF {
fmt.Fprintf(w, "event: container-stopped\ndata: end of stream\n\n")
f.Flush()
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}
defer reader.Close()
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
message := scanner.Text()
fmt.Fprintf(w, "data: %s\n", message)
if index := strings.IndexAny(message, " "); index != -1 {
id := message[:index]
if _, err := time.Parse(time.RFC3339Nano, id); err == nil {
fmt.Fprintf(w, "id: %s\n", id)
}
}
fmt.Fprintf(w, "\n")
f.Flush()
}
log.Debugf("streaming stopped: %v", container.ID)
if scanner.Err() == nil {
log.Debugf("container stopped: %v", container.ID)
fmt.Fprintf(w, "event: container-stopped\ndata: end of stream\n\n")
f.Flush()
} else if scanner.Err() != context.Canceled {
log.Errorf("unknown error while streaming %v", scanner.Err())
}
log.WithField("routines", runtime.NumGoroutine()).Debug("runtime goroutine stats")
if log.IsLevelEnabled(log.DebugLevel) {
var m runtime.MemStats
runtime.ReadMemStats(&m)
// For info on each, see: https://golang.org/pkg/runtime/#MemStats
log.WithFields(log.Fields{
"allocated": humanize.Bytes(m.Alloc),
"totalAllocated": humanize.Bytes(m.TotalAlloc),
"system": humanize.Bytes(m.Sys),
}).Debug("runtime mem stats")
}
}
func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) {
f, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
w.Header().Set("X-Accel-Buffering", "no")
ctx := r.Context()
events, err := h.client.Events(ctx)
stats := make(chan docker.ContainerStat)
if containers, err := h.client.ListContainers(); err == nil {
for _, c := range containers {
if c.State == "running" {
if err := h.client.ContainerStats(ctx, c.ID, stats); err != nil {
log.Errorf("error while streaming container stats: %v", err)
}
}
}
}
if err := sendContainersJSON(h.client, w); err != nil {
log.Errorf("error while encoding containers to stream: %v", err)
}
f.Flush()
for {
select {
case stat := <-stats:
bytes, _ := json.Marshal(stat)
if _, err := fmt.Fprintf(w, "event: container-stat\ndata: %s\n\n", string(bytes)); err != nil {
log.Errorf("error writing stat to event stream: %v", err)
return
}
f.Flush()
case event, ok := <-events:
if !ok {
return
}
switch event.Name {
case "start", "die":
log.Debugf("triggering docker event: %v", event.Name)
if event.Name == "start" {
log.Debugf("found new container with id: %v", event.ActorID)
if err := h.client.ContainerStats(ctx, event.ActorID, stats); err != nil {
log.Errorf("error when streaming new container stats: %v", err)
}
if err := sendContainersJSON(h.client, w); err != nil {
log.Errorf("error encoding containers to stream: %v", err)
return
}
}
bytes, _ := json.Marshal(event)
if _, err := fmt.Fprintf(w, "event: container-%s\ndata: %s\n\n", event.Name, string(bytes)); err != nil {
log.Errorf("error writing event to event stream: %v", err)
return
}
f.Flush()
default:
// do nothing
}
case <-ctx.Done():
return
case <-err:
if !isAuthorized(req) && req.URL.Path != "login" {
http.Redirect(w, req, h.config.Base+"login", http.StatusTemporaryRedirect)
return
}
h.executeTemplate(w, req)
}
}
func (h *handler) executeTemplate(w http.ResponseWriter, req *http.Request) {
file, err := h.content.Open("index.html")
if err != nil {
log.Panic(err)
}
bytes, err := ioutil.ReadAll(file)
if err != nil {
log.Panic(err)
}
tmpl, err := template.New("index.html").Parse(string(bytes))
if err != nil {
log.Panic(err)
}
path := ""
if h.config.Base != "/" {
path = h.config.Base
}
data := struct {
Base string
Version string
AuthorizationNeeded bool
Secured bool
}{
path,
h.config.Version,
h.isAuthorizationNeeded(req),
secured,
}
err = tmpl.Execute(w, data)
if err != nil {
log.Panic(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func (h *handler) version(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%v", h.config.Version)
}
func sendContainersJSON(client docker.Client, w http.ResponseWriter) error {
containers, err := client.ListContainers()
if err != nil {
return err
}
if _, err := fmt.Fprint(w, "event: containers-changed\ndata: "); err != nil {
return err
}
if err := json.NewEncoder(w).Encode(containers); err != nil {
return err
}
if _, err := fmt.Fprint(w, "\n\n"); err != nil {
return err
}
return nil
}

View File

@@ -1,16 +1,21 @@
package web
import (
"bytes"
"context"
"errors"
"io"
"io/fs"
"io/ioutil"
"mime/multipart"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"time"
"github.com/gorilla/mux"
"github.com/magiconair/properties/assert"
"github.com/amir20/dozzle/docker"
@@ -238,14 +243,9 @@ func Test_handler_streamEvents_error_request(t *testing.T) {
}
func Test_createRoutes_index(t *testing.T) {
mockedClient := new(MockedClient)
fs := afero.NewMemMapFs()
require.NoError(t, afero.WriteFile(fs, "index.html", []byte("index page"), 0644), "WriteFile should have no error.")
handler := createRouter(&handler{
client: mockedClient,
content: afero.NewIOFS(fs),
config: &Config{Base: "/"},
})
handler := createHandler(nil, afero.NewIOFS(fs), Config{Base: "/"})
req, err := http.NewRequest("GET", "/", nil)
require.NoError(t, err, "NewRequest should not return an error.")
rr := httptest.NewRecorder()
@@ -255,15 +255,10 @@ func Test_createRoutes_index(t *testing.T) {
}
func Test_createRoutes_redirect(t *testing.T) {
mockedClient := new(MockedClient)
fs := afero.NewMemMapFs()
require.NoError(t, afero.WriteFile(fs, "index.html", []byte("index page"), 0644), "WriteFile should have no error.")
handler := createRouter(&handler{
client: mockedClient,
content: afero.NewIOFS(fs),
config: &Config{Base: "/foobar"},
})
handler := createHandler(nil, afero.NewIOFS(fs), Config{Base: "/foobar"})
req, err := http.NewRequest("GET", "/foobar", nil)
require.NoError(t, err, "NewRequest should not return an error.")
rr := httptest.NewRecorder()
@@ -273,15 +268,9 @@ func Test_createRoutes_redirect(t *testing.T) {
}
func Test_createRoutes_foobar(t *testing.T) {
mockedClient := new(MockedClient)
fs := afero.NewMemMapFs()
require.NoError(t, afero.WriteFile(fs, "index.html", []byte("foo page"), 0644), "WriteFile should have no error.")
handler := createRouter(&handler{
client: mockedClient,
content: afero.NewIOFS(fs),
config: &Config{Base: "/foobar"},
})
handler := createHandler(nil, afero.NewIOFS(fs), Config{Base: "/foobar"})
req, err := http.NewRequest("GET", "/foobar/", nil)
require.NoError(t, err, "NewRequest should not return an error.")
rr := httptest.NewRecorder()
@@ -291,17 +280,11 @@ func Test_createRoutes_foobar(t *testing.T) {
}
func Test_createRoutes_foobar_file(t *testing.T) {
mockedClient := new(MockedClient)
fs := afero.NewMemMapFs()
require.NoError(t, afero.WriteFile(fs, "index.html", []byte("index page"), 0644), "WriteFile should have no error.")
require.NoError(t, afero.WriteFile(fs, "test", []byte("test page"), 0644), "WriteFile should have no error.")
handler := createRouter(&handler{
client: mockedClient,
content: afero.NewIOFS(fs),
config: &Config{Base: "/foobar"},
fileServer: nil,
})
handler := createHandler(nil, afero.NewIOFS(fs), Config{Base: "/foobar"})
req, err := http.NewRequest("GET", "/foobar/test", nil)
require.NoError(t, err, "NewRequest should not return an error.")
rr := httptest.NewRecorder()
@@ -311,15 +294,9 @@ func Test_createRoutes_foobar_file(t *testing.T) {
}
func Test_createRoutes_version(t *testing.T) {
mockedClient := new(MockedClient)
fs := afero.NewMemMapFs()
require.NoError(t, afero.WriteFile(fs, "index.html", []byte("index page"), 0644), "WriteFile should have no error.")
handler := createRouter(&handler{
client: mockedClient,
content: afero.NewIOFS(fs),
config: &Config{Base: "/", Version: "dev"},
})
handler := createHandler(nil, afero.NewIOFS(fs), Config{Base: "/", Version: "dev"})
req, err := http.NewRequest("GET", "/version", nil)
require.NoError(t, err, "NewRequest should not return an error.")
rr := httptest.NewRecorder()
@@ -328,6 +305,137 @@ func Test_createRoutes_version(t *testing.T) {
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
}
func Test_createRoutes_username_password(t *testing.T) {
handler := createHandler(nil, nil, Config{Base: "/", Username: "amir", Password: "password", Key: "key"})
req, err := http.NewRequest("GET", "/", nil)
require.NoError(t, err, "NewRequest should not return an error.")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
}
func Test_createRoutes_username_password_invalid(t *testing.T) {
handler := createHandler(nil, nil, Config{Base: "/", Username: "amir", Password: "password", Key: "key"})
req, err := http.NewRequest("GET", "/api/logs/stream?id=123", nil)
require.NoError(t, err, "NewRequest should not return an error.")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
}
func Test_createRoutes_username_password_login_happy(t *testing.T) {
handler := createHandler(nil, nil, Config{Base: "/", Username: "amir", Password: "password", Key: "key"})
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
fw, err := writer.CreateFormField("username")
require.NoError(t, err, "Creating field should not be error.")
_, err = io.Copy(fw, strings.NewReader("amir"))
require.NoError(t, err, "Copying field should not result in error.")
fw, err = writer.CreateFormField("password")
require.NoError(t, err, "Creating field should not be error.")
_, err = io.Copy(fw, strings.NewReader("password"))
require.NoError(t, err, "Copying field should not result in error.")
writer.Close()
req, err := http.NewRequest("POST", "/api/validateCredentials", bytes.NewReader(body.Bytes()))
req.Header.Set("Content-Type", writer.FormDataContentType())
require.NoError(t, err, "NewRequest should not return an error.")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
assert.Equal(t, rr.Code, 200)
cookie := rr.Header().Get("Set-Cookie")
assert.Matches(t, cookie, "session=.+")
}
func Test_createRoutes_username_password_login_failed(t *testing.T) {
handler := createHandler(nil, nil, Config{Base: "/", Username: "amir", Password: "password", Key: "key"})
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
fw, err := writer.CreateFormField("username")
require.NoError(t, err, "Creating field should not be error.")
_, err = io.Copy(fw, strings.NewReader("amir"))
require.NoError(t, err, "Copying field should not result in error.")
fw, err = writer.CreateFormField("password")
require.NoError(t, err, "Creating field should not be error.")
_, err = io.Copy(fw, strings.NewReader("bad"))
require.NoError(t, err, "Copying field should not result in error.")
writer.Close()
req, err := http.NewRequest("POST", "/api/validateCredentials", bytes.NewReader(body.Bytes()))
req.Header.Set("Content-Type", writer.FormDataContentType())
require.NoError(t, err, "NewRequest should not return an error.")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
assert.Equal(t, rr.Code, 401)
}
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)
handler := createHandler(mockedClient, nil, Config{Base: "/", Username: "amir", Password: "password", Key: "key"})
// Get cookie first
req, err := http.NewRequest("GET", "/api/logs/stream?id=123", nil)
require.NoError(t, err, "NewRequest should not return an error.")
session, _ := store.Get(req, sessionName)
session.Values[authorityKey] = time.Now().Unix()
recorder := httptest.NewRecorder()
session.Save(req, recorder)
cookies := recorder.Result().Cookies()
// Test with cookie
req, err = http.NewRequest("GET", "/api/logs/stream?id=123", nil)
require.NoError(t, err, "NewRequest should not return an error.")
req.AddCookie(cookies[0])
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
}
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)
handler := createHandler(mockedClient, nil, Config{Base: "/", Username: "amir", Password: "password", Key: "key"})
req, err := http.NewRequest("GET", "/api/logs/stream?id=123", nil)
require.NoError(t, err, "NewRequest should not return an error.")
req.AddCookie(&http.Cookie{Name: "session", Value: "baddata"})
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
assert.Equal(t, rr.Code, 401)
}
func createHandler(client docker.Client, content fs.FS, config Config) *mux.Router {
if client == nil {
client = new(MockedClient)
}
if content == nil {
fs := afero.NewMemMapFs()
afero.WriteFile(fs, "index.html", []byte("index page"), 0644)
content = afero.NewIOFS(fs)
}
return createRouter(&handler{
client: client,
content: content,
config: &config,
})
}
func TestMain(m *testing.M) {
exit := m.Run()
abide.Cleanup()

276
yarn.lock
View File

@@ -9,29 +9,29 @@
dependencies:
"@babel/highlight" "^7.12.13"
"@babel/compat-data@^7.13.0":
version "7.13.0"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.13.0.tgz#7889eb7ee6518e2afa5d312b15fd7fd1fe9f3744"
integrity sha512-mKgFbYQ+23pjwNGBNPNWrBfa3g/EcmrPnwQpjWoNxq9xYf+M8wcLhMlz/wkWimLjzNzGnl3D+C2186gMzk0VuA==
"@babel/compat-data@^7.13.11":
version "7.13.15"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.13.15.tgz#7e8eea42d0b64fda2b375b22d06c605222e848f4"
integrity sha512-ltnibHKR1VnrU4ymHyQ/CXtNXI6yZC0oJThyW78Hft8XndANwi+9H+UIklBDraIjFEJzw8wmcM427oDd9KS5wA==
"@babel/compat-data@^7.13.12":
version "7.13.12"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.13.12.tgz#a8a5ccac19c200f9dd49624cac6e19d7be1236a1"
integrity sha512-3eJJ841uKxeV8dcN/2yGEUy+RfgQspPEgQat85umsE1rotuquQ2AbIub4S6j7c50a2d+4myc+zSlnXeIHrOnhQ==
"@babel/core@^7.1.0", "@babel/core@^7.13.14", "@babel/core@^7.7.5":
version "7.13.14"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.13.14.tgz#8e46ebbaca460a63497c797e574038ab04ae6d06"
integrity sha512-wZso/vyF4ki0l0znlgM4inxbdrUvCb+cVz8grxDq+6C9k6qbqoIJteQOKicaKjCipU3ISV+XedCqpL2RJJVehA==
"@babel/core@^7.1.0", "@babel/core@^7.13.15", "@babel/core@^7.7.5":
version "7.13.15"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.13.15.tgz#a6d40917df027487b54312202a06812c4f7792d0"
integrity sha512-6GXmNYeNjS2Uz+uls5jalOemgIhnTMeaXo+yBUA72kC2uX/8VW6XyhVIo2L8/q0goKQA3EVKx0KOQpVKSeWadQ==
dependencies:
"@babel/code-frame" "^7.12.13"
"@babel/generator" "^7.13.9"
"@babel/helper-compilation-targets" "^7.13.13"
"@babel/helper-module-transforms" "^7.13.14"
"@babel/helpers" "^7.13.10"
"@babel/parser" "^7.13.13"
"@babel/parser" "^7.13.15"
"@babel/template" "^7.12.13"
"@babel/traverse" "^7.13.13"
"@babel/traverse" "^7.13.15"
"@babel/types" "^7.13.14"
convert-source-map "^1.7.0"
debug "^4.1.0"
@@ -59,10 +59,10 @@
browserslist "^4.14.5"
semver "^6.3.0"
"@babel/helper-define-polyfill-provider@^0.1.2":
version "0.1.2"
resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.1.2.tgz#619f01afe1deda460676c25c463b42eaefdb71a2"
integrity sha512-hWeolZJivTNGHXHzJjQz/NwDaG4mGXf22ZroOP8bQYgvHNzaQ5tylsVbAcAS2oDjXBwpu8qH2I/654QFS2rDpw==
"@babel/helper-define-polyfill-provider@^0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.0.tgz#a640051772045fedaaecc6f0c6c69f02bdd34bf1"
integrity sha512-JT8tHuFjKBo8NnaUbblz7mIu1nnvUDiHVjXXkulZULyidvo/7P6TY7+YqpV37IfF+KUFxmlK04elKtGKXaiVgw==
dependencies:
"@babel/helper-compilation-targets" "^7.13.0"
"@babel/helper-module-imports" "^7.12.13"
@@ -96,14 +96,7 @@
dependencies:
"@babel/types" "^7.13.12"
"@babel/helper-module-imports@^7.12.13":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz#ec67e4404f41750463e455cc3203f6a32e93fcb0"
integrity sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g==
dependencies:
"@babel/types" "^7.12.13"
"@babel/helper-module-imports@^7.13.12":
"@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.13.12":
version "7.13.12"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz#c6a369a6f3621cb25da014078684da9196b61977"
integrity sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==
@@ -188,10 +181,10 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
"@babel/parser@^7.1.0", "@babel/parser@^7.12.13", "@babel/parser@^7.13.13":
version "7.13.13"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.13.tgz#42f03862f4aed50461e543270916b47dd501f0df"
integrity sha512-OhsyMrqygfk5v8HmWwOzlYjJrtLaFhF34MrfG/Z73DgYCI6ojNUTUp2TYbtnjo8PegeJp12eamsNettCQjKjVw==
"@babel/parser@^7.1.0", "@babel/parser@^7.12.13", "@babel/parser@^7.13.15":
version "7.13.15"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.15.tgz#8e66775fb523599acb6a289e12929fa5ab0954d8"
integrity sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==
"@babel/plugin-syntax-async-generators@^7.8.4":
version "7.8.4"
@@ -277,16 +270,16 @@
dependencies:
"@babel/helper-plugin-utils" "^7.10.4"
"@babel/plugin-transform-runtime@^7.13.10":
version "7.13.10"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.13.10.tgz#a1e40d22e2bf570c591c9c7e5ab42d6bf1e419e1"
integrity sha512-Y5k8ipgfvz5d/76tx7JYbKQTcgFSU6VgJ3kKQv4zGTKr+a9T/KBvfRvGtSFgKDQGt/DBykQixV0vNWKIdzWErA==
"@babel/plugin-transform-runtime@^7.13.15":
version "7.13.15"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.13.15.tgz#2eddf585dd066b84102517e10a577f24f76a9cd7"
integrity sha512-d+ezl76gx6Jal08XngJUkXM4lFXK/5Ikl9Mh4HKDxSfGJXmZ9xG64XT2oivBzfxb/eQ62VfvoMkaCZUKJMVrBA==
dependencies:
"@babel/helper-module-imports" "^7.12.13"
"@babel/helper-module-imports" "^7.13.12"
"@babel/helper-plugin-utils" "^7.13.0"
babel-plugin-polyfill-corejs2 "^0.1.4"
babel-plugin-polyfill-corejs3 "^0.1.3"
babel-plugin-polyfill-regenerator "^0.1.2"
babel-plugin-polyfill-corejs2 "^0.2.0"
babel-plugin-polyfill-corejs3 "^0.2.0"
babel-plugin-polyfill-regenerator "^0.2.0"
semver "^6.3.0"
"@babel/runtime@^7.7.2":
@@ -305,21 +298,21 @@
"@babel/parser" "^7.12.13"
"@babel/types" "^7.12.13"
"@babel/traverse@^7.1.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.13.13":
version "7.13.13"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.13.13.tgz#39aa9c21aab69f74d948a486dd28a2dbdbf5114d"
integrity sha512-CblEcwmXKR6eP43oQGG++0QMTtCjAsa3frUuzHoiIJWpaIIi8dwMyEFUJoXRLxagGqCK+jALRwIO+o3R9p/uUg==
"@babel/traverse@^7.1.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.13.13", "@babel/traverse@^7.13.15":
version "7.13.15"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.13.15.tgz#c38bf7679334ddd4028e8e1f7b3aa5019f0dada7"
integrity sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ==
dependencies:
"@babel/code-frame" "^7.12.13"
"@babel/generator" "^7.13.9"
"@babel/helper-function-name" "^7.12.13"
"@babel/helper-split-export-declaration" "^7.12.13"
"@babel/parser" "^7.13.13"
"@babel/types" "^7.13.13"
"@babel/parser" "^7.13.15"
"@babel/types" "^7.13.14"
debug "^4.1.0"
globals "^11.1.0"
"@babel/types@^7.0.0", "@babel/types@^7.12.13", "@babel/types@^7.13.0", "@babel/types@^7.13.12", "@babel/types@^7.13.13", "@babel/types@^7.13.14", "@babel/types@^7.3.0", "@babel/types@^7.3.3":
"@babel/types@^7.0.0", "@babel/types@^7.12.13", "@babel/types@^7.13.0", "@babel/types@^7.13.12", "@babel/types@^7.13.14", "@babel/types@^7.3.0", "@babel/types@^7.3.3":
version "7.13.14"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.14.tgz#c35a4abb15c7cd45a2746d78ab328e362cbace0d"
integrity sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==
@@ -891,10 +884,10 @@
resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-4.0.1.tgz#bafd3d173974827ba0b733fcca7f1860cb71a9aa"
integrity sha512-k2hRcfcLRyPJjtYfJLzg404n7HZ6sUpAWAR/uNI8tf96NgatWOpw1ocdF+WFfx/trO1ivBh7ckynO1rn+xAw/Q==
"@octokit/openapi-types@^5.3.2":
version "5.3.2"
resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-5.3.2.tgz#b8ac43c5c3d00aef61a34cf744e315110c78deb4"
integrity sha512-NxF1yfYOUO92rCx3dwvA2onF30Vdlg7YUkMVXkeptqpzA3tRLplThhFleV/UKWFgh7rpKu1yYRbvNDUtzSopKA==
"@octokit/openapi-types@^6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-6.0.0.tgz#7da8d7d5a72d3282c1a3ff9f951c8133a707480d"
integrity sha512-CnDdK7ivHkBtJYzWzZm7gEkanA7gKH6a09Eguz7flHw//GacPJLmkHA3f3N++MJmlxD1Fl+mB7B32EEpSCwztQ==
"@octokit/plugin-paginate-rest@^2.6.2":
version "2.9.1"
@@ -908,12 +901,12 @@
resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.3.tgz#70a62be213e1edc04bb8897ee48c311482f9700d"
integrity sha512-4RFU4li238jMJAzLgAwkBAw+4Loile5haQMQr+uhFq27BmyJXcXSKvoQKqh0agsZEiUlW6iSv3FAgvmGkur7OQ==
"@octokit/plugin-rest-endpoint-methods@4.13.5":
version "4.13.5"
resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.13.5.tgz#ad76285b82fe05fbb4adf2774a9c887f3534a880"
integrity sha512-kYKcWkFm4Ldk8bZai2RVEP1z97k1C/Ay2FN9FNTBg7JIyKoiiJjks4OtT6cuKeZX39tqa+C3J9xeYc6G+6g8uQ==
"@octokit/plugin-rest-endpoint-methods@5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.0.0.tgz#cf2cdeb24ea829c31688216a5b165010b61f9a98"
integrity sha512-Jc7CLNUueIshXT+HWt6T+M0sySPjF32mSFQAK7UfAg8qGeRI6OM1GSBxDLwbXjkqy2NVdnqCedJcP1nC785JYg==
dependencies:
"@octokit/types" "^6.12.2"
"@octokit/types" "^6.13.0"
deprecation "^2.3.1"
"@octokit/request-error@^2.0.0":
@@ -953,15 +946,15 @@
once "^1.4.0"
universal-user-agent "^6.0.0"
"@octokit/rest@18.3.5":
version "18.3.5"
resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.3.5.tgz#a89903d46e0b4273bd3234674ec2777a651d68ab"
integrity sha512-ZPeRms3WhWxQBEvoIh0zzf8xdU2FX0Capa7+lTca8YHmRsO3QNJzf1H3PcuKKsfgp91/xVDRtX91sTe1kexlbw==
"@octokit/rest@18.5.2":
version "18.5.2"
resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.5.2.tgz#0369e554b7076e3749005147be94c661c7a5a74b"
integrity sha512-Kz03XYfKS0yYdi61BkL9/aJ0pP2A/WK5vF/syhu9/kY30J8He3P68hv9GRpn8bULFx2K0A9MEErn4v3QEdbZcw==
dependencies:
"@octokit/core" "^3.2.3"
"@octokit/plugin-paginate-rest" "^2.6.2"
"@octokit/plugin-request-log" "^1.0.2"
"@octokit/plugin-rest-endpoint-methods" "4.13.5"
"@octokit/plugin-rest-endpoint-methods" "5.0.0"
"@octokit/types@^5.0.0", "@octokit/types@^5.0.1":
version "5.5.0"
@@ -978,12 +971,12 @@
"@octokit/openapi-types" "^4.0.0"
"@types/node" ">= 8"
"@octokit/types@^6.12.2":
version "6.12.2"
resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.12.2.tgz#5b44add079a478b8eb27d78cf384cc47e4411362"
integrity sha512-kCkiN8scbCmSq+gwdJV0iLgHc0O/GTPY1/cffo9kECu1MvatLPh9E+qFhfRIktKfHEA6ZYvv6S1B4Wnv3bi3pA==
"@octokit/types@^6.13.0":
version "6.13.0"
resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.13.0.tgz#779e5b7566c8dde68f2f6273861dd2f0409480d0"
integrity sha512-W2J9qlVIU11jMwKHUp5/rbVUeErqelCsO5vW5PKNb7wAXQVUz87Rc+imjlEvpvbH8yUb+KHmv8NEjVZdsdpyxA==
dependencies:
"@octokit/openapi-types" "^5.3.2"
"@octokit/openapi-types" "^6.0.0"
"@sindresorhus/is@^0.14.0":
version "0.14.0"
@@ -1218,10 +1211,10 @@
optionalDependencies:
prettier "^1.18.2"
"@vue/test-utils@^1.1.3":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.1.3.tgz#747f5683d8d4633c85a385fe2e02c1bb35bec153"
integrity sha512-BAY1Cwe9JpkJseimC295EW3YlAmgIJI9OPkg2FSP62+PHZooB0B+wceDi9TYyU57oqzL0yLbcP73JKFpKiLc9A==
"@vue/test-utils@^1.1.4":
version "1.1.4"
resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.1.4.tgz#a9acb32ea1fa4535b2e1ce5ca100bceb4fade2db"
integrity sha512-9BeL8IqGvJKy553lq/07rhYURQkpS/k+j19rJ/4eDpGJk7z872M0YrBWFhjS14yMKlvYVYOCfWnVIXyrAx0xNw==
dependencies:
dom-event-types "^1.0.0"
lodash "^4.17.15"
@@ -1835,29 +1828,29 @@ babel-plugin-jest-hoist@^26.6.2:
"@types/babel__core" "^7.0.0"
"@types/babel__traverse" "^7.0.6"
babel-plugin-polyfill-corejs2@^0.1.4:
version "0.1.5"
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.1.5.tgz#8fc4779965311393594a1b9ad3adefab3860c8fe"
integrity sha512-5IzdFIjYWqlOFVr/hMYUpc+5fbfuvJTAISwIY58jhH++ZtawtNlcJnxAixlk8ahVwHCz1ipW/kpXYliEBp66wg==
babel-plugin-polyfill-corejs2@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.0.tgz#686775bf9a5aa757e10520903675e3889caeedc4"
integrity sha512-9bNwiR0dS881c5SHnzCmmGlMkJLl0OUZvxrxHo9w/iNoRuqaPjqlvBf4HrovXtQs/au5yKkpcdgfT1cC5PAZwg==
dependencies:
"@babel/compat-data" "^7.13.0"
"@babel/helper-define-polyfill-provider" "^0.1.2"
"@babel/compat-data" "^7.13.11"
"@babel/helper-define-polyfill-provider" "^0.2.0"
semver "^6.1.1"
babel-plugin-polyfill-corejs3@^0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.1.4.tgz#2ae290200e953bade30907b7a3bebcb696e6c59d"
integrity sha512-ysSzFn/qM8bvcDAn4mC7pKk85Y5dVaoa9h4u0mHxOEpDzabsseONhUpR7kHxpUinfj1bjU7mUZqD23rMZBoeSg==
babel-plugin-polyfill-corejs3@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.0.tgz#f4b4bb7b19329827df36ff56f6e6d367026cb7a2"
integrity sha512-zZyi7p3BCUyzNxLx8KV61zTINkkV65zVkDAFNZmrTCRVhjo1jAS+YLvDJ9Jgd/w2tsAviCwFHReYfxO3Iql8Yg==
dependencies:
"@babel/helper-define-polyfill-provider" "^0.1.2"
core-js-compat "^3.8.1"
"@babel/helper-define-polyfill-provider" "^0.2.0"
core-js-compat "^3.9.1"
babel-plugin-polyfill-regenerator@^0.1.2:
version "0.1.3"
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.1.3.tgz#350f857225fc640ae1ec78d1536afcbb457db841"
integrity sha512-hRjTJQiOYt/wBKEc+8V8p9OJ9799blAJcuKzn1JXh3pApHoWl1Emxh2BHc6MC7Qt6bbr3uDpNxaYQnATLIudEg==
babel-plugin-polyfill-regenerator@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.0.tgz#853f5f5716f4691d98c84f8069c7636ea8da7ab8"
integrity sha512-J7vKbCuD2Xi/eEHxquHN14bXAW9CXtecwuLrOIDJtcZzTaPzV1VdEfoUf9AzcRBMolKUQKM9/GVojeh0hFiqMg==
dependencies:
"@babel/helper-define-polyfill-provider" "^0.1.2"
"@babel/helper-define-polyfill-provider" "^0.2.0"
babel-plugin-syntax-async-functions@^6.8.0:
version "6.13.0"
@@ -2403,10 +2396,10 @@ bser@2.1.1:
dependencies:
node-int64 "^0.4.0"
buefy@^0.9.5:
version "0.9.5"
resolved "https://registry.yarnpkg.com/buefy/-/buefy-0.9.5.tgz#03e0e9d425f897dcbf6c0beb33d35a5f994b14c0"
integrity sha512-0LBY4XfvT/dgAg8AQBh2pdjTgDj5lpqBkmG4C2wWUAwZZ9UG75UxiH+wyct+b1DjkcuvQDzT4I3hlZvbduVdww==
buefy@^0.9.6:
version "0.9.6"
resolved "https://registry.yarnpkg.com/buefy/-/buefy-0.9.6.tgz#83c026c4a6f8fdcab80ded59181efc20873e3a99"
integrity sha512-qoYtbTf78xvC5fcRsuUKqUizJCAk2rg6LiAzON8X1G0GTsHkCWRWBHsJmU/jk1/6B+TQ10pSGkQgB+OLrREeXg==
dependencies:
bulma "0.9.2"
@@ -2525,10 +2518,10 @@ camelcase@^6.0.0, camelcase@^6.2.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30001181, caniuse-lite@^1.0.30001196, caniuse-lite@^1.0.30001205:
version "1.0.30001205"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001205.tgz#d79bf6a6fb13196b4bb46e5143a22ca0242e0ef8"
integrity sha512-TL1GrS5V6LElbitPazidkBMD9sa448bQDDLrumDqaggmKFcuU2JW1wTOHJPukAcOMtEmLcmDJEzfRrf+GjM0Og==
caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30001181, caniuse-lite@^1.0.30001196, caniuse-lite@^1.0.30001208:
version "1.0.30001208"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001208.tgz#a999014a35cebd4f98c405930a057a0d75352eb9"
integrity sha512-OE5UE4+nBOro8Dyvv0lfx+SRtfVIOM9uhKqFmJeUbGriqhhStgp1A0OyBpgy3OUF8AhYCT+PVwPC1gMl2ZcQMA==
capture-exit@^2.0.0:
version "2.0.0"
@@ -2911,10 +2904,10 @@ copy-descriptor@^0.1.0:
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
core-js-compat@^3.8.1:
version "3.9.0"
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.9.0.tgz#29da39385f16b71e1915565aa0385c4e0963ad56"
integrity sha512-YK6fwFjCOKWwGnjFUR3c544YsnA/7DoLL0ysncuOJ4pwbriAtOpvM2bygdlcXbvQCQZ7bBU9CL4t7tGl7ETRpQ==
core-js-compat@^3.9.1:
version "3.10.1"
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.10.1.tgz#62183a3a77ceeffcc420d907a3e6fc67d9b27f1c"
integrity sha512-ZHQTdTPkqvw2CeHiZC970NNJcnwzT6YIueDMASKt+p3WbZsLXOcoD392SkcWhkC0wBBHhlfhqGKKsNCQUozYtg==
dependencies:
browserslist "^4.16.3"
semver "7.0.0"
@@ -2970,10 +2963,10 @@ css-color-names@1.0.1:
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-1.0.1.tgz#6ff7ee81a823ad46e020fa2fd6ab40a887e2ba67"
integrity sha512-/loXYOch1qU1biStIFsHH8SxTmOseh1IJqFvy8IujXOm1h+QjUdDhkzOrR5HG8K8mlxREj0yfi8ewCHx0eMxzA==
css-loader@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.0.tgz#a9ecda190500863673ce4434033710404efbff00"
integrity sha512-MfRo2MjEeLXMlUkeUwN71Vx5oc6EJnx5UQ4Yi9iUtYQvrPtwLUucYptz0hc6n++kdNcyF5olYBS4vPjJDAcLkw==
css-loader@^5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.1.tgz#15fbd5b6ac4c1b170a098f804c5abd0722f2aa73"
integrity sha512-YCyRzlt/jgG1xanXZDG/DHqAueOtXFHeusP9TS478oP1J++JSKOyEgGW1GHVoCj/rkS+GWOlBwqQJBr9yajQ9w==
dependencies:
camelcase "^6.2.0"
cssesc "^3.0.0"
@@ -3051,10 +3044,10 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0"
date-fns@^2.19.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.19.0.tgz#65193348635a28d5d916c43ec7ce6fbd145059e1"
integrity sha512-X3bf2iTPgCAQp9wvjOQytnf5vO5rESYRXlPIVcgSbtT5OTScPcsf9eZU+B/YIkKAtYr5WeCii58BgATrNitlWg==
date-fns@^2.20.2:
version "2.20.2"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.20.2.tgz#7f05d1275e1e43c3bdde5998201920098e19c6a1"
integrity sha512-QS0Z8SD/ALhKFvhtU4Fhz+1crsI7fPzBquXmdWay33KJPEU7btro2hnmmErpQRmt2D624B1lbjXQKDUMLnQTmQ==
de-indent@^1.0.2:
version "1.0.2"
@@ -4164,10 +4157,10 @@ globals@^9.18.0:
resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==
globby@11.0.2:
version "11.0.2"
resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.2.tgz#1af538b766a3b540ebfb58a32b2e2d5897321d83"
integrity sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og==
globby@11.0.3:
version "11.0.3"
resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.3.tgz#9b1f0cb523e171dd1ad8c7b2a9fb4b644b9593cb"
integrity sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==
dependencies:
array-union "^2.1.0"
dir-glob "^3.0.1"
@@ -5988,22 +5981,22 @@ micromatch@^4.0.2:
braces "^3.0.1"
picomatch "^2.0.5"
mime-db@1.46.0:
version "1.46.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.46.0.tgz#6267748a7f799594de3cbc8cde91def349661cee"
integrity sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==
mime-db@1.47.0:
version "1.47.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.47.0.tgz#8cb313e59965d3c05cfbf898915a267af46a335c"
integrity sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==
"mime-db@>= 1.43.0 < 2":
version "1.45.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea"
integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==
mime-types@2.1.29, mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24:
version "2.1.29"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.29.tgz#1d4ab77da64b91f5f72489df29236563754bb1b2"
integrity sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==
mime-types@2.1.30, mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24:
version "2.1.30"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.30.tgz#6e7be8b4c479825f85ed6326695db73f9305d62d"
integrity sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==
dependencies:
mime-db "1.46.0"
mime-db "1.47.0"
mime@1.6.0, mime@^1.3.4:
version "1.6.0"
@@ -6037,10 +6030,10 @@ min-document@^2.19.0:
dependencies:
dom-walk "^0.1.0"
mini-css-extract-plugin@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.4.0.tgz#c8e571c4b6d63afa56c47260343adf623349c473"
integrity sha512-DyQr5DhXXARKZoc4kwvCvD95kh69dUupfuKOmBUqZ4kBTmRaRZcU32lYu3cLd6nEGXhQ1l7LzZ3F/CjItaY6VQ==
mini-css-extract-plugin@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.4.1.tgz#975e27c1d0bd8e052972415f47c79cea5ed37548"
integrity sha512-COAGbpAsU0ioFzj+/RRfO5Qv177L1Z/XAx2EmCF33b8GDDqKygMffBTws2lit8iaPdrbKEY5P+zsseBUCREZWQ==
dependencies:
loader-utils "^2.0.0"
schema-utils "^3.0.0"
@@ -6874,10 +6867,10 @@ postcss@^7.0.14:
source-map "^0.6.1"
supports-color "^6.1.0"
postcss@^8.2.8, postcss@^8.2.9:
version "8.2.9"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.9.tgz#fd95ff37b5cee55c409b3fdd237296ab4096fba3"
integrity sha512-b+TmuIL4jGtCHtoLi+G/PisuIl9avxs8IZMSmlABRwNz5RLUUACrC+ws81dcomz1nRezm5YPdXiMEzBEKgYn+Q==
postcss@^8.2.10, postcss@^8.2.8:
version "8.2.10"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.10.tgz#ca7a042aa8aff494b334d0ff3e9e77079f6f702b"
integrity sha512-b/h7CPV7QEdrqIxtAf2j31U5ef05uBDuvoXv6L51Q4rcS1jdlXAVKJv+atCFdUXYl9dyTHGyoMzIepwowRJjFw==
dependencies:
colorette "^1.2.2"
nanoid "^3.1.22"
@@ -7233,13 +7226,13 @@ relateurl@^0.2.7:
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=
release-it@^14.5.0:
version "14.5.0"
resolved "https://registry.yarnpkg.com/release-it/-/release-it-14.5.0.tgz#84ced16247b2ea92bc8e7b06c87daa60d5ce2b7e"
integrity sha512-3xwKx3B5i4TVMlCZsNXOCc/S3iviQmi64FJExsvMg5ZjlbBsEYF4qbPLVMq308i7MeDWhubiFHIb3XYuh8pt6Q==
release-it@^14.6.1:
version "14.6.1"
resolved "https://registry.yarnpkg.com/release-it/-/release-it-14.6.1.tgz#a04623312d67886b37b8a6d298844ee3e0c7150a"
integrity sha512-noBho2997G3yrm6YvdLJj4Ua2SCFOU7ajCqtvteI3DZtpM1IhiyXSgcn2Q5irq8lTNK0it4eiNq9TSrAWNYDkA==
dependencies:
"@iarna/toml" "2.2.5"
"@octokit/rest" "18.3.5"
"@octokit/rest" "18.5.2"
async-retry "1.3.1"
chalk "4.1.0"
cosmiconfig "7.0.0"
@@ -7249,17 +7242,17 @@ release-it@^14.5.0:
find-up "5.0.0"
form-data "4.0.0"
git-url-parse "11.4.4"
globby "11.0.2"
globby "11.0.3"
got "11.8.2"
import-cwd "3.0.0"
inquirer "8.0.0"
is-ci "3.0.0"
lodash "4.17.21"
mime-types "2.1.29"
mime-types "2.1.30"
ora "5.4.0"
os-name "4.0.0"
parse-json "5.2.0"
semver "7.3.4"
semver "7.3.5"
shelljs "0.8.4"
update-notifier "5.1.0"
url-join "4.0.1"
@@ -7601,10 +7594,10 @@ semver@7.0.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
semver@7.3.4:
version "7.3.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97"
integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==
semver@7.3.5, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5:
version "7.3.5"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
dependencies:
lru-cache "^6.0.0"
@@ -7613,13 +7606,6 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
semver@^7.3.2, semver@^7.3.4, semver@^7.3.5:
version "7.3.5"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
dependencies:
lru-cache "^6.0.0"
send@0.17.1:
version "0.17.1"
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
@@ -8808,10 +8794,10 @@ webpack-sources@^2.1.1:
source-list-map "^2.0.1"
source-map "^0.6.1"
webpack@^5.30.0:
version "5.30.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.30.0.tgz#07d87c182a060e0c2491062f3dc0edc85a29d884"
integrity sha512-Zr9NIri5yzpfmaMea2lSMV1UygbW0zQsSlGLMgKUm63ACXg6alhd1u4v5UBSBjzYKXJN6BNMGVM7w165e7NxYA==
webpack@^5.32.0:
version "5.32.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.32.0.tgz#f013932d778dad81bd51292d0ea00865056dc22c"
integrity sha512-jB9PrNMFnPRiZGnm/j3qfNqJmP3ViRzkuQMIf8za0dgOYvSLi/cgA+UEEGvik9EQHX1KYyGng5PgBTTzGrH9xg==
dependencies:
"@types/eslint-scope" "^3.7.0"
"@types/estree" "^0.0.46"