mirror of
https://github.com/tarampampam/error-pages.git
synced 2024-08-30 18:22:40 +00:00
wip: 🔕 temporary commit
This commit is contained in:
parent
42c4e7bf84
commit
2776c41e0d
@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -109,7 +110,7 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit,gocy
|
||||
OnlyOnce: true,
|
||||
}
|
||||
proxyHeadersListFlag = cli.StringFlag{
|
||||
Name: "proxy-headers",
|
||||
Name: "proxy-headers", // TODO: add support for the "*" wildcard
|
||||
Usage: "listed here HTTP headers will be proxied from the original request to the error page response " +
|
||||
"(comma-separated list)",
|
||||
Value: strings.Join(cfg.ProxyHeaders, ","),
|
||||
@ -235,6 +236,11 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit,gocy
|
||||
}
|
||||
}
|
||||
|
||||
// check if there are any templates available to render error pages
|
||||
if len(cfg.Templates.Names()) == 0 {
|
||||
return errors.New("no templates available to render error pages")
|
||||
}
|
||||
|
||||
// if the rotation mode is set to random-on-startup, pick a random template (ignore the user-provided
|
||||
// template name)
|
||||
if cfg.RotationMode == config.RotationModeRandomOnStartup {
|
||||
@ -291,7 +297,7 @@ func NewCommand(log *logger.Logger) *cli.Command { //nolint:funlen,gocognit,gocy
|
||||
}
|
||||
|
||||
// Run current command.
|
||||
func (cmd *command) Run(ctx context.Context, log *logger.Logger, cfg *config.Config) error {
|
||||
func (cmd *command) Run(ctx context.Context, log *logger.Logger, cfg *config.Config) error { //nolint:funlen
|
||||
var srv = appHttp.NewServer(ctx, log)
|
||||
|
||||
if err := srv.Register(cfg); err != nil {
|
||||
@ -301,6 +307,33 @@ func (cmd *command) Run(ctx context.Context, log *logger.Logger, cfg *config.Con
|
||||
var startingErrCh = make(chan error, 1) // channel for server starting error
|
||||
defer close(startingErrCh)
|
||||
|
||||
// to track the frequency of each template's use, we send a simple GET request to the GoatCounter API
|
||||
// (https://goatcounter.com, https://github.com/arp242/goatcounter) to increment the counter. this service is
|
||||
// free and does not require an API key. no private data is sent, as shown in the URL below. this is necessary
|
||||
// to render a badge displaying the number of template usages on the error-pages repository README file :D
|
||||
//
|
||||
// badge code example:
|
||||
// ![Used times](https://img.shields.io/badge/dynamic/json?
|
||||
// url=https%3A%2F%2Ferror-pages.goatcounter.com%2Fcounter%2F%2Fuse-template%2Flost-in-space.json
|
||||
// &query=%24.count&label=Used%20times)
|
||||
go func() {
|
||||
req, reqErr := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf(
|
||||
"https://error-pages.goatcounter.com/count?event=true&p=/use-template/%s", url.QueryEscape(cfg.TemplateName),
|
||||
), http.NoBody)
|
||||
if reqErr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
resp, respErr := (&http.Client{Timeout: 10 * time.Second}).Do(req) //nolint:mnd // don't care about the response
|
||||
if respErr != nil {
|
||||
log.Debug("Cannot send a request to increment the template usage counter", logger.Error(respErr))
|
||||
|
||||
return
|
||||
} else if resp != nil {
|
||||
_ = resp.Body.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// start HTTP server in separate goroutine
|
||||
go func(errCh chan<- error) {
|
||||
var now = time.Now()
|
||||
|
@ -53,9 +53,12 @@ func New(cfg *config.Config, log *logger.Logger) http.Handler { //nolint:funlen,
|
||||
// disallow indexing of the error pages
|
||||
w.Header().Set("X-Robots-Tag", "noindex")
|
||||
|
||||
if code >= 500 && code < 600 {
|
||||
switch code {
|
||||
case http.StatusRequestTimeout, http.StatusTooEarly, http.StatusTooManyRequests,
|
||||
http.StatusInternalServerError, http.StatusBadGateway, http.StatusServiceUnavailable,
|
||||
http.StatusGatewayTimeout:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
|
||||
// tell the client (search crawler) to retry the request after 120 seconds, it makes sense for the 5xx errors
|
||||
// tell the client (search crawler) to retry the request after 120 seconds
|
||||
w.Header().Set("Retry-After", "120")
|
||||
}
|
||||
|
||||
|
BIN
internal/http/handlers/static/favicon.ico
Normal file
BIN
internal/http/handlers/static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
27
internal/http/handlers/static/handler.go
Normal file
27
internal/http/handlers/static/handler.go
Normal file
@ -0,0 +1,27 @@
|
||||
package static
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
//go:embed favicon.ico
|
||||
var Favicon []byte
|
||||
|
||||
// New creates a new handler that returns the provided content for GET and HEAD requests.
|
||||
func New(content []byte) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
w.Header().Set("Content-Type", http.DetectContentType(content))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write(content)
|
||||
|
||||
case http.MethodHead:
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
default:
|
||||
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||
}
|
||||
})
|
||||
}
|
80
internal/http/handlers/static/handler_test.go
Normal file
80
internal/http/handlers/static/handler_test.go
Normal file
@ -0,0 +1,80 @@
|
||||
package static_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"gh.tarampamp.am/error-pages/internal/http/handlers/static"
|
||||
)
|
||||
|
||||
func TestServeHTTP(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var handler = static.New([]byte{1, 2, 3})
|
||||
|
||||
t.Run("get", func(t *testing.T) {
|
||||
var (
|
||||
req = httptest.NewRequest(http.MethodGet, "http://testing", http.NoBody)
|
||||
rr = httptest.NewRecorder()
|
||||
)
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
assert.Equal(t, rr.Header().Get("Content-Type"), "application/octet-stream")
|
||||
assert.Equal(t, rr.Code, http.StatusOK)
|
||||
assert.Equal(t, rr.Body.Bytes(), []byte{1, 2, 3})
|
||||
})
|
||||
|
||||
t.Run("head", func(t *testing.T) {
|
||||
var (
|
||||
req = httptest.NewRequest(http.MethodHead, "http://testing", http.NoBody)
|
||||
rr = httptest.NewRecorder()
|
||||
)
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
assert.Equal(t, rr.Code, http.StatusOK)
|
||||
assert.Empty(t, rr.Header().Get("Content-Type"))
|
||||
assert.Empty(t, rr.Body.Bytes())
|
||||
})
|
||||
|
||||
t.Run("method not allowed", func(t *testing.T) {
|
||||
for _, method := range []string{
|
||||
http.MethodDelete,
|
||||
http.MethodPatch,
|
||||
http.MethodPost,
|
||||
http.MethodPut,
|
||||
} {
|
||||
var (
|
||||
req = httptest.NewRequest(method, "http://testing", http.NoBody)
|
||||
rr = httptest.NewRecorder()
|
||||
)
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
assert.Equal(t, rr.Header().Get("Content-Type"), "text/plain; charset=utf-8")
|
||||
assert.Equal(t, rr.Code, http.StatusMethodNotAllowed)
|
||||
assert.Equal(t, "Method Not Allowed\n", rr.Body.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestServeHTTP_Favicon(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
handler = static.New(static.Favicon)
|
||||
|
||||
req = httptest.NewRequest(http.MethodGet, "http://testing", http.NoBody)
|
||||
rr = httptest.NewRecorder()
|
||||
)
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
assert.Equal(t, rr.Header().Get("Content-Type"), "image/x-icon")
|
||||
assert.Equal(t, rr.Code, http.StatusOK)
|
||||
assert.Equal(t, rr.Body.Bytes(), static.Favicon)
|
||||
}
|
@ -12,6 +12,7 @@ import (
|
||||
"gh.tarampamp.am/error-pages/internal/config"
|
||||
ep "gh.tarampamp.am/error-pages/internal/http/handlers/error_page"
|
||||
"gh.tarampamp.am/error-pages/internal/http/handlers/live"
|
||||
"gh.tarampamp.am/error-pages/internal/http/handlers/static"
|
||||
"gh.tarampamp.am/error-pages/internal/http/handlers/version"
|
||||
"gh.tarampamp.am/error-pages/internal/http/middleware/logreq"
|
||||
"gh.tarampamp.am/error-pages/internal/logger"
|
||||
@ -50,6 +51,7 @@ func (s *Server) Register(cfg *config.Config) error {
|
||||
liveHandler = live.New()
|
||||
versionHandler = version.New(appmeta.Version())
|
||||
errorPagesHandler = ep.New(cfg, s.log)
|
||||
faviconHandler = static.New(static.Favicon)
|
||||
)
|
||||
|
||||
s.server.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@ -64,6 +66,10 @@ func (s *Server) Register(cfg *config.Config) error {
|
||||
case url == "/version":
|
||||
versionHandler.ServeHTTP(w, r)
|
||||
|
||||
// favicon.ico endpoint
|
||||
case url == "/favicon.ico":
|
||||
faviconHandler.ServeHTTP(w, r)
|
||||
|
||||
// error pages endpoints:
|
||||
// - /
|
||||
// - /{code}.html
|
||||
@ -87,8 +93,9 @@ func (s *Server) Register(cfg *config.Config) error {
|
||||
|
||||
// apply middleware
|
||||
s.server.Handler = logreq.New(s.log, func(r *http.Request) bool {
|
||||
// skip logging healthcheck requests
|
||||
return strings.Contains(strings.ToLower(r.UserAgent()), "healthcheck")
|
||||
// skip logging healthcheck and .ico (favicon) requests
|
||||
return strings.Contains(strings.ToLower(r.UserAgent()), "healthcheck") ||
|
||||
strings.HasSuffix(r.URL.Path, ".ico")
|
||||
})(s.server.Handler)
|
||||
|
||||
return nil
|
||||
|
@ -6,7 +6,7 @@
|
||||
<title>{{ message }}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!-- {{ if or (eq code 408) (eq code 425) (eq code 429) (eq code 500) (eq code 502) (eq code 503) (eq code 504) }} -->
|
||||
<meta http-equiv="refresh" content="30" />
|
||||
<meta http-equiv="refresh" content="30">
|
||||
<!-- {{ end }} -->
|
||||
<meta name="title" content="{{ code }}: {{ message | escape }}">
|
||||
<meta name="description" content="{{ description | escape }}">
|
||||
|
@ -6,7 +6,7 @@
|
||||
<title>{{ message }}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!-- {{ if or (eq code 408) (eq code 425) (eq code 429) (eq code 500) (eq code 502) (eq code 503) (eq code 504) }} -->
|
||||
<meta http-equiv="refresh" content="30" />
|
||||
<meta http-equiv="refresh" content="30">
|
||||
<!-- {{ end }} -->
|
||||
<meta name="title" content="{{ code }}: {{ message | escape }}">
|
||||
<meta name="description" content="{{ description | escape }}">
|
||||
|
@ -6,7 +6,7 @@
|
||||
<title>{{ code }} | {{ message }}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!-- {{ if or (eq code 408) (eq code 425) (eq code 429) (eq code 500) (eq code 502) (eq code 503) (eq code 504) }} -->
|
||||
<meta http-equiv="refresh" content="30" />
|
||||
<meta http-equiv="refresh" content="30">
|
||||
<!-- {{ end }} -->
|
||||
<meta name="title" content="{{ code }}: {{ message | escape }}">
|
||||
<meta name="description" content="{{ description | escape }}">
|
||||
|
@ -6,7 +6,7 @@
|
||||
<title>{{ code }}: {{ message }}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!-- {{ if or (eq code 408) (eq code 425) (eq code 429) (eq code 500) (eq code 502) (eq code 503) (eq code 504) }} -->
|
||||
<meta http-equiv="refresh" content="30" />
|
||||
<meta http-equiv="refresh" content="30">
|
||||
<!-- {{ end }} -->
|
||||
<meta name="title" content="{{ code }}: {{ message | escape }}">
|
||||
<meta name="description" content="{{ description | escape }}">
|
||||
|
@ -6,7 +6,7 @@
|
||||
<title>{{ message }}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!-- {{ if or (eq code 408) (eq code 425) (eq code 429) (eq code 500) (eq code 502) (eq code 503) (eq code 504) }} -->
|
||||
<meta http-equiv="refresh" content="30" />
|
||||
<meta http-equiv="refresh" content="30">
|
||||
<!-- {{ end }} -->
|
||||
<meta name="title" content="{{ code }}: {{ message | escape }}">
|
||||
<meta name="description" content="{{ description | escape }}">
|
||||
|
@ -6,7 +6,7 @@
|
||||
<title>{{ message }}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!-- {{ if or (eq code 408) (eq code 425) (eq code 429) (eq code 500) (eq code 502) (eq code 503) (eq code 504) }} -->
|
||||
<meta http-equiv="refresh" content="30"/>
|
||||
<meta http-equiv="refresh" content="30">
|
||||
<!-- {{ end }} -->
|
||||
<meta name="title" content="{{ code }}: {{ message | escape }}">
|
||||
<meta name="description" content="{{ description | escape }}">
|
||||
|
@ -6,7 +6,7 @@
|
||||
<title>{{ message }}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!-- {{ if or (eq code 408) (eq code 425) (eq code 429) (eq code 500) (eq code 502) (eq code 503) (eq code 504) }} -->
|
||||
<meta http-equiv="refresh" content="30"/>
|
||||
<meta http-equiv="refresh" content="30">
|
||||
<!-- {{ end }} -->
|
||||
<meta name="title" content="{{ code }}: {{ message | escape }}">
|
||||
<meta name="description" content="{{ description | escape }}">
|
||||
|
267
templates/shuffle.html
Normal file
267
templates/shuffle.html
Normal file
@ -0,0 +1,267 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="robots" content="nofollow,noarchive,noindex">
|
||||
<title>{{ code }} - {{ message }}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!-- {{ if or (eq code 408) (eq code 425) (eq code 429) (eq code 500) (eq code 502) (eq code 503) (eq code 504) }} -->
|
||||
<meta http-equiv="refresh" content="30">
|
||||
<!-- {{ end }} -->
|
||||
<meta name="title" content="{{ code }}: {{ message | escape }}">
|
||||
<meta name="description" content="{{ description | escape }}">
|
||||
<meta property="og:title" content="{{ code }}: {{ message | escape }}">
|
||||
<meta property="og:description" content="{{ description | escape }}">
|
||||
<meta property="twitter:title" content="{{ code }}: {{ message | escape }}">
|
||||
<meta property="twitter:description" content="{{ description | escape }}">
|
||||
<style>
|
||||
:root {
|
||||
--color-primary: #eee;
|
||||
--color-inverted: #222;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-primary: #222;
|
||||
--color-inverted: #aaa;
|
||||
}
|
||||
}
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-inverted);
|
||||
font-family: monospace;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 2000px) {
|
||||
html, body {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
article {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
article #error_text h1 {
|
||||
font-size: 2em;
|
||||
font-weight: normal;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* {{ if show_details }} */
|
||||
#details {
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
opacity: .75;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
#details td {
|
||||
white-space: nowrap;
|
||||
font-size: 0.7em;
|
||||
transition: opacity 1.4s, font-size .3s;
|
||||
}
|
||||
|
||||
#details.hidden td {
|
||||
opacity: 0;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
#details .name,
|
||||
#details .value {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
#details .name::first-letter,
|
||||
#details .value::first-letter {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#details .name {
|
||||
text-align: right;
|
||||
padding-right: .2em;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
#details .value {
|
||||
text-align: left;
|
||||
padding-left: .4em;
|
||||
font-family: monospace;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
/* {{ end }} */
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<article>
|
||||
<div id="error_text">
|
||||
<h1 class="source">{{ code }}: <span data-l10n>{{ message }}</span></h1>
|
||||
<h1 class="target"></h1>
|
||||
</div>
|
||||
|
||||
<!-- {{- if show_details -}} -->
|
||||
<table id="details" class="hidden">
|
||||
<!-- {{- if host -}} -->
|
||||
<tr>
|
||||
<td class="name"><span data-l10n>Host</span>:</td>
|
||||
<td class="value">{{ host }}</td>
|
||||
</tr>
|
||||
<!-- {{- end }}{{ if original_uri -}} -->
|
||||
<tr>
|
||||
<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"><span data-l10n>Forwarded for</span>:</td>
|
||||
<td class="value">{{ forwarded_for }}</td>
|
||||
</tr>
|
||||
<!-- {{- end }}{{ if namespace -}} -->
|
||||
<tr>
|
||||
<td class="name"><span data-l10n>Namespace</span>:</td>
|
||||
<td class="value">{{ namespace }}</td>
|
||||
</tr>
|
||||
<!-- {{- end }}{{ if ingress_name -}} -->
|
||||
<tr>
|
||||
<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"><span data-l10n>Service name</span>:</td>
|
||||
<td class="value">{{ service_name }}</td>
|
||||
</tr>
|
||||
<!-- {{- end }}{{ if service_port -}} -->
|
||||
<tr>
|
||||
<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"><span data-l10n>Request ID</span>:</td>
|
||||
<td class="value">{{ request_id }}</td>
|
||||
</tr>
|
||||
<!-- {{- end -}} -->
|
||||
<tr>
|
||||
<td class="name"><span data-l10n>Timestamp</span>:</td>
|
||||
<td class="value">{{ now.Unix }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- {{- end -}} -->
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} $el
|
||||
*/
|
||||
const Shuffle = function ($el) {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-=+<>,./?[{()}]!@#$%^&*~`\|'.split('');
|
||||
const $source = $el.querySelector('.source');
|
||||
const $target = $el.querySelector('.target');
|
||||
|
||||
let cursor = 0;
|
||||
/** @type {Number|undefined} */
|
||||
let scrambleInterval;
|
||||
/** @type {Number|undefined} */
|
||||
let cursorDelayInterval;
|
||||
/** @type {Number|undefined} */
|
||||
let cursorInterval;
|
||||
|
||||
/**
|
||||
* @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)];
|
||||
}
|
||||
|
||||
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 () {
|
||||
document.getElementById('details').classList.remove('hidden');
|
||||
}, 550);
|
||||
// {{ end }}
|
||||
</script>
|
||||
|
||||
<!-- {{- if l10n_enabled -}} -->
|
||||
<script> // {{ l10nScript }}</script>
|
||||
<!-- {{- end -}} -->
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user