This commit is contained in:
Marcel Pociot
2020-04-16 15:30:53 +02:00
parent e49708b290
commit 2778d5a489
22 changed files with 960 additions and 206 deletions

View File

@@ -2,9 +2,82 @@
<head>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tailwindcss/ui@latest/dist/tailwind-ui.min.css">
<script src="https://cdn.jsdelivr.net/gh/google/code-prettify@master/loader/run_prettify.js?skin=sunburst"></script>
<script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
!function(a,b){"function"==typeof define&&define.amd?define([],b):"undefined"!=typeof module&&module.exports?module.exports=b():a.ReconnectingWebSocket=b()}(this,function(){function a(b,c,d){function l(a,b){var c=document.createEvent("CustomEvent");return c.initCustomEvent(a,!1,!1,b),c}var e={debug:!1,automaticOpen:!0,reconnectInterval:1e3,maxReconnectInterval:3e4,reconnectDecay:1.5,timeoutInterval:2e3};d||(d={});for(var f in e)this[f]="undefined"!=typeof d[f]?d[f]:e[f];this.url=b,this.reconnectAttempts=0,this.readyState=WebSocket.CONNECTING,this.protocol=null;var h,g=this,i=!1,j=!1,k=document.createElement("div");k.addEventListener("open",function(a){g.onopen(a)}),k.addEventListener("close",function(a){g.onclose(a)}),k.addEventListener("connecting",function(a){g.onconnecting(a)}),k.addEventListener("message",function(a){g.onmessage(a)}),k.addEventListener("error",function(a){g.onerror(a)}),this.addEventListener=k.addEventListener.bind(k),this.removeEventListener=k.removeEventListener.bind(k),this.dispatchEvent=k.dispatchEvent.bind(k),this.open=function(b){h=new WebSocket(g.url,c||[]),b||k.dispatchEvent(l("connecting")),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","attempt-connect",g.url);var d=h,e=setTimeout(function(){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","connection-timeout",g.url),j=!0,d.close(),j=!1},g.timeoutInterval);h.onopen=function(){clearTimeout(e),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onopen",g.url),g.protocol=h.protocol,g.readyState=WebSocket.OPEN,g.reconnectAttempts=0;var d=l("open");d.isReconnect=b,b=!1,k.dispatchEvent(d)},h.onclose=function(c){if(clearTimeout(e),h=null,i)g.readyState=WebSocket.CLOSED,k.dispatchEvent(l("close"));else{g.readyState=WebSocket.CONNECTING;var d=l("connecting");d.code=c.code,d.reason=c.reason,d.wasClean=c.wasClean,k.dispatchEvent(d),b||j||((g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onclose",g.url),k.dispatchEvent(l("close")));var e=g.reconnectInterval*Math.pow(g.reconnectDecay,g.reconnectAttempts);setTimeout(function(){g.reconnectAttempts++,g.open(!0)},e>g.maxReconnectInterval?g.maxReconnectInterval:e)}},h.onmessage=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onmessage",g.url,b.data);var c=l("message");c.data=b.data,k.dispatchEvent(c)},h.onerror=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onerror",g.url,b),k.dispatchEvent(l("error"))}},1==this.automaticOpen&&this.open(!1),this.send=function(b){if(h)return(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","send",g.url,b),h.send(b);throw"INVALID_STATE_ERR : Pausing to reconnect websocket"},this.close=function(a,b){"undefined"==typeof a&&(a=1e3),i=!0,h&&h.close(a,b)},this.refresh=function(){h&&h.close()}}return a.prototype.onopen=function(){},a.prototype.onclose=function(){},a.prototype.onconnecting=function(){},a.prototype.onmessage=function(){},a.prototype.onerror=function(){},a.debugAll=!1,a.CONNECTING=WebSocket.CONNECTING,a.OPEN=WebSocket.OPEN,a.CLOSING=WebSocket.CLOSING,a.CLOSED=WebSocket.CLOSED,a});
!function (a, b) {
"function" == typeof define && define.amd ? define([], b) : "undefined" != typeof module && module.exports ? module.exports = b() : a.ReconnectingWebSocket = b()
}(this, function () {
function a(b, c, d) {
function l(a, b) {
var c = document.createEvent("CustomEvent");
return c.initCustomEvent(a, !1, !1, b), c
}
var e = {
debug: !1,
automaticOpen: !0,
reconnectInterval: 1e3,
maxReconnectInterval: 3e4,
reconnectDecay: 1.5,
timeoutInterval: 2e3
};
d || (d = {});
for (var f in e) this[f] = "undefined" != typeof d[f] ? d[f] : e[f];
this.url = b, this.reconnectAttempts = 0, this.readyState = WebSocket.CONNECTING, this.protocol = null;
var h, g = this, i = !1, j = !1, k = document.createElement("div");
k.addEventListener("open", function (a) {
g.onopen(a)
}), k.addEventListener("close", function (a) {
g.onclose(a)
}), k.addEventListener("connecting", function (a) {
g.onconnecting(a)
}), k.addEventListener("message", function (a) {
g.onmessage(a)
}), k.addEventListener("error", function (a) {
g.onerror(a)
}), this.addEventListener = k.addEventListener.bind(k), this.removeEventListener = k.removeEventListener.bind(k), this.dispatchEvent = k.dispatchEvent.bind(k), this.open = function (b) {
h = new WebSocket(g.url, c || []), b || k.dispatchEvent(l("connecting")), (g.debug || a.debugAll) && console.debug("ReconnectingWebSocket", "attempt-connect", g.url);
var d = h, e = setTimeout(function () {
(g.debug || a.debugAll) && console.debug("ReconnectingWebSocket", "connection-timeout", g.url), j = !0, d.close(), j = !1
}, g.timeoutInterval);
h.onopen = function () {
clearTimeout(e), (g.debug || a.debugAll) && console.debug("ReconnectingWebSocket", "onopen", g.url), g.protocol = h.protocol, g.readyState = WebSocket.OPEN, g.reconnectAttempts = 0;
var d = l("open");
d.isReconnect = b, b = !1, k.dispatchEvent(d)
}, h.onclose = function (c) {
if (clearTimeout(e), h = null, i) g.readyState = WebSocket.CLOSED, k.dispatchEvent(l("close")); else {
g.readyState = WebSocket.CONNECTING;
var d = l("connecting");
d.code = c.code, d.reason = c.reason, d.wasClean = c.wasClean, k.dispatchEvent(d), b || j || ((g.debug || a.debugAll) && console.debug("ReconnectingWebSocket", "onclose", g.url), k.dispatchEvent(l("close")));
var e = g.reconnectInterval * Math.pow(g.reconnectDecay, g.reconnectAttempts);
setTimeout(function () {
g.reconnectAttempts++, g.open(!0)
}, e > g.maxReconnectInterval ? g.maxReconnectInterval : e)
}
}, h.onmessage = function (b) {
(g.debug || a.debugAll) && console.debug("ReconnectingWebSocket", "onmessage", g.url, b.data);
var c = l("message");
c.data = b.data, k.dispatchEvent(c)
}, h.onerror = function (b) {
(g.debug || a.debugAll) && console.debug("ReconnectingWebSocket", "onerror", g.url, b), k.dispatchEvent(l("error"))
}
}, 1 == this.automaticOpen && this.open(!1), this.send = function (b) {
if (h) return (g.debug || a.debugAll) && console.debug("ReconnectingWebSocket", "send", g.url, b), h.send(b);
throw"INVALID_STATE_ERR : Pausing to reconnect websocket"
}, this.close = function (a, b) {
"undefined" == typeof a && (a = 1e3), i = !0, h && h.close(a, b)
}, this.refresh = function () {
h && h.close()
}
}
return a.prototype.onopen = function () {
}, a.prototype.onclose = function () {
}, a.prototype.onconnecting = function () {
}, a.prototype.onmessage = function () {
}, a.prototype.onerror = function () {
}, a.debugAll = !1, a.CONNECTING = WebSocket.CONNECTING, a.OPEN = WebSocket.OPEN, a.CLOSING = WebSocket.CLOSING, a.CLOSED = WebSocket.CLOSED, a
});
</script>
<style>
.even\:bg-gray-50:nth-child(even) {
@@ -13,10 +86,29 @@
</style>
</head>
<body>
<div id="app" class="p-5 flex flex-row">
<div id="app" class="">
<div class="relative bg-indigo-600" style="marign-left: -1px">
<div class="max-w-screen-xl mx-auto py-3 px-3 sm:px-6 lg:px-8">
<div class="pr-16 sm:text-center sm:px-16">
<p class="font-medium text-white flex justify-center">
<span class="inline-block">Waiting for requests on: <a class="underline" target="_blank"
href="%subdomains%">%subdomains%</a></span>
</p>
</div>
</div>
</div>
<div class="p-5 flex flex-row">
<div class="w-1/3 flex flex-col mr-5">
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
<div class="align-middle inline-block min-w-full shadow overflow-hidden sm:rounded-lg border-b border-gray-200">
<span class="inline-flex rounded-md shadow-sm mb-4">
<button @click.prevent="clearLogs"
type="button"
class="inline-flex items-center px-2.5 py-1.5 border border-gray-300 text-xs leading-4 font-medium rounded text-gray-700 bg-white hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:text-gray-800 active:bg-gray-50 transition ease-in-out duration-150">
Clear
</button>
</span>
<div
class="align-middle inline-block min-w-full shadow overflow-hidden sm:rounded-lg border-b border-gray-200">
<table class="min-w-full">
<thead>
<tr>
@@ -32,30 +124,30 @@
</tr>
</thead>
<tbody class="bg-white">
<tr v-for="log in logs"
:class="{'bg-gray-100': currentLog === log}"
@click="setLog(log)">
<td class="cursor.pointer px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 font-medium text-gray-900">
<p>
{{ log.request.method }}
{{ log.request.uri }}
</p>
<span class="text-xs">{{ log.subdomain }}</span>
</td>
<td class="cursor.pointer px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500">
<div v-if="log.response">
{{ log.response.status }} - {{ log.response.reason }}
</div>
<div v-else>
...
</div>
</td>
<td class="cursor.pointer px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500">
<div v-if="log.response">
{{ log.duration }}ms
</div>
</td>
</tr>
<tr v-for="log in logs"
:class="{'bg-gray-100': currentLog === log}"
@click="setLog(log)">
<td class="cursor.pointer px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 font-medium text-gray-900">
<p>
{{ log.request.method }}
{{ log.request.uri }}
</p>
<span class="text-xs">{{ log.subdomain }}</span>
</td>
<td class="cursor.pointer px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500">
<div v-if="log.response">
{{ log.response.status }} - {{ log.response.reason }}
</div>
<div v-else>
...
</div>
</td>
<td class="cursor.pointer px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500">
<div v-if="log.response">
{{ log.duration }}ms
</div>
</td>
</tr>
</tbody>
</table>
</div>
@@ -68,11 +160,20 @@
{{ currentLog.request.method }} {{ currentLog.request.uri }}
<div class="flex-grow"></div>
<span class="inline-flex rounded-md shadow-sm">
<button @click.prevent="replay(currentLog)"
type="button" class="inline-flex items-center px-2.5 py-1.5 border border-gray-300 text-xs leading-4 font-medium rounded text-gray-700 bg-white hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:text-gray-800 active:bg-gray-50 transition ease-in-out duration-150">
Replay
</button>
</span>
<button @click.prevent="replay(currentLog)"
type="button"
class="inline-flex items-center px-2.5 py-1.5 border border-gray-300 text-xs leading-4 font-medium rounded text-gray-700 bg-white hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:text-gray-800 active:bg-gray-50 transition ease-in-out duration-150">
Replay
</button>
</span>
<span class="inline-flex rounded-md shadow-sm ml-4">
<button
:data-clipboard-text="currentLog.request.curl"
type="button"
class="clipboard inline-flex items-center px-2.5 py-1.5 border border-gray-300 text-xs leading-4 font-medium rounded text-gray-700 bg-white hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:text-gray-800 active:bg-gray-50 transition ease-in-out duration-150">
Copy as curl
</button>
</span>
</h3>
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500">
Status code: {{ currentLog.response?.status}}
@@ -135,7 +236,8 @@
{{ parameter.name }}
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<span v-if="parameter.is_file">File: {{ parameter.filename }} ({{ parameter.mime_type }})</span>
<span
v-if="parameter.is_file">File: {{ parameter.filename }} ({{ parameter.mime_type }})</span>
<span v-else>{{ parameter.value }}</span>
</dd>
</div>
@@ -177,14 +279,66 @@
{{ value }}
</dd>
</div>
<div v-if="currentLog.request.additional_data.length !== 0">
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium text-gray-900">
Debug
</dt>
</div>
<div v-for="(value, key) in currentLog.request.additional_data"
:key="'debug'+key"
class="even:bg-gray-50 odd:bg-gray-50 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium text-gray-500">
{{ key }}
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<div v-html="value">
</dd>
</div>
</div>
<div>
<pre class="p-6 prettyprint">{{ currentLog.response.body }}</pre>
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium text-gray-900">
Response
</dt>
<div>
<div class="sm:hidden">
<select class="form-select block w-full">
<option>My Account</option>
<option>Company</option>
<option selected>Team Members</option>
<option>Billing</option>
</select>
</div>
<div class="hidden sm:block">
<nav class="flex">
<a href="#"
@click.prevent="setActiveTab('raw')"
:class="{'outline-none text-gray-700 bg-gray-100': activeTab === 'raw'}"
class="px-3 py-2 font-medium text-sm leading-5 rounded-md text-gray-500 hover:text-gray-700">
Raw
</a>
<a href="#"
@click.prevent="setActiveTab('preview')"
:class="{'outline-none text-gray-700 bg-gray-100': activeTab === 'preview'}"
class="ml-4 px-3 py-2 font-medium text-sm leading-5 rounded-md text-gray-500 hover:text-gray-700 focus:outline-none focus:text-gray-700 focus:bg-gray-100">
Preview
</a>
</nav>
</div>
</div>
</div>
<div v-if="activeTab === 'raw'">
<pre class="p-6 text-sm">{{ currentLog.response.body }}</pre>
</div>
<div v-if="activeTab === 'preview'">
<iframe :srcdoc="currentLog.response.body" style="height: 500px;" class="w-full h-full"></iframe>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
new Vue({
@@ -193,35 +347,38 @@
data: {
currentLog: null,
view: 'request',
activeTab: 'raw',
logs: [],
},
methods: {
setLog: function(log) {
setActiveTab: function(tab) {
this.activeTab = tab;
},
clearLogs: function() {
fetch('/logs/clear');
},
setLog: function (log) {
this.currentLog = log;
this.$nextTick(function(){
PR.prettyPrint();
this.$nextTick(function () {
let clipboard = new ClipboardJS('.clipboard');
});
},
setView: function(view) {
setView: function (view) {
this.view = view;
this.$nextTick(function(){
PR.prettyPrint();
});
},
replay: function(log) {
fetch('/replay/'+log.id);
replay: function (log) {
fetch('/replay/' + log.id);
},
connect: function() {
connect: function () {
let conn = new ReconnectingWebSocket(`ws://${window.location.hostname}:${window.location.port}/socket`);
conn.onmessage = (e) => {
this.logs = JSON.parse(e.data);
};
},
loadLogs: function(log) {
loadLogs: function (log) {
fetch('/logs')
.then((response) => {
return response.json();
@@ -232,7 +389,7 @@
},
},
mounted: function() {
mounted: function () {
this.connect();
this.loadLogs();