Current section

Files

Jump to
plug lib plug templates debugger.html.eex
Raw

lib/plug/templates/debugger.html.eex

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title><%= h @title %> at <%= h method(@conn) %> <%= h @conn.request_path %></title>
<meta name="viewport" content="width=device-width">
<style>/*! normalize.css v4.2.0 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}audio,canvas,progress,video{display:inline-block}audio:not([controls]){display:none;height:0}progress{vertical-align:baseline}template,[hidden]{display:none}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}img{border-style:none}svg:not(:root){overflow:hidden}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}button,input,optgroup,select,textarea{font:inherit;margin:0}optgroup{font-weight:bold}button,input{overflow:visible}button,select{text-transform:none}button,html [type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}[type="checkbox"],[type="radio"]{box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-cancel-button,[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-input-placeholder{color:inherit;opacity:0.54}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}</style>
<style>
html, body, td, input {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
}
* {
box-sizing: border-box;
}
html {
font-size: 15px;
line-height: 1.6;
background: #fff;
color: <%= @style.text_color %>;
}
@media (max-width: 768px) {
html {
font-size: 14px;
}
}
@media (max-width: 480px) {
html {
font-size: 13px;
}
}
button:focus,
summary:focus {
outline: 0;
}
summary {
cursor: pointer;
}
pre {
font-family: <%= @style.monospace_font %>;
overflow: auto;
max-width: 100%;
}
.top-details {
padding: 48px;
background: #f9f9fa;
}
.top-details,
.conn-info {
padding: 48px;
}
@media (max-width: 768px) {
.top-details,
.conn-info {
padding: 32px;
}
}
@media (max-width: 480px) {
.top-details,
.conn-info {
padding: 16px;
}
}
/*
* Exception logo
*/
<%= if @style.logo do %>
.exception-logo {
position: absolute;
right: 48px;
top: 48px;
pointer-events: none;
width: 100%;
}
.exception-logo:before {
content: '';
display: block;
height: 64px;
width: 100%;
background-size: auto 100%;
background-image: url("<%= @style.logo %>");
background-position: right 0;
background-repeat: no-repeat;
margin-bottom: 16px;
}
@media (max-width: 768px) {
.exception-logo {
position: static;
}
.exception-logo:before {
height: 32px;
background-position: left 0;
}
}
@media (max-width: 480px) {
.exception-logo {
display: none;
}
}
<% end %>
/*
* Exception info
*/
/* Compensate for logo placement */
@media (min-width: 769px) {
.exception-info {
max-width: 90%;
}
}
.exception-info > .struct {
margin: 0;
padding: 0;
}
.exception-info > .struct {
font-size: 1em;
font-weight: 700;
color: <%= @style.primary %>;
}
.exception-info > .struct > small {
font-size: 1em;
color: <%= @style.accent %>;
font-weight: 400;
}
.exception-info > code {
line-height: 1.5;
}
/*
* Code explorer
*/
.code-explorer {
margin: 32px 0 0 0;
}
@media (max-width: 768px) {
.code-explorer {
margin-top: 16px;
}
}
.code-explorer:after {
content: '';
display: table;
clear: both;
zoom: 1;
}
.code-explorer > .code-snippets {
float: left;
width: 45%;
}
.code-explorer > .stack-trace {
float: right;
width: 55%;
padding-left: 32px;
}
.code-explorer .hidden-contents {
position: absolute;
left: -999em;
}
/* Collapse to single-column */
@media (max-width: 960px) {
.code-explorer > .code-snippets {
float: none;
width: auto;
margin-bottom: 16px;
}
.code-explorer > .stack-trace {
float: none;
width: auto;
padding-left: 0;
}
}
/*
* Frame info:
* Holds the code (code-block) and more
*/
.frame-info {
background: white;
border-radius: 4px;
box-shadow:
0 1px 3px rgba(80, 100, 140, .1),
0 8px 15px rgba(80, 100, 140, .05);
}
.frame-info > .meta,
.frame-info > .file {
padding: 12px 16px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: <%= :math.pow(1.2, -1) %>em;
}
@media (max-width: 480px) {
.frame-info > .meta,
.frame-info > .file {
padding: 6px 16px;
font-size: <%= :math.pow(1.1, -1) %>em;
}
}
.frame-info > .file > a {
text-decoration: none;
color: <%= @style.text_color %>;
font-weight: 700;
}
.frame-info > .code {
border-top: solid 1px <%= @style.line_color %>;
border-bottom: solid 1px <%= @style.line_color %>;
font-size: 0.8em;
}
/* Hiding */
.frame-info {
display: none;
}
.frame-info.-active {
display: block;
}
.frame-info > details.meta {
border-top: solid 1px <%= @style.line_color %>;
padding: 0;
}
.frame-info > details.meta > summary {
padding: 12px 16px;
}
.frame-info > details.meta > pre {
line-height: 16px;
padding: 6px 0 24px 30px;
}
.frame-info > details.meta > ol {
counter-reset: item;
padding: 6px 0 24px 46px;
list-style: none;
}
.frame-info > details.meta > ol > li {
counter-increment: item;
}
.frame-info > details.meta > ol > li:before {
content: counter(item) ". ";
color: <%= @style.accent %>;
}
/*
* Frame details
*/
.frame-mfa {
color: <%= @style.accent %>;
}
.frame-mfa > .app {
color: <%= @style.accent %>;
}
.frame-mfa > .docs {
color: <%= @style.primary %>;
text-decoration: none;
}
.frame-mfa .docs:hover {
text-decoration: underline;
}
.frame-mfa > .right {
float: right;
padding-left: 5px;
}
.frame-mfa > .right:after {
content: '·';
margin-left: .2em;
padding-left: .2em;
}
.frame-mfa > .right:first-child:after {
content: '';
}
/*
* Code block:
* The `pre` that holds the code
*/
.code-block {
margin: 0;
padding: 12px 0;
font-size: <%= :math.pow(1.2, -1) %>em;
line-height: 1.4;
white-space: normal;
}
.code-block > .line {
white-space: pre;
display: block;
padding: 0 16px;
}
/* Line highlight */
.code-block > .line.-highlight {
background-color: <%= @style.highlight %>;
-webkit-animation: line-highlight 750ms linear;
animation: line-highlight 750ms linear;
}
@-webkit-keyframes line-highlight {
0% { background-color: <%= @style.highlight %>; }
25% { background-color: <%= @style.red_highlight %>; }
50% { background-color: <%= @style.highlight %>; }
75% { background-color: <%= @style.red_highlight %>; }
}
@keyframes line-highlight {
0% { background-color: <%= @style.highlight %>; }
25% { background-color: <%= @style.red_highlight %>; }
50% { background-color: <%= @style.highlight %>; }
75% { background-color: <%= @style.red_highlight %>; }
}
.code-block > .line > .ln {
color: <%= @style.accent %>;
margin-right: 1.5em;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.code-block > .line > .code {
font-family: <%= @style.monospace_font %>;
}
/*
* Empty code
*/
.code-block-empty {
text-align: center;
color: <%= @style.accent %>;
padding-top: 48px;
padding-bottom: 48px;
}
/*
* Stack trace heading
*/
.stack-trace-heading {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: 8px;
}
.stack-trace-heading > h3 {
display: none;
}
.stack-trace-heading > label {
display: inline-block;
padding-left: 5px;
line-height: 1.9;
font-size: <%= :math.pow(1.2, -1) %>em;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.stack-trace-heading > label > input {
margin: -0.2em 0.5em 0 0;
vertical-align: middle;
}
.stack-trace-heading .copy-markdown {
color: <%= @style.text_color %>;
background-color: transparent;
display: inline-flex;
align-items: center;
font-size: <%= :math.pow(1.2, -1) %>em;
line-height: 1.9;
border-width: 0;
}
.stack-trace-heading .copy-markdown:active {
cursor: pointer;
}
.stack-trace-heading .copy-markdown:hover {
cursor: pointer;
}
.stack-trace-heading .copy-markdown-icon {
height: 1rem;
width: 1rem;
margin: 0.1em 0.3rem 0;
}
@media (max-width: 480px) {
.stack-trace-heading > label {
font-size: <%= :math.pow(1.1, -1) %>em;
}
}
/*
* Stack trace
*/
.stack-trace-list,
.stack-trace-list > li {
margin: 0;
padding: 0;
list-style-type: none;
}
.stack-trace-list > li > .stack-trace-item.-all {
display: none;
}
.stack-trace-list.-show-all > li > .stack-trace-item.-all {
display: block;
}
/*
* Stack trace item:
* The clickable line to inspect a stack trace
*/
.stack-trace-item {
font-size: <%= :math.pow(1.2, -1) %>em;
display: block;
width: 100%;
border: 0;
margin: 0;
padding: 4px 8px;
background: transparent;
cursor: pointer;
text-align: left;
overflow: hidden;
white-space: nowrap;
}
.stack-trace-item:hover,
.stack-trace-item:focus {
background-color: rgba(80, 100, 140, 0.05);
border-radius: 4px;
}
.stack-trace-item,
.stack-trace-item:active {
color: <%= @style.text_color %>;
}
.stack-trace-item:active {
background-color: rgba(80, 100, 140, 0.1);
}
.stack-trace-item.-active {
background-color: rgba(80, 100, 140, 0.1);
border-radius: 4px;
}
/* Circle */
.stack-trace-item > .left:before {
content: '';
display: inline-block;
width: 8px;
height: 8px;
background: #a0b0c0;
border-radius: 50%;
margin-right: 8px;
opacity: 0.5;
}
.stack-trace-item.-active > .left:before {
opacity: 1;
}
.stack-trace-item.-app > .left:before {
background: <%= @style.primary %>;
}
.stack-trace-item.-app > .left > .app {
display: none;
}
.stack-trace-item > .left {
float: left;
max-width: 55%;
}
.stack-trace-item > .info {
color: <%= @style.accent %>;
float: right;
max-width: 45%;
}
.stack-trace-item > .left,
.stack-trace-item > .info {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.stack-trace-item > .left > .filename > .line {
color: <%= @style.accent %>;
}
/* App name */
.stack-trace-item > .left > .app {
color: <%= @style.accent %>;
}
.stack-trace-item > .left > .app:after {
content: '·';
margin: 0 .2em;
}
/*
* Code as a blockquote:
* Like `pre` but with wrapping
*/
.code-quote {
font-family: <%= @style.monospace_font %>;
font-size: <%= :math.pow(1.2, -1) %>em;
margin: 0;
overflow: auto;
max-width: 100%;
word-wrap: break-word;
white-space: normal;
}
.code-quote > pre {
margin: 0;
}
.code.-padded {
padding: 0 16px 16px 16px;
}
.code i.red {
color: #CC0000;
font-style: normal;
}
.code i.green {
font-style: normal;
}
.banner {
padding: 24px 48px 24px 48px;
border-top: solid 1px <%= @style.line_color %>;
}
/*
* Conn info
*/
.conn-info {
border-top: solid 1px <%= @style.line_color %>;
}
/*
* Conn details
*/
.conn-details {
}
.conn-details + .conn-details {
margin-top: 16px;
}
.conn-details > summary {
}
.conn-details > dl {
display: block;
overflow: hidden;
margin: 0;
padding: 4px 0;
border-bottom: solid 1px <%= @style.line_color %>;
white-space: nowrap;
text-overflow: ellipsis;
}
.conn-details > dl:first-of-type {
margin-top: 16px;
border-top: solid 1px <%= @style.line_color %>;
}
/* Term */
.conn-details > dl > dt {
width: 20%;
float: left;
font-size: <%= :math.pow(1.2, -1) %>em;
color: <%= @style.accent %>;
overflow: hidden;
text-overflow: ellipsis;
position: relative;
top: -1px; /* Compensate for font metrics */
}
/* Definition */
.conn-details > dl > dd {
width: 80%;
float: left;
}
@media (max-width: 480px) {
.conn-details > dl > dt {
font-size: <%= :math.pow(1.1, -1) %>em;
}
}
.action-form {
display: inline-block;
margin: 20px 10px 5px 0;
}
.action-button {
padding: 0 10px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="top-details">
<%= if @style.logo do %>
<aside class="exception-logo"></aside>
<% end %>
<header class="exception-info">
<h5 class="struct">
<%= h @title %>
<small>at <%= h method(@conn) %></small>
<small class="path"><%= h @conn.request_path %></small>
</h5>
<code><pre><%= h @message %></pre></code>
</header>
<%= for %{label: label, encoded_handler: encoded_handler} <- @actions do %>
<form class="action-form" action="/__plug__/debugger/action" method="POST">
<input type="hidden" name="encoded_handler" value="<%= encoded_handler %>">
<input type="hidden" name="last_path" value="<%= @last_path %>">
<button class="action-button" type="submit"> <%= label %> </button>
</form>
<% end %>
<%= if @banner do %>
</div>
<div class="banner"><%= @banner %></div>
<div class="top-details">
<% end %>
<div class="code-explorer">
<textarea class="hidden-contents" role="copy-contents"><%= @markdown %></textarea>
<div class="code-snippets">
<%= for frame <- @frames do %>
<div class="frame-info" data-index="<%= frame.index %>" role="stack-trace-details">
<div class="file">
<a href="<%= frame.link %>"><%= h frame.file %></a>
</div>
<%= if (snippet = frame.snippet) && snippet != [] do %>
<pre class="code code-block"><%= for {index, line, highlight} <- snippet do %><span class="line <%= if highlight, do: "-highlight" %>"><span class="ln"><%= h index %></span><span class="code"><%= h String.trim_trailing(line) %></span></span>
<% end %></pre>
<% else %>
<div class="code code-block-empty">No code available.</div>
<% end %>
<div class="meta">
<div class="frame-mfa">
<%= h frame.info %>
<%= if doc = frame.doc do %>
<a class="docs right" href="<%= h doc %>">Docs</a>
<% end %>
<%= if app = frame.app do %>
<span class="app right"><%= h app %></span>
<% end %>
</div>
</div>
<%= if frame.args do %>
<details class="meta" open>
<summary>Called with <%= length(frame.args) %> arguments</summary>
<ol class="code code-quote -padded">
<%= for arg <- frame.args do %>
<li><%= h inspect arg %></li>
<% end %>
</ol>
</details>
<% end %>
<%= if frame.clauses do %>
<% {min, max, clauses} = frame.clauses %>
<details class="meta" open>
<summary>Attempted function clauses (showing <%= min %> out of <%= max %>)</summary>
<pre class="code code-block -padded">
<%= for clause <- clauses do %>
<span class="line"><%= clause %></span>
<% end %>
</pre>
</details>
<% end %>
</div>
<% end %>
</div>
<div class="stack-trace">
<div class="stack-trace-heading">
<label><input type="checkbox" role="show-all-toggle">Show only app frames</label>
<button class="copy-markdown" role="copy-to-markdown" type="button">
<span role="copy-to-markdown-text">Copy markdown</span>
<svg xmlns="http://www.w3.org/2000/svg" class="copy-markdown-icon" viewBox="0 0 115.77 122.88"><g><path d="M89.62,13.96v7.73h12.19h0.01v0.02c3.85,0.01,7.34,1.57,9.86,4.1c2.5,2.51,4.06,5.98,4.07,9.82h0.02v0.02 v73.27v0.01h-0.02c-0.01,3.84-1.57,7.33-4.1,9.86c-2.51,2.5-5.98,4.06-9.82,4.07v0.02h-0.02h-61.7H40.1v-0.02 c-3.84-0.01-7.34-1.57-9.86-4.1c-2.5-2.51-4.06-5.98-4.07-9.82h-0.02v-0.02V92.51H13.96h-0.01v-0.02c-3.84-0.01-7.34-1.57-9.86-4.1 c-2.5-2.51-4.06-5.98-4.07-9.82H0v-0.02V13.96v-0.01h0.02c0.01-3.85,1.58-7.34,4.1-9.86c2.51-2.5,5.98-4.06,9.82-4.07V0h0.02h61.7 h0.01v0.02c3.85,0.01,7.34,1.57,9.86,4.1c2.5,2.51,4.06,5.98,4.07,9.82h0.02V13.96L89.62,13.96z M79.04,21.69v-7.73v-0.02h0.02 c0-0.91-0.39-1.75-1.01-2.37c-0.61-0.61-1.46-1-2.37-1v0.02h-0.01h-61.7h-0.02v-0.02c-0.91,0-1.75,0.39-2.37,1.01 c-0.61,0.61-1,1.46-1,2.37h0.02v0.01v64.59v0.02h-0.02c0,0.91,0.39,1.75,1.01,2.37c0.61,0.61,1.46,1,2.37,1v-0.02h0.01h12.19V35.65 v-0.01h0.02c0.01-3.85,1.58-7.34,4.1-9.86c2.51-2.5,5.98-4.06,9.82-4.07v-0.02h0.02H79.04L79.04,21.69z M105.18,108.92V35.65v-0.02 h0.02c0-0.91-0.39-1.75-1.01-2.37c-0.61-0.61-1.46-1-2.37-1v0.02h-0.01h-61.7h-0.02v-0.02c-0.91,0-1.75,0.39-2.37,1.01 c-0.61,0.61-1,1.46-1,2.37h0.02v0.01v73.27v0.02h-0.02c0,0.91,0.39,1.75,1.01,2.37c0.61,0.61,1.46,1,2.37,1v-0.02h0.01h61.7h0.02 v0.02c0.91,0,1.75-0.39,2.37-1.01c0.61-0.61,1-1.46,1-2.37h-0.02V108.92L105.18,108.92z"/></g></svg>
</button>
</div>
<ul class="stack-trace-list -show-all" role="stack-trace-list">
<%= for frame <- @frames do %>
<li>
<button class="stack-trace-item -<%= frame.context %>" role="stack-trace-item" data-index="<%= frame.index %>">
<span class="left">
<%= if app = frame.app do %>
<span class="app"><%= h app %></span>
<% end %>
<span class="filename">
<%= h frame.file %><%= if frame.line do %><span class="line">:<%= h frame.line %></span><% end %>
</span>
</span>
<span class="info"><%= h frame.info %></span>
</button>
</li>
<% end %>
</ul>
</div>
</div>
</div>
<div class="conn-info">
<%= if @params && @params != %{} do %>
<details class="conn-details" open>
<summary>Params</summary>
<%= for {key, value} <- @params do %>
<dl>
<dt><%= h key %></dt>
<dd><pre><%= h inspect(value) %></pre></dd>
</dl>
<% end %>
</details>
<% end %>
<details class="conn-details">
<summary>Request info</summary>
<dl>
<dt>URI:</dt>
<dd class="code-quote"><%= h url(@conn) %></dd>
</dl>
<dl>
<dt>Query string:</dt>
<dd class="code-quote"><%= h @conn.query_string %></dd>
</dl>
</details>
<details class="conn-details">
<summary>Headers</summary>
<%= for {key, value} <- Enum.sort(@conn.req_headers) do %>
<dl>
<dt><%= h key %></dt>
<dd class="code-quote"><%= h value %></dd>
</dl>
<% end %>
</details>
<%= if @session && @session != %{} do %>
<details class="conn-details">
<summary>Session</summary>
<%= for {key, value} <- @session do %>
<dl>
<dt><%= h key %></dt>
<dd class="code-quote"><pre><%= h inspect(value) %></pre></dd>
</dl>
<% end %>
</details>
<% end %>
</div>
<script>(function () {
var $items = document.querySelectorAll('[role~="stack-trace-item"]')
var $toggle = document.querySelector('[role~="show-all-toggle"]')
var $list = document.querySelector('[role~="stack-trace-list"]')
var $copyBtn = document.querySelector('[role~="copy-to-markdown"]')
var $copyBtnText = document.querySelector('[role~="copy-to-markdown-text"]')
var $copy = document.querySelector('[role~="copy-contents"]')
each($items, function ($item) {
on($item, 'click', itemOnclick)
})
on($toggle, 'click', toggleOnclick)
on($copyBtn, 'click', copyToClipboard)
function copyToClipboard () {
if(navigator.clipboard) {
// For those working on localhost or HTTPS
navigator.clipboard.writeText($copy.innerHTML).then(copiedClipboard).catch(() => {})
} else {
// For those working on HTTP
$copy.select()
if (document.execCommand("copy")) copiedClipboard()
}
}
function copiedClipboard () {
$copyBtnText.innerText = "Copied!"
setTimeout(function () {
$copyBtnText.innerText = "Copy markdown"
}, 5000)
}
function toggleOnclick () {
if (this.checked) {
var $first = document.querySelector('[role~="stack-trace-item"].-app:first-of-type')
if ($first) itemOnclick.call($first)
removeClass($list, '-show-all')
} else {
addClass($list, '-show-all')
}
}
function itemOnclick () {
var idx = this.getAttribute('data-index')
var $detail = document.querySelector('[role~="stack-trace-details"].-active')
if ($detail) removeClass($detail, '-active')
$detail = document.querySelector('[role~="stack-trace-details"][data-index="' + idx + '"]')
if ($detail) addClass($detail, '-active')
var $item = document.querySelector('[role~="stack-trace-item"].-active')
if ($item) removeClass($item, '-active')
$item = document.querySelector('[role~="stack-trace-item"][data-index="' + idx + '"]')
if ($item) addClass($item, '-active')
}
var $first = document.querySelector('[role~="stack-trace-item"]:first-of-type')
if ($first) itemOnclick.call($first)
/*
* Helpers
*/
function each (list, fn) {
for (var i = 0, len = list.length; i < len; i++) {
var item = list[i]
fn(item)
}
}
function addClass (el, className) {
if (el.classList) {
el.classList.add(className)
} else {
el.className += ' ' + className
}
}
function removeClass (el, className) {
if (el.classList) {
el.classList.remove(className)
} else {
var expr = new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi')
el.className = el.className.replace(expr, ' ')
}
}
function on (el, event, handler) {
if (el.addEventListener) {
el.addEventListener(event, handler)
} else {
el.attachEvent('on' + event, function () {
handler.call(el)
})
}
}
}())</script>
</body>
</html>