Compare commits

...

107 Commits

Author SHA1 Message Date
b84b3ba9f4 chore: 🔧 Changes that do not modify src or test files 2023-02-23 21:50:35 +04:00
5343d8c934 docs(changelog): 📚 Changelog file updated 2023-02-23 21:50:31 +04:00
80891b0b46 ci: 👷 CI system updated 2023-02-23 21:50:20 +04:00
48313685ec chore: Module name changed, deps updated 2023-02-23 21:49:45 +04:00
7dc47c00ac chore(deps): update golangci/golangci-lint docker tag to v1.51 (#174)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-02 17:08:43 +04:00
830b5bb934 chore(deps): update golang docker tag to v1.20 (#173)
* chore(deps): update golang docker tag to v1.20

* updated

* update

* update

* Update .github/workflows/tests.yml

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Paramtamtam <7326800+tarampampam@users.noreply.github.com>
2023-02-02 13:09:29 +04:00
8d43984644 Bump github.com/urfave/cli/v2 from 2.24.1 to 2.24.2 (#168) 2023-02-02 07:25:44 +00:00
1b152c1a80 Bump github.com/fasthttp/router from 1.4.14 to 1.4.16 (#169) 2023-02-02 03:26:30 +00:00
175b9f0cfb Bump go.uber.org/goleak from 1.1.11 to 1.2.0 (#170) 2023-02-02 03:25:57 +00:00
16180be7e2 Bump github.com/valyala/fasthttp from 1.43.0 to 1.44.0 (#171) 2023-02-02 03:22:36 +00:00
11b270ee68 Bump github.com/fatih/color from 1.13.0 to 1.14.1 (#172) 2023-02-02 03:22:15 +00:00
97ab8a4475 Bump docker/build-push-action from 3 to 4 (#167) 2023-02-02 03:20:00 +00:00
da3b864e02 fix: docker-compose server running command 2023-01-29 15:56:58 +04:00
0bd989e493 docs(changelog): file updated 2023-01-29 15:49:28 +04:00
59c4d2022c ci: workflow files have been updated 2023-01-29 15:39:49 +04:00
1ec17caa1d feat: Possibility to use custom env variables in templates (#165) 2023-01-29 15:25:38 +04:00
252618a975 chore: Better CLI (#163) 2023-01-29 14:54:56 +04:00
315c7660d1 Update README.md 2022-12-27 11:41:51 +04:00
6a6809b07f Bump go.uber.org/zap from 1.23.0 to 1.24.0 (#159) 2022-12-02 06:09:55 +00:00
a960b5928e Bump github.com/valyala/fasthttp from 1.41.0 to 1.43.0 (#157) 2022-12-02 06:06:16 +00:00
46d96d7bb4 Bump github.com/prometheus/client_golang from 1.13.0 to 1.14.0 (#160) 2022-12-02 06:02:10 +00:00
69063a9cf7 Bump github.com/fasthttp/router from 1.4.13 to 1.4.14 (#158) 2022-12-02 06:01:28 +00:00
7c8d2f54c7 Update orangeopensource/hurl Docker tag to v1.8.0 (#155)
* Update orangeopensource/hurl Docker tag to v1.8.0

* wip: temporary commit

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Paramtamtam <7326800+tarampampam@users.noreply.github.com>
2022-11-18 23:46:46 +04:00
49aed23f8c Update README.md 2022-11-15 13:06:39 +04:00
3418f85292 Update CHANGELOG.md 2022-11-02 01:38:14 +04:00
361afd87aa Bump github.com/stretchr/testify from 1.8.0 to 1.8.1 (#152)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.0...v1.8.1)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-02 01:33:17 +04:00
7f6815c274 Bump github.com/spf13/cobra from 1.5.0 to 1.6.1 (#151) 2022-11-01 21:31:51 +00:00
4ecd70330a Bump aquasecurity/trivy-action from 0.7.1 to 0.8.0 (#148) 2022-11-01 21:28:27 +00:00
ae13905512 Bump github.com/prometheus/client_model from 0.2.0 to 0.3.0 (#149) 2022-11-01 21:27:39 +00:00
e3377d0f28 Bump github.com/fasthttp/router from 1.4.12 to 1.4.13 (#153) 2022-11-01 21:27:23 +00:00
b15061a110 wip: Readme file updated 2022-11-02 01:25:19 +04:00
CDN
6d6945bf44 Add 🇨🇳 Chinese Translation (#147) 2022-11-02 01:23:29 +04:00
cf7c526d4f golangci-lint issues fixed 2022-11-02 01:13:03 +04:00
8e21be0340 Update golangci/golangci-lint Docker tag to v1.50 (#144)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-04 15:41:42 +04:00
37265ccb4f Bump github.com/fasthttp/router from 1.4.11 to 1.4.12 (#142) 2022-10-02 05:28:04 +00:00
169fbe3b93 Bump github.com/valyala/fasthttp from 1.39.0 to 1.40.0 (#143) 2022-10-02 02:22:55 +00:00
617b378c36 Update orangeopensource/hurl Docker tag to v1.7.0 (#141)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-24 10:31:48 +04:00
438e954dd6 Bump go.uber.org/zap from 1.21.0 to 1.23.0 (#135) 2022-09-02 02:57:20 +00:00
df1a0e20ee Bump aquasecurity/trivy-action from 0.6.1 to 0.7.1 (#137) 2022-09-02 02:53:52 +00:00
7b3c286790 Bump github.com/valyala/fasthttp from 1.38.0 to 1.39.0 (#136) 2022-09-02 02:53:34 +00:00
fb7d7c75cf Bump github.com/prometheus/client_golang from 1.12.2 to 1.13.0 (#138) 2022-09-02 02:52:41 +00:00
1eafe58d16 Update golang Docker tag to v1.19 (#127)
* Update golang Docker tag to v1.19

* wip: temporary commit

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Paramtamtam <7326800+tarampampam@users.noreply.github.com>
2022-08-24 21:38:26 +04:00
e7a909dc4e Update golangci/golangci-lint Docker tag to v1.49 (#134)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-24 20:38:41 +04:00
9deee9ddba replace google by bunny fonts (#131) 2022-08-19 23:19:10 +04:00
e769c2103f Update dependency golangci/golangci-lint to v1.48 (#128)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-05 06:05:19 +04:00
ca9cdf0379 Bump github.com/fasthttp/router from 1.4.10 to 1.4.11 (#125) 2022-08-02 04:56:43 +00:00
7ef471381c Bump aquasecurity/trivy-action from 0.5.1 to 0.6.1 (#126) 2022-08-02 04:56:24 +00:00
48e9b20836 go get -u golang.org/x/sys 2022-07-26 21:49:19 +04:00
7329d7697c Readme & changelog files are updated 2022-07-25 11:13:18 +04:00
83f38cdd16 Spanish Translation (#124) 2022-07-25 11:05:06 +04:00
057006366d Update dependency golangci/golangci-lint to v1.47 (#123)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-18 19:32:55 +04:00
f08539962e Bump github.com/valyala/fasthttp from 1.37.0 to 1.38.0 (#122) 2022-07-02 11:06:00 +00:00
2cc8549cef Bump github.com/spf13/cobra from 1.4.0 to 1.5.0 (#121) 2022-07-02 11:02:45 +00:00
8f8e5abd3d Bump github.com/fasthttp/router from 1.4.9 to 1.4.10 (#120) 2022-07-02 10:59:19 +00:00
1889a57c05 Bump github.com/stretchr/testify from 1.7.1 to 1.8.0 (#119) 2022-07-02 10:58:56 +00:00
c42ff85dd6 Bump aquasecurity/trivy-action from 0.3.0 to 0.5.1 (#118) 2022-07-02 10:58:42 +00:00
00d3c10e5e Update tests.yml 2022-06-12 13:38:17 +05:00
7153c260d8 Update dependency orangeopensource/hurl to v1.6.1 (#117)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-12 13:11:52 +05:00
48bd1a44e6 Configure Renovate (#116)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
Co-authored-by: Paramtamtam <7326800+tarampampam@users.noreply.github.com>
2022-06-12 12:55:49 +05:00
bde35e2c79 Bump golang from 1.18.1-alpine to 1.18.3-alpine (#114) 2022-06-05 09:22:08 +00:00
d649e371a5 Update CHANGELOG.md 2022-06-04 17:11:20 +05:00
01c2a37055 Add 🇩🇪 German translation (#115)
* german translation

* update readmes and changelog
2022-06-04 17:06:39 +05:00
a932f94ec0 Bump docker/setup-buildx-action from 1 to 2 (#111) 2022-06-02 05:14:32 +00:00
3ac2c74249 Bump github.com/valyala/fasthttp from 1.36.0 to 1.37.0 (#106) 2022-06-02 05:14:08 +00:00
18af96bada Bump github.com/prometheus/client_golang from 1.12.1 to 1.12.2 (#108) 2022-06-02 05:10:46 +00:00
445aad8b41 Bump docker/login-action from 1 to 2 (#109) 2022-06-02 05:10:27 +00:00
b61cc7460f Bump github.com/fasthttp/router from 1.4.8 to 1.4.9 (#110) 2022-06-02 05:10:14 +00:00
c9586fe79a Bump aquasecurity/trivy-action from 0.2.5 to 0.3.0 (#112) 2022-06-02 05:09:47 +00:00
405afec38a Bump docker/setup-qemu-action from 1 to 2 (#107) 2022-06-02 05:09:16 +00:00
5e0be010b7 Bump docker/build-push-action from 2 to 3 (#113) 2022-06-02 05:09:00 +00:00
9bc00fa4ca Update release.yml 2022-05-12 17:20:02 +05:00
6742381562 Update tests.yml 2022-05-12 17:18:45 +05:00
6d3ced480d Add 🇳🇱 Dutch translation (#104)
Co-authored-by: Paramtamtam <7326800+tarampampam@users.noreply.github.com>
2022-05-07 15:09:23 +05:00
e8fa8896c9 Update CHANGELOG.md 2022-05-06 22:28:42 +05:00
c9bd47618d adding translation to Portuguese language (#103)
Co-authored-by: Fabio Correia <fabiocorreia@trixlog.com>
Co-authored-by: Paramtamtam <7326800+tarampampam@users.noreply.github.com>
2022-05-06 22:28:12 +05:00
3ffb952cdd Bump golang from 1.18.0-alpine to 1.18.1-alpine (#95) 2022-05-02 06:54:34 +00:00
b5892f44d9 Bump github.com/valyala/fasthttp from 1.34.0 to 1.36.0 (#96) 2022-05-02 04:24:25 +00:00
769b0cebb6 Bump github.com/fasthttp/router from 1.4.7 to 1.4.8 (#98) 2022-05-02 04:18:32 +00:00
8f49ff7204 Bump github/codeql-action from 1 to 2 (#99) 2022-05-02 04:18:12 +00:00
f89bdfbd51 Bump aquasecurity/trivy-action from 0.2.2 to 0.2.5 (#97) 2022-05-02 04:17:53 +00:00
1b2e899201 Bump actions/download-artifact from 2 to 3 (#100) 2022-05-02 04:17:31 +00:00
6c0c85544e Bump actions/upload-artifact from 2 to 3 (#101) 2022-05-02 04:17:11 +00:00
d3d1c62411 Bump codecov/codecov-action from 2 to 3 (#102) 2022-05-02 04:16:54 +00:00
8019d07cab Update CHANGELOG.md 2022-04-12 15:35:19 +05:00
d21a6f2797 Added possibility to disable error pages auto-localization (#94) 2022-04-12 15:34:35 +05:00
a3389aaafa Changing UID/GID to the numeric values (#93) 2022-04-09 20:39:52 +05:00
c6a7e30609 Bump github.com/fasthttp/router from 1.4.6 to 1.4.7 (#90) 2022-04-01 22:07:18 +00:00
01abc48a01 CI updated 2022-04-01 13:14:16 +05:00
d6374d7edf Bump actions/setup-go from 2 to 3 2022-04-01 13:01:22 +05:00
7ebfac9dc2 Bump actions/cache from 2 to 3.0.1 (#89) 2022-04-01 07:51:10 +00:00
64d4798156 Bump peter-evans/dockerhub-description from 2 to 3 (#88) 2022-04-01 07:48:54 +00:00
4adad3df10 CI updated (#87) 2022-03-30 21:23:42 +05:00
30a7b2793f Fix translation FR (#86) 2022-03-30 18:00:32 +05:00
2d9deb7370 CI now purges the CDN cache (#84) 2022-03-28 22:13:19 +05:00
873944f90f Changelog updated 2022-03-28 16:05:44 +05:00
cd5abe458b l10n file formatted, CI updated (#83) 2022-03-28 16:04:23 +05:00
481e11d527 Error pages now translated in 🇫🇷 (#82) 2022-03-28 15:11:44 +05:00
fac7394ae2 Update CHANGELOG.md 2022-03-27 20:38:04 +05:00
4a918b1899 Template matrix (#81) 2022-03-27 20:33:31 +05:00
05be3841d7 Update .gitattributes 2022-03-25 14:51:50 +05:00
02cadcd907 Create .gitattributes 2022-03-25 14:46:59 +05:00
94dff2421c Changelog updated 2022-03-24 13:54:50 +05:00
51f8824659 shuffle template fixed 2022-03-24 13:54:07 +05:00
e82c02c768 fix the translation mistakes 2022-03-24 12:28:03 +05:00
dc51e3192c Update CHANGELOG.md 2022-03-24 00:32:40 +05:00
45ca69432b Translated in 🇺🇦 and 🇷🇺 languages (#80) 2022-03-24 00:31:34 +05:00
f5f572a4d3 small template fixes 2022-03-23 12:30:33 +05:00
76 changed files with 2988 additions and 1626 deletions

View File

@ -1,14 +1,9 @@
.dockerignore
Dockerfile
.github
.git
.gitignore
.editorconfig
.idea
.vscode
test
temp
tmp
LICENSE
Makefile
error-pages
## Ignore everything
*
## Except the following files and directories
!/cmd
!/internal
!/templates
!/error-pages.yml
!/go.*

View File

@ -7,11 +7,8 @@ charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
[*.{yml, yaml, sh, conf}]
indent_size = 2
trim_trailing_whitespace = true
[{Makefile, go.mod, *.go}]
indent_style = tab

9
.gitattributes vendored Normal file
View File

@ -0,0 +1,9 @@
# Text files have auto line endings
* text=auto
# Go source files always have LF line endings
*.go text eol=lf
# Disable next extensions in project "used languages" list
*.lua linguist-detectable=false
*.html linguist-detectable=false

7
.github/renovate.json vendored Normal file
View File

@ -0,0 +1,7 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"github>tarampampam/.github//renovate/default",
":rebaseStalePrs"
]
}

View File

@ -8,11 +8,11 @@ on:
jobs:
docker-hub-description:
name: Docker Hub Description
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: peter-evans/dockerhub-description@v2 # Action page: <https://github.com/peter-evans/dockerhub-description>
- uses: peter-evans/dockerhub-description@v3 # Action page: <https://github.com/peter-evans/dockerhub-description>
with:
username: ${{ secrets.DOCKER_LOGIN }}
password: ${{ secrets.DOCKER_USER_PASSWORD }}

View File

@ -5,22 +5,33 @@ on:
types: [published]
jobs:
purge-cdn-cache:
name: Purge jsDelivr CDN cache
runs-on: ubuntu-latest
steps:
- uses: gacts/purge-jsdelivr-cache@v1 # Action page: <https://github.com/gacts/purge-jsdelivr-cache>
with:
url: |
https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.js
https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js
https://cdn.jsdelivr.net/gh/tarampampam/error-pages@latest/l10n/l10n.js
https://cdn.jsdelivr.net/gh/tarampampam/error-pages@latest/l10n/l10n.min.js
build:
name: Build for ${{ matrix.os }} (${{ matrix.arch }})
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os: [linux, darwin] # linux, freebsd, darwin, windows
arch: [amd64] # amd64, 386
steps:
- uses: actions/setup-go@v2
with: {go-version: 1.18.0}
- uses: actions/checkout@v3
- uses: gacts/github-slug@v1
id: slug
- uses: gacts/setup-go-with-cache@v1
with: {go-version-file: go.mod}
- {uses: gacts/github-slug@v1, id: slug}
- name: Generate builder values
id: values
@ -31,7 +42,7 @@ jobs:
GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }}
CGO_ENABLED: 0
LDFLAGS: -s -w -X github.com/tarampampam/error-pages/internal/version.version=${{ steps.slug.outputs.version }}
LDFLAGS: -s -w -X gh.tarampamp.am/error-pages/internal/version.version=${{ steps.slug.outputs.version }}
run: go build -trimpath -ldflags "$LDFLAGS" -o "./${{ steps.values.outputs.binary-name }}" ./cmd/error-pages/
- name: Upload binary file to release
@ -44,29 +55,28 @@ jobs:
docker-image:
name: Build docker image
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: gacts/github-slug@v1
id: slug
- {uses: gacts/github-slug@v1, id: slug}
- uses: docker/setup-qemu-action@v1 # Action page: <https://github.com/docker/setup-qemu-action>
- uses: docker/setup-qemu-action@v2 # Action page: <https://github.com/docker/setup-qemu-action>
- uses: docker/setup-buildx-action@v1 # Action page: <https://github.com/docker/setup-buildx-action>
- uses: docker/setup-buildx-action@v2 # Action page: <https://github.com/docker/setup-buildx-action>
- uses: docker/login-action@v1 # Action page: <https://github.com/docker/login-action>
- uses: docker/login-action@v2 # Action page: <https://github.com/docker/login-action>
with:
username: ${{ secrets.DOCKER_LOGIN }}
password: ${{ secrets.DOCKER_PASSWORD }}
- uses: docker/login-action@v1 # Action page: <https://github.com/docker/login-action>
- uses: docker/login-action@v2 # Action page: <https://github.com/docker/login-action>
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GHCR_PASSWORD }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v2 # Action page: <https://github.com/docker/build-push-action>
- uses: docker/build-push-action@v4 # Action page: <https://github.com/docker/build-push-action>
with:
context: .
file: Dockerfile
@ -75,17 +85,20 @@ jobs:
build-args: "APP_VERSION=${{ steps.slug.outputs.version }}"
tags: |
tarampampam/error-pages:${{ steps.slug.outputs.version }}
tarampampam/error-pages:${{ steps.slug.outputs.version-major }}.${{ steps.slug.outputs.version-minor }}
tarampampam/error-pages:${{ steps.slug.outputs.version-major }}
tarampampam/error-pages:latest
ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:${{ steps.slug.outputs.version }}
ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:${{ steps.slug.outputs.version-major }}.${{ steps.slug.outputs.version-minor }}
ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:${{ steps.slug.outputs.version-major }}
ghcr.io/${{ github.actor }}/${{ github.event.repository.name }}:latest
demo:
name: Update the demonstration
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
needs: [docker-image]
steps:
- uses: gacts/github-slug@v1
id: slug
- {uses: gacts/github-slug@v1, id: slug}
- name: Take rendered templates from the built docker image
run: |

View File

@ -8,39 +8,41 @@ on:
pull_request:
paths-ignore: ['**.md']
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs: # Docs: <https://git.io/JvxXE>
gitleaks:
name: Gitleaks
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with: {fetch-depth: 0}
- uses: zricethezav/gitleaks-action@v1 # Action page: <https://github.com/zricethezav/gitleaks-action>
- name: Check for GitLeaks
uses: gacts/gitleaks@v1 # Action page: <https://github.com/gacts/gitleaks>
golangci-lint:
name: Golang-CI (lint)
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v2
with: {go-version: 1.17} # On v1.18 I had an error "panic: load embedded ruleguard rules: rules/rules.go:13: can't load fmt"
- uses: gacts/setup-go-with-cache@v1
with: {go-version: 1.19}
- name: Run linter
uses: golangci/golangci-lint-action@v3 # Action page: <https://github.com/golangci/golangci-lint-action>
with:
version: v1.44 # without patch version
only-new-issues: false # show only new issues if it's a pull request
- uses: golangci/golangci-lint-action@v3
with: {skip-pkg-cache: true, skip-build-cache: true}
validate-config-file:
name: Validate config file
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with: {node-version: '16'}
- uses: gacts/setup-node-with-cache@v1
with: {node-version: 16}
- name: Install linter
run: npm install -g ajv-cli # Package page: <https://www.npmjs.com/package/ajv-cli>
@ -48,31 +50,36 @@ jobs: # Docs: <https://git.io/JvxXE>
- name: Run linter
run: ajv validate --all-errors --verbose -s ./schemas/config/1.0.schema.json -d ./error-pages.y*ml
lint-l10n:
name: Lint l10n file(s)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: gacts/setup-node-with-cache@v1
with: {node-version: 16}
- name: Install eslint
run: npm install -g eslint@v8 # Package page: <https://www.npmjs.com/package/eslint>
- name: Run linter
working-directory: l10n
run: eslint ./*.js
go-test:
name: Unit tests
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v2
with: {go-version: 1.18}
- uses: actions/checkout@v3
with: {fetch-depth: 2} # Fixes codecov error 'Issue detecting commit SHA'
- name: Go modules Cache # Docs: <https://git.io/JfAKn#go---modules>
uses: actions/cache@v2
id: go-cache
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-go-
- if: steps.go-cache.outputs.cache-hit != 'true'
run: go mod download
- uses: gacts/setup-go-with-cache@v1
with: {go-version-file: go.mod}
- name: Run Unit tests
run: go test -race -covermode=atomic -coverprofile /tmp/coverage.txt ./...
- uses: codecov/codecov-action@v2 # https://github.com/codecov/codecov-action
- uses: codecov/codecov-action@v3 # https://github.com/codecov/codecov-action
continue-on-error: true
with:
file: /tmp/coverage.txt
@ -80,7 +87,7 @@ jobs: # Docs: <https://git.io/JvxXE>
build:
name: Build for ${{ matrix.os }} (${{ matrix.arch }})
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
@ -88,36 +95,26 @@ jobs: # Docs: <https://git.io/JvxXE>
arch: [amd64] # amd64, 386
needs: [golangci-lint, go-test, validate-config-file]
steps:
- uses: actions/setup-go@v2
with: {go-version: 1.18}
- uses: actions/checkout@v3
- uses: gacts/github-slug@v1
id: slug
- uses: gacts/setup-go-with-cache@v1
with: {go-version-file: go.mod}
- name: Go modules Cache # Docs: <https://git.io/JfAKn#go---modules>
uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-go-
- run: go mod download
- {uses: gacts/github-slug@v1, id: slug}
- name: Build application
env:
GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }}
CGO_ENABLED: 0
LDFLAGS: -s -w -X github.com/tarampampam/error-pages/internal/version.version=${{ steps.slug.outputs.branch-name-slug }}@${{ steps.slug.outputs.commit-hash-short }}
LDFLAGS: -s -w -X gh.tarampamp.am/error-pages/internal/version.version=${{ steps.slug.outputs.branch-name-slug }}@${{ steps.slug.outputs.commit-hash-short }}
run: go build -trimpath -ldflags "$LDFLAGS" -o ./error-pages ./cmd/error-pages/
- name: Try to execute
if: matrix.os == 'linux'
run: ./error-pages version && ./error-pages -h
run: ./error-pages --version && ./error-pages -h
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
with:
name: error-pages-${{ matrix.os }}-${{ matrix.arch }}
path: error-pages
@ -126,12 +123,12 @@ jobs: # Docs: <https://git.io/JvxXE>
generate:
name: Run templates generator
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
needs: [build]
steps:
- uses: actions/checkout@v3
- uses: actions/download-artifact@v2
- uses: actions/download-artifact@v3
with:
name: error-pages-linux-amd64
path: .artifact
@ -141,7 +138,7 @@ jobs: # Docs: <https://git.io/JvxXE>
run: mv ./error-pages ./../error-pages && chmod +x ./../error-pages
- name: Run generator
run: ./error-pages build ./out --verbose --index
run: ./error-pages --verbose build --index ./out
- name: Test files creation
run: |
@ -156,18 +153,18 @@ jobs: # Docs: <https://git.io/JvxXE>
test -f ./out/lost-in-space/404.html
test -f ./out/app-down/404.html
test -f ./out/connection/404.html
test -f ./out/matrix/404.html
docker-image:
name: Build docker image
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
needs: [golangci-lint, go-test, validate-config-file]
steps:
- uses: actions/checkout@v3
- uses: gacts/github-slug@v1
id: slug
- {uses: gacts/github-slug@v1, id: slug}
- uses: docker/build-push-action@v2 # Action page: <https://github.com/docker/build-push-action>
- uses: docker/build-push-action@v4 # Action page: <https://github.com/docker/build-push-action>
with:
context: .
file: Dockerfile
@ -177,7 +174,7 @@ jobs: # Docs: <https://git.io/JvxXE>
- run: docker save app:ci > ./docker-image.tar
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
with:
name: docker-image
path: ./docker-image.tar
@ -185,17 +182,17 @@ jobs: # Docs: <https://git.io/JvxXE>
scan-docker-image:
name: Scan the docker image
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
needs: [docker-image]
steps:
- uses: actions/checkout@v3 # is needed for `upload-sarif` action
- uses: actions/download-artifact@v2
- uses: actions/download-artifact@v3
with:
name: docker-image
path: .artifact
- uses: aquasecurity/trivy-action@0.2.2 # action page: <https://github.com/aquasecurity/trivy-action>
- uses: aquasecurity/trivy-action@0.8.0 # action page: <https://github.com/aquasecurity/trivy-action>
with:
input: .artifact/docker-image.tar
format: sarif
@ -203,20 +200,20 @@ jobs: # Docs: <https://git.io/JvxXE>
exit-code: 1
output: trivy-results.sarif
- uses: github/codeql-action/upload-sarif@v1
- uses: github/codeql-action/upload-sarif@v2
if: always()
continue-on-error: true
with: {sarif_file: trivy-results.sarif}
poke-docker-image:
name: Run the docker image
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
needs: [docker-image]
timeout-minutes: 2
steps:
- uses: actions/checkout@v3
- uses: actions/download-artifact@v2
- uses: actions/download-artifact@v3
with:
name: docker-image
path: .artifact
@ -224,13 +221,7 @@ jobs: # Docs: <https://git.io/JvxXE>
- working-directory: .artifact
run: docker load < docker-image.tar
- name: Download hurl
env:
VERSION: 1.5.0
run: curl -SL -o hurl.deb https://github.com/Orange-OpenSource/hurl/releases/download/${VERSION}/hurl_${VERSION}_amd64.deb
- name: Install hurl
run: sudo dpkg -i hurl.deb
- uses: gacts/install-hurl@v1
- name: Run container with the app
run: docker run --rm -d -p "8080:8080/tcp" -e "SHOW_DETAILS=true" -e "PROXY_HTTP_HEADERS=X-Foo,Bar,Baz_blah" --name app app:ci
@ -238,7 +229,7 @@ jobs: # Docs: <https://git.io/JvxXE>
- name: Wait for container "healthy" state
run: until [[ "`docker inspect -f {{.State.Health.Status}} app`" == "healthy" ]]; do echo "wait 1 sec.."; sleep 1; done
- run: hurl --color --test --fail-at-end --variable host=127.0.0.1 --variable port=8080 --summary ./test/hurl/*.hurl
- run: hurl --color --test --fail-at-end --variable host=127.0.0.1 --variable port=8080 ./test/hurl/*.hurl
- name: Stop the container
if: always()

View File

@ -43,9 +43,6 @@ linters: # All available linters list: <https://golangci-lint.run/usage/linters/
enable:
- asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers
- bidichk # Checks for dangerous unicode character sequences
- bodyclose # Checks whether HTTP response body is closed successfully
- contextcheck # check the function whether use a non-inherited context
- deadcode # Finds unused code
- depguard # Go linter that checks if package imports are in a list of acceptable packages
- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())
- dupl # Tool for code clone detection
@ -73,19 +70,13 @@ linters: # All available linters list: <https://golangci-lint.run/usage/linters/
- nakedret # Finds naked returns in functions greater than a specified function length
- nestif # Reports deeply nested if statements
- nlreturn # checks for a new line before return and branch statements to increase code clarity
- noctx # finds sending http request without context.Context
- nolintlint # Reports ill-formed or insufficient nolint directives
- prealloc # Finds slice declarations that could potentially be preallocated
- rowserrcheck # Checks whether Err of rows is checked successfully
- staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks
- structcheck # Finds unused struct fields
- stylecheck # Stylecheck is a replacement for golint
- tparallel # detects inappropriate usage of t.Parallel() method in your Go test codes
- typecheck # Like the front-end of a Go compiler, parses and type-checks Go code
- unconvert # Remove unnecessary type conversions
- unparam # Reports unused function parameters
- unused # Checks Go code for unused constants, variables, functions and types
- varcheck # Finds unused global variables and constants
- whitespace # Tool for detection of leading and trailing whitespace
- wsl # Whitespace Linter - Forces you to use empty lines!
@ -97,4 +88,3 @@ issues:
- funlen
- scopelint
- gocognit
- noctx

View File

@ -4,6 +4,142 @@ All notable changes to this package will be documented in this file.
The format is based on [Keep a Changelog][keepachangelog] and this project adheres to [Semantic Versioning][semver].
## v2.21.0
### Changed
- Go updated from `1.19` up to `1.20`
- Go dependencies updated
- Module name changed from `github.com/tarampampam/error-pages` to `gh.tarampamp.am/error-pages`
## v2.20.0
### Changed
- `version` subcommand replaced by `--version` flag [#163]
- `--config-file` flag is not global anymore (use `error-pages (serve|build) --config-file ...` instead of `error-pages --config-file ... (serve|build) ...`) [#163]
- Flags `--verbose`, `--debug` and `--log-json` are deprecated, use `--log-level` and `--log-format` instead [#163]
### Added
- Possibility to use custom env variables in templates [#164], [#165]
[#164]:https://github.com/tarampampam/error-pages/issues/164
[#165]:https://github.com/tarampampam/error-pages/pull/165
[#163]:https://github.com/tarampampam/error-pages/pull/163
## v2.19.0
### Changed
- Go updated from `1.18` up to `1.19`
### Added
- Error pages now translated into Chinese 🇨🇳 [#147]
[#147]:https://github.com/tarampampam/error-pages/pull/147
## v2.18.0
### Changed
- Replaced `fonts.googleapis.com` by `fonts.bunny.net` regarding GDPR compliance [#131]
[#131]:https://github.com/tarampampam/error-pages/pull/131
## v2.17.0
### Added
- Error pages now translated into Spanish 🇪🇸 [#124]
[#124]:https://github.com/tarampampam/error-pages/pull/124
## v2.16.0
### Added
- Error pages are now translated into German 🇩🇪 [#115]
[#115]:https://github.com/tarampampam/error-pages/pull/115
## v2.15.0
### Added
- Error pages now translated into Dutch 🇳🇱 [#104]
[#104]:https://github.com/tarampampam/error-pages/pull/104
## v2.14.0
### Added
- Error pages now translated into Portuguese 🇵🇹 [#103]
### Changed
- Go updated from `1.18.0` up to `1.18.1`
[#103]:https://github.com/tarampampam/error-pages/pull/103
## v2.13.0
### Added
- Possibility to disable error pages auto-localization (using `--disable-l10n` flag for the `serve` & `build` commands or environment variable `DISABLE_L10N`) [#91]
### Fixed
- User UID/GID changed to the numeric values in the dockerfile [#92]
[#92]:https://github.com/tarampampam/error-pages/issues/92
[#91]:https://github.com/tarampampam/error-pages/issues/91
## v2.12.1
### Fixed
- Fix translation 🇫🇷 [#86]
[#85]:https://github.com/tarampampam/error-pages/pull/86
## v2.12.0
### Changed
- Error pages now translated into 🇫🇷 [#82]
[#82]:https://github.com/tarampampam/error-pages/pull/82
## v2.11.0
### Added
- Template `matrix` [#81]
### Fixed
- Localization mistakes [#81]
[#81]:https://github.com/tarampampam/error-pages/pull/81
## v2.10.1
### Fixed
- Template `shuffle`
- Localization mistakes
## v2.10.0
### Changed
- Error pages now translated into 🇺🇦 and 🇷🇺 languages [#80]
[#80]:https://github.com/tarampampam/error-pages/pull/80
## v2.9.0
### Added

View File

@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1.2
# Image page: <https://hub.docker.com/_/golang>
FROM golang:1.18.0-alpine as builder
FROM golang:1.20-alpine as builder
# can be passed with any prefix (like `v1.2.3@GITHASH`), e.g.: `docker build --build-arg "APP_VERSION=v1.2.3@GITHASH" .`
ARG APP_VERSION="undefined@docker"
@ -11,12 +11,12 @@ WORKDIR /src
COPY . .
# arguments to pass on each go tool link invocation
ENV LDFLAGS="-s -w -X github.com/tarampampam/error-pages/internal/version.version=$APP_VERSION"
ENV LDFLAGS="-s -w -X gh.tarampamp.am/error-pages/internal/version.version=$APP_VERSION"
RUN set -x \
&& go version \
&& CGO_ENABLED=0 go build -trimpath -ldflags "$LDFLAGS" -o ./error-pages ./cmd/error-pages/ \
&& ./error-pages version \
&& ./error-pages --version \
&& ./error-pages -h
WORKDIR /tmp/rootfs
@ -38,7 +38,7 @@ WORKDIR /tmp/rootfs/opt
# generate static error pages (for usage inside another docker images, for example)
RUN set -x \
&& ./../bin/error-pages --config-file ./error-pages.yml build ./html --verbose --index \
&& ./../bin/error-pages --verbose build --config-file ./error-pages.yml --index ./html \
&& ls -l ./html
# use empty filesystem
@ -60,7 +60,7 @@ LABEL \
COPY --from=builder /tmp/rootfs /
# Use an unprivileged user
USER appuser:appuser
USER 10001:10001
WORKDIR /opt
@ -68,11 +68,12 @@ ENV LISTEN_PORT="8080" \
TEMPLATE_NAME="ghost" \
DEFAULT_ERROR_PAGE="404" \
DEFAULT_HTTP_CODE="404" \
SHOW_DETAILS="false"
SHOW_DETAILS="false" \
DISABLE_L10N="false"
# Docs: <https://docs.docker.com/engine/reference/builder/#healthcheck>
HEALTHCHECK --interval=7s --timeout=2s CMD ["/bin/error-pages", "healthcheck", "--log-json"]
HEALTHCHECK --interval=7s --timeout=2s CMD ["/bin/error-pages", "--log-json", "healthcheck"]
ENTRYPOINT ["/bin/error-pages"]
CMD ["serve", "--log-json"]
CMD ["--log-json", "serve"]

View File

@ -3,7 +3,7 @@
# Makefile readme (en): <https://www.gnu.org/software/make/manual/html_node/index.html#SEC_Contents>
SHELL = /bin/sh
LDFLAGS = "-s -w -X github.com/tarampampam/error-pages/internal/version.version=$(shell git rev-parse HEAD)"
LDFLAGS = "-s -w -X gh.tarampamp.am/error-pages/internal/version.version=$(shell git rev-parse HEAD)"
DC_RUN_ARGS = --rm --user "$(shell id -u):$(shell id -g)"
APP_NAME = $(notdir $(CURDIR))
@ -43,7 +43,7 @@ gotest: ## Run app tests
docker-compose run $(DC_RUN_ARGS) --no-deps app go test -v -race -timeout 10s ./...
int-test: ## Run integration tests (docs: https://hurl.dev/docs/man-page.html#options)
docker-compose run --rm hurl --color --test --fail-at-end --variable host=web --variable port=8080 --summary ./test/hurl/*.hurl
docker-compose run --rm hurl --color --test --fail-at-end --variable host=web --variable port=8080 ./test/hurl/*.hurl
test: lint gotest int-test ## Run app tests and linters

View File

@ -5,8 +5,8 @@
<p align="center">
<a href="#"><img src="https://img.shields.io/github/go-mod/go-version/tarampampam/error-pages?longCache=true&label=&logo=go&logoColor=white&style=flat-square" alt="" /></a>
<a href="https://codecov.io/gh/tarampampam/error-pages"><img src="https://img.shields.io/codecov/c/github/tarampampam/error-pages/master.svg?maxAge=30&label=&logo=codecov&logoColor=white&style=flat-square" alt="" /></a>
<a href="https://github.com/tarampampam/error-pages/actions"><img src="https://img.shields.io/github/workflow/status/tarampampam/error-pages/tests?maxAge=30&label=tests&logo=github&style=flat-square" alt="" /></a>
<a href="https://github.com/tarampampam/error-pages/actions"><img src="https://img.shields.io/github/workflow/status/tarampampam/error-pages/release?maxAge=30&label=release&logo=github&style=flat-square" alt="" /></a>
<a href="https://github.com/tarampampam/error-pages/actions"><img src="https://img.shields.io/github/actions/workflow/status/tarampampam/error-pages/tests.yml?branch=master&maxAge=30&label=tests&logo=github&style=flat-square" alt="" /></a>
<a href="https://github.com/tarampampam/error-pages/actions"><img src="https://img.shields.io/github/actions/workflow/status/tarampampam/error-pages/release.yml?maxAge=30&label=release&logo=github&style=flat-square" alt="" /></a>
<a href="https://hub.docker.com/r/tarampampam/error-pages"><img src="https://img.shields.io/docker/pulls/tarampampam/error-pages.svg?maxAge=30&label=pulls&logo=docker&logoColor=white&style=flat-square" alt="" /></a>
<a href="https://hub.docker.com/r/tarampampam/error-pages"><img src="https://img.shields.io/docker/image-size/tarampampam/error-pages/latest?maxAge=30&label=size&logo=docker&logoColor=white&style=flat-square" alt="" /></a>
<a href="https://github.com/tarampampam/error-pages/blob/master/LICENSE"><img src="https://img.shields.io/github/license/tarampampam/error-pages.svg?maxAge=30&style=flat-square" alt="" /></a>
@ -34,13 +34,12 @@ One day you may want to replace the standard error pages of your HTTP server wit
- Error pages can be [embedded into your own `nginx`][wiki-usage-with-nginx] docker image
- Fully configurable (take a look at the [configuration file](https://github.com/tarampampam/error-pages/blob/master/error-pages.yml) and [project Wiki][wiki])
- Distributed using docker image and compiled binary files
- Localized (🇺🇸, 🇫🇷, 🇺🇦, 🇷🇺, 🇵🇹, 🇳🇱, 🇩🇪, 🇪🇸, 🇨🇳) HTML error pages (translation process [described here](https://github.com/tarampampam/error-pages/tree/master/l10n) - other translations are welcome!)
## 🧩 Install
Download the latest binary file for your os/arch from the [releases page][releases] or use our docker image:
[![image stats](https://dockeri.co/image/tarampampam/error-pages)][docker-hub-tags]
| Registry | Image |
|-----------------------------------|-----------------------------------|
| [Docker Hub][docker-hub] | `tarampampam/error-pages` |
@ -123,6 +122,7 @@ Transfer/sec: 140.23MB
| `lost-in-space` | [![lost-in-space][lost-in-space-screen]][lost-in-space-link] |
| `app-down` | [![app-down][app-down-screen]][app-down-link] |
| `connection` | [![connection][connection-screen]][connection-link] |
| `matrix` | [![matrix][matrix-screen]][matrix-link] |
> Note: `noise` template highly uses the CPU, be careful
@ -146,6 +146,8 @@ Transfer/sec: 140.23MB
[app-down-link]:https://tarampampam.github.io/error-pages/app-down/404.html
[connection-screen]:https://hsto.org/webt/x4/ah/jb/x4ahjboo4-arm3bxpaash_sflmw.png
[connection-link]:https://tarampampam.github.io/error-pages/connection/404.html
[matrix-screen]:https://hsto.org/webt/ng/tf/oi/ngtfoiolvmq6hf15kimcxmhprhk.gif
[matrix-link]:https://tarampampam.github.io/error-pages/matrix/404.html
## 🦾 Contributors

View File

@ -5,9 +5,14 @@ import (
"path/filepath"
"github.com/fatih/color"
"github.com/tarampampam/error-pages/internal/cli"
"go.uber.org/automaxprocs/maxprocs"
"gh.tarampamp.am/error-pages/internal/cli"
)
// set GOMAXPROCS to match Linux container CPU quota.
var _, _ = maxprocs.Set(maxprocs.Min(1), maxprocs.Logger(func(_ string, _ ...any) {}))
// exitFn is a function for application exiting.
var exitFn = os.Exit //nolint:gochecknoglobals
@ -17,9 +22,7 @@ func main() { exitFn(run()) }
// run this CLI application.
// Exit codes documentation: <https://tldp.org/LDP/abs/html/exitcodes.html>
func run() int {
cmd := cli.NewCommand(filepath.Base(os.Args[0]))
if err := cmd.Execute(); err != nil {
if err := (cli.NewApp(filepath.Base(os.Args[0]))).Run(os.Args); err != nil {
_, _ = color.New(color.FgHiRed, color.Bold).Fprintln(os.Stderr, err.Error())
return 1

View File

@ -6,36 +6,16 @@ import (
"github.com/kami-zh/go-capturer"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_Main(t *testing.T) {
func Test_MainHelp(t *testing.T) {
os.Args = []string{"", "--help"}
exitFn = func(code int) { assert.Equal(t, 0, code) }
exitFn = func(code int) { require.Equal(t, 0, code) }
output := capturer.CaptureStdout(main)
assert.Contains(t, output, "Usage:")
assert.Contains(t, output, "Available Commands:")
assert.Contains(t, output, "Flags:")
}
func Test_MainWithoutCommands(t *testing.T) {
os.Args = []string{""}
exitFn = func(code int) { assert.Equal(t, 0, code) }
output := capturer.CaptureStdout(main)
assert.Contains(t, output, "Usage:")
assert.Contains(t, output, "Available Commands:")
assert.Contains(t, output, "Flags:")
}
func Test_MainUnknownSubcommand(t *testing.T) {
os.Args = []string{"", "foobar"}
exitFn = func(code int) { assert.Equal(t, 1, code) }
output := capturer.CaptureStderr(main)
assert.Contains(t, output, "unknown command")
assert.Contains(t, output, "foobar")
assert.Contains(t, output, "USAGE:")
assert.Contains(t, output, "COMMANDS:")
assert.Contains(t, output, "GLOBAL OPTIONS:")
}

View File

@ -4,11 +4,12 @@ version: '3.8'
volumes:
tmp-data: {}
golint-go: {}
golint-cache: {}
services:
app: &app-service
image: golang:1.18.0-buster # Image page: <https://hub.docker.com/_/golang>
image: golang:1.20-buster # Image page: <https://hub.docker.com/_/golang>
working_dir: /src
environment:
HOME: /tmp
@ -18,40 +19,35 @@ services:
- /etc/group:/etc/group:ro
- .:/src:rw
- tmp-data:/tmp:rw
security_opt: [no-new-privileges:true]
web:
<<: *app-service
ports:
- "8080:8080/tcp" # Open <http://127.0.0.1:8080>
command:
- go
- run
- ./cmd/error-pages
- serve
- --verbose
- --port=8080
- --show-details
- --proxy-headers=X-Foo,Bar,Baz_blah
command: sh -c "go build -buildvcs=false -o /tmp/app ./cmd/error-pages && /tmp/app serve --show-details --proxy-headers=X-Foo,Bar,Baz_blah"
healthcheck:
test: ['CMD', 'wget', '--spider', '-q', 'http://127.0.0.1:8080/healthz']
interval: 5s
timeout: 2s
test: ['CMD', '/tmp/app', '--log-json', 'healthcheck']
interval: 4s
start_period: 5s
retries: 5
golint:
image: golangci/golangci-lint:v1.44-alpine # Image page: <https://hub.docker.com/r/golangci/golangci-lint>
image: golangci/golangci-lint:v1.51-alpine # Image page: <https://hub.docker.com/r/golangci/golangci-lint>
environment:
GOLANGCI_LINT_CACHE: /tmp/golint # <https://github.com/golangci/golangci-lint/blob/v1.42.0/internal/cache/default.go#L68>
volumes:
- .:/src:ro
- golint-go:/go:rw # go dependencies will be downloaded on each run without this
- golint-cache:/tmp/golint:rw
- .:/src:ro
working_dir: /src
command: /bin/true
security_opt: [no-new-privileges:true]
hurl:
image: orangeopensource/hurl:1.5.0
image: orangeopensource/hurl:1.8.0
volumes:
- .:/src:ro
working_dir: /src
depends_on:
web:
condition: service_healthy
web: {condition: service_healthy}
security_opt: [no-new-privileges:true]

View File

@ -14,6 +14,7 @@ templates:
- path: ./templates/lost-in-space.html
- path: ./templates/app-down.html
- path: ./templates/connection.html
- path: ./templates/matrix.html
formats:
json:

61
go.mod
View File

@ -1,41 +1,48 @@
module github.com/tarampampam/error-pages
module gh.tarampamp.am/error-pages
go 1.18
go 1.20
require (
github.com/a8m/envsubst v1.3.0
github.com/fasthttp/router v1.4.6
github.com/fatih/color v1.13.0
github.com/a8m/envsubst v1.4.2
github.com/fasthttp/router v1.4.16
github.com/fatih/color v1.14.1
github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.12.1
github.com/prometheus/client_model v0.2.0
github.com/spf13/cobra v1.4.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.1
github.com/valyala/fasthttp v1.34.0
go.uber.org/zap v1.21.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
github.com/prometheus/client_golang v1.14.0
github.com/prometheus/client_model v0.3.0
github.com/stretchr/testify v1.8.1
github.com/urfave/cli/v2 v2.24.4
github.com/valyala/fasthttp v1.44.0
go.uber.org/automaxprocs v1.5.1
go.uber.org/goleak v1.2.0
go.uber.org/zap v1.24.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/klauspost/compress v1.15.0 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.15.15 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/savsgio/gotils v0.0.0-20211223103454-d0aaa54c5899 // indirect
github.com/prometheus/common v0.40.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
google.golang.org/protobuf v1.27.1 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/sys v0.5.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
)

571
go.sum
View File

@ -1,545 +1,124 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/a8m/envsubst v1.3.0 h1:GmXKmVssap0YtlU3E230W98RWtWCyIZzjtf1apWWyAg=
github.com/a8m/envsubst v1.3.0/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/a8m/envsubst v1.4.2 h1:4yWIHXOLEJHQEFd4UjrWDrYeYlV7ncFWJOCBRLOZHQg=
github.com/a8m/envsubst v1.4.2/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fasthttp/router v1.4.6 h1:KfETdHGBnvoBfBHeRe/8TVYz8Bp/mASBVC5UXO9CpZI=
github.com/fasthttp/router v1.4.6/go.mod h1:Iv800u3hYFNuBBcmJNs/VBVpub+JfBihGBp5spSocbw=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/fasthttp/router v1.4.16 h1:faWJ9OtaHvAtodreyQLps58M80YFNzphMJtOJzeESXs=
github.com/fasthttp/router v1.4.16/go.mod h1:NFNlTCilbRVkeLc+E5JDkcxUdkpiJGKDL8Zy7Ey2JTI=
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d h1:cVtBfNW5XTHiKQe7jDaDBSh/EVM4XLPutLAGboIXuM0=
github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.14.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U=
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
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/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/common v0.40.0 h1:Afz7EVRqGg2Mqqf4JuF9vdvp1pi220m55Pi9T2JnO4Q=
github.com/prometheus/common v0.40.0/go.mod h1:L65ZJPSmfn/UBWLQIHV7dBrKFidB/wPlF1y5TlSt9OE=
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/savsgio/gotils v0.0.0-20211223103454-d0aaa54c5899 h1:Orn7s+r1raRTBKLSc9DmbktTT04sL+vkzsbRD2Q8rOI=
github.com/savsgio/gotils v0.0.0-20211223103454-d0aaa54c5899/go.mod h1:oejLrk1Y/5zOF+c/aHtXqn3TFlzzbAgPWg8zBiAHDas=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/urfave/cli/v2 v2.24.4 h1:0gyJJEBYtCV87zI/x2nZCPyDxD51K6xM8SkwjHFCNEU=
github.com/urfave/cli/v2 v2.24.4/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.33.0/go.mod h1:KJRK/MXx0J+yd0c5hlR+s1tIHD72sniU8ZJjl97LIw4=
github.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4=
github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0=
github.com/valyala/fasthttp v1.44.0 h1:R+gLUhldIsfg1HokMuQjdQ5bh9nuXHPIfvkYUu9eR5Q=
github.com/valyala/fasthttp v1.44.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk=
go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220111093109-d55c255bac03/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -8,7 +8,8 @@ import (
"time"
"github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/breaker"
"gh.tarampamp.am/error-pages/internal/breaker"
)
func TestNewOSSignals(t *testing.T) {

View File

@ -3,12 +3,13 @@ package checkers_test
import (
"bytes"
"context"
"io/ioutil"
"io"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/checkers"
"gh.tarampamp.am/error-pages/internal/checkers"
)
type httpClientFunc func(*http.Request) (*http.Response, error)
@ -22,7 +23,7 @@ func TestHealthChecker_CheckSuccess(t *testing.T) {
assert.Equal(t, "HealthChecker/internal", req.Header.Get("User-Agent"))
return &http.Response{
Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
Body: io.NopCloser(bytes.NewReader([]byte{})),
StatusCode: http.StatusOK,
}, nil
}
@ -35,7 +36,7 @@ func TestHealthChecker_CheckSuccess(t *testing.T) {
func TestHealthChecker_CheckFail(t *testing.T) {
var httpMock httpClientFunc = func(req *http.Request) (*http.Response, error) {
return &http.Response{
Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
Body: io.NopCloser(bytes.NewReader([]byte{})),
StatusCode: http.StatusBadGateway,
}, nil
}

View File

@ -4,7 +4,8 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/checkers"
"gh.tarampamp.am/error-pages/internal/checkers"
)
func TestLiveChecker_Check(t *testing.T) {

103
internal/cli/app.go Normal file
View File

@ -0,0 +1,103 @@
package cli
import (
"context"
"fmt"
"runtime"
"strings"
"github.com/urfave/cli/v2"
"gh.tarampamp.am/error-pages/internal/checkers"
"gh.tarampamp.am/error-pages/internal/cli/build"
"gh.tarampamp.am/error-pages/internal/cli/healthcheck"
"gh.tarampamp.am/error-pages/internal/cli/serve"
"gh.tarampamp.am/error-pages/internal/env"
"gh.tarampamp.am/error-pages/internal/logger"
"gh.tarampamp.am/error-pages/internal/version"
)
// NewApp creates new console application.
func NewApp(appName string) *cli.App { //nolint:funlen
const (
logLevelFlagName = "log-level"
logFormatFlagName = "log-format"
verboseFlagName = "verbose"
debugFlagName = "debug"
logJSONFlagName = "log-json"
defaultLogLevel = logger.InfoLevel
defaultLogFormat = logger.ConsoleFormat
)
// create "default" logger (will be overwritten later with customized)
var log, _ = logger.New(defaultLogLevel, defaultLogFormat) // error will never occurs
return &cli.App{
Usage: appName,
Before: func(c *cli.Context) (err error) {
_ = log.Sync() // sync previous logger instance
var logLevel, logFormat = defaultLogLevel, defaultLogFormat //nolint:ineffassign
if c.Bool(verboseFlagName) || c.Bool(debugFlagName) {
logLevel = logger.DebugLevel
} else {
// parse logging level
if logLevel, err = logger.ParseLevel(c.String(logLevelFlagName)); err != nil {
return err
}
}
if c.Bool(logJSONFlagName) {
logFormat = logger.JSONFormat
} else {
// parse logging format
if logFormat, err = logger.ParseFormat(c.String(logFormatFlagName)); err != nil {
return err
}
}
configured, err := logger.New(logLevel, logFormat) // create new logger instance
if err != nil {
return err
}
*log = *configured // replace "default" logger with customized
return nil
},
Commands: []*cli.Command{
healthcheck.NewCommand(checkers.NewHealthChecker(context.TODO())),
build.NewCommand(log),
serve.NewCommand(log),
},
Version: fmt.Sprintf("%s (%s)", version.Version(), runtime.Version()),
Flags: []cli.Flag{ // global flags
&cli.BoolFlag{ // kept for backward compatibility
Name: verboseFlagName,
Usage: "verbose output (DEPRECATED FLAG)",
},
&cli.BoolFlag{ // kept for backward compatibility
Name: debugFlagName,
Usage: "debug output (DEPRECATED FLAG)",
},
&cli.BoolFlag{ // kept for backward compatibility
Name: logJSONFlagName,
Usage: "logs in JSON format (DEPRECATED FLAG)",
},
&cli.StringFlag{
Name: logLevelFlagName,
Value: defaultLogLevel.String(),
Usage: "logging level (`" + strings.Join(logger.LevelStrings(), "/") + "`)",
EnvVars: []string{env.LogLevel.String()},
},
&cli.StringFlag{
Name: logFormatFlagName,
Value: defaultLogFormat.String(),
Usage: "logging format (`" + strings.Join(logger.FormatStrings(), "/") + "`)",
EnvVars: []string{env.LogFormat.String()},
},
},
}
}

19
internal/cli/app_test.go Normal file
View File

@ -0,0 +1,19 @@
package cli_test
import (
"testing"
"github.com/stretchr/testify/assert"
"gh.tarampamp.am/error-pages/internal/cli"
)
func TestNewCommand(t *testing.T) {
t.Parallel()
app := cli.NewApp("app")
assert.NotEmpty(t, app.Flags)
assert.NoError(t, app.Run([]string{"", "--log-level", "debug", "--log-format", "json"}))
}

View File

@ -5,52 +5,59 @@ import (
"path"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/tarampampam/error-pages/internal/config"
"github.com/tarampampam/error-pages/internal/tpl"
"github.com/urfave/cli/v2"
"go.uber.org/zap"
"gh.tarampamp.am/error-pages/internal/cli/shared"
"gh.tarampamp.am/error-pages/internal/config"
"gh.tarampamp.am/error-pages/internal/tpl"
)
type command struct {
c *cli.Command
}
// NewCommand creates `build` command.
func NewCommand(log *zap.Logger, configFile *string) *cobra.Command {
var (
generateIndex bool
cfg *config.Config
func NewCommand(log *zap.Logger) *cli.Command {
var cmd = command{}
const (
generateIndexFlagName = "index"
disableL10nFlagName = "disable-l10n"
)
cmd := &cobra.Command{
Use: "build <output-directory>",
Aliases: []string{"b"},
Short: "Build the error pages",
Args: cobra.ExactArgs(1),
PreRunE: func(*cobra.Command, []string) (err error) {
if configFile == nil {
return errors.New("path to the config file is required for this command")
cmd.c = &cli.Command{
Name: "build",
Aliases: []string{"b"},
Usage: "build <output-directory>",
Description: "Build the error pages",
Action: func(c *cli.Context) error {
cfg, cfgErr := config.FromYamlFile(c.String(shared.ConfigFileFlag.Name))
if cfgErr != nil {
return cfgErr
}
if cfg, err = config.FromYamlFile(*configFile); err != nil {
return err
}
return
},
RunE: func(_ *cobra.Command, args []string) error {
if len(args) != 1 {
if c.Args().Len() != 1 {
return errors.New("wrong arguments count")
}
return run(log, cfg, args[0], generateIndex)
return cmd.Run(log, cfg, c.Args().First(), c.Bool(generateIndexFlagName), c.Bool(disableL10nFlagName))
},
Flags: []cli.Flag{ // global flags
&cli.BoolFlag{
Name: generateIndexFlagName,
Aliases: []string{"i"},
Usage: "generate index page",
},
&cli.BoolFlag{
Name: disableL10nFlagName,
Usage: "disable error pages localization",
},
shared.ConfigFileFlag,
},
}
cmd.Flags().BoolVarP(
&generateIndex,
"index", "i",
false,
"generate index page",
)
return cmd
return cmd.c
}
const (
@ -60,14 +67,14 @@ const (
outDirPerm = os.FileMode(0775)
)
func run(log *zap.Logger, cfg *config.Config, outDirectoryPath string, generateIndex bool) error { //nolint:funlen
func (cmd *command) Run(log *zap.Logger, cfg *config.Config, outDirectoryPath string, generateIndex, disableL10n bool) error { //nolint:funlen,lll
if len(cfg.Templates) == 0 {
return errors.New("no loaded templates")
}
log.Info("output directory preparing", zap.String("path", outDirectoryPath))
if err := createDirectory(outDirectoryPath, outDirPerm); err != nil {
if err := cmd.createDirectory(outDirectoryPath, outDirPerm); err != nil {
return errors.Wrap(err, "cannot prepare output directory")
}
@ -78,7 +85,7 @@ func run(log *zap.Logger, cfg *config.Config, outDirectoryPath string, generateI
log.Debug("template processing", zap.String("name", template.Name()))
for _, page := range cfg.Pages {
if err := createDirectory(path.Join(outDirectoryPath, template.Name()), outDirPerm); err != nil {
if err := cmd.createDirectory(path.Join(outDirectoryPath, template.Name()), outDirPerm); err != nil {
return err
}
@ -92,6 +99,7 @@ func run(log *zap.Logger, cfg *config.Config, outDirectoryPath string, generateI
Message: page.Message(),
Description: page.Description(),
ShowRequestDetails: false,
L10nDisabled: disableL10n,
})
if renderingErr != nil {
return renderingErr
@ -129,7 +137,7 @@ func run(log *zap.Logger, cfg *config.Config, outDirectoryPath string, generateI
return nil
}
func createDirectory(path string, perm os.FileMode) error {
func (cmd *command) createDirectory(path string, perm os.FileMode) error {
stat, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {

View File

@ -1,7 +1,26 @@
package build_test
import "testing"
import (
"flag"
"testing"
func TestNothing(t *testing.T) {
t.Skip("tests for this package have not been implemented yet")
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v2"
"go.uber.org/goleak"
"go.uber.org/zap"
"gh.tarampamp.am/error-pages/internal/cli/build"
)
func TestNewCommand(t *testing.T) {
defer goleak.VerifyNone(t)
cmd := build.NewCommand(zap.NewNop())
assert.NotEmpty(t, cmd.Flags)
assert.Error(t, cmd.Run(
cli.NewContext(cli.NewApp(), &flag.FlagSet{}, nil),
"",
), "should fail because of missing external services")
}

View File

@ -34,7 +34,7 @@
<footer class="footer">
<div class="container text-center text-muted mt-3 mb-3">
For online documentation and support please refer to the
<a href="https://github.com/tarampampam/error-pages">project repository</a>.
<a href="https://gh.tarampamp.am/error-pages">project repository</a>.
</div>
</footer>
</body>

View File

@ -2,56 +2,35 @@
package healthcheck
import (
"fmt"
"strconv"
"errors"
"math"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/tarampampam/error-pages/internal/env"
"github.com/urfave/cli/v2"
"gh.tarampamp.am/error-pages/internal/cli/shared"
)
type checker interface {
Check(port uint16) error
}
const portFlagName = "port"
// NewCommand creates `healthcheck` command.
func NewCommand(checker checker) *cobra.Command {
var port uint16
cmd := &cobra.Command{
Use: "healthcheck",
func NewCommand(checker checker) *cli.Command {
return &cli.Command{
Name: "healthcheck",
Aliases: []string{"chk", "health", "check"},
Short: "Health checker for the HTTP server. Use case - docker healthcheck",
PreRunE: func(c *cobra.Command, _ []string) (lastErr error) {
c.Flags().VisitAll(func(flag *pflag.Flag) {
// flag was NOT defined using CLI (flags should have maximal priority)
if !flag.Changed && flag.Name == portFlagName {
if envPort, exists := env.ListenPort.Lookup(); exists && envPort != "" {
if p, err := strconv.ParseUint(envPort, 10, 16); err == nil { //nolint:gomnd
port = uint16(p)
} else {
lastErr = fmt.Errorf("wrong TCP port environment variable [%s] value", envPort)
}
}
}
})
Usage: "Health checker for the HTTP server. Use case - docker healthcheck",
Action: func(c *cli.Context) error {
var port = c.Uint(shared.ListenPortFlag.Name)
return lastErr
if port <= 0 || port > math.MaxUint16 {
return errors.New("port value out of range")
}
return checker.Check(uint16(port))
},
RunE: func(*cobra.Command, []string) error {
return checker.Check(port)
Flags: []cli.Flag{
shared.ListenPortFlag,
},
}
cmd.Flags().Uint16VarP(
&port,
portFlagName,
"p",
8080, //nolint:gomnd // must be same as default serve `--port` flag value
fmt.Sprintf("TCP port number [$%s]", env.ListenPort),
)
return cmd
}

View File

@ -2,13 +2,13 @@ package healthcheck_test
import (
"errors"
"os"
"flag"
"testing"
"github.com/kami-zh/go-capturer"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/cli/healthcheck"
"github.com/urfave/cli/v2"
"gh.tarampamp.am/error-pages/internal/cli/healthcheck"
)
type fakeChecker struct{ err error }
@ -18,77 +18,30 @@ func (c *fakeChecker) Check(port uint16) error { return c.err }
func TestProperties(t *testing.T) {
cmd := healthcheck.NewCommand(&fakeChecker{err: nil})
assert.Equal(t, "healthcheck", cmd.Use)
assert.Equal(t, "healthcheck", cmd.Name)
assert.ElementsMatch(t, []string{"chk", "health", "check"}, cmd.Aliases)
assert.NotNil(t, cmd.RunE)
assert.NotNil(t, cmd.Action)
}
func TestCommandRun(t *testing.T) {
cmd := healthcheck.NewCommand(&fakeChecker{err: nil})
cmd.SetArgs([]string{})
output := capturer.CaptureOutput(func() {
assert.NoError(t, cmd.Execute())
})
assert.Empty(t, output)
assert.NoError(t, cmd.Run(cli.NewContext(cli.NewApp(), &flag.FlagSet{}, nil)))
}
func TestCommandRunFailed(t *testing.T) {
cmd := healthcheck.NewCommand(&fakeChecker{err: errors.New("foo err")})
cmd.SetArgs([]string{})
output := capturer.CaptureStderr(func() {
assert.Error(t, cmd.Execute())
})
assert.Contains(t, output, "foo err")
assert.ErrorContains(t, cmd.Run(cli.NewContext(cli.NewApp(), &flag.FlagSet{}, nil)), "foo err")
}
func TestPortFlagWrongArgument(t *testing.T) {
cmd := healthcheck.NewCommand(&fakeChecker{err: nil})
cmd.SetArgs([]string{"-p", "65536"}) // 65535 is max
var executed bool
err := cmd.Run(
cli.NewContext(cli.NewApp(), &flag.FlagSet{}, nil),
"", "-p", "65536",
)
cmd.RunE = func(*cobra.Command, []string) error {
executed = true
return nil
}
output := capturer.CaptureStderr(func() {
assert.Error(t, cmd.Execute())
})
assert.Contains(t, output, "invalid argument")
assert.Contains(t, output, "65536")
assert.Contains(t, output, "value out of range")
assert.False(t, executed)
}
func TestPortFlagWrongEnvValue(t *testing.T) {
cmd := healthcheck.NewCommand(&fakeChecker{err: nil})
cmd.SetArgs([]string{})
assert.NoError(t, os.Setenv("LISTEN_PORT", "65536")) // 65535 is max
defer func() { assert.NoError(t, os.Unsetenv("LISTEN_PORT")) }()
var executed bool
cmd.RunE = func(*cobra.Command, []string) error {
executed = true
return nil
}
output := capturer.CaptureStderr(func() {
assert.Error(t, cmd.Execute())
})
assert.Contains(t, output, "wrong TCP port")
assert.Contains(t, output, "environment variable")
assert.Contains(t, output, "65536")
assert.False(t, executed)
assert.ErrorContains(t, err, "port value out of range")
}

View File

@ -1,93 +0,0 @@
// Package cli contains CLI command handlers.
package cli
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/tarampampam/error-pages/internal/checkers"
buildCmd "github.com/tarampampam/error-pages/internal/cli/build"
healthcheckCmd "github.com/tarampampam/error-pages/internal/cli/healthcheck"
serveCmd "github.com/tarampampam/error-pages/internal/cli/serve"
versionCmd "github.com/tarampampam/error-pages/internal/cli/version"
"github.com/tarampampam/error-pages/internal/env"
"github.com/tarampampam/error-pages/internal/logger"
"github.com/tarampampam/error-pages/internal/version"
)
const configFileFlagName = "config-file"
// NewCommand creates root command.
func NewCommand(appName string) *cobra.Command { //nolint:funlen
var (
configFile string
verbose bool
debug bool
logJSON bool
)
ctx := context.Background() // main CLI context
// create "default" logger (will be overwritten later with customized)
log, err := logger.New(false, false, false)
if err != nil {
panic(err)
}
cmd := &cobra.Command{
Use: appName,
PersistentPreRunE: func(c *cobra.Command, _ []string) error {
_ = log.Sync() // sync previous logger instance
customizedLog, e := logger.New(verbose, debug, logJSON)
if e != nil {
return e
}
*log = *customizedLog // override "default" logger with customized
c.Flags().VisitAll(func(flag *pflag.Flag) {
// flag was NOT defined using CLI (flags should have maximal priority)
if !flag.Changed && flag.Name == configFileFlagName {
if envConfigFile, exists := env.ConfigFilePath.Lookup(); exists && envConfigFile != "" {
configFile = envConfigFile
}
}
})
return nil
},
PersistentPostRun: func(*cobra.Command, []string) {
// error ignoring reasons:
// - <https://github.com/uber-go/zap/issues/772>
// - <https://github.com/uber-go/zap/issues/328>
_ = log.Sync()
},
SilenceErrors: true,
SilenceUsage: true,
CompletionOptions: cobra.CompletionOptions{
DisableDefaultCmd: true,
},
}
cmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
cmd.PersistentFlags().BoolVarP(&debug, "debug", "", false, "debug output")
cmd.PersistentFlags().BoolVarP(&logJSON, "log-json", "", false, "logs in JSON format")
cmd.PersistentFlags().StringVarP(
&configFile,
configFileFlagName, "c",
"./error-pages.yml",
fmt.Sprintf("path to the config file [$%s]", env.ConfigFilePath),
)
cmd.AddCommand(
versionCmd.NewCommand(version.Version()),
healthcheckCmd.NewCommand(checkers.NewHealthChecker(ctx)),
buildCmd.NewCommand(log, &configFile),
serveCmd.NewCommand(ctx, log, &configFile),
)
return cmd
}

View File

@ -1,84 +0,0 @@
package cli_test
import (
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/cli"
)
func TestSubcommands(t *testing.T) {
cmd := cli.NewCommand("unit test")
cases := []struct {
giveName string
}{
{giveName: "build"},
{giveName: "version"},
{giveName: "healthcheck"},
{giveName: "serve"},
}
// get all existing subcommands and put into the map
subcommands := make(map[string]*cobra.Command)
for _, sub := range cmd.Commands() {
subcommands[sub.Name()] = sub
}
for _, tt := range cases {
tt := tt
t.Run(tt.giveName, func(t *testing.T) {
if _, exists := subcommands[tt.giveName]; !exists {
assert.Failf(t, "command not found", "command [%s] was not found", tt.giveName)
}
})
}
}
func TestFlags(t *testing.T) {
cmd := cli.NewCommand("unit test")
cases := []struct {
giveName string
wantShorthand string
wantDefault string
}{
{giveName: "verbose", wantShorthand: "v", wantDefault: "false"},
{giveName: "debug", wantShorthand: "", wantDefault: "false"},
{giveName: "log-json", wantShorthand: "", wantDefault: "false"},
{giveName: "config-file", wantShorthand: "c", wantDefault: "./error-pages.yml"},
}
for _, tt := range cases {
tt := tt
t.Run(tt.giveName, func(t *testing.T) {
flag := cmd.Flag(tt.giveName)
if flag == nil {
assert.Failf(t, "flag not found", "flag [%s] was not found", tt.giveName)
return
}
assert.Equal(t, tt.wantShorthand, flag.Shorthand)
assert.Equal(t, tt.wantDefault, flag.DefValue)
})
}
}
func TestExecuting(t *testing.T) {
cmd := cli.NewCommand("unit test")
cmd.SetArgs([]string{})
var executed bool
if cmd.Run == nil { // override "Run" property for test (if it was not set)
cmd.Run = func(cmd *cobra.Command, args []string) {
executed = true
}
}
assert.NoError(t, cmd.Execute())
assert.True(t, executed)
}

View File

@ -3,53 +3,164 @@ package serve
import (
"context"
"errors"
"fmt"
"net"
"os"
"strings"
"time"
"github.com/spf13/cobra"
"github.com/tarampampam/error-pages/internal/breaker"
"github.com/tarampampam/error-pages/internal/config"
appHttp "github.com/tarampampam/error-pages/internal/http"
"github.com/tarampampam/error-pages/internal/pick"
"github.com/urfave/cli/v2"
"go.uber.org/zap"
"gh.tarampamp.am/error-pages/internal/breaker"
"gh.tarampamp.am/error-pages/internal/cli/shared"
"gh.tarampamp.am/error-pages/internal/config"
"gh.tarampamp.am/error-pages/internal/env"
appHttp "gh.tarampamp.am/error-pages/internal/http"
"gh.tarampamp.am/error-pages/internal/options"
"gh.tarampamp.am/error-pages/internal/pick"
)
type command struct {
c *cli.Command
}
const (
templateNameFlagName = "template-name"
defaultErrorPageFlagName = "default-error-page"
defaultHTTPCodeFlagName = "default-http-code"
showDetailsFlagName = "show-details"
proxyHTTPHeadersFlagName = "proxy-headers"
disableL10nFlagName = "disable-l10n"
)
const (
useRandomTemplate = "random"
useRandomTemplateOnEachRequest = "i-said-random"
useRandomTemplateDaily = "random-daily"
useRandomTemplateHourly = "random-hourly"
)
// NewCommand creates `serve` command.
func NewCommand(ctx context.Context, log *zap.Logger, configFile *string) *cobra.Command {
var (
f flags
cfg *config.Config
)
func NewCommand(log *zap.Logger) *cli.Command { //nolint:funlen
var cmd = command{}
cmd := &cobra.Command{
Use: "serve",
cmd.c = &cli.Command{
Name: "serve",
Aliases: []string{"s", "server"},
Short: "Start HTTP server",
PreRunE: func(cmd *cobra.Command, _ []string) (err error) {
if configFile == nil {
Usage: "Start HTTP server",
Action: func(c *cli.Context) error {
var cfg *config.Config
if configPath := c.String(shared.ConfigFileFlag.Name); configPath == "" { // load config from file
return errors.New("path to the config file is required for this command")
}
if err = f.overrideUsingEnv(cmd.Flags()); err != nil {
} else if loadedCfg, err := config.FromYamlFile(c.String(shared.ConfigFileFlag.Name)); err != nil {
return err
} else {
cfg = loadedCfg
}
if cfg, err = config.FromYamlFile(*configFile); err != nil {
return err
var (
ip = c.String(shared.ListenAddrFlag.Name)
port = uint16(c.Uint(shared.ListenPortFlag.Name))
o options.ErrorPage
)
if net.ParseIP(ip) == nil {
return fmt.Errorf("wrong IP address [%s] for listening", ip)
}
return f.validate()
{ // fill options
o.Template.Name = c.String(templateNameFlagName)
o.L10n.Disabled = c.Bool(disableL10nFlagName)
o.Default.PageCode = c.String(defaultErrorPageFlagName)
o.Default.HTTPCode = uint16(c.Uint(defaultHTTPCodeFlagName))
o.ShowDetails = c.Bool(showDetailsFlagName)
if headers := c.String(proxyHTTPHeadersFlagName); headers != "" { //nolint:nestif
var m = make(map[string]struct{})
// make unique and ignore empty strings
for _, header := range strings.Split(headers, ",") {
if h := strings.TrimSpace(header); h != "" {
if strings.ContainsRune(h, ' ') {
return fmt.Errorf("whitespaces in the HTTP headers for proxying [%s] are not allowed", header)
}
if _, ok := m[h]; !ok {
m[h] = struct{}{}
}
}
}
// convert map into slice
o.ProxyHTTPHeaders = make([]string, 0, len(m))
for h := range m {
o.ProxyHTTPHeaders = append(o.ProxyHTTPHeaders, h)
}
}
}
if o.Default.HTTPCode > 599 { //nolint:gomnd
return fmt.Errorf("wrong default HTTP response code [%d]", o.Default.HTTPCode)
}
return cmd.Run(c.Context, log, cfg, ip, port, o)
},
Flags: []cli.Flag{
shared.ConfigFileFlag,
shared.ListenPortFlag,
shared.ListenAddrFlag,
&cli.StringFlag{
Name: templateNameFlagName,
Aliases: []string{"t"},
Usage: fmt.Sprintf(
"template name (set \"%s\" to use a randomized or \"%s\" to use a randomized template on "+
"each request or \"%s/%s\" daily/hourly randomized)",
useRandomTemplate,
useRandomTemplateOnEachRequest,
useRandomTemplateDaily,
useRandomTemplateHourly,
),
EnvVars: []string{env.TemplateName.String()},
},
&cli.StringFlag{
Name: defaultErrorPageFlagName,
Value: "404",
Usage: "default error page",
EnvVars: []string{env.DefaultErrorPage.String()},
},
&cli.UintFlag{
Name: defaultHTTPCodeFlagName,
Value: 404, //nolint:gomnd
Usage: "default HTTP response code",
EnvVars: []string{env.DefaultHTTPCode.String()},
},
&cli.BoolFlag{
Name: showDetailsFlagName,
Usage: "show request details in response",
EnvVars: []string{env.ShowDetails.String()},
},
&cli.StringFlag{
Name: proxyHTTPHeadersFlagName,
Usage: "proxy HTTP request headers list (comma-separated)",
EnvVars: []string{env.ProxyHTTPHeaders.String()},
},
&cli.BoolFlag{
Name: disableL10nFlagName,
Usage: "disable error pages localization",
EnvVars: []string{env.DisableL10n.String()},
},
},
RunE: func(*cobra.Command, []string) error { return run(ctx, log, f, cfg) },
}
f.init(cmd.Flags())
return cmd
return cmd.c
}
// run current command.
func run(parentCtx context.Context, log *zap.Logger, f flags, cfg *config.Config) error { //nolint:funlen
// Run current command.
func (cmd *command) Run( //nolint:funlen
parentCtx context.Context, log *zap.Logger, cfg *config.Config, ip string, port uint16, opt options.ErrorPage,
) error {
var (
ctx, cancel = context.WithCancel(parentCtx) // serve context creation
oss = breaker.NewOSSignals(ctx) // OS signals listener
@ -72,7 +183,7 @@ func run(parentCtx context.Context, log *zap.Logger, f flags, cfg *config.Config
picker interface{ Pick() string }
)
switch f.template.name {
switch opt.Template.Name {
case useRandomTemplate:
log.Info("A random template will be used")
@ -99,28 +210,19 @@ func run(parentCtx context.Context, log *zap.Logger, f flags, cfg *config.Config
picker = pick.NewStringsSlice(templateNames, pick.First)
default:
if t, found := cfg.Template(f.template.name); found {
if t, found := cfg.Template(opt.Template.Name); found {
log.Info("We will use the requested template", zap.String("name", t.Name()))
picker = pick.NewStringsSlice([]string{t.Name()}, pick.First)
} else {
return errors.New("requested nonexistent template: " + f.template.name)
return errors.New("requested nonexistent template: " + opt.Template.Name)
}
}
var proxyHTTPHeaders = f.HeadersToProxy()
// create HTTP server
server := appHttp.NewServer(log)
// register server routes, middlewares, etc.
if err := server.Register(
cfg,
picker,
f.defaultErrorPage,
f.defaultHTTPCode,
f.showDetails,
proxyHTTPHeaders,
); err != nil {
if err := server.Register(cfg, picker, opt); err != nil {
return err
}
@ -131,15 +233,16 @@ func run(parentCtx context.Context, log *zap.Logger, f flags, cfg *config.Config
defer close(errCh)
log.Info("Server starting",
zap.String("addr", f.listen.ip),
zap.Uint16("port", f.listen.port),
zap.String("default error page", f.defaultErrorPage),
zap.Uint16("default HTTP response code", f.defaultHTTPCode),
zap.Strings("proxy headers", proxyHTTPHeaders),
zap.Bool("show request details", f.showDetails),
zap.String("addr", ip),
zap.Uint16("port", port),
zap.String("default error page", opt.Default.PageCode),
zap.Uint16("default HTTP response code", opt.Default.HTTPCode),
zap.Strings("proxy headers", opt.ProxyHTTPHeaders),
zap.Bool("show request details", opt.ShowDetails),
zap.Bool("localization disabled", opt.L10n.Disabled),
)
if err := server.Start(f.listen.ip, f.listen.port); err != nil {
if err := server.Start(ip, port); err != nil {
errCh <- err
}
}(startingErrCh)

View File

@ -1,206 +1,220 @@
package serve
import (
"fmt"
"net"
"sort"
"strconv"
"strings"
"github.com/spf13/pflag"
"github.com/tarampampam/error-pages/internal/env"
)
type flags struct {
listen struct {
ip string
port uint16
}
template struct {
name string
}
defaultErrorPage string
defaultHTTPCode uint16
showDetails bool
proxyHTTPHeaders string // comma-separated
}
// HeadersToProxy converts a comma-separated string with headers list into strings slice (with a sorting and without
// duplicates).
func (f *flags) HeadersToProxy() []string {
var raw = strings.Split(f.proxyHTTPHeaders, ",")
if len(raw) == 0 {
return []string{}
} else if len(raw) == 1 {
if h := strings.TrimSpace(raw[0]); h != "" {
return []string{h}
} else {
return []string{}
}
}
var m = make(map[string]struct{}, len(raw))
// make unique and ignore empty strings
for _, h := range raw {
if h = strings.TrimSpace(h); h != "" {
if _, ok := m[h]; !ok {
m[h] = struct{}{}
}
}
}
// convert map into slice
var headers = make([]string, 0, len(m))
for h := range m {
headers = append(headers, h)
}
// make sort
sort.Strings(headers)
return headers
}
const (
listenFlagName = "listen"
portFlagName = "port"
templateNameFlagName = "template-name"
defaultErrorPageFlagName = "default-error-page"
defaultHTTPCodeFlagName = "default-http-code"
showDetailsFlagName = "show-details"
proxyHTTPHeadersFlagName = "proxy-headers"
)
const (
useRandomTemplate = "random"
useRandomTemplateOnEachRequest = "i-said-random"
useRandomTemplateDaily = "random-daily"
useRandomTemplateHourly = "random-hourly"
)
func (f *flags) init(flagSet *pflag.FlagSet) {
flagSet.StringVarP(
&f.listen.ip,
listenFlagName, "l",
"0.0.0.0",
fmt.Sprintf("IP address to listen on [$%s]", env.ListenAddr),
)
flagSet.Uint16VarP(
&f.listen.port,
portFlagName, "p",
8080, //nolint:gomnd // must be same as default healthcheck `--port` flag value
fmt.Sprintf("TCP port number [$%s]", env.ListenPort),
)
flagSet.StringVarP(
&f.template.name,
templateNameFlagName, "t",
"",
fmt.Sprintf(
"template name (set \"%s\" to use a randomized or \"%s\" to use a randomized template on each request "+
"or \"%s/%s\" daily/hourly randomized) [$%s]",
useRandomTemplate,
useRandomTemplateOnEachRequest,
useRandomTemplateDaily,
useRandomTemplateHourly,
env.TemplateName,
),
)
flagSet.StringVarP(
&f.defaultErrorPage,
defaultErrorPageFlagName, "",
"404",
fmt.Sprintf("default error page [$%s]", env.DefaultErrorPage),
)
flagSet.Uint16VarP(
&f.defaultHTTPCode,
defaultHTTPCodeFlagName, "",
404, //nolint:gomnd
fmt.Sprintf("default HTTP response code [$%s]", env.DefaultHTTPCode),
)
flagSet.BoolVarP(
&f.showDetails,
showDetailsFlagName, "",
false,
fmt.Sprintf("show request details in response [$%s]", env.ShowDetails),
)
flagSet.StringVarP(
&f.proxyHTTPHeaders,
proxyHTTPHeadersFlagName, "",
"",
fmt.Sprintf("proxy HTTP request headers list (comma-separated) [$%s]", env.ProxyHTTPHeaders),
)
}
func (f *flags) overrideUsingEnv(flagSet *pflag.FlagSet) (lastErr error) { //nolint:gocognit,gocyclo
flagSet.VisitAll(func(flag *pflag.Flag) {
// flag was NOT defined using CLI (flags should have maximal priority)
if !flag.Changed { //nolint:nestif
switch flag.Name {
case listenFlagName:
if envVar, exists := env.ListenAddr.Lookup(); exists {
f.listen.ip = strings.TrimSpace(envVar)
}
case portFlagName:
if envVar, exists := env.ListenPort.Lookup(); exists {
if p, err := strconv.ParseUint(envVar, 10, 16); err == nil { //nolint:gomnd
f.listen.port = uint16(p)
} else {
lastErr = fmt.Errorf("wrong TCP port environment variable [%s] value", envVar)
}
}
case templateNameFlagName:
if envVar, exists := env.TemplateName.Lookup(); exists {
f.template.name = strings.TrimSpace(envVar)
}
case defaultErrorPageFlagName:
if envVar, exists := env.DefaultErrorPage.Lookup(); exists {
f.defaultErrorPage = strings.TrimSpace(envVar)
}
case defaultHTTPCodeFlagName:
if envVar, exists := env.DefaultHTTPCode.Lookup(); exists {
if code, err := strconv.ParseUint(envVar, 10, 16); err == nil { //nolint:gomnd
f.defaultHTTPCode = uint16(code)
} else {
lastErr = fmt.Errorf("wrong default HTTP response code environment variable [%s] value", envVar)
}
}
case showDetailsFlagName:
if envVar, exists := env.ShowDetails.Lookup(); exists {
if b, err := strconv.ParseBool(envVar); err == nil {
f.showDetails = b
}
}
case proxyHTTPHeadersFlagName:
if envVar, exists := env.ProxyHTTPHeaders.Lookup(); exists {
f.proxyHTTPHeaders = strings.TrimSpace(envVar)
}
}
}
})
return lastErr
}
func (f *flags) validate() error {
if net.ParseIP(f.listen.ip) == nil {
return fmt.Errorf("wrong IP address [%s] for listening", f.listen.ip)
}
if f.defaultHTTPCode > 599 { //nolint:gomnd
return fmt.Errorf("wrong default HTTP response code [%d]", f.defaultHTTPCode)
}
if strings.ContainsRune(f.proxyHTTPHeaders, ' ') {
return fmt.Errorf("whitespaces in the HTTP headers for proxying [%s] are not allowed", f.proxyHTTPHeaders)
}
return nil
}
// import (
// "fmt"
// "net"
// "sort"
// "strconv"
// "strings"
//
// "github.com/spf13/pflag"
//
// "gh.tarampamp.am/error-pages/internal/env"
// "gh.tarampamp.am/error-pages/internal/options"
// )
//
// type flags struct {
// Listen struct {
// IP string
// Port uint16
// }
// template struct {
// name string
// }
// l10n struct {
// disabled bool
// }
// defaultErrorPage string
// defaultHTTPCode uint16
// showDetails bool
// proxyHTTPHeaders string // comma-separated
// }
//
//
//
// func (f *flags) Init(flagSet *pflag.FlagSet) {
// flagSet.StringVarP(
// &f.Listen.IP,
// listenFlagName, "l",
// "0.0.0.0",
// fmt.Sprintf("IP address to Listen on [$%s]", env.ListenAddr),
// )
// flagSet.Uint16VarP(
// &f.Listen.Port,
// portFlagName, "p",
// 8080, //nolint:gomnd // must be same as default healthcheck `--port` flag value
// fmt.Sprintf("TCP prt number [$%s]", env.ListenPort),
// )
// flagSet.StringVarP(
// &f.template.name,
// templateNameFlagName, "t",
// "",
// fmt.Sprintf(
// "template name (set \"%s\" to use a randomized or \"%s\" to use a randomized template on each request "+
// "or \"%s/%s\" daily/hourly randomized) [$%s]",
// useRandomTemplate,
// useRandomTemplateOnEachRequest,
// useRandomTemplateDaily,
// useRandomTemplateHourly,
// env.TemplateName,
// ),
// )
// flagSet.StringVarP(
// &f.defaultErrorPage,
// defaultErrorPageFlagName, "",
// "404",
// fmt.Sprintf("default error page [$%s]", env.DefaultErrorPage),
// )
// flagSet.Uint16VarP(
// &f.defaultHTTPCode,
// defaultHTTPCodeFlagName, "",
// 404, //nolint:gomnd
// fmt.Sprintf("default HTTP response code [$%s]", env.DefaultHTTPCode),
// )
// flagSet.BoolVarP(
// &f.showDetails,
// showDetailsFlagName, "",
// false,
// fmt.Sprintf("show request details in response [$%s]", env.ShowDetails),
// )
// flagSet.StringVarP(
// &f.proxyHTTPHeaders,
// proxyHTTPHeadersFlagName, "",
// "",
// fmt.Sprintf("proxy HTTP request headers list (comma-separated) [$%s]", env.ProxyHTTPHeaders),
// )
// flagSet.BoolVarP(
// &f.l10n.disabled,
// disableL10nFlagName, "",
// false,
// fmt.Sprintf("disable error pages localization [$%s]", env.DisableL10n),
// )
// }
//
// func (f *flags) OverrideUsingEnv(flagSet *pflag.FlagSet) (lastErr error) { //nolint:gocognit,gocyclo
// flagSet.VisitAll(func(flag *pflag.Flag) {
// // flag was NOT defined using CLI (flags should have maximal priority)
// if !flag.Changed { //nolint:nestif
// switch flag.Name {
// case listenFlagName:
// if envVar, exists := env.ListenAddr.Lookup(); exists {
// f.Listen.IP = strings.TrimSpace(envVar)
// }
//
// case portFlagName:
// if envVar, exists := env.ListenPort.Lookup(); exists {
// if p, err := strconv.ParseUint(envVar, 10, 16); err == nil {
// f.Listen.Port = uint16(p)
// } else {
// lastErr = fmt.Errorf("wrong TCP port environment variable [%s] value", envVar)
// }
// }
//
// case templateNameFlagName:
// if envVar, exists := env.TemplateName.Lookup(); exists {
// f.template.name = strings.TrimSpace(envVar)
// }
//
// case defaultErrorPageFlagName:
// if envVar, exists := env.DefaultErrorPage.Lookup(); exists {
// f.defaultErrorPage = strings.TrimSpace(envVar)
// }
//
// case defaultHTTPCodeFlagName:
// if envVar, exists := env.DefaultHTTPCode.Lookup(); exists {
// if code, err := strconv.ParseUint(envVar, 10, 16); err == nil {
// f.defaultHTTPCode = uint16(code)
// } else {
// lastErr = fmt.Errorf("wrong default HTTP response code environment variable [%s] value", envVar)
// }
// }
//
// case showDetailsFlagName:
// if envVar, exists := env.ShowDetails.Lookup(); exists {
// if b, err := strconv.ParseBool(envVar); err == nil {
// f.showDetails = b
// }
// }
//
// case proxyHTTPHeadersFlagName:
// if envVar, exists := env.ProxyHTTPHeaders.Lookup(); exists {
// f.proxyHTTPHeaders = strings.TrimSpace(envVar)
// }
//
// case disableL10nFlagName:
// if envVar, exists := env.DisableL10n.Lookup(); exists {
// if b, err := strconv.ParseBool(envVar); err == nil {
// f.l10n.disabled = b
// }
// }
// }
// }
// })
//
// return lastErr
// }
//
// func (f *flags) Validate() error {
// if net.ParseIP(f.Listen.IP) == nil {
// return fmt.Errorf("wrong IP address [%s] for listening", f.Listen.IP)
// }
//
// if f.defaultHTTPCode > 599 { //nolint:gomnd
// return fmt.Errorf("wrong default HTTP response code [%d]", f.defaultHTTPCode)
// }
//
// if strings.ContainsRune(f.proxyHTTPHeaders, ' ') {
// return fmt.Errorf("whitespaces in the HTTP headers for proxying [%s] are not allowed", f.proxyHTTPHeaders)
// }
//
// return nil
// }
//
// // headersToProxy converts a comma-separated string with headers list into strings slice (with a sorting and without
// // duplicates).
// func (f *flags) headersToProxy() []string {
// var raw = strings.Split(f.proxyHTTPHeaders, ",")
//
// if len(raw) == 0 {
// return []string{}
// } else if len(raw) == 1 {
// if h := strings.TrimSpace(raw[0]); h != "" {
// return []string{h}
// } else {
// return []string{}
// }
// }
//
// var m = make(map[string]struct{}, len(raw))
//
// // make unique and ignore empty strings
// for _, h := range raw {
// if h = strings.TrimSpace(h); h != "" {
// if _, ok := m[h]; !ok {
// m[h] = struct{}{}
// }
// }
// }
//
// // convert map into slice
// var headers = make([]string, 0, len(m))
// for h := range m {
// headers = append(headers, h)
// }
//
// // make sort
// sort.Strings(headers)
//
// return headers
// }
//
// func (f *flags) ToOptions() (o options.ErrorPage) {
// o.Default.PageCode = f.defaultErrorPage
// o.Default.HTTPCode = f.defaultHTTPCode
// o.L10n.Disabled = f.l10n.disabled
// o.Template.Name = f.template.name
// o.ShowDetails = f.showDetails
// o.ProxyHTTPHeaders = f.headersToProxy()
//
// return o
// }

View File

@ -0,0 +1,31 @@
package shared
import (
"github.com/urfave/cli/v2"
"gh.tarampamp.am/error-pages/internal/env"
)
var ConfigFileFlag = &cli.StringFlag{ //nolint:gochecknoglobals
Name: "config-file",
Aliases: []string{"c"},
Usage: "path to the config file (yaml)",
Value: "./error-pages.yml",
EnvVars: []string{env.ConfigFilePath.String()},
}
var ListenAddrFlag = &cli.StringFlag{ //nolint:gochecknoglobals
Name: "listen",
Aliases: []string{"l"},
Usage: "IP address to Listen on",
Value: "0.0.0.0",
EnvVars: []string{env.ListenAddr.String()},
}
var ListenPortFlag = &cli.UintFlag{ //nolint:gochecknoglobals
Name: "port",
Aliases: []string{"p"},
Usage: "TCP port number",
Value: 8080, //nolint:gomnd
EnvVars: []string{env.ListenPort.String()},
}

View File

@ -1,24 +0,0 @@
// Package version contains CLI `version` command implementation.
package version
import (
"fmt"
"os"
"runtime"
"github.com/spf13/cobra"
)
// NewCommand creates `version` command.
func NewCommand(ver string) *cobra.Command {
return &cobra.Command{
Use: "version",
Aliases: []string{"v", "ver"},
Short: "Display application version",
RunE: func(*cobra.Command, []string) (err error) {
_, err = fmt.Fprintf(os.Stdout, "app version:\t%s (%s)\n", ver, runtime.Version())
return
},
}
}

View File

@ -1,30 +0,0 @@
package version_test
import (
"runtime"
"testing"
"github.com/kami-zh/go-capturer"
"github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/cli/version"
)
func TestProperties(t *testing.T) {
cmd := version.NewCommand("")
assert.Equal(t, "version", cmd.Use)
assert.ElementsMatch(t, []string{"v", "ver"}, cmd.Aliases)
assert.NotNil(t, cmd.RunE)
}
func TestCommandRun(t *testing.T) {
cmd := version.NewCommand("1.2.3@foobar")
cmd.SetArgs([]string{})
output := capturer.CaptureStdout(func() {
assert.NoError(t, cmd.Execute())
})
assert.Contains(t, output, "1.2.3@foobar")
assert.Contains(t, output, runtime.Version())
}

View File

@ -1,7 +1,6 @@
package config
import (
"io/ioutil"
"os"
"path"
"path/filepath"
@ -68,7 +67,7 @@ func (t Template) Name() string { return t.name }
func (t Template) Content() []byte { return t.content }
func (t *Template) loadContentFromFile(filePath string) (err error) {
if t.content, err = ioutil.ReadFile(filePath); err != nil {
if t.content, err = os.ReadFile(filePath); err != nil {
return errors.Wrap(err, "cannot load content for the template "+t.Name()+" from file "+filePath)
}
@ -236,7 +235,7 @@ func FromYaml(in []byte) (_ *Config, err error) {
// FromYamlFile creates new Config instance using YAML file.
func FromYamlFile(filepath string) (*Config, error) {
bytes, err := ioutil.ReadFile(filepath)
bytes, err := os.ReadFile(filepath)
if err != nil {
return nil, errors.Wrap(err, "cannot read configuration file")
}

View File

@ -5,7 +5,8 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/config"
"gh.tarampamp.am/error-pages/internal/config"
)
func TestFromYaml(t *testing.T) {

4
internal/env/env.go vendored
View File

@ -6,6 +6,9 @@ import "os"
type envVariable string
const (
LogLevel envVariable = "LOG_LEVEL" // logging level
LogFormat envVariable = "LOG_FORMAT" // logging format (json|console)
ListenAddr envVariable = "LISTEN_ADDR" // IP address for listening
ListenPort envVariable = "LISTEN_PORT" // port number for listening
TemplateName envVariable = "TEMPLATE_NAME" // template name
@ -14,6 +17,7 @@ const (
DefaultHTTPCode envVariable = "DEFAULT_HTTP_CODE" // default HTTP response code
ShowDetails envVariable = "SHOW_DETAILS" // show request details in response
ProxyHTTPHeaders envVariable = "PROXY_HTTP_HEADERS" // proxy HTTP request headers list (request -> response)
DisableL10n envVariable = "DISABLE_L10N" // disable pages localization
)
// String returns environment variable name in the string representation.

View File

@ -16,6 +16,7 @@ func TestConstants(t *testing.T) {
assert.Equal(t, "DEFAULT_HTTP_CODE", string(DefaultHTTPCode))
assert.Equal(t, "SHOW_DETAILS", string(ShowDetails))
assert.Equal(t, "PROXY_HTTP_HEADERS", string(ProxyHTTPHeaders))
assert.Equal(t, "DISABLE_L10N", string(DisableL10n))
}
func TestEnvVariable_Lookup(t *testing.T) {
@ -30,6 +31,7 @@ func TestEnvVariable_Lookup(t *testing.T) {
{giveEnv: DefaultHTTPCode},
{giveEnv: ShowDetails},
{giveEnv: ProxyHTTPHeaders},
{giveEnv: DisableL10n},
}
for _, tt := range cases {

View File

@ -3,9 +3,11 @@ package core
import (
"strconv"
"github.com/tarampampam/error-pages/internal/config"
"github.com/tarampampam/error-pages/internal/tpl"
"github.com/valyala/fasthttp"
"gh.tarampamp.am/error-pages/internal/config"
"gh.tarampamp.am/error-pages/internal/options"
"gh.tarampamp.am/error-pages/internal/tpl"
)
type templatePicker interface {
@ -24,8 +26,7 @@ func RespondWithErrorPage( //nolint:funlen,gocyclo
rdr renderer,
pageCode string,
httpCode int,
showRequestDetails bool,
proxyHeaders []string,
opt options.ErrorPage,
) {
ctx.Response.Header.Set("X-Robots-Tag", "noindex") // block Search indexing
@ -33,10 +34,14 @@ func RespondWithErrorPage( //nolint:funlen,gocyclo
clientWant = ClientWantFormat(ctx)
json, canJSON = cfg.JSONFormat()
xml, canXML = cfg.XMLFormat()
props = tpl.Properties{Code: pageCode, ShowRequestDetails: showRequestDetails}
props = tpl.Properties{
Code: pageCode,
ShowRequestDetails: opt.ShowDetails,
L10nDisabled: opt.L10n.Disabled,
}
)
if showRequestDetails {
if opt.ShowDetails {
props.OriginalURI = string(ctx.Request.Header.Peek(OriginalURI))
props.Namespace = string(ctx.Request.Header.Peek(Namespace))
props.IngressName = string(ctx.Request.Header.Peek(IngressName))
@ -66,7 +71,7 @@ func RespondWithErrorPage( //nolint:funlen,gocyclo
}
// proxy required HTTP headers from the request to the response
for _, headerToProxy := range proxyHeaders {
for _, headerToProxy := range opt.ProxyHTTPHeaders {
if reqHeader := ctx.Request.Header.Peek(headerToProxy); len(reqHeader) > 0 {
ctx.Response.Header.SetBytesV(headerToProxy, reqHeader)
}

View File

@ -39,7 +39,7 @@ func ClientWantFormat(ctx *fasthttp.RequestCtx) ContentType {
f := format{b[0:idx], 0}
if len(b) > idx+3 {
if weight, err := strconv.ParseFloat(string(b[idx+3:]), 32); err == nil { //nolint:gomnd
if weight, err := strconv.ParseFloat(string(b[idx+3:]), 32); err == nil {
f.weight = float32(weight)
}
}

View File

@ -4,8 +4,9 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/http/core"
"github.com/valyala/fasthttp"
"gh.tarampamp.am/error-pages/internal/http/core"
)
func TestClientWantFormat(t *testing.T) {

View File

@ -1,10 +1,12 @@
package errorpage
import (
"github.com/tarampampam/error-pages/internal/config"
"github.com/tarampampam/error-pages/internal/http/core"
"github.com/tarampampam/error-pages/internal/tpl"
"github.com/valyala/fasthttp"
"gh.tarampamp.am/error-pages/internal/config"
"gh.tarampamp.am/error-pages/internal/http/core"
"gh.tarampamp.am/error-pages/internal/options"
"gh.tarampamp.am/error-pages/internal/tpl"
)
type (
@ -19,18 +21,12 @@ type (
)
// NewHandler creates handler for error pages serving.
func NewHandler(
cfg *config.Config,
p templatePicker,
rdr renderer,
showRequestDetails bool,
proxyHTTPHeaders []string,
) fasthttp.RequestHandler {
func NewHandler(cfg *config.Config, p templatePicker, rdr renderer, opt options.ErrorPage) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
core.SetClientFormat(ctx, core.PlainTextContentType) // default content type
if code, ok := ctx.UserValue("code").(string); ok {
core.RespondWithErrorPage(ctx, cfg, p, rdr, code, fasthttp.StatusOK, showRequestDetails, proxyHTTPHeaders)
core.RespondWithErrorPage(ctx, cfg, p, rdr, code, fasthttp.StatusOK, opt)
} else { // will never occur
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
_, _ = ctx.WriteString("cannot extract requested code from the request")

View File

@ -3,10 +3,12 @@ package index
import (
"strconv"
"github.com/tarampampam/error-pages/internal/config"
"github.com/tarampampam/error-pages/internal/http/core"
"github.com/tarampampam/error-pages/internal/tpl"
"github.com/valyala/fasthttp"
"gh.tarampamp.am/error-pages/internal/config"
"gh.tarampamp.am/error-pages/internal/http/core"
"gh.tarampamp.am/error-pages/internal/options"
"gh.tarampamp.am/error-pages/internal/tpl"
)
type (
@ -21,23 +23,15 @@ type (
)
// NewHandler creates handler for the index page serving.
func NewHandler(
cfg *config.Config,
p templatePicker,
rdr renderer,
defaultPageCode string,
defaultHTTPCode uint16,
showRequestDetails bool,
proxyHTTPHeaders []string,
) fasthttp.RequestHandler {
func NewHandler(cfg *config.Config, p templatePicker, rdr renderer, opt options.ErrorPage) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
pageCode, httpCode := defaultPageCode, int(defaultHTTPCode)
pageCode, httpCode := opt.Default.PageCode, int(opt.Default.HTTPCode)
if returnCode, ok := extractCodeToReturn(ctx); ok {
pageCode, httpCode = strconv.Itoa(returnCode), returnCode
}
core.RespondWithErrorPage(ctx, cfg, p, rdr, pageCode, httpCode, showRequestDetails, proxyHTTPHeaders)
core.RespondWithErrorPage(ctx, cfg, p, rdr, pageCode, httpCode, opt)
}
}

View File

@ -5,20 +5,22 @@ import (
"time"
"github.com/fasthttp/router"
"github.com/tarampampam/error-pages/internal/checkers"
"github.com/tarampampam/error-pages/internal/config"
"github.com/tarampampam/error-pages/internal/http/common"
errorpageHandler "github.com/tarampampam/error-pages/internal/http/handlers/errorpage"
healthzHandler "github.com/tarampampam/error-pages/internal/http/handlers/healthz"
indexHandler "github.com/tarampampam/error-pages/internal/http/handlers/index"
metricsHandler "github.com/tarampampam/error-pages/internal/http/handlers/metrics"
notfoundHandler "github.com/tarampampam/error-pages/internal/http/handlers/notfound"
versionHandler "github.com/tarampampam/error-pages/internal/http/handlers/version"
"github.com/tarampampam/error-pages/internal/metrics"
"github.com/tarampampam/error-pages/internal/tpl"
"github.com/tarampampam/error-pages/internal/version"
"github.com/valyala/fasthttp"
"go.uber.org/zap"
"gh.tarampamp.am/error-pages/internal/checkers"
"gh.tarampamp.am/error-pages/internal/config"
"gh.tarampamp.am/error-pages/internal/http/common"
errorpageHandler "gh.tarampamp.am/error-pages/internal/http/handlers/errorpage"
healthzHandler "gh.tarampamp.am/error-pages/internal/http/handlers/healthz"
indexHandler "gh.tarampamp.am/error-pages/internal/http/handlers/index"
metricsHandler "gh.tarampamp.am/error-pages/internal/http/handlers/metrics"
notfoundHandler "gh.tarampamp.am/error-pages/internal/http/handlers/notfound"
versionHandler "gh.tarampamp.am/error-pages/internal/http/handlers/version"
"gh.tarampamp.am/error-pages/internal/metrics"
"gh.tarampamp.am/error-pages/internal/options"
"gh.tarampamp.am/error-pages/internal/tpl"
"gh.tarampamp.am/error-pages/internal/version"
)
type Server struct {
@ -66,14 +68,7 @@ type templatePicker interface {
// Register server routes, middlewares, etc.
// Router docs: <https://github.com/fasthttp/router>
func (s *Server) Register(
cfg *config.Config,
templatePicker templatePicker,
defaultPageCode string,
defaultHTTPCode uint16,
showDetails bool,
proxyHTTPHeaders []string,
) error {
func (s *Server) Register(cfg *config.Config, templatePicker templatePicker, opt options.ErrorPage) error {
reg, m := metrics.NewRegistry(), metrics.NewMetrics()
if err := m.Register(reg); err != nil {
@ -82,8 +77,9 @@ func (s *Server) Register(
s.fast.Handler = common.DurationMetrics(common.LogRequest(s.router.Handler, s.log), &m)
s.router.GET("/", indexHandler.NewHandler(cfg, templatePicker, s.rdr, defaultPageCode, defaultHTTPCode, showDetails, proxyHTTPHeaders)) //nolint:lll
s.router.GET("/{code}.html", errorpageHandler.NewHandler(cfg, templatePicker, s.rdr, showDetails, proxyHTTPHeaders)) //nolint:lll
s.router.GET("/", indexHandler.NewHandler(cfg, templatePicker, s.rdr, opt))
s.router.GET("/{code}.html", errorpageHandler.NewHandler(cfg, templatePicker, s.rdr, opt))
s.router.GET("/version", versionHandler.NewHandler(version.Version()))
liveHandler := healthzHandler.NewHandler(checkers.NewLiveChecker())

68
internal/logger/format.go Normal file
View File

@ -0,0 +1,68 @@
package logger
import (
"fmt"
"strings"
)
// A Format is a logging format.
type Format uint8
const (
ConsoleFormat Format = iota // useful for console output (for humans)
JSONFormat // useful for logging aggregation systems (for robots)
)
// String returns a lower-case ASCII representation of the log format.
func (f Format) String() string {
switch f {
case ConsoleFormat:
return "console"
case JSONFormat:
return "json"
}
return fmt.Sprintf("format(%d)", f)
}
// Formats returns a slice of all logging formats.
func Formats() []Format {
return []Format{ConsoleFormat, JSONFormat}
}
// FormatStrings returns a slice of all logging formats as strings.
func FormatStrings() []string {
var (
formats = Formats()
result = make([]string, len(formats))
)
for i := range formats {
result[i] = formats[i].String()
}
return result
}
// ParseFormat parses a format (case is ignored) based on the ASCII representation of the log format.
// If the provided ASCII representation is invalid an error is returned.
//
// This is particularly useful when dealing with text input to configure log formats.
func ParseFormat[T string | []byte](text T) (Format, error) {
var format string
if s, ok := any(text).(string); ok {
format = s
} else {
format = string(any(text).([]byte))
}
switch strings.ToLower(format) {
case "console", "": // make the zero value useful
return ConsoleFormat, nil
case "json":
return JSONFormat, nil
}
return Format(0), fmt.Errorf("unrecognized logging format: %q", text)
}

View File

@ -0,0 +1,62 @@
package logger_test
import (
"errors"
"testing"
"github.com/stretchr/testify/require"
"gh.tarampamp.am/error-pages/internal/logger"
)
func TestFormat_String(t *testing.T) {
for name, tt := range map[string]struct {
giveFormat logger.Format
wantString string
}{
"json": {giveFormat: logger.JSONFormat, wantString: "json"},
"console": {giveFormat: logger.ConsoleFormat, wantString: "console"},
"<unknown>": {giveFormat: logger.Format(255), wantString: "format(255)"},
} {
t.Run(name, func(t *testing.T) {
require.Equal(t, tt.wantString, tt.giveFormat.String())
})
}
}
func TestParseFormat(t *testing.T) {
for name, tt := range map[string]struct {
giveBytes []byte
giveString string
wantFormat logger.Format
wantError error
}{
"<empty value>": {giveBytes: []byte(""), wantFormat: logger.ConsoleFormat},
"<empty value> (string)": {giveString: "", wantFormat: logger.ConsoleFormat},
"console": {giveBytes: []byte("console"), wantFormat: logger.ConsoleFormat},
"console (string)": {giveString: "console", wantFormat: logger.ConsoleFormat},
"json": {giveBytes: []byte("json"), wantFormat: logger.JSONFormat},
"json (string)": {giveString: "json", wantFormat: logger.JSONFormat},
"foobar": {giveBytes: []byte("foobar"), wantError: errors.New("unrecognized logging format: \"foobar\"")}, //nolint:lll
} {
t.Run(name, func(t *testing.T) {
var (
f logger.Format
err error
)
if tt.giveString != "" {
f, err = logger.ParseFormat(tt.giveString)
} else {
f, err = logger.ParseFormat(tt.giveBytes)
}
if tt.wantError == nil {
require.NoError(t, err)
require.Equal(t, tt.wantFormat, f)
} else {
require.EqualError(t, err, tt.wantError.Error())
}
})
}
}

83
internal/logger/level.go Normal file
View File

@ -0,0 +1,83 @@
package logger
import (
"fmt"
"strings"
)
// A Level is a logging level.
type Level int8
const (
DebugLevel Level = iota - 1
InfoLevel // default level (zero-value)
WarnLevel
ErrorLevel
FatalLevel
)
// String returns a lower-case ASCII representation of the log level.
func (l Level) String() string {
switch l {
case DebugLevel:
return "debug"
case InfoLevel:
return "info"
case WarnLevel:
return "warn"
case ErrorLevel:
return "error"
case FatalLevel:
return "fatal"
}
return fmt.Sprintf("level(%d)", l)
}
// Levels returns a slice of all logging levels.
func Levels() []Level {
return []Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, FatalLevel}
}
// LevelStrings returns a slice of all logging levels as strings.
func LevelStrings() []string {
var (
levels = Levels()
result = make([]string, len(levels))
)
for i := range levels {
result[i] = levels[i].String()
}
return result
}
// ParseLevel parses a level (case is ignored) based on the ASCII representation of the log level.
// If the provided ASCII representation is invalid an error is returned.
//
// This is particularly useful when dealing with text input to configure log levels.
func ParseLevel[T string | []byte](text T) (Level, error) {
var lvl string
if s, ok := any(text).(string); ok {
lvl = s
} else {
lvl = string(any(text).([]byte))
}
switch strings.ToLower(lvl) {
case "debug", "verbose", "trace":
return DebugLevel, nil
case "info", "": // make the zero value useful
return InfoLevel, nil
case "warn":
return WarnLevel, nil
case "error":
return ErrorLevel, nil
case "fatal":
return FatalLevel, nil
}
return Level(0), fmt.Errorf("unrecognized logging level: %q", text)
}

View File

@ -0,0 +1,84 @@
package logger_test
import (
"errors"
"testing"
"github.com/stretchr/testify/require"
"gh.tarampamp.am/error-pages/internal/logger"
)
func TestLevel_String(t *testing.T) {
for name, tt := range map[string]struct {
giveLevel logger.Level
wantString string
}{
"debug": {giveLevel: logger.DebugLevel, wantString: "debug"},
"info": {giveLevel: logger.InfoLevel, wantString: "info"},
"warn": {giveLevel: logger.WarnLevel, wantString: "warn"},
"error": {giveLevel: logger.ErrorLevel, wantString: "error"},
"fatal": {giveLevel: logger.FatalLevel, wantString: "fatal"},
"<unknown>": {giveLevel: logger.Level(127), wantString: "level(127)"},
} {
t.Run(name, func(t *testing.T) {
require.Equal(t, tt.wantString, tt.giveLevel.String())
})
}
}
func TestParseLevel(t *testing.T) {
for name, tt := range map[string]struct {
giveBytes []byte
giveString string
wantLevel logger.Level
wantError error
}{
"<empty value>": {giveBytes: []byte(""), wantLevel: logger.InfoLevel},
"<empty value> (string)": {giveString: "", wantLevel: logger.InfoLevel},
"trace": {giveBytes: []byte("debug"), wantLevel: logger.DebugLevel},
"verbose": {giveBytes: []byte("debug"), wantLevel: logger.DebugLevel},
"debug": {giveBytes: []byte("debug"), wantLevel: logger.DebugLevel},
"debug (string)": {giveString: "debug", wantLevel: logger.DebugLevel},
"info": {giveBytes: []byte("info"), wantLevel: logger.InfoLevel},
"warn": {giveBytes: []byte("warn"), wantLevel: logger.WarnLevel},
"error": {giveBytes: []byte("error"), wantLevel: logger.ErrorLevel},
"fatal": {giveBytes: []byte("fatal"), wantLevel: logger.FatalLevel},
"fatal (string)": {giveString: "fatal", wantLevel: logger.FatalLevel},
"foobar": {giveBytes: []byte("foobar"), wantError: errors.New("unrecognized logging level: \"foobar\"")}, //nolint:lll
} {
t.Run(name, func(t *testing.T) {
var (
l logger.Level
err error
)
if tt.giveString != "" {
l, err = logger.ParseLevel(tt.giveString)
} else {
l, err = logger.ParseLevel(tt.giveBytes)
}
if tt.wantError == nil {
require.NoError(t, err)
require.Equal(t, tt.wantLevel, l)
} else {
require.EqualError(t, err, tt.wantError.Error())
}
})
}
}
func TestLevels(t *testing.T) {
require.Equal(t, []logger.Level{
logger.DebugLevel,
logger.InfoLevel,
logger.WarnLevel,
logger.ErrorLevel,
logger.FatalLevel,
}, logger.Levels())
}
func TestLevelStrings(t *testing.T) {
require.Equal(t, []string{"debug", "info", "warn", "error", "fatal"}, logger.LevelStrings())
}

View File

@ -2,20 +2,27 @@
package logger
import (
"errors"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// New creates new "zap" logger with little customization.
func New(verbose, debug, logJSON bool) (*zap.Logger, error) {
// New creates new "zap" logger with a small customization.
func New(l Level, f Format) (*zap.Logger, error) {
var config zap.Config
if logJSON {
config = zap.NewProductionConfig()
} else {
switch f {
case ConsoleFormat:
config = zap.NewDevelopmentConfig()
config.EncoderConfig.EncodeLevel = zapcore.LowercaseColorLevelEncoder
config.EncoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout("15:04:05")
case JSONFormat:
config = zap.NewProductionConfig() // json encoder is used by default
default:
return nil, errors.New("unsupported logging format")
}
// default configuration for all encoders
@ -24,15 +31,31 @@ func New(verbose, debug, logJSON bool) (*zap.Logger, error) {
config.DisableStacktrace = true
config.DisableCaller = true
if debug {
// enable additional features for debugging
if l <= DebugLevel {
config.Development = true
config.DisableStacktrace = false
config.DisableCaller = false
}
if verbose || debug {
config.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
var zapLvl zapcore.Level
switch l { // convert level to zap.Level
case DebugLevel:
zapLvl = zap.DebugLevel
case InfoLevel:
zapLvl = zap.InfoLevel
case WarnLevel:
zapLvl = zap.WarnLevel
case ErrorLevel:
zapLvl = zap.ErrorLevel
case FatalLevel:
zapLvl = zap.FatalLevel
default:
return nil, errors.New("unsupported logging level")
}
config.Level = zap.NewAtomicLevelAt(zapLvl)
return config.Build()
}

View File

@ -8,64 +8,51 @@ import (
"github.com/kami-zh/go-capturer"
"github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/logger"
"github.com/stretchr/testify/require"
"gh.tarampamp.am/error-pages/internal/logger"
)
func TestNewNotVerboseDebugJSON(t *testing.T) {
func TestNewDebugLevelConsoleFormat(t *testing.T) {
output := capturer.CaptureStderr(func() {
log, err := logger.New(false, false, false)
assert.NoError(t, err)
log, err := logger.New(logger.DebugLevel, logger.ConsoleFormat)
require.NoError(t, err)
log.Info("inf msg")
log.Debug("dbg msg")
log.Info("inf msg")
log.Error("err msg")
})
assert.Contains(t, output, time.Now().Format("15:04:05"))
assert.Regexp(t, `\t.+info.+\tinf msg`, output)
assert.NotContains(t, output, "dbg msg")
assert.Contains(t, output, "err msg")
}
func TestNewVerboseNotDebugJSON(t *testing.T) {
output := capturer.CaptureStderr(func() {
log, err := logger.New(true, false, false)
assert.NoError(t, err)
log.Info("inf msg")
log.Debug("dbg msg")
log.Error("err msg")
})
assert.Contains(t, output, time.Now().Format("15:04:05"))
assert.Regexp(t, `\t.+info.+\tinf msg`, output)
assert.Contains(t, output, "dbg msg")
assert.Contains(t, output, "err msg")
}
func TestNewVerboseDebugNotJSON(t *testing.T) {
output := capturer.CaptureStderr(func() {
log, err := logger.New(true, true, false)
assert.NoError(t, err)
log.Info("inf msg")
log.Debug("dbg msg")
log.Error("err msg")
})
assert.Contains(t, output, time.Now().Format("15:04:05"))
assert.Regexp(t, `\t.+info.+\t.+logger_test\.go:\d+\tinf msg`, output)
assert.Contains(t, output, "dbg msg")
assert.Contains(t, output, "err msg")
}
func TestNewNotVerboseDebugButJSON(t *testing.T) {
func TestNewErrorLevelConsoleFormat(t *testing.T) {
output := capturer.CaptureStderr(func() {
log, err := logger.New(false, false, true)
assert.NoError(t, err)
log, err := logger.New(logger.ErrorLevel, logger.ConsoleFormat)
require.NoError(t, err)
log.Info("inf msg")
log.Debug("dbg msg")
log.Info("inf msg")
log.Error("err msg")
})
assert.NotContains(t, output, "inf msg")
assert.NotContains(t, output, "dbg msg")
assert.Contains(t, output, "err msg")
}
func TestNewWarnLevelJSONFormat(t *testing.T) {
output := capturer.CaptureStderr(func() {
log, err := logger.New(logger.WarnLevel, logger.JSONFormat)
require.NoError(t, err)
log.Debug("dbg msg")
log.Info("inf msg")
log.Warn("warn msg")
log.Error("err msg")
})
@ -75,6 +62,14 @@ func TestNewNotVerboseDebugButJSON(t *testing.T) {
lines := strings.Split(strings.Trim(output, "\n"), "\n")
assert.JSONEq(t, `{"level":"info","ts":0.1,"msg":"inf msg"}`, lines[0])
assert.JSONEq(t, `{"level":"warn","ts":0.1,"msg":"warn msg"}`, lines[0])
assert.JSONEq(t, `{"level":"error","ts":0.1,"msg":"err msg"}`, lines[1])
}
func TestNewErrors(t *testing.T) {
_, err := logger.New(logger.Level(127), logger.ConsoleFormat)
require.EqualError(t, err, "unsupported logging level")
_, err = logger.New(logger.WarnLevel, logger.Format(255))
require.EqualError(t, err, "unsupported logging format")
}

View File

@ -8,7 +8,8 @@ import (
"github.com/prometheus/client_golang/prometheus/testutil"
dto "github.com/prometheus/client_model/go"
"github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/metrics"
"gh.tarampamp.am/error-pages/internal/metrics"
)
func TestMetrics_Register(t *testing.T) {

View File

@ -5,7 +5,8 @@ import (
"github.com/prometheus/client_golang/prometheus/testutil"
"github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/metrics"
"gh.tarampamp.am/error-pages/internal/metrics"
)
func TestNewRegistry(t *testing.T) {

View File

@ -0,0 +1,16 @@
package options
type ErrorPage struct {
Default struct {
PageCode string // default error page code
HTTPCode uint16 // default HTTP response code
}
L10n struct {
Disabled bool // disable error pages localization
}
Template struct {
Name string // template name
}
ShowDetails bool // show request details in response
ProxyHTTPHeaders []string // proxy HTTP request headers list
}

View File

@ -4,7 +4,8 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/pick"
"gh.tarampamp.am/error-pages/internal/pick"
)
func TestPicker_NextIndex_First(t *testing.T) {

View File

@ -5,7 +5,8 @@ import (
"time"
"github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/pick"
"gh.tarampamp.am/error-pages/internal/pick"
)
func TestStringsSlice_Pick(t *testing.T) {

View File

@ -4,7 +4,8 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/tpl"
"gh.tarampamp.am/error-pages/internal/tpl"
)
func TestHashBytes(t *testing.T) {

View File

@ -16,6 +16,7 @@ type Properties struct { // only string properties with a "token" tag, please
RequestID string `token:"request_id"`
ForwardedFor string `token:"forwarded_for"`
Host string `token:"host"`
L10nDisabled bool
ShowRequestDetails bool
}

View File

@ -4,7 +4,8 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/tpl"
"gh.tarampamp.am/error-pages/internal/tpl"
)
func TestProperties_Replaces(t *testing.T) {

View File

@ -10,7 +10,8 @@ import (
"time"
"github.com/pkg/errors"
"github.com/tarampampam/error-pages/internal/version"
"gh.tarampamp.am/error-pages/internal/version"
)
// These functions are always allowed in the templates.
@ -30,6 +31,7 @@ var tplFnMap = template.FuncMap{ //nolint:gochecknoglobals
return 0
},
"env": os.Getenv,
}
var ErrClosed = errors.New("closed")
@ -140,8 +142,10 @@ func (tr *TemplateRenderer) Render(content []byte, props Properties) ([]byte, er
}
var funcMap = template.FuncMap{
"show_details": func() bool { return props.ShowRequestDetails },
"hide_details": func() bool { return !props.ShowRequestDetails },
"show_details": func() bool { return props.ShowRequestDetails },
"hide_details": func() bool { return !props.ShowRequestDetails },
"l10n_disabled": func() bool { return props.L10nDisabled },
"l10n_enabled": func() bool { return !props.L10nDisabled },
}
// make a copy of template functions map
@ -204,7 +208,7 @@ func (tr *TemplateRenderer) Close() error {
}
func (tr *TemplateRenderer) mixHashes(a, b Hash) (result cacheEntryHash) {
for i := 0; i < len(a); i++ {
for i := 0; i < len(a); i++ { //nolint:gosimple
result[i] = a[i]
}

View File

@ -2,18 +2,25 @@ package tpl_test
import (
"math/rand"
"os"
"strconv"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tarampampam/error-pages/internal/tpl"
"github.com/stretchr/testify/require"
"gh.tarampamp.am/error-pages/internal/tpl"
)
func Test_Render(t *testing.T) {
renderer := tpl.NewTemplateRenderer()
defer func() { _ = renderer.Close() }()
require.NoError(t, os.Setenv("TEST_ENV_VAR", "unit-test"))
defer func() { require.NoError(t, os.Unsetenv("TEST_ENV_VAR")) }()
for name, tt := range map[string]struct {
giveContent string
giveProps tpl.Properties
@ -56,6 +63,22 @@ func Test_Render(t *testing.T) {
giveProps: tpl.Properties{Code: "201", Message: "lorem ipsum"},
wantContent: `{"code": "201", "message": {"here":[ " Yeah " ]}}`,
},
"fn l10n_enabled": {
giveContent: "{{ if l10n_enabled }}Y{{ else }}N{{ end }}",
giveProps: tpl.Properties{L10nDisabled: true},
wantContent: "N",
},
"fn l10n_disabled": {
giveContent: "{{ if l10n_disabled }}Y{{ else }}N{{ end }}",
giveProps: tpl.Properties{L10nDisabled: true},
wantContent: "Y",
},
"env": {
giveContent: `{{ env "TEST_ENV_VAR" }}`,
wantContent: "unit-test",
},
} {
t.Run(name, func(t *testing.T) {
content, err := renderer.Render([]byte(tt.giveContent), tt.giveProps)

11
l10n/.eslintrc.json Normal file
View File

@ -0,0 +1,11 @@
{
"extends": [
"eslint:recommended"
],
"parserOptions": {
"ecmaVersion": 2017
},
"env": {
"browser": true
}
}

799
l10n/l10n.js Normal file
View File

@ -0,0 +1,799 @@
Object.defineProperty(window, 'l10n', {
value: new function () {
// language codes list: <https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes>
const data = { // all keys should be in english (it is default/main locale)
'Error': {
fr: 'Erreur',
ru: 'Ошибка',
uk: 'Помилка',
pt: 'Erro',
nl: 'Fout',
de: 'Fehler',
es: 'Error',
zh: '错误',
},
'Good luck': {
fr: 'Bonne chance',
ru: 'Удачи',
uk: 'Успіхів',
pt: 'Boa sorte',
nl: 'Veel succes',
de: 'Viel Glück',
es: 'Buena Suerte',
zh: '祝好运',
},
'UH OH': {
fr: 'Oups',
ru: 'Ох',
uk: 'Ох',
pt: 'Ops',
nl: 'Oeps',
de: 'Hoppla',
es: 'Oups',
zh: '哎呀',
},
'Request details': {
fr: 'Détails de la requête',
ru: 'Детали запроса',
uk: 'Деталі запиту',
pt: 'Detalhes da solicitação',
nl: 'Details van verzoek',
de: 'Details der Anfrage',
es: 'Detalles de la petición',
zh: '请求详情',
},
'Double-check the URL': {
fr: 'Vérifiez lURL',
ru: 'Дважды проверьте URL',
uk: 'Двічі перевіряйте URL-адресу',
pt: 'Verifique novamente a URL',
nl: 'Controleer de URL',
de: 'Überprüfen Sie die URL',
es: 'Verifique la url',
zh: '请再次检查地址',
},
'Alternatively, go back': {
fr: 'Essayer de revenir en arrière',
ru: 'Или можете вернуться назад',
uk: 'Або ви можете повернутися',
pt: "Como alternativa, tente voltar",
nl: 'Of ga terug',
de: 'Alternativ gehen Sie zurück',
es: 'Como alternativa, vuelva atrás',
zh: '或返回上一页',
},
'Here\'s what might have happened': {
fr: 'Voici ce qui aurait pu se passer',
ru: 'Из-за чего это могло случиться',
uk: 'Що це може статися',
pt: 'Aqui está o que pode ter acontecido',
nl: 'Wat er gebeurd kan zijn',
de: 'Folgendes könnte passiert sein',
es: 'Esto es lo que ha podido pasar',
zh: '可能原因有',
},
'You may have mistyped the URL': {
fr: 'Vous avez peut-être mal tapé lURL',
ru: 'Вы могли ошибиться в URL',
uk: 'Ви можете зробити помилку в URL-адресі',
pt: 'Você pode ter digitado incorretamente a URL',
nl: 'De URL bevat een typefout',
de: 'Möglicherweise haben Sie die URL falsch eingegeben',
es: 'Igual ha escrito mal la URL',
zh: '您可能输入了错误的地址',
},
'The site was moved': {
fr: 'Le site a été déplacé',
ru: 'Сайт был перемещён',
uk: 'Сайт був переміщений',
pt: 'O site foi movido',
nl: 'De site is verplaatst',
de: 'Die Seite wurde verschoben',
es: 'El sitio se ha movido',
zh: '站点已被转移',
},
'It was never here': {
fr: 'Il na jamais été ici',
ru: 'Он никогда не был здесь',
uk: 'Він ніколи не був тут',
pt: 'Nunca esteve aqui',
nl: 'Het was hier nooit',
de: 'Es war nie hier',
es: 'Nunca ha estado aquí',
zh: '站点从未存在',
},
'Bad Request': {
fr: 'Mauvaise requête',
ru: 'Некорректный запрос',
uk: 'Неправильний запит',
pt: 'Requisição inválida',
nl: 'Foutieve anvraag',
de: 'Fehlerhafte Anfrage',
es: 'Petición inválida',
zh: '错误请求',
},
'The server did not understand the request': {
fr: 'Le serveur ne comprend pas la requête',
ru: 'Сервер не смог обработать запрос из-за ошибки в нём',
uk: 'Сервер не міг обробити запит через помилку в ньому',
pt: 'O servidor não entendeu a solicitação',
nl: 'De server begreep het verzoek niet',
de: 'Der Server hat die Anfrage nicht verstanden',
es: 'El servidor no entendió la petición',
zh: '服务器不理解该请求',
},
'Unauthorized': {
fr: 'Non autorisé',
ru: 'Запрос не авторизован',
uk: 'Несанкціонований доступ',
pt: 'Não autorizado',
nl: 'Niet geautoriseerd',
de: 'Nicht autorisiert',
es: 'No autorizado',
zh: '未经授权',
},
'The requested page needs a username and a password': {
fr: 'La page demandée nécessite un nom dutilisateur et un mot de passe',
ru: 'Для доступа к странице требуется логин и пароль',
uk: 'Щоб отримати доступ до сторінки, потрібний логін та пароль',
pt: 'A página solicitada precisa de um nome de usuário e uma senha',
nl: 'De pagina heeft een gebruikersnaam en wachtwoord nodig',
de: 'Die angeforderte Seite benötigt einen Benutzernamen und ein Passwort',
es: 'La página solicitada necesita un usuario y una contraseña',
zh: '请求的页面需要用户名和密码',
},
'Forbidden': {
fr: 'Interdit',
ru: 'Запрещено',
uk: 'Заборонено',
pt: 'Proibido',
nl: 'Verboden',
de: 'Verboten',
es: 'Prohibido',
zh: '禁止访问',
},
'Access is forbidden to the requested page': {
fr: 'Accès interdit à la page demandée',
ru: 'Доступ к странице запрещён',
uk: 'Доступ до сторінки заборонено',
pt: 'É proibido o acesso à página solicitada',
nl: 'Toegang tot de pagina is verboden',
de: 'Der Zugriff auf die angeforderte Seite ist verboten',
es: 'El acceso está prohibido para la página solicitada',
zh: '禁止访问请求的页面',
},
'Not Found': {
fr: 'Introuvable',
ru: 'Страница не найдена',
uk: 'Сторінка не знайдена',
pt: 'Não encontrado',
nl: 'Niet gevonden',
de: 'Nicht gefunden',
es: 'No encontrado',
zh: '未找到',
},
'The server can not find the requested page': {
fr: 'Le serveur ne peut trouver la page demandée',
ru: 'Сервер не смог найти запрашиваемую страницу',
uk: 'Сервер не міг знайти запитану сторінку',
pt: 'O servidor não consegue encontrar a página solicitada',
nl: 'De server kan de pagina niet vinden',
de: 'Der Server kann die angeforderte Seite nicht finden',
es: 'El servidor no puede encontrar la página solicitada',
zh: '服务器找不到请求的页面',
},
'Method Not Allowed': {
fr: 'Méthode Non Autorisée',
ru: 'Метод не поддерживается',
uk: 'Неприпустимий метод',
pt: 'Método não permitido',
nl: 'Methode niet toegestaan',
de: 'Methode nicht erlaubt',
es: 'Método no permitido',
zh: '方法不被允许',
},
'The method specified in the request is not allowed': {
fr: 'La méthode spécifiée dans la requête nest pas autorisée',
ru: 'Указанный в запросе метод не поддерживается',
uk: 'Метод, зазначений у запиті, не підтримується',
pt: 'O método especificado na solicitação não é permitido',
nl: 'De methode in het verzoek is niet toegestaan',
de: 'Die in der Anfrage angegebene Methode ist nicht zulässig',
es: 'El método especificado en la petición no está permitido',
zh: '请求指定的方法不被允许',
},
'Proxy Authentication Required': {
fr: 'Authentification proxy requise',
ru: 'Нужна аутентификация прокси',
uk: 'Потрібна ідентифікація проксі',
pt: 'Autenticação de proxy necessária',
nl: 'Authenticatie op de proxyserver verplicht',
de: 'Proxy-Authentifizierung benötigt',
es: 'Autenticación de proxy requerida',
zh: '需要代理服务器身份验证',
},
'You must authenticate with a proxy server before this request can be served': {
fr: 'Vous devez vous authentifier avec un serveur proxy avant que cette requête puisse être servie',
ru: 'Вы должны быть авторизованы на прокси сервере для обработки этого запроса',
uk: 'Ви повинні увійти до проксі-сервера для обробки цього запиту',
pt: 'Você deve se autenticar com um servidor proxy antes que esta solicitação possa ser atendida',
nl: 'Je moet authenticeren bij een proxyserver voordat dit verzoek uitgevoerd kan worden',
de: 'Sie müssen sich bei einem Proxy-Server authentifizieren, bevor diese Anfrage bedient werden kann',
es: 'Debes autentificarte con un servidor proxy antes de que esta petición pueda ser atendida',
zh: '您必须对代理服务器进行身份验证,然后才能让请求得到处理',
},
'Request Timeout': {
fr: 'Requête expiré',
ru: 'Истекло время ожидания',
uk: 'Час запиту закінчився',
pt: 'Tempo limite de solicitação excedido',
nl: 'Aanvraagtijd verstreken',
de: 'Zeitüberschreitung der Anforderung',
es: 'Tiempo límite de la petición excedido',
zh: '请求超时',
},
'The request took longer than the server was prepared to wait': {
fr: 'La requête prend plus de temps que prévu',
ru: 'Отправка запроса заняла слишком много времени',
uk: 'Надсилання запиту зайняв занадто багато часу',
pt: 'A solicitação demorou mais do que o servidor estava preparado para esperar',
nl: 'Het verzoek duurde langer dan de server wilde wachten',
de: 'Die Anfrage hat länger gedauert, als der Server bereit war zu warten',
es: 'La petición esta tardando más de lo que el servidor estaba preparado para esperar',
zh: '请求用时超过了服务器设置的最长等待时间',
},
'Conflict': {
fr: 'Conflit',
ru: 'Конфликт',
uk: 'Конфлікт',
pt: 'Conflito',
nl: 'Conflict',
de: 'Konflikt',
es: 'Conflicto',
zh: '冲突',
},
'The request could not be completed because of a conflict': {
fr: 'La requête na pas pu être complétée à cause dun conflit',
ru: 'Запрос не может быть обработан из-за конфликта',
uk: 'Запит не може бути оброблений через конфлікт',
pt: 'A solicitação não pôde ser concluída devido a um conflito',
nl: 'Het verzoek kon niet worden verwerkt vanwege een conflict',
de: 'Die Anfrage konnte aufgrund eines Konflikts nicht abgeschlossen werden',
es: 'La petición no ha podido ser completada por un conflicto',
zh: '由于冲突,请求无法完成',
},
'Gone': {
fr: 'Supprimé',
ru: 'Удалено',
uk: 'Вилучений',
pt: 'Removido',
nl: 'Verdwenen',
de: 'Verschwunden',
es: 'Eliminado',
zh: '已移除',
},
'The requested page is no longer available': {
fr: 'La page demandée nest plus disponible',
ru: 'Запрошенная страница была удалена',
uk: 'Запитана сторінка була видалена',
pt: 'A página solicitada não está mais disponível',
nl: 'De pagina is niet langer beschikbaar',
de: 'Die angeforderte Seite ist nicht mehr verfügbar',
es: 'La página solicitada no está ya disponible',
zh: '请求的页面不再可用',
},
'Length Required': {
fr: 'Longueur requise',
ru: 'Необходима длина',
uk: 'Потрібно вказати розмір',
pt: 'Content-Length necessário',
nl: 'Lengte benodigd',
de: 'Länge benötigt',
es: 'Longitud requerida',
zh: '需要长度',
},
'The "Content-Length" is not defined. The server will not accept the request without it': {
fr: 'Le "Content-Length" nest pas défini. Le serveur ne prendra pas en compte la requête',
ru: 'Заголовок "Content-Length" не был передан. Сервер не может обработать запрос без него',
uk: 'Заголовок "Content-Length" не був переданий. Сервер не може обробити запит без нього',
pt: 'O "Content-Length" não está definido. O servidor não aceitará a solicitação sem ele',
nl: 'De "Content-Length" is niet gespecificeerd. De server accepteert het verzoek niet zonder',
de: 'Die "Content-Length" ist nicht definiert. Ohne sie akzeptiert der Server die Anfrage nicht',
es: 'El "Content-Legth" no eta definido. Este servidor no aceptará la petición sin él',
zh: '未指定Content-Length(内容长度)。服务器将不接受不包含此头信息的请求',
},
'Precondition Failed': {
fr: 'Échec de la condition préalable',
ru: 'Условие ложно',
uk: 'Збій під час обробки попередньої умови',
pt: 'Falha na pré-condição',
nl: 'Niet voldaan aan vooraf gestelde voorwaarde',
de: 'Vorbedingung fehlgeschlagen',
es: 'Precondición fallida',
zh: '前置条件判定失败',
},
'The pre condition given in the request evaluated to false by the server': {
fr: 'La précondition donnée dans la requête a été évaluée comme étant fausse par le serveur',
ru: 'Ни одно из условных полей заголовка запроса не было выполнено',
uk: 'Жодна з умовних полів заголовка запиту не була виконана',
pt: 'A pré-condição dada na solicitação avaliada como falsa pelo servidor',
nl: 'De vooraf gestelde voorwaarde is afgewezen door de server',
de: 'Die in der Anfrage angegebene Vorbedingung wird vom Server als falsch bewertet',
es: 'La precondición ha sido evaluada como negativa para esta petición por el servidor',
zh: '服务器评估请求中给出的前置条件的结果为false(假)',
},
'Payload Too Large': {
fr: 'Charge trop volumineuse',
ru: 'Слишком большой запрос',
uk: 'Занадто великий запит',
pt: 'Payload muito grande',
nl: 'Aanvraag te grood',
de: 'Anfrage zu groß',
es: 'Carga muy grande',
zh: '请求体过大',
},
'The server will not accept the request, because the request entity is too large': {
fr: 'Le serveur ne prendra pas en compte la requête, car lentité de la requête est trop volumineuse',
ru: 'Сервер не может обработать запрос, так как он слишком большой',
uk: 'Сервер не може обробити запит, оскільки він занадто великий',
pt: 'O servidor não aceitará a solicitação porque a entidade da solicitação é muito grande',
nl: 'De server accepteert het verzoek niet omdat de aanvraag te groot is',
de: 'Der Server akzeptiert die Anfrage nicht, da die Datenmenge zu groß ist',
es: 'El servidor no aceptará esta petición, porque la carga es demasiado grande',
zh: '请求体过大,服务器将不接受该请求',
},
'Requested Range Not Satisfiable': {
fr: 'Requête non satisfaisante',
ru: 'Диапазон не достижим',
uk: 'Запитуваний діапазон недосяжний',
pt: 'Intervalo Solicitado Não Satisfatório',
nl: 'Aangevraagd gedeelte niet opvraagbaar',
de: 'Anfrage-Bereich nicht erfüllbar',
es: 'Intervalo solicitado no satisfactorio',
zh: '不满足请求范围',
},
'The requested byte range is not available and is out of bounds': {
fr: 'Le byte range demandé nest pas disponible et est hors des limites',
ru: 'Запрошенный диапазон данных недоступен или вне допустимых пределов',
uk: 'Описаний діапазон даних недоступний або з допустимих меж',
pt: 'O intervalo de bytes solicitado não está disponível e está fora dos limites',
nl: 'De aangevraagde bytes zijn buiten het limiet',
de: 'Der angefragte Teilbereich der Ressource existiert nicht oder ist ungültig',
es: 'El intervalo de bytes requerido no está disponible o se encuentra fuera de los límites',
zh: '请求的字节范围不可用,超出边界',
},
'I\'m a teapot': {
fr: 'Je suis une théière',
ru: 'Я чайник',
uk: 'Я чайник',
pt: 'Eu sou um bule',
nl: 'Ik ben een theepot',
de: 'Ich bin eine Teekanne',
es: 'Soy una tetera',
zh: '我是一只茶壶',
},
'Attempt to brew coffee with a teapot is not supported': {
fr: 'Tenter de préparer du café avec une théière nest pas pris en charge',
ru: 'Попытка заварить кофе в чайнике обречена на фиаско',
uk: 'Спроба виварити каву в чайник приречена на фіаско',
pt: 'A tentativa de preparar café com um bule não é suportada',
nl: 'Koffie maken met een theepot is niet ondersteund',
de: 'Der Versuch, Kaffee mit einer Teekanne zuzubereiten, wird nicht unterstützt',
es: 'Intentar hacer un café en una tetera no está soportado',
zh: '用茶壶泡咖啡不受支持',
},
'Too Many Requests': {
fr: 'Trop de requêtes',
ru: 'Слишком много запросов',
uk: 'Занадто багато запитів',
pt: 'Excesso de solicitações',
nl: 'Te veel requests',
de: 'Zu viele Anfragen',
es: 'Demasiadas peticiones',
zh: '请求过多',
},
'Too many requests in a given amount of time': {
fr: 'Trop de requêtes dans un délai donné',
ru: 'Отправлено слишком много запросов за короткое время',
uk: 'Надіслано занадто багато запитів на короткий час',
pt: 'Excesso de solicitações em um determinado período de tempo',
nl: 'Te veel verzoeken binnen een bepaalde tijd',
de: 'Der Client hat zu viele Anfragen in einem bestimmten Zeitraum gesendet',
es: 'Demasiadas peticiones en un determinado periodo de tiempo',
zh: '在给定的时间内发送了过多请求',
},
'Internal Server Error': {
fr: 'Erreur interne du serveur',
ru: 'Внутренняя ошибка сервера',
uk: 'Внутрішня помилка сервера',
pt: 'Erro do Servidor Interno',
nl: 'Interne serverfout',
de: 'Interner Server-Fehler',
es: 'Error Interno',
zh: '内部服务器错误',
},
'The server met an unexpected condition': {
fr: 'Le serveur a rencontré une condition inattendue',
ru: 'Произошло что-то неожиданное на сервере',
uk: 'На сервері було щось несподіване',
pt: 'O servidor encontrou uma condição inesperada',
nl: 'De server ondervond een onverwachte conditie',
de: 'Der Server hat einen internen Fehler festgestellt',
es: 'El servidor ha encontrado una condición no esperada',
zh: '服务器遇到了意外情况',
},
'Bad Gateway': {
fr: 'Mauvaise passerelle',
ru: 'Ошибка шлюза',
uk: 'Помилка шлюзу',
pt: 'Gateway inválido',
nl: 'Ongeldige Gateway',
de: 'Fehlerhafter Gateway',
es: 'Puerta de enlace no valida',
zh: '无效网关',
},
'The server received an invalid response from the upstream server': {
fr: 'Le serveur a reçu une réponse invalide du serveur distant',
ru: 'Сервер получил некорректный ответ от вышестоящего сервера',
uk: 'Сервер отримав неправильну відповідь з сервера Upstream',
pt: 'O servidor recebeu uma resposta inválida do servidor upstream',
nl: 'De server ontving een ongeldig antwoord van een bovenliggende server',
de: 'Der Server hat eine ungültige Antwort vom Upstream-Server erhalten',
es: 'El servidor ha recibido una respuesta no válida del servidor de origen',
zh: '服务器从上游服务器收到了无效的响应',
},
'Service Unavailable': {
fr: 'Service indisponible',
ru: 'Сервис недоступен',
uk: 'Сервіс недоступний',
pt: 'Serviço não disponível',
nl: 'Dienst niet beschikbaar',
de: 'Dienst nicht verfügbar',
es: 'Servicio no disponible',
zh: '服务不可用',
},
'The server is temporarily overloading or down': {
fr: 'Le serveur est temporairement en surcharge ou indisponible',
ru: 'Сервер временно не может обрабатывать запросы по техническим причинам',
uk: 'Сервер тимчасово не може обробляти запити з технічних причин',
pt: 'O servidor está temporariamente sobrecarregado ou inativo',
nl: 'De server is tijdelijk overbelast of niet bereikbaar',
de: 'Der Server ist vorübergehend überlastet oder ausgefallen',
es: 'El servidor está temporalmente sobrecargado o inactivo',
zh: '服务器暂时过载或不可用',
},
'Gateway Timeout': {
fr: 'Expiration Passerelle',
ru: 'Шлюз не отвечает',
uk: 'Шлюз не відповідає',
pt: 'Tempo limite do gateway excedido',
nl: 'Gateway Verlopen',
de: 'Gateway Zeitüberschreitung',
es: 'Tiempo límite de puerta de enlace excedido',
zh: '网关超时',
},
'The gateway has timed out': {
fr: 'Le temps dattente de la passerelle est dépassé',
ru: 'Сервер не дождался ответа от вышестоящего сервера',
uk: 'Сервер не чекав відповіді від сервера Upstream',
pt: 'O gateway esgotou o tempo limite',
nl: 'De verbinding naar de bovenliggende server is verlopen',
de: 'Das Zeitlimit für den Verbindungsaufbau mit dem Upstream-Server ist abgelaufen',
es: 'La puerta de enlace ha sobrepasado el tiempo límite',
zh: '网关响应已经超时',
},
'HTTP Version Not Supported': {
fr: 'Version HTTP non prise en charge',
ru: 'Версия HTTP не поддерживается',
uk: 'Версія НТТР не підтримується',
pt: 'Versão HTTP não suportada',
nl: 'HTTP-versie wordt niet ondersteunt',
de: 'HTTP-Version wird nicht unterstützt',
es: 'Versión de HTTP no soportada',
zh: 'HTTP版本不受支持',
},
'The server does not support the "http protocol" version': {
fr: 'Le serveur ne supporte pas la version du protocole HTTP',
ru: 'Сервер не поддерживает запрошенную версию HTTP протокола',
uk: 'Сервер не підтримує запитану версію HTTP-протоколу',
pt: 'O servidor não suporta a versão do protocolo HTTP',
nl: 'De server ondersteunt deze HTTP-versie niet',
de: 'Der Server unterstützt die HTTP-Protokoll-Version nicht',
es: 'El servidor no soporta la versión del protocolo HTTP',
zh: '服务器不支持该HTTP协议版本',
},
'Host': {
fr: 'Hôte',
ru: 'Хост',
uk: 'Хост',
pt: 'Hospedeiro',
nl: 'Host',
de: 'Host',
es: 'Host',
zh: '主机',
},
'Original URI': {
fr: 'URI dorigine',
ru: 'Исходный URI',
uk: 'Вихідний URI',
pt: 'URI original',
nl: 'Originele URI',
de: 'Originale URI',
es: 'URI original',
zh: '原始URI',
},
'Forwarded for': {
fr: 'Transmis pour',
ru: 'Перенаправлен',
uk: 'Перенаправлений',
pt: 'Encaminhado para',
nl: 'Doorgestuurd voor',
de: 'Weitergeleitet für',
es: 'Remitido por',
zh: '转发自',
},
'Namespace': {
fr: 'Espace de noms',
ru: 'Пространство имён',
uk: 'Простір імен',
pt: 'Namespace',
nl: 'Elementnaam',
de: 'Namensraum',
es: 'Namespace',
zh: '命名空间',
},
'Ingress name': {
fr: 'Nom ingress',
ru: 'Имя Ingress',
uk: 'Ім\'я Ingress',
pt: 'Nome Ingress',
nl: 'Ingress naam',
de: 'Ingress Name',
es: 'Nombre Ingress',
zh: '入口名',
},
'Service name': {
fr: 'Nom du service',
ru: 'Имя сервиса',
uk: 'Ім\'я сервісу',
pt: 'Nome do Serviço',
nl: 'Service naam',
de: 'Service Name',
es: 'Nombre del servicio',
zh: '服务名',
},
'Service port': {
fr: 'Port du service',
ru: 'Порт сервиса',
uk: 'Порт сервісу',
pt: 'Porta do serviço',
nl: 'Service poort',
de: 'Service Port',
es: 'Puerto del servicio',
zh: '服务端口',
},
'Request ID': {
fr: 'Identifiant de la requête',
ru: 'ID запроса',
uk: 'ID запиту',
pt: 'ID da solicitação',
nl: 'ID van het verzoek',
de: 'Anfrage ID',
es: 'ID de la petición',
zh: '请求ID',
},
'Timestamp': {
fr: 'Horodatage',
ru: 'Временная метка',
uk: 'Тимчасова мітка',
pt: 'Timestamp',
nl: 'Tijdstempel',
de: 'Zeitstempel',
es: 'Timestamp',
zh: '时间戳',
},
'client-side error': {
fr: 'Erreur Client',
ru: 'ошибка на стороне клиента',
uk: 'помилка на стороні клієнта',
pt: 'erro do lado do cliente',
nl: 'fout aan de gebruikerskant',
de: 'Clientseitiger Fehler',
es: 'Error del lado del cliente',
zh: '客户端错误',
},
'server-side error': {
fr: 'Erreur Serveur',
ru: 'ошибка на стороне сервера',
uk: 'помилка на стороні сервера',
pt: 'erro do lado do servidor',
nl: 'fout aan de serverkant',
de: 'Serverseitiger Fehler',
es: 'Error del lado del servidor',
zh: '服务端错误',
},
'Your Client': {
fr: 'Votre Client',
ru: 'Ваш Браузер',
uk: 'Ваш Браузер',
pt: 'Seu Cliente',
nl: 'Jouw Client',
de: 'Ihr Client',
es: 'Tu cliente',
zh: '您的客户端',
},
'Network': {
fr: 'Réseau',
ru: 'Сеть',
uk: 'Сіть',
pt: 'Rede',
nl: 'Netwerk',
de: 'Netzwerk',
es: 'Red',
zh: '网络',
},
'Web Server': {
fr: 'Serveur Web',
ru: 'Web Сервер',
uk: 'Web Сервер',
pt: 'Servidor web',
nl: 'Web Server',
de: 'Webserver',
es: 'Servidor web',
zh: 'Web服务器',
},
'What happened?': {
fr: 'Que sest-il passé ?',
ru: 'Что произошло?',
uk: 'Що сталося?',
pt: 'O que aconteceu?',
nl: 'Wat is er gebeurd?',
de: 'Was ist passiert?',
es: '¿Que ha pasado?',
zh: '发生了什么?',
},
'What can i do?': {
fr: 'Que puis-je faire ?',
ru: 'Что можно сделать?',
uk: 'Що можна зробити?',
pt: 'O que eu posso fazer?',
nl: 'Wat kan ik doen?',
de: 'Was kann ich machen?',
es: '¿Que puedo hacer?',
zh: '我能做什么?',
},
'Please try again in a few minutes': {
fr: 'Veuillez réessayer dans quelques minutes',
ru: 'Пожалуйста, попробуйте повторить запрос ещё раз чуть позже',
uk: 'Будь ласка, спробуйте повторити запит ще раз трохи пізніше',
pt: 'Por favor, tente novamente em alguns minutos',
nl: 'Probeer het alstublieft opnieuw over een paar minuten',
de: 'Bitte versuchen Sie es in ein paar Minuten erneut',
es: 'Por favor, intente nuevamente en unos minutos',
zh: '请在几分钟后重试',
},
'Working': {
fr: 'Opérationnel',
ru: 'Работает',
uk: 'Працює',
pt: 'Funcionando',
nl: 'Functioneel',
de: 'Funktioniert',
es: 'Trabajando',
zh: '正常运行',
},
'Unknown': {
fr: 'Inconnu',
ru: 'Неизвестно',
uk: 'Невідомо',
pt: 'Desconhecido',
nl: 'Onbekend',
de: 'Unbekannt',
es: 'Desconocido',
zh: '未知',
},
'Please try to change the request method, headers, payload, or URL': {
fr: 'Veuillez essayer de changer la méthode de requête, les en-têtes, le contenu ou lURL',
ru: 'Пожалуйста, попробуйте изменить метод запроса, заголовки, его содержимое или URL',
uk: 'Будь ласка, спробуйте змінити метод запиту, заголовки, його вміст або URL-адресу',
pt: 'Tente alterar o método de solicitação, cabeçalhos, payload ou URL',
nl: 'Probeer het opnieuw met een andere methode, headers, payload of URL',
de: 'Bitte versuchen Sie, die Anfragemethode, Header, Payload oder URL zu ändern',
es: 'Por favor intente cambiar el método de la petición, cabeceras, carga o URL',
zh: '请尝试更改请求方法、标头、有效负载或URL',
},
'Please check your authorization data': {
fr: 'Veuillez vérifier vos données dautorisation',
ru: 'Пожалуйста, проверьте данные авторизации',
uk: 'Будь ласка, перевірте дані авторизації',
pt: 'Verifique seus dados de autorização',
nl: 'Controleer de authenticatiegegevens',
de: 'Bitte überprüfen Sie Ihre Zugangsdaten',
es: 'Verifique sus datos de autorización',
zh: '请检查您的授权数据',
},
'Please double-check the URL and try again': {
fr: 'Veuillez vérifier lURL et réessayer',
ru: 'Пожалуйста, дважды проверьте URL и попробуйте снова',
uk: 'Будь ласка, двічі перевірте URL-адресу і спробуйте знову',
pt: 'Verifique novamente o URL e tente novamente',
nl: 'Controleer de URL en probeer het opnieuw',
de: 'Bitte überprüfen Sie die URL und versuchen Sie es erneut',
es: 'Verifique de nuevo la URL y vuelva a probar',
zh: '请再次检查URL并重试',
},
};
/**
* @param {string} token
* @return {string}
*/
const serializeToken = function (token) {
return token.toLowerCase().replaceAll(/[^a-z0-9]/g, '');
};
// normalize the data keys
for (const key in data) {
Object.defineProperty(data, serializeToken(key), Object.getOwnPropertyDescriptor(data, key));
delete data[key];
}
// detect browser locale (take only 2 first symbols)
let activeLocale = navigator.language.substring(0, 2).toLowerCase();
/**
* @param {string} locale
*/
this.setLocale = function (locale) {
activeLocale = locale.toLowerCase();
}
/**
* @param {string} token
* @param {string|undefined?} def
*/
this.translate = function (token, def) {
const t = serializeToken(token);
if (activeLocale === 'en' && Object.prototype.hasOwnProperty.call(data, t)) {
return token
}
if (Object.prototype.hasOwnProperty.call(data, t) && Object.prototype.hasOwnProperty.call(data[t], activeLocale)) {
return data[t][activeLocale];
}
return def;
};
/**
* Localize all elements with HTML attribute `data-l10n`.
*/
this.localizeDocument = function () {
const dataAttributeName = 'data-l10n';
Array.prototype.forEach.call(document.querySelectorAll('[' + dataAttributeName + ']'), ($el) => {
const attr = $el.getAttribute(dataAttributeName).trim(),
token = attr.length > 0 ? attr : $el.innerText.trim(),
localized = this.translate(token, undefined);
if (attr.length === 0) {
$el.setAttribute(dataAttributeName, token);
}
if (localized !== undefined) {
$el.innerText = localized;
} else {
console.debug(`Unsupported l10n token detected: "${token}" (locale "${activeLocale}")`, $el);
}
});
};
},
writable: false,
enumerable: false,
});
window.l10n.localizeDocument();

21
l10n/readme.md Normal file
View File

@ -0,0 +1,21 @@
# 🔤 Localization
[![jsDelivr hits](https://img.shields.io/jsdelivr/gh/hm/tarampampam/error-pages)](https://www.jsdelivr.com/package/gh/tarampampam/error-pages)
This directory contains file [l10n.js](l10n.js) for the error pages localization. The working logic is very simple - pages load this script using [jsdelivr.com](https://www.jsdelivr.com/) as a CDN for [versioned content from the GitHub repository](https://www.jsdelivr.com/features#gh), and it translates tag content with the special HTML attribute `data-l10n`.
By default, pages markup contains strings in English (`en` locale). If you want to localize the error pages on the different locales, you should:
- Find your locale name on [this page](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (column `639-1`)
- Make a fork of this repository
- Edit file [l10n.js](l10n.js) in `data` section (append new localized strings) using locale name from the step 1
- Make a PR with your changes
## 👍 Translators
- 🇫🇷 French by [@jvin042](https://github.com/jvin042)
- 🇵🇹 Portuguese by [@fabtrompet](https://github.com/fabtrompet)
- 🇳🇱 Dutch by [@SchoNie](https://github.com/SchoNie)
- 🇩🇪 German by [@mschoeffmann](https://github.com/mschoeffmann)
- 🇪🇸 Spanish by [@Runig006](https://github.com/Runig006)
- 🇨🇳 Chinese by [@CDN18](https://github.com/CDN18)

View File

@ -10,9 +10,9 @@
<meta name="robots" content="noindex, nofollow"/>
<title>{{ message }}</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
<link rel="preconnect" href="https://fonts.bunny.net" crossorigin>
<link rel="dns-prefetch" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
<style>
:root{--color-bg-primary:#fff;--color-bg-secondary:#eef6fa;--color-bg-sign:#fff;--color-text-primary:#333;--color-text-secondary:#777;--color-img-details:#f62f37;--color-img-primary:#7990a1;--color-img-secondary:#00baff;--font-size-small:13px;--font-size-normal:16px;--font-size-large:45px}
@media (prefers-color-scheme:dark){
@ -35,7 +35,7 @@
.details ul li{padding-top:calc(var(--font-size-small) * 1.5)}
.details ul li:first-child{padding-top:calc(var(--font-size-small) * .6)}
.details code,.details span,.details ul li::before{font-size:var(--font-size-small);font-weight:400}
.details span{padding-right:7px}
.details code{padding-left:7px}
/* {{ end }} */
a{text-decoration:none;color:var(--color-img-secondary)}
.hidden{display:none}
@ -79,30 +79,30 @@
<body>
<main>
<div class="content">
<h2 class="title">{{ message }}</h2>
<p>{{ description }}</p>
<h2 class="title" data-l10n>{{ message }}</h2>
<p data-l10n>{{ description }}</p>
<div class="subtitle if-not-found hidden">
<p>Here's what might have happened:</p>
<p><span data-l10n>Here's what might have happened</span>:</p>
<ul>
<li>You may have mistyped the URL</li>
<li>The site was moved</li>
<li>It was never here</li>
<li data-l10n>You may have mistyped the URL</li>
<li data-l10n>The site was moved</li>
<li data-l10n>It was never here</li>
</ul>
</div>
<p class="if-maybe-wrong-uri">Double-check the URL. <a class="go-back hidden">Alternatively, go back</a></p>
<p class="if-maybe-wrong-uri"><span data-l10n>Double-check the URL</span>. <a class="go-back hidden" data-l10n>Alternatively, go back</a></p>
{{ if show_details }}
<div class="details">
<p>Request details:</p>
<p><span data-l10n>Request details</span>:</p>
<ul>
{{- if host }}<li><span>Host:</span> <code>{{ host }}</code></li>{{ end -}}
{{- if original_uri }}<li><span>Original URI:</span> <code>{{ original_uri }}</code></li>{{ end -}}
{{- if forwarded_for }}<li><span>Forwarded for:</span> <code>{{ forwarded_for }}</code></li>{{ end -}}
{{- if namespace }}<li><span>Namespace:</span> <code>{{ namespace }}</code></li>{{ end -}}
{{- if ingress_name }}<li><span>Ingress name:</span> <code>{{ ingress_name }}</code></li>{{ end -}}
{{- if service_name }}<li><span>Service name:</span> <code>{{ service_name }}</code></li>{{ end -}}
{{- if service_port }}<li><span>Service port:</span> <code>{{ service_port }}</code></li>{{ end -}}
{{- if request_id }}<li><span>Request ID:</span> <code>{{ request_id }}</code></li>{{ end -}}
<li><span>Timestamp:</span> <code>{{ now.Unix }}</code></li>
{{- if host }}<li><span><span data-l10n>Host</span>:</span> <code>{{ host }}</code></li>{{ end -}}
{{- if original_uri }}<li><span><span data-l10n>Original URI</span>:</span> <code>{{ original_uri }}</code></li>{{ end -}}
{{- if forwarded_for }}<li><span><span data-l10n>Forwarded for</span>:</span> <code>{{ forwarded_for }}</code></li>{{ end -}}
{{- if namespace }}<li><span><span data-l10n>Namespace</span>:</span> <code>{{ namespace }}</code></li>{{ end -}}
{{- if ingress_name }}<li><span><span data-l10n>Ingress name</span>:</span> <code>{{ ingress_name }}</code></li>{{ end -}}
{{- if service_name }}<li><span><span data-l10n>Service name</span>:</span> <code>{{ service_name }}</code></li>{{ end -}}
{{- if service_port }}<li><span><span data-l10n>Service port</span>:</span> <code>{{ service_port }}</code></li>{{ end -}}
{{- if request_id }}<li><span><span data-l10n>Request ID</span>:</span> <code>{{ request_id }}</code></li>{{ end -}}
<li><span><span data-l10n>Timestamp</span>:</span> <code>{{ now.Unix }}</code></li>
</ul>
</div>
{{ end }}
@ -224,6 +224,17 @@
$el.style.display = 'none'; // hide the element
}
});
// {{ if l10n_enabled }}
if (navigator.language.substring(0, 2).toLowerCase() !== 'en') {
((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n)
s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
s.async = s.defer = true;
s.addEventListener('load', () => p.removeChild(s));
p.appendChild(s);
})(document.createElement('script'), document.body);
}
// {{ end }}
</script>
</body>
<!--

View File

@ -63,39 +63,39 @@
<div class="details">
<table>
{{- if host }}<tr>
<td class="name">Host</td>
<td class="name" data-l10n>Host</td>
<td class="value">{{ host }}</td>
</tr>{{ end -}}
{{- if original_uri }}<tr>
<td class="name">Original URI</td>
<td class="name" data-l10n>Original URI</td>
<td class="value">{{ original_uri }}</td>
</tr>{{ end -}}
{{- if forwarded_for }}<tr>
<td class="name">Forwarded for</td>
<td class="name" data-l10n>Forwarded for</td>
<td class="value">{{ forwarded_for }}</td>
</tr>{{ end -}}
{{- if namespace }}<tr>
<td class="name">Namespace</td>
<td class="name" data-l10n>Namespace</td>
<td class="value">{{ namespace }}</td>
</tr>{{ end -}}
{{- if ingress_name }}<tr>
<td class="name">Ingress name</td>
<td class="name" data-l10n>Ingress name</td>
<td class="value">{{ ingress_name }}</td>
</tr>{{ end -}}
{{- if service_name }}<tr>
<td class="name">Service name</td>
<td class="name" data-l10n>Service name</td>
<td class="value">{{ service_name }}</td>
</tr>{{ end -}}
{{- if service_port }}<tr>
<td class="name">Service port</td>
<td class="name" data-l10n>Service port</td>
<td class="value">{{ service_port }}</td>
</tr>{{ end -}}
{{- if request_id }}<tr>
<td class="name">Request ID</td>
<td class="name" data-l10n>Request ID</td>
<td class="value">{{ request_id }}</td>
</tr>{{ end -}}
<tr>
<td class="name">Timestamp</td>
<td class="name" data-l10n>Timestamp</td>
<td class="value">{{ now.Unix }}</td>
</tr>
</table>
@ -103,6 +103,18 @@
{{ end }}
</div>
</div>
<script>
// {{ if l10n_enabled }}
if (navigator.language.substring(0, 2).toLowerCase() !== 'en') {
((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n)
s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
s.async = s.defer = true;
s.addEventListener('load', () => p.removeChild(s));
p.appendChild(s);
})(document.createElement('script'), document.body);
}
// {{ end }}
</script>
</body>
<!--
Error {{ code }}: {{ message }}

View File

@ -1,4 +1,8 @@
<!DOCTYPE html>
<!--
Error {{ code }}: {{ message }}
Description: {{ description }}
-->
<html lang="en">
<head>
<meta charset="utf-8"/>
@ -6,8 +10,9 @@
<meta name="robots" content="noindex, nofollow"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>{{ code }} | {{ message }}</title>
<link rel="preconnect" href="https://fonts.gstatic.com"/>
<link href="https://fonts.googleapis.com/css2?family=Red+Hat+Display:wght@500&family=Fira+Mono&family=Ubuntu&display=swap" rel="stylesheet"/>
<link rel="preconnect" href="https://fonts.bunny.net" crossorigin>
<link rel="dns-prefetch" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css2?family=Red+Hat+Display:wght@500&family=Fira+Mono&family=Ubuntu&display=swap" rel="stylesheet"/>
<style>
/** Idea author: https://github.com/186526/CloudflareCustomErrorPage */
:root{--color-bg-primary:#fff;--color-text-primary:#000;--color-text-secondary:#575958;--font-size-primary:56px;--font-size-secondary:20px;--ui-card-color-bg:#f2f2f2;--color-text-ok:#137333;--color-bg-ok:#e6f4ea;--color-text-error:#c5221f;--color-bg-error:#fce8e6;--color-text-warning:#b05a00;--color-bg-warning:#fef7e0;--icon-size:48px}
@ -34,16 +39,20 @@
.reason>*{display:block;margin:1rem;flex-grow:1;max-width:40%}
.reason h2{font-size:calc(var(--font-size-secondary) + .2rem);margin:0 0 .6em 0;font-weight:550}
footer{margin:1rem;color:var(--color-text-secondary);font-size:0}
/* {{ if show_details }} */
footer .details{margin-top:20px}
footer .details ul{padding:0}
footer .details code,footer .details span{font-size:calc(var(--font-size-secondary) - .6rem)}
footer .details span{padding-right:7px}
footer .details code{padding-left:7px}
/* {{ end }} */
@media screen and (max-width:820px){
.arrows{display:none}
}
@media screen and (max-width:480px){
.reason>*{max-width:100%}
/* {{ if show_details }} */
footer .details code,footer .details span{font-size:calc(var(--font-size-secondary) - .3rem)}
/* {{ end }} */
}
@media screen and (min-width:768px){
body{margin:8% 10%}
@ -64,8 +73,8 @@
<path d="M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.89-2-2-2zm0 14H5V8h14v10z"/>
</svg>
</i>
<main>Your Client</main>
<p class="status-text">Unknown</p>
<main data-l10n>Your Client</main>
<p class="status-text" data-l10n>Unknown</p>
</div>
<div class="arrows">
@ -88,8 +97,8 @@
<path d="M12 6c2.62 0 4.88 1.86 5.39 4.43l.3 1.5 1.53.11c1.56.1 2.78 1.41 2.78 2.96 0 1.65-1.35 3-3 3H6c-2.21 0-4-1.79-4-4 0-2.05 1.53-3.76 3.56-3.97l1.07-.11.5-.95C8.08 7.14 9.94 6 12 6m0-2C9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96C18.67 6.59 15.64 4 12 4z"/>
</svg>
</i>
<main>Network</main>
<p class="status-text">Working</p>
<main data-l10n>Network</main>
<p class="status-text" data-l10n>Working</p>
</div>
<div class="arrows">
@ -105,18 +114,18 @@
<path d="M19 15v4H5v-4h14m1-2H4c-.55 0-1 .45-1 1v6c0 .55.45 1 1 1h16c.55 0 1-.45 1-1v-6c0-.55-.45-1-1-1zM7 18.5c-.82 0-1.5-.67-1.5-1.5s.68-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zM19 5v4H5V5h14m1-2H4c-.55 0-1 .45-1 1v6c0 .55.45 1 1 1h16c.55 0 1-.45 1-1V4c0-.55-.45-1-1-1zM7 8.5c-.82 0-1.5-.67-1.5-1.5S6.18 5.5 7 5.5s1.5.68 1.5 1.5S7.83 8.5 7 8.5z"/>
</svg>
</i>
<main>Web Server</main>
<p class="status-text">Unknown</p>
<main data-l10n>Web Server</main>
<p class="status-text" data-l10n>Unknown</p>
</div>
</div>
<div class="reason">
<div class="what-happened">
<h2>What happened?</h2>
<p class="description">{{ description }}</p>
<h2 data-l10n>What happened?</h2>
<p class="description" data-l10n>{{ description }}</p>
</div>
<div class="what-can-i-do">
<h2>What can I do?</h2>
<p class="description">Please try again in a few minutes</p>
<h2 data-l10n>What can I do?</h2>
<p class="description" data-l10n>Please try again in a few minutes</p>
</div>
</div>
</body>
@ -124,15 +133,15 @@
{{ if show_details }}
<div class="details">
<ul>
{{- if host }}<li><span>Host:</span> <code>{{ host }}</code></li>{{ end -}}
{{- if original_uri }}<li><span>Original URI:</span> <code>{{ original_uri }}</code></li>{{ end -}}
{{- if forwarded_for }}<li><span>Forwarded for:</span> <code>{{ forwarded_for }}</code></li>{{ end -}}
{{- if namespace }}<li><span>Namespace:</span> <code>{{ namespace }}</code></li>{{ end -}}
{{- if ingress_name }}<li><span>Ingress name:</span> <code>{{ ingress_name }}</code></li>{{ end -}}
{{- if service_name }}<li><span>Service name:</span> <code>{{ service_name }}</code></li>{{ end -}}
{{- if service_port }}<li><span>Service port:</span> <code>{{ service_port }}</code></li>{{ end -}}
{{- if request_id }}<li><span>Request ID:</span> <code>{{ request_id }}</code></li>{{ end -}}
<li><span>Timestamp:</span> <code>{{ now.Unix }}</code></li>
{{- if host }}<li><span><span data-l10n>Host</span>:</span> <code>{{ host }}</code></li>{{ end -}}
{{- if original_uri }}<li><span><span data-l10n>Original URI</span>:</span> <code>{{ original_uri }}</code></li>{{ end -}}
{{- if forwarded_for }}<li><span><span data-l10n>Forwarded for</span>:</span> <code>{{ forwarded_for }}</code></li>{{ end -}}
{{- if namespace }}<li><span><span data-l10n>Namespace</span>:</span> <code>{{ namespace }}</code></li>{{ end -}}
{{- if ingress_name }}<li><span><span data-l10n>Ingress name</span>:</span> <code>{{ ingress_name }}</code></li>{{ end -}}
{{- if service_name }}<li><span><span data-l10n>Service name</span>:</span> <code>{{ service_name }}</code></li>{{ end -}}
{{- if service_port }}<li><span><span data-l10n>Service port</span>:</span> <code>{{ service_port }}</code></li>{{ end -}}
{{- if request_id }}<li><span><span data-l10n>Request ID</span>:</span> <code>{{ request_id }}</code></li>{{ end -}}
<li><span><span data-l10n>Timestamp</span>:</span> <code>{{ now.Unix }}</code></li>
</ul>
</div>
{{ end }}
@ -199,7 +208,7 @@
*/
const setErrorDescription = function (text) {
Array.prototype.forEach.call(document.getElementsByClassName('error-description'), function ($el) {
$el.innerText = text;
$el.innerHTML = text;
});
};
@ -214,20 +223,20 @@
switch (true) {
case errorCode >= 400 && errorCode <= 499:
switch (errorCode) {
case 400: case 405: case 411: case 413: whatToDo = 'Change the request method, payload or URL'; break;
case 401: case 403: case 407: whatToDo = 'Check your authorization data'; break;
case 404: whatToDo = 'Double-check the URL and try again'; break;
case 400: case 405: case 411: case 413: whatToDo = 'Please try to change the request method, headers, payload, or URL'; break;
case 401: case 403: case 407: whatToDo = 'Please check your authorization data'; break;
case 404: whatToDo = 'Please double-check the URL and try again'; break;
case 409: case 410: case 418: whatToDo = '¯\\_(ツ)_/¯'; break;
}
setErrorDescription(`${message} (client-side error)`);
setErrorDescription(`<span data-l10n>${message}</span> (<span data-l10n>client-side error</span>)`);
setCardState(cards.$client, {isError: true}, message)
setCardState(cards.$network, {isOk: true}, 'Working')
setCardState(cards.$server, {isOk: true}, 'Working')
break;
case errorCode >= 500 && errorCode <= 599:
setErrorDescription(`${message} (server-side error)`);
setErrorDescription(`<span data-l10n>${message}</span> (<span data-l10n>server-side error</span>)`);
setCardState(cards.$client, {isOk: true}, 'Working')
setCardState(cards.$network, {isOk: true}, 'Working')
setCardState(cards.$server, {isError: true}, message)
@ -245,5 +254,20 @@
} else {
console.warn('Cannot parse the error code:', errorCode);
}
// {{ if l10n_enabled }}
if (navigator.language.substring(0, 2).toLowerCase() !== 'en') {
((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n)
s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
s.async = s.defer = true;
s.addEventListener('load', () => p.removeChild(s));
p.appendChild(s);
})(document.createElement('script'), document.body);
}
// {{ end }}
</script>
<!--
Error {{ code }}: {{ message }}
Description: {{ description }}
-->
</html>

View File

@ -9,7 +9,9 @@
<meta name="robots" content="noindex, nofollow" />
<title>{{ code }}: {{ message }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet" />
<link rel="preconnect" href="https://fonts.bunny.net" crossorigin>
<link rel="dns-prefetch" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css2?family=Open+Sans:wght@400;700&display=swap" rel="stylesheet">
<style>
html,body {background-color:#1a1a1a;color:#fff;font-family:'Open Sans',sans-serif;height:100vh;margin:0;font-size:0}
.container {height:100vh;align-items:center;display:flex;justify-content:center;position:relative}
@ -33,7 +35,7 @@
<body>
<div class="container">
<div class="wrap">
<svg class="ghost" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="127.433px" height="132.743px" viewBox="0 0 127.433 132.743" enable-background="new 0 0 127.433 132.743" xml:space="preserve">
<svg class="ghost" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="127.433px" height="132.743px" viewBox="0 0 127.433 132.743" xml:space="preserve">
<path fill="#FFF6F4" d="M116.223,125.064c1.032-1.183,1.323-2.73,1.391-3.747V54.76c0,0-4.625-34.875-36.125-44.375 s-66,6.625-72.125,44l-0.781,63.219c0.062,4.197,1.105,6.177,1.808,7.006c1.94,1.811,5.408,3.465,10.099-0.6 c7.5-6.5,8.375-10,12.75-6.875s5.875,9.75,13.625,9.25s12.75-9,13.75-9.625s4.375-1.875,7,1.25s5.375,8.25,12.875,7.875 s12.625-8.375,12.625-8.375s2.25-3.875,7.25,0.375s7.625,9.75,14.375,8.125C114.739,126.01,115.412,125.902,116.223,125.064z"></path>
<circle fill="#1a1a1a" cx="86.238" cy="57.885" r="6.667"></circle>
<circle fill="#1a1a1a" cx="40.072" cy="57.885" r="6.667"></circle>
@ -46,49 +48,49 @@
<path fill="#FCEFED" stroke="#FEEBE6" stroke-miterlimit="10" d="M116.279,55.814c-0.021-0.286-2.323-28.744-30.221-41.012 c-7.806-3.433-15.777-5.173-23.691-5.173c-16.889,0-30.283,7.783-37.187,15.067c-9.229,9.736-13.84,26.712-14.191,30.259 l-0.748,62.332c0.149,2.133,1.389,6.167,5.019,6.167c1.891,0,4.074-1.083,6.672-3.311c4.96-4.251,7.424-6.295,9.226-6.295 c1.339,0,2.712,1.213,5.102,3.762c4.121,4.396,7.461,6.355,10.833,6.355c2.713,0,5.311-1.296,7.942-3.962 c3.104-3.145,5.701-5.239,8.285-5.239c2.116,0,4.441,1.421,7.317,4.473c2.638,2.8,5.674,4.219,9.022,4.219 c4.835,0,8.991-2.959,11.27-5.728l0.086-0.104c1.809-2.2,3.237-3.938,5.312-3.938c2.208,0,5.271,1.942,9.359,5.936 c0.54,0.743,3.552,4.674,6.86,4.674c1.37,0,2.559-0.65,3.531-1.932l0.203-0.268L116.279,55.814z M114.281,121.405 c-0.526,0.599-1.096,0.891-1.734,0.891c-2.053,0-4.51-2.82-5.283-3.907l-0.116-0.136c-4.638-4.541-7.975-6.566-10.82-6.566 c-3.021,0-4.884,2.267-6.857,4.667l-0.086,0.104c-1.896,2.307-5.582,4.999-9.725,4.999c-2.775,0-5.322-1.208-7.567-3.59 c-3.325-3.528-6.03-5.102-8.772-5.102c-3.278,0-6.251,2.332-9.708,5.835c-2.236,2.265-4.368,3.366-6.518,3.366 c-2.772,0-5.664-1.765-9.374-5.723c-2.488-2.654-4.29-4.395-6.561-4.395c-2.515,0-5.045,2.077-10.527,6.777 c-2.727,2.337-4.426,2.828-5.37,2.828c-2.662,0-3.017-4.225-3.021-4.225l0.745-62.163c0.332-3.321,4.767-19.625,13.647-28.995 c3.893-4.106,10.387-8.632,18.602-11.504c-0.458,0.503-0.744,1.165-0.744,1.898c0,1.565,1.269,2.833,2.833,2.833 c1.564,0,2.833-1.269,2.833-2.833c0-1.355-0.954-2.485-2.226-2.764c4.419-1.285,9.269-2.074,14.437-2.074 c7.636,0,15.336,1.684,22.887,5.004c26.766,11.771,29.011,39.047,29.027,39.251V121.405z"></path>
</svg>
<p class="shadowFrame">
<svg version="1.1" class="shadow" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="61px" y="20px" width="122.436px" height="39.744px" viewBox="0 0 122.436 39.744" enable-background="new 0 0 122.436 39.744" xml:space="preserve">
<svg class="shadow" xmlns="http://www.w3.org/2000/svg" x="61px" y="20px" width="122.436px" height="39.744px" viewBox="0 0 122.436 39.744" xml:space="preserve">
<ellipse fill="#262626" cx="61.128" cy="19.872" rx="49.25" ry="8.916"></ellipse>
</svg>
</p>
<h3>Error {{ code }}</h3>
<p class="description">{{ description }}</p>
<h3><span data-l10n>Error</span> {{ code }}</h3>
<p class="description" data-l10n>{{ description }}</p>
{{ if show_details }}
<div class="details">
<table>
{{- if host }}<tr>
<td class="name">Host</td>
<td class="name" data-l10n>Host</td>
<td class="value">{{ host }}</td>
</tr>{{ end -}}
{{- if original_uri }}<tr>
<td class="name">Original URI</td>
<td class="name" data-l10n>Original URI</td>
<td class="value">{{ original_uri }}</td>
</tr>{{ end -}}
{{- if forwarded_for }}<tr>
<td class="name">Forwarded for</td>
<td class="name" data-l10n>Forwarded for</td>
<td class="value">{{ forwarded_for }}</td>
</tr>{{ end -}}
{{- if namespace }}<tr>
<td class="name">Namespace</td>
<td class="name" data-l10n>Namespace</td>
<td class="value">{{ namespace }}</td>
</tr>{{ end -}}
{{- if ingress_name }}<tr>
<td class="name">Ingress name</td>
<td class="name" data-l10n>Ingress name</td>
<td class="value">{{ ingress_name }}</td>
</tr>{{ end -}}
{{- if service_name }}<tr>
<td class="name">Service name</td>
<td class="name" data-l10n>Service name</td>
<td class="value">{{ service_name }}</td>
</tr>{{ end -}}
{{- if service_port }}<tr>
<td class="name">Service port</td>
<td class="name" data-l10n>Service port</td>
<td class="value">{{ service_port }}</td>
</tr>{{ end -}}
{{- if request_id }}<tr>
<td class="name">Request ID</td>
<td class="name" data-l10n>Request ID</td>
<td class="value">{{ request_id }}</td>
</tr>{{ end -}}
<tr>
<td class="name">Timestamp</td>
<td class="name" data-l10n>Timestamp</td>
<td class="value">{{ now.Unix }}</td>
</tr>
</table>
@ -96,6 +98,18 @@
{{ end }}
</div>
</div>
<script>
// {{ if l10n_enabled }}
if (navigator.language.substring(0, 2).toLowerCase() !== 'en') {
((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n)
s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
s.async = s.defer = true;
s.addEventListener('load', () => p.removeChild(s));
p.appendChild(s);
})(document.createElement('script'), document.body);
}
// {{ end }}
</script>
</body>
<!--
Error {{ code }}: {{ message }}

View File

@ -9,8 +9,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow" />
<title>{{ message }}</title>
<link rel="dns-prefetch" href="//fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Inconsolata" rel="stylesheet">
<link rel="preconnect" href="https://fonts.bunny.net" crossorigin>
<link rel="dns-prefetch" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css2?family=Inconsolata:wght@400;700&display=swap" rel="stylesheet">
<style>
/** Idea author: https://codepen.io/robinselmer */
html, body {
@ -145,23 +146,35 @@
<body>
<div class="overlay"></div>
<div class="terminal">
<h1>Error <span class="error_code">{{ code }}</span></h1>
<p class="output">{{ description }}.</p>
<p class="output">Good luck.</p>
<h1><span data-l10n>Error</span> <span class="error_code">{{ code }}</span></h1>
<p class="output" data-l10n>{{ description }}.</p>
<p class="output"><span data-l10n>Good luck</span>.</p>
{{ if show_details }}
<div class="details">
{{- if host }}<p class="output small">Host: <code>{{ host }}</code></p>{{ end -}}
{{- if original_uri }}<p class="output small">Original URI: <code>{{ original_uri }}</code></p>{{ end -}}
{{- if forwarded_for }}<p class="output small">Forwarded for: <code>{{ forwarded_for }}</code></p>{{ end -}}
{{- if namespace }}<p class="output small">Namespace: <code>{{ namespace }}</code></p>{{ end -}}
{{- if ingress_name }}<p class="output small">Ingress name: <code>{{ ingress_name }}</code></p>{{ end -}}
{{- if service_name }}<p class="output small">Service name: <code>{{ service_name }}</code></p>{{ end -}}
{{- if service_port }}<p class="output small">Service port: <code>{{ service_port }}</code></p>{{ end -}}
{{- if request_id }}<p class="output small">Request ID: <code>{{ request_id }}</code></p>{{ end -}}
<p class="output small">Timestamp: <code>{{ now.Unix }}</code></p>
{{- if host }}<p class="output small"><span data-l10n>Host</span>: <code>{{ host }}</code></p>{{ end -}}
{{- if original_uri }}<p class="output small"><span data-l10n>Original URI</span>: <code>{{ original_uri }}</code></p>{{ end -}}
{{- if forwarded_for }}<p class="output small"><span data-l10n>Forwarded for</span>: <code>{{ forwarded_for }}</code></p>{{ end -}}
{{- if namespace }}<p class="output small"><span data-l10n>Namespace</span>: <code>{{ namespace }}</code></p>{{ end -}}
{{- if ingress_name }}<p class="output small"><span data-l10n>Ingress name</span>: <code>{{ ingress_name }}</code></p>{{ end -}}
{{- if service_name }}<p class="output small"><span data-l10n>Service name</span>: <code>{{ service_name }}</code></p>{{ end -}}
{{- if service_port }}<p class="output small"><span data-l10n>Service port</span>: <code>{{ service_port }}</code></p>{{ end -}}
{{- if request_id }}<p class="output small"><span data-l10n>Request ID</span>: <code>{{ request_id }}</code></p>{{ end -}}
<p class="output small"><span data-l10n>Timestamp</span>: <code>{{ now.Unix }}</code></p>
</div>
{{ end }}
</div>
<script>
// {{ if l10n_enabled }}
if (navigator.language.substring(0, 2).toLowerCase() !== 'en') {
((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n)
s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
s.async = s.defer = true;
s.addEventListener('load', () => p.removeChild(s));
p.appendChild(s);
})(document.createElement('script'), document.body);
}
// {{ end }}
</script>
</body>
<!--
Error {{ code }}: {{ message }}

View File

@ -9,8 +9,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow" />
<title>{{ message }}</title>
<link rel="dns-prefetch" href="//fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">
<link rel="preconnect" href="https://fonts.bunny.net" crossorigin>
<link rel="dns-prefetch" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=Nunito" rel="stylesheet">
<style>
html,body {background-color:#222526;color:#fff;font-family:'Nunito',sans-serif;font-weight:100;height:100vh;margin:0;font-size:0}
.full-height {height:100vh}
@ -33,7 +34,7 @@
<div class="code">
{{ code }}
</div>
<div class="message">
<div class="message" data-l10n>
{{ message }}
</div>
</div>
@ -41,39 +42,39 @@
<div class="details">
<table>
{{- if host }}<tr>
<td class="name">Host</td>
<td class="name" data-l10n>Host</td>
<td class="value">{{ host }}</td>
</tr>{{ end -}}
{{- if original_uri }}<tr>
<td class="name">Original URI</td>
<td class="name" data-l10n>Original URI</td>
<td class="value">{{ original_uri }}</td>
</tr>{{ end -}}
{{- if forwarded_for }}<tr>
<td class="name">Forwarded for</td>
<td class="name" data-l10n>Forwarded for</td>
<td class="value">{{ forwarded_for }}</td>
</tr>{{ end -}}
{{- if namespace }}<tr>
<td class="name">Namespace</td>
<td class="name" data-l10n>Namespace</td>
<td class="value">{{ namespace }}</td>
</tr>{{ end -}}
{{- if ingress_name }}<tr>
<td class="name">Ingress name</td>
<td class="name" data-l10n>Ingress name</td>
<td class="value">{{ ingress_name }}</td>
</tr>{{ end -}}
{{- if service_name }}<tr>
<td class="name">Service name</td>
<td class="name" data-l10n>Service name</td>
<td class="value">{{ service_name }}</td>
</tr>{{ end -}}
{{- if service_port }}<tr>
<td class="name">Service port</td>
<td class="name" data-l10n>Service port</td>
<td class="value">{{ service_port }}</td>
</tr>{{ end -}}
{{- if request_id }}<tr>
<td class="name">Request ID</td>
<td class="name" data-l10n>Request ID</td>
<td class="value">{{ request_id }}</td>
</tr>{{ end -}}
<tr>
<td class="name">Timestamp</td>
<td class="name" data-l10n>Timestamp</td>
<td class="value">{{ now.Unix }}</td>
</tr>
</table>
@ -81,6 +82,18 @@
{{ end }}
</div>
</div>
<script>
// {{ if l10n_enabled }}
if (navigator.language.substring(0, 2).toLowerCase() !== 'en') {
((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n)
s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
s.async = s.defer = true;
s.addEventListener('load', () => p.removeChild(s));
p.appendChild(s);
})(document.createElement('script'), document.body);
}
// {{ end }}
</script>
</body>
<!--
Error {{ code }}: {{ message }}

View File

@ -9,8 +9,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow" />
<title>{{ message }}</title>
<link rel="dns-prefetch" href="//fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">
<link rel="preconnect" href="https://fonts.bunny.net" crossorigin>
<link rel="dns-prefetch" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=Nunito" rel="stylesheet">
<style>
:root {--color-bg-primary:#fff;--color-text-primary:#636b6f;--color-text-secondary:#777}
@media (prefers-color-scheme: dark) {:root {--color-bg-primary:#222526;--color-text-primary:#fff;--color-text-secondary:#999}}
@ -35,7 +36,7 @@
<div class="code">
{{ code }}
</div>
<div class="message">
<div class="message" data-l10n>
{{ message }}
</div>
</div>
@ -43,39 +44,39 @@
<div class="details">
<table>
{{- if host }}<tr>
<td class="name">Host</td>
<td class="name" data-l10n>Host</td>
<td class="value">{{ host }}</td>
</tr>{{ end -}}
{{- if original_uri }}<tr>
<td class="name">Original URI</td>
<td class="name" data-l10n>Original URI</td>
<td class="value">{{ original_uri }}</td>
</tr>{{ end -}}
{{- if forwarded_for }}<tr>
<td class="name">Forwarded for</td>
<td class="name" data-l10n>Forwarded for</td>
<td class="value">{{ forwarded_for }}</td>
</tr>{{ end -}}
{{- if namespace }}<tr>
<td class="name">Namespace</td>
<td class="name" data-l10n>Namespace</td>
<td class="value">{{ namespace }}</td>
</tr>{{ end -}}
{{- if ingress_name }}<tr>
<td class="name">Ingress name</td>
<td class="name" data-l10n>Ingress name</td>
<td class="value">{{ ingress_name }}</td>
</tr>{{ end -}}
{{- if service_name }}<tr>
<td class="name">Service name</td>
<td class="name" data-l10n>Service name</td>
<td class="value">{{ service_name }}</td>
</tr>{{ end -}}
{{- if service_port }}<tr>
<td class="name">Service port</td>
<td class="name" data-l10n>Service port</td>
<td class="value">{{ service_port }}</td>
</tr>{{ end -}}
{{- if request_id }}<tr>
<td class="name">Request ID</td>
<td class="name" data-l10n>Request ID</td>
<td class="value">{{ request_id }}</td>
</tr>{{ end -}}
<tr>
<td class="name">Timestamp</td>
<td class="name" data-l10n>Timestamp</td>
<td class="value">{{ now.Unix }}</td>
</tr>
</table>
@ -83,6 +84,18 @@
{{ end }}
</div>
</div>
<script>
// {{ if l10n_enabled }}
if (navigator.language.substring(0, 2).toLowerCase() !== 'en') {
((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n)
s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
s.async = s.defer = true;
s.addEventListener('load', () => p.removeChild(s));
p.appendChild(s);
})(document.createElement('script'), document.body);
}
// {{ end }}
</script>
</body>
<!--
Error {{ code }}: {{ message }}

View File

@ -10,8 +10,9 @@
<meta name="robots" content="noindex, nofollow"/>
<title>{{ message }}</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="dns-prefetch" href="//fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Nunito+Sans" rel="stylesheet">
<link rel="preconnect" href="https://fonts.bunny.net" crossorigin>
<link rel="dns-prefetch" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=Nunito+Sans" rel="stylesheet">
<style>
/** Codepen: https://codepen.io/kdbkapsere/pen/oNXLbqQ */
@ -104,12 +105,12 @@
.details li span {
font-size: 14px;
font-weight: bold;
padding-right: 7px;
}
.details li code {
font-size: 14px;
font-weight: normal;
padding-left: 7px;
}
/* {{ end }} */
@ -335,20 +336,20 @@
</div>
<div class="content">
<h1>{{ code }}</h1>
<h2>UH OH! {{ message }}</h2>
<p>{{ description }}</p>
<h2><span data-l10n>UH OH</span>! <span data-l10n>{{ message }}</span></h2>
<p data-l10n>{{ description }}</p>
{{ if show_details }}
<ul class="details">
{{- if host }}<li><span>Host:</span> <code>{{ host }}</code></li>{{ end -}}
{{- if original_uri }}<li><span>Original URI:</span> <code>{{ original_uri }}</code></li>{{ end -}}
{{- if forwarded_for }}<li><span>Forwarded for:</span> <code>{{ forwarded_for }}</code></li>{{ end -}}
{{- if namespace }}<li><span>Namespace:</span> <code>{{ namespace }}</code></li>{{ end -}}
{{- if ingress_name }}<li><span>Ingress name:</span> <code>{{ ingress_name }}</code></li>{{ end -}}
{{- if service_name }}<li><span>Service name:</span> <code>{{ service_name }}</code></li>{{ end -}}
{{- if service_port }}<li><span>Service port:</span> <code>{{ service_port }}</code></li>{{ end -}}
{{- if request_id }}<li><span>Request ID:</span> <code>{{ request_id }}</code></li>{{ end -}}
<li><span>Timestamp:</span> <code>{{ now.Unix }}</code></li>
{{- if host }}<li><span><span data-l10n>Host</span>:</span> <code>{{ host }}</code></li>{{ end -}}
{{- if original_uri }}<li><span><span data-l10n>Original URI</span>:</span> <code>{{ original_uri }}</code></li>{{ end -}}
{{- if forwarded_for }}<li><span><span data-l10n>Forwarded for</span>:</span> <code>{{ forwarded_for }}</code></li>{{ end -}}
{{- if namespace }}<li><span><span data-l10n>Namespace</span>:</span> <code>{{ namespace }}</code></li>{{ end -}}
{{- if ingress_name }}<li><span><span data-l10n>Ingress name</span>:</span> <code>{{ ingress_name }}</code></li>{{ end -}}
{{- if service_name }}<li><span><span data-l10n>Service name</span>:</span> <code>{{ service_name }}</code></li>{{ end -}}
{{- if service_port }}<li><span><span data-l10n>Service port</span>:</span> <code>{{ service_port }}</code></li>{{ end -}}
{{- if request_id }}<li><span><span data-l10n>Request ID</span>:</span> <code>{{ request_id }}</code></li>{{ end -}}
<li><span><span data-l10n>Timestamp</span>:</span> <code>{{ now.Unix }}</code></li>
</ul>
{{ end }}
</div>
@ -377,6 +378,17 @@
} else {
console.warn('gsap library is not initialized (network error?)')
}
// {{ if l10n_enabled }}
if (navigator.language.substring(0, 2).toLowerCase() !== 'en') {
((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n)
s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
s.async = s.defer = true;
s.addEventListener('load', () => p.removeChild(s));
p.appendChild(s);
})(document.createElement('script'), document.body);
}
// {{ end }}
</script>
</body>

282
templates/matrix.html Normal file
View File

@ -0,0 +1,282 @@
<!DOCTYPE html>
<!--
Error {{ code }}: {{ message }}
Description: {{ description }}
-->
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>{{ message }} ({{ code }})</title>
<link rel="preconnect" href="https://fonts.bunny.net" crossorigin>
<link rel="dns-prefetch" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css2?family=Inconsolata:wght@400;700&display=swap" rel="stylesheet">
<style>
:root{--matrix-glyph-size:15px;--matrix-glyph-font-size:15px;--matrix-glyph-front-color:rgba(255, 255, 255, 0.8);--matrix-glyph-tail-color:#0f0;--matrix-overlay-color:rgba(0, 0, 0, 0.12)}
body,html{margin:0;padding:0;background-color:#000;height:100vh}
#matrix{display:block;position:fixed;width:100vw;height:100vh}
.container{align-items:center;display:flex;justify-content:center;position:absolute;top:0;left:0;width:100vw;height:100vh;z-index:1}
.container .message{background-color:rgba(0,0,0,.85);border:2px solid var(--matrix-glyph-tail-color);padding:15px 20px;margin:0 20px;font-family:Inconsolata,Helvetica,sans-serif;text-align:center;font-size:0;color:var(--matrix-glyph-tail-color);text-shadow:1px 0 2px var(--matrix-glyph-tail-color),-1px 0 2px var(--matrix-glyph-tail-color);box-shadow:1px 0 5px var(--matrix-glyph-tail-color),-1px 0 2px var(--matrix-glyph-tail-color);max-width:640px}
.container .message h1{margin:0;font-size:52px}
.container .message p{margin:.3em 0 0 0;font-size:17px;color:var(--matrix-glyph-front-color)}
/* {{ if show_details }} */
.container .details{margin-top:20px}
.container .details ul{padding:0}
.container .details code,.container .details span{font-size:11px}
.container .details code{padding-left:7px}
/* {{ end }} */
.hidden {display:none}
@media screen and (max-width:820px){
:root{--matrix-glyph-size:10px;--matrix-glyph-font-size:10px}
.container .message h1{font-size:38px}
.container .message p{font-size:13px}
/* {{ if show_details }} */
.container .details code,.container .details span{font-size:11px}
/* {{ end }} */
}
</style>
</head>
<body>
<div class="container">
<ul id="matrix-words" class="hidden">
<li>{{ code }}</li>
<li>{{ message }}</li>
<li>{{ description }}</li>
<li>{{ code }} {{ message }}</li>
</ul>
<div class="message">
<h1>{{ code }}: <span data-l10n>{{ message }}</span></h1>
<p data-l10n>{{ description }}</p>
{{ if show_details }}
<div class="details">
<ul>
{{- if host }}<li><span><span data-l10n>Host</span>:</span> <code>{{ host }}</code></li>{{ end -}}
{{- if original_uri }}<li><span><span data-l10n>Original URI</span>:</span> <code>{{ original_uri }}</code></li>{{ end -}}
{{- if forwarded_for }}<li><span><span data-l10n>Forwarded for</span>:</span> <code>{{ forwarded_for }}</code></li>{{ end -}}
{{- if namespace }}<li><span><span data-l10n>Namespace</span>:</span> <code>{{ namespace }}</code></li>{{ end -}}
{{- if ingress_name }}<li><span><span data-l10n>Ingress name</span>:</span> <code>{{ ingress_name }}</code></li>{{ end -}}
{{- if service_name }}<li><span><span data-l10n>Service name</span>:</span> <code>{{ service_name }}</code></li>{{ end -}}
{{- if service_port }}<li><span><span data-l10n>Service port</span>:</span> <code>{{ service_port }}</code></li>{{ end -}}
{{- if request_id }}<li><span><span data-l10n>Request ID</span>:</span> <code>{{ request_id }}</code></li>{{ end -}}
<li><span><span data-l10n>Timestamp</span>:</span> <code>{{ now.Unix }}</code></li>
</ul>
</div>
{{ end }}
</div>
</div>
<canvas id="matrix"></canvas>
<script>
'use strict';
/**
* @param {HTMLCanvasElement} $canvas
* @constructor
*/
const Matrix = function ($canvas) {
const symbols = 'ラドクリフマラソンわたしワタシんょンョたばこタバコとうきょうトウキョウ '.split('');
/**
* @return {string}
*/
const getRandomSymbol = function () {
return symbols[Math.floor(Math.random() * symbols.length)];
}
const ctx = $canvas.getContext('2d');
ctx.globalCompositeOperation = 'lighter'; // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
this.redrawInterval = 90; // fade oud speed
this.glyphSize = 0;
this.rowsCapacity = 0;
this.columnsCapacity = 0;
/**
* @return {void}
*/
this.init = () => {
$canvas.width = $canvas.clientWidth;
$canvas.height = $canvas.clientHeight;
this.glyphSize = parseInt(getComputedStyle($canvas).getPropertyValue('--matrix-glyph-size'), 10);
this.rowsCapacity = Math.ceil($canvas.clientHeight / this.glyphSize);
this.columnsCapacity = Math.ceil($canvas.clientWidth / this.glyphSize);
};
/**
* @param {string} symbol
* @param {number} row
* @param {number} column
* @param {string} color
*/
const drawSymbol = (symbol, row, column, color) => {
if (row > this.rowsCapacity || column > this.columnsCapacity) {
return;
}
ctx.fillStyle = color;
ctx.font = getComputedStyle($canvas).getPropertyValue('--matrix-glyph-font-size') + ' monospace';
if (symbol.length > 1) {
symbol = symbol.charAt(0); // only one char is allowed
}
let xOffset = 0, charCode = symbol.charCodeAt(0);
if (charCode >= 33 && charCode <= 126) { // is ascii
xOffset = this.glyphSize / 5;
}
ctx.fillText(symbol, (column * this.glyphSize) + xOffset, row * this.glyphSize);
};
/**
* @param {number} column
* @param {number} speed Lowest = fastest, largest = slowest
* @param {string?} text
* @param {number?} offset
*/
const drawLine = (column, speed, text, offset) => {
let cursor = 0;
const tailColor = getComputedStyle($canvas).getPropertyValue('--matrix-glyph-tail-color'),
frontColor = getComputedStyle($canvas).getPropertyValue('--matrix-glyph-front-color');
const handler = window.setInterval(() => {
if (column > this.columnsCapacity) {
return window.clearInterval(handler);
}
if (cursor <= this.rowsCapacity) {
let symbol = getRandomSymbol();
if (typeof text === 'string' && typeof offset === 'number') {
if (cursor >= offset && text.length >= cursor - offset) {
symbol = text.charAt(cursor - offset);
}
}
if (typeof symbol === 'string' && symbol !== ' ') {
const prev = cursor;
window.setTimeout(() => { // redraw with a green color
drawSymbol(symbol, prev, column, tailColor);
}, speed / 1.3);
drawSymbol(symbol, cursor, column, frontColor); // white color first
}
cursor++;
} else {
window.clearInterval(handler);
}
}, speed);
};
/**
* @return {void}
*/
this.redraw = () => {
ctx.fillStyle = getComputedStyle($canvas).getPropertyValue('--matrix-overlay-color');
ctx.fillRect(0, 0, $canvas.clientWidth, $canvas.clientHeight);
};
let redrawIntervalHandler = undefined, dropsIntervalHandler = undefined;
/**
* @param {HTMLUListElement?} $linesList
*/
this.run = ($linesList) => {
if (redrawIntervalHandler === undefined) {
redrawIntervalHandler = window.setInterval(this.redraw, this.redrawInterval);
}
if (dropsIntervalHandler === undefined) {
const fn = () => {
const randomColumn = Math.floor(Math.random() * (this.columnsCapacity + 1)),
minSpeed = 200, maxSpeed = 50,
randomSpeed = Math.floor(Math.random() * (maxSpeed - minSpeed + 1)) + minSpeed;
const list = [];
let line = undefined, offset = undefined;
if ($linesList !== undefined) {
Array.prototype.forEach.call($linesList.querySelectorAll('li'), $li => {
const text = $li.innerText.trim();
if (text.length > 0) {
list.push(text);
}
});
if (list.length > 0 && Math.random() > 0.4) {
line = list[Math.floor(Math.random() * list.length)];
offset = Math.floor(Math.random() * line.length);
if (offset <= 5) {
offset *= 3;
}
}
}
drawLine(randomColumn, randomSpeed, line, offset);
if (dropsIntervalHandler !== undefined) {
window.clearInterval(dropsIntervalHandler);
dropsIntervalHandler = undefined;
}
dropsIntervalHandler = window.setInterval(fn, ((minSpeed + maxSpeed) / 2 * this.rowsCapacity) / this.columnsCapacity / 0.5);
};
fn();
}
};
/**
* @return {void}
*/
this.stop = () => {
if (redrawIntervalHandler !== undefined) {
window.clearInterval(redrawIntervalHandler);
redrawIntervalHandler = undefined;
}
if (dropsIntervalHandler !== undefined) {
window.clearInterval(dropsIntervalHandler);
dropsIntervalHandler = undefined;
}
};
if (typeof ResizeObserver === 'function') {
(new ResizeObserver(this.init)).observe($canvas);
} else {
this.init();
}
};
(new Matrix(document.getElementById('matrix'))).run(document.getElementById('matrix-words'));
// {{ if l10n_enabled }}
if (navigator.language.substring(0, 2).toLowerCase() !== 'en') {
((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n)
s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
s.async = s.defer = true;
s.addEventListener('load', () => p.removeChild(s));
p.appendChild(s);
})(document.createElement('script'), document.body);
}
// {{ end }}
</script>
</body>
<!--
Error {{ code }}: {{ message }}
Description: {{ description }}
-->
</html>

View File

@ -130,7 +130,7 @@
<div class="container-center">
<div>
<h1>{{ code }}</h1>
<h2>{{ description }}</h2>
<h2 data-l10n>{{ description }}</h2>
</div>
</div>
@ -174,6 +174,17 @@
window.addEventListener('beforeunload', function (/** @param BeforeUnloadEvent event */ event) {
window.clearInterval(flickerInterval);
});
// {{ if l10n_enabled }}
if (navigator.language.substring(0, 2).toLowerCase() !== 'en') {
((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n)
s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
s.async = s.defer = true;
s.addEventListener('load', () => p.removeChild(s));
p.appendChild(s);
})(document.createElement('script'), document.body);
}
// {{ end }}
</script>
</body>
<!--

View File

@ -10,24 +10,26 @@ Creating templates is a very simple operation, even for those who know nothing a
### Error page & request data
| Signature | Description | Example |
|-------------------------------------|---------------------------------------------------------------|----------------------------------------------|
| `{{ code }}` | Error page code | `404` |
| `{{ message }}` | Error code message | `Not found` |
| `{{ description }}` | Error code description | `The server can not find the requested page` |
| `{{ original_uri }}` | `X-Original-URI` header value | `/foo1/bar2` |
| `{{ namespace }}` | `X-Namespace` header value | `foo` |
| `{{ ingress_name }}` | `X-Ingress-Name` header value | `bar` |
| `{{ service_name }}` | `X-Service-Name` header value | `baz` |
| `{{ service_port }}` | `X-Service-Port` header value | `8080` |
| `{{ request_id }}` | `X-Request-ID` header value | `12AB34CD56EF78` |
| `{{ forwarded_for }}` | `X-Forwarded-For` header value | `203.0.113.195, 70.41.3.18` |
| `{{ host }}` | `Host` header value | `example.com` |
| `{{ now.Unix }}` | Current timestamp (e.g. in Unix format) | `1643621927` |
| `{{ hostname }}` | OS hostname | `ab12cd34ef56` |
| `{{ version }}` | Application version | `2.5.0` |
| `{{ if show_details }}...{{ end }}` | Logical operator (server started with "show details" option?) | |
| `{{ if hide_details }}...{{ end }}` | Same as above, but inverted | |
| Signature | Description | Example |
|--------------------------------------|---------------------------------------------------------------|----------------------------------------------|
| `{{ code }}` | Error page code | `404` |
| `{{ message }}` | Error code message | `Not found` |
| `{{ description }}` | Error code description | `The server can not find the requested page` |
| `{{ original_uri }}` | `X-Original-URI` header value | `/foo1/bar2` |
| `{{ namespace }}` | `X-Namespace` header value | `foo` |
| `{{ ingress_name }}` | `X-Ingress-Name` header value | `bar` |
| `{{ service_name }}` | `X-Service-Name` header value | `baz` |
| `{{ service_port }}` | `X-Service-Port` header value | `8080` |
| `{{ request_id }}` | `X-Request-ID` header value | `12AB34CD56EF78` |
| `{{ forwarded_for }}` | `X-Forwarded-For` header value | `203.0.113.195, 70.41.3.18` |
| `{{ host }}` | `Host` header value | `example.com` |
| `{{ now.Unix }}` | Current timestamp (e.g. in Unix format) | `1643621927` |
| `{{ hostname }}` | OS hostname | `ab12cd34ef56` |
| `{{ version }}` | Application version | `2.5.0` |
| `{{ if show_details }}...{{ end }}` | Logical operator (server started with "show details" option?) | |
| `{{ if hide_details }}...{{ end }}` | Same as above, but inverted | |
| `{{ if l10n_enabled }}...{{ end }}` | Logical operator (l10n is enabled?) | |
| `{{ if l10n_disabled }}...{{ end }}` | Same as above, but inverted | |
### Modifiers

View File

@ -72,60 +72,63 @@
<body>
<div class="flex-center full-height">
<div>
<span id="error_text">{{ code }}: {{ message }}</span>
<div id="error_text">
<span class="source">{{ code }}: <span data-l10n>{{ message }}</span></span>
<span class="target"></span>
</div>
{{ if show_details }}
<div class="hidden" id="details">
<table>
{{- if host }}
<tr>
<td class="name">Host:</td>
<td class="name"><span data-l10n>Host</span>:</td>
<td class="value">{{ host }}</td>
</tr>
{{ end -}}
{{- if original_uri }}
<tr>
<td class="name">Original URI:</td>
<td class="name"><span data-l10n>Original URI</span>:</td>
<td class="value">{{ original_uri }}</td>
</tr>
{{ end -}}
{{- if forwarded_for }}
<tr>
<td class="name">Forwarded for:</td>
<td class="name"><span data-l10n>Forwarded for</span>:</td>
<td class="value">{{ forwarded_for }}</td>
</tr>
{{ end -}}
{{- if namespace }}
<tr>
<td class="name">Namespace:</td>
<td class="name"><span data-l10n>Namespace</span>:</td>
<td class="value">{{ namespace }}</td>
</tr>
{{ end -}}
{{- if ingress_name }}
<tr>
<td class="name">Ingress name:</td>
<td class="name"><span data-l10n>Ingress name</span>:</td>
<td class="value">{{ ingress_name }}</td>
</tr>
{{ end -}}
{{- if service_name }}
<tr>
<td class="name">Service name:</td>
<td class="name"><span data-l10n>Service name</span>:</td>
<td class="value">{{ service_name }}</td>
</tr>
{{ end -}}
{{- if service_port }}
<tr>
<td class="name">Service port:</td>
<td class="name"><span data-l10n>Service port</span>:</td>
<td class="value">{{ service_port }}</td>
</tr>
{{ end -}}
{{- if request_id }}
<tr>
<td class="name">Request ID:</td>
<td class="name"><span data-l10n>Request ID</span>:</td>
<td class="value">{{ request_id }}</td>
</tr>
{{ end -}}
<tr>
<td class="name">Timestamp:</td>
<td class="name"><span data-l10n>Timestamp</span>:</td>
<td class="value">{{ now.Unix }}</td>
</tr>
</table>
@ -137,24 +140,74 @@
<script>
'use strict';
const $errorText = document.getElementById('error_text'),
text = $errorText.innerText,
characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-=+<>,./?[{()}]!@#$%^&*~`\|'.split('');
let progress = 0;
/**
* @param {HTMLElement} $el
*/
const Shuffle = function ($el) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-=+<>,./?[{()}]!@#$%^&*~`\|'.split(''),
$source = $el.querySelector('.source'), $target = $el.querySelector('.target');
const scrambleInterval = window.setInterval(function () {
let newText = text;
let cursor = 0, scrambleInterval = undefined, cursorDelayInterval = undefined, cursorInterval = undefined;
for (let i = 0; i < text.length; i++) {
if (i >= progress) {
newText = newText.substr(0, i) +
characters[Math.round(Math.random() * (characters.length - 1))] +
newText.substr(i + 1);
/**
* @param {Number} len
* @return {string}
*/
const getRandomizedString = function (len) {
let s = '';
for (let i = 0; i < len; i++) {
s += chars[Math.floor(Math.random() * chars.length)];
}
}
$errorText.innerText = newText;
}, 450 / 60);
return s;
};
this.start = function () {
$source.style.display = 'none';
$target.style.display = 'block';
scrambleInterval = window.setInterval(() => {
if (cursor <= $source.innerText.length) {
$target.innerText = $source.innerText.substring(0, cursor) + getRandomizedString($source.innerText.length - cursor);
}
}, 450 / 30);
cursorDelayInterval = window.setTimeout(() => {
cursorInterval = window.setInterval(() => {
if (cursor > $source.innerText.length - 1) {
this.stop();
}
cursor++;
}, 70);
}, 350);
};
this.stop = function () {
$source.style.display = 'block';
$target.style.display = 'none';
$target.innerText = '';
cursor = 0;
if (scrambleInterval !== undefined) {
window.clearInterval(scrambleInterval);
scrambleInterval = undefined;
}
if (cursorInterval !== undefined) {
window.clearInterval(cursorInterval);
cursorInterval = undefined;
}
if (cursorDelayInterval !== undefined) {
window.clearInterval(cursorDelayInterval);
cursorDelayInterval = undefined;
}
};
};
(new Shuffle(document.getElementById('error_text'))).start();
// {{ if show_details }}
window.setTimeout(function () {
@ -162,16 +215,16 @@
}, 550);
// {{ end }}
window.setTimeout(function () {
let revealInterval = window.setInterval(function () {
if (progress < text.length) {
progress++;
} else {
window.clearInterval(revealInterval);
window.clearInterval(scrambleInterval);
}
}, 70);
}, 350);
// {{ if l10n_enabled }}
if (navigator.language.substring(0, 2).toLowerCase() !== 'en') {
((s, p) => { // localize the page (details here - https://github.com/tarampampam/error-pages/tree/master/l10n)
s.src = 'https://cdn.jsdelivr.net/gh/tarampampam/error-pages@2/l10n/l10n.min.js'; // '../l10n/l10n.js';
s.async = s.defer = true;
s.addEventListener('load', () => p.removeChild(s));
p.appendChild(s);
})(document.createElement('script'), document.body);
}
// {{ end }}
</script>
</body>
<!--