Files
expose/resources/views/client/dashboard.twig
2021-05-19 11:57:25 +02:00

569 lines
33 KiB
Twig

<html lang="en">
<head>
<title>Expose Dashboard :: {{ subdomains|join(", ") }}</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js"></script>
<script src="https://unpkg.com/tailwindcss-jit-cdn"></script>
<script type="tailwind-config">
{
"darkMode": "media",
"theme": {
"extend": {
"colors": {
"dark-blue-800": "#ff9900"
}
}
}
}
</script>
<script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<link rel="stylesheet" href="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.1.0/build/styles/github.min.css">
<script src="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.1.0/build/highlight.min.js" async></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
});
</script>
<style>
.even\:bg-gray-50:nth-child(even) {
background-color: #f7fafc;
}
</style>
</head>
<body class="dark:bg-gray-900 dark:text-white">
<div id="app" class="container px-8 mx-auto dark:bg-gray-900">
<header class="text-pink-500 font-semibold pt-12 pb-6 flex items-center justify-center">
<a href="https://beyondco.de" target="_blank" class="inline-flex items-center self-start">
<img src="https://beyondco.de/apps/icons/expose.png" class="h-12">
<p class="ml-4 font-headline text-lg">Expose</p>
</a>
<span class="inline-block justify-self-stretch text-center w-full">Waiting for requests on:
{% for subdomain in subdomains %}
<a class="underline" target="_blank" href="{{ subdomain }}">{{ subdomain }}</a>
{% endfor %}
</span>
</header>
<div class="p-5 flex flex-col md:flex-row">
<div class="w-full md: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="flex mb-4">
<span class="h-8 inline-flex rounded-md shadow-sm">
<button @click.prevent="clearLogs"
type="button"
class="dark:bg-gray-800 dark:text-pink-500 dark:border-pink-500 text-gray-700 bg-white hover:text-gray-500 active:text-gray-800 active:bg-gray-50 border-gray-300
dark:text-gray-200 dark:bg-gray-700 dark:hover:text-gray-500 active:text-gray-800 dark:active:bg-gray-900 dark:border-gray-800
inline-flex items-center px-2.5 py-1.5 border text-xs leading-4 font-medium rounded focus:outline-none focus:border-blue-300 focus:shadow-outline-blue transition ease-in-out duration-150">
Clear
</button>
</span>
<div class="ml-4 flex-grow relative rounded-md shadow-sm">
<input class="h-8 rounded-md dark:bg-gray-800 p-2 block w-full sm:text-sm sm:leading-5" v-model="search" placeholder="Search" />
</div>
</div>
<div
class="align-middle inline-block min-w-full shadow overflow-hidden sm:rounded-lg border-b border-gray-200 dark:border-gray-800">
<table class="min-w-full">
<thead>
<tr>
<th class="px-6 py-3 border-b dark:border-gray-700 border-gray-200 bg-gray-50 dark:bg-gray-800 dark:text-gray-300 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
URL
</th>
<th class="px-6 py-3 border-b dark:border-gray-700 border-gray-200 bg-gray-50 dark:bg-gray-800 dark:text-gray-300 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
Response
</th>
<th class="px-6 py-3 border-b dark:border-gray-700 border-gray-200 bg-gray-50 dark:bg-gray-800 dark:text-gray-300 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
Duration
</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-gray-700">
<tr v-for="log in filteredLogs"
:class="{'dark:bg-gray-800 bg-gray-100': currentLog === log}"
@click="setLog(log)">
<td class="cursor-pointer px-6 py-4 whitespace-normal border-b dark:border-gray-800 border-gray-200 text-sm leading-5 font-medium text-gray-900 dark:text-gray-200">
<p class="flex">
<span class="text-xs">@{ log.request.method }</span>
<span
style="max-width: 150px"
:title="log.request.uri"
class="font-mono pl-1 text-xs truncate">@{ log.request.uri }</span>
</p>
{% if subdomains|length > 1 %}
<span class="text-xs font-mono">@{ log.subdomain }</span>
{% endif %}
<span class="text-xs text-gray-600 dark:text-gray-50">@{ log.performed_at }</span>
</td>
<td class="cursor-pointer px-6 py-4 whitespace-nowrap border-b dark:border-gray-800 border-gray-200 text-sm leading-5 text-gray-500 dark:text-gray-200 ">
<div v-if="log.response">
<span
v-if="log.response.status >= 200 && log.response.status < 300"
class="inline-flex items-center px-3 py-0.5 rounded-full text-xs font-medium leading-5 bg-green-100 text-green-800">
@{ log.response.status } - @{ log.response.reason }
</span>
<span
v-if="log.response.status >= 300 && log.response.status < 400"
class="inline-flex items-center px-3 py-0.5 rounded-full text-xs font-medium leading-5 bg-blue-100 text-blue-800">
@{ log.response.status } - @{ log.response.reason }
</span>
<span
v-if="log.response.status >= 400 && log.response.status < 500"
class="inline-flex items-center px-3 py-0.5 rounded-full text-xs font-medium leading-5 bg-yellow-100 text-yellow-800">
@{ log.response.status } - @{ log.response.reason }
</span>
<span
v-if="log.response.status >= 500"
class="inline-flex items-center px-3 py-0.5 rounded-full text-xs font-medium leading-5 bg-red-100 text-red-800">
@{ log.response.status } - @{ log.response.reason }
</span>
</div>
<div v-else>
...
</div>
</td>
<td class="cursor-pointer px-6 py-4 whitespace-no-wrap border-b dark:border-gray-800 border-gray-200 text-sm leading-5 dark:text-gray-200 text-gray-500">
<div v-if="log.response">
@{ log.duration }ms
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="w-full md:w-2/3 mt-5 md:mt-0 md:ml-5">
<div v-if="currentLog" class="bg-white dark:bg-gray-800 shadow overflow-hidden sm:rounded-lg dark:border-gray-700 border">
<div class="dark:bg-gray-700 px-4 py-5 border-b dark:border-gray-700 border-gray-200 sm:px-6">
<h3 class="text-lg leading-6 font-medium dark:text-gray-50 text-gray-900 flex">
<span>@{ currentLog.request.method } @{ currentLog.request.uri }</span>
<div class="flex-grow"></div>
<span class="inline-flex rounded-md shadow-sm">
<button @click.prevent="replay(currentLog)"
type="button"
class="
dark:bg-gray-800 dark:text-pink-500 dark:border-pink-500
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="dark:bg-gray-800 dark:text-pink-500 dark:border-pink-500 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>
<div class="mt-1 max-w-2xl text-sm leading-5 text-gray-500 flex items-center">
<div class="mr-4" v-if="currentLog.response">
<span
v-if="currentLog.response.status >= 200 && currentLog.response.status < 300"
class="inline-flex items-center px-3 py-0.5 rounded-full text-xs font-medium leading-5 bg-green-100 text-green-800">
@{ currentLog.response.status } - @{ currentLog.response.reason }
</span>
<span
v-if="currentLog.response.status >= 400 && currentLog.response.status < 500"
class="inline-flex items-center px-3 py-0.5 rounded-full text-xs font-medium leading-5 bg-yellow-100 text-yellow-800">
@{ currentLog.response.status } - @{ currentLog.response.reason }
</span>
<span
v-if="currentLog.response.status >= 500"
class="inline-flex items-center px-3 py-0.5 rounded-full text-xs font-medium leading-5 bg-red-100 text-red-800">
@{ currentLog.response.status } - @{ currentLog.response.reason }
</span>
</div>
<span class="text-xs text-gray-600 dark:text-gray-200">Received at: @{ currentLog.performed_at }</span>
</div>
</div>
<div>
<div class="hidden sm:block">
<div class="border-b border-gray-200 dark:border-gray-700">
<nav class="-mb-px flex">
<a href="#"
@click.prevent="setView('request')"
:class="{
'dark:bg-gray-900 dark:border-pink-500 border-indigo-500 dark:text-pink-500 text-indigo-600 dark:focus:text-pink-400 focus:text-indigo-800 dark:focus:border-pink-400 focus:border-indigo-700': view === 'request',
'border-border-transparent dark:text-gray-200 text-gray-500 dark:hover:text-gray-100 hover:text-gray-700 hover:border-gray-300 focus:text-gray-700 focus:border-gray-300': view === 'response',
}"
class="w-1/2 py-4 px-1 text-center border-b-2 font-medium text-sm leading-5 focus:outline-none">
Request
</a>
<a href="#"
@click.prevent="setView('response')"
:class="{
'dark:bg-gray-900 dark:border-pink-500 border-indigo-500 dark:text-pink-500 text-indigo-600 dark:focus:text-pink-400 focus:text-indigo-800 dark:focus:border-pink-400 focus:border-indigo-700': view === 'response',
'border-border-transparent dark:text-gray-200 text-gray-500 dark:hover:text-gray-100 hover:text-gray-700 hover:border-gray-300 focus:text-gray-700 focus:border-gray-300': view === 'request',
}"
class="w-1/2 py-4 px-1 text-center border-b-2 font-medium text-sm leading-5 focus:outline-none">
Response
</a>
</nav>
</div>
</div>
</div>
<div v-if="view === 'request'">
<div class="px-4 py-5 border-b dark:border-gray-700 border-gray-200 sm:px-6 flex justify-between" v-if="Object.keys(currentLog.request.query).length > 0">
<h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">
Query Parameters
</h3>
<span class="inline-flex rounded-md shadow-sm ml-4">
<button
type="button"
class="dark:bg-gray-800 dark:text-pink-500 dark:border-pink-500 clipboard-query 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 PHP array
</button>
</span>
</div>
<div v-for="(value, name) in currentLog.request.query"
:key="'query_' + name"
class="even:bg-gray-50 odd:bg-gray-50 dark:even:bg-gray-700 dark:odd:bg-gray-800 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium dark:text-gray-200 text-gray-700">
@{ name }
</dt>
<dd class="mt-1 text-sm leading-5 dark:text-gray-200 text-gray-900 sm:mt-0 sm:col-span-2 break-all">
@{ value }
</dd>
</div>
<div class="px-4 py-5 border-b border-t dark:border-gray-700 border-gray-200 sm:px-6 flex justify-between" v-if="Object.keys(currentLog.request.post).length > 0">
<h3 class="text-lg leading-6 font-medium text-gray-900">
Post Parameters
</h3>
<span class="inline-flex rounded-md shadow-sm ml-4">
<button
type="button"
class="dark:bg-gray-800 dark:text-pink-500 dark:border-pink-500 clipboard-post 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 PHP array
</button>
</span>
</div>
<div v-for="parameter in currentLog.request.post"
:key="'post_' + parameter.name"
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-700">
@{ parameter.name }
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2 break-all">
<span
v-if="parameter.is_file">File: @{ parameter.filename } (@{ parameter.mime_type })</span>
<span v-else>@{ parameter.value }</span>
</dd>
</div>
<div class="px-4 py-5 border-b border-t dark:border-gray-700 border-gray-200 sm:px-6 flex justify-between">
<h3 class="text-lg leading-6 font-medium dark:text-gray-100 text-gray-900">
Headers
</h3>
<span class="inline-flex rounded-md shadow-sm ml-4">
<button
type="button"
class="dark:bg-gray-800 dark:text-pink-500 dark:border-pink-500 clipboard-headers 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 PHP array
</button>
</span>
</div>
<div v-for="(value, header) in currentLog.request.headers"
:key="header"
class="even:bg-gray-50 odd:bg-gray-50 dark:even:bg-gray-700 dark:odd:bg-gray-800 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium dark:text-gray-200 text-gray-700">
@{ header }
</dt>
<dd class="mt-1 text-sm leading-5 dark:text-gray-200 text-gray-900 sm:mt-0 sm:col-span-2">
@{ value }
</dd>
</div>
<div class="px-4 py-5 border-b border-t dark:border-gray-700 border-gray-200 sm:px-6">
<h3 class="text-lg leading-6 font-medium dark:text-gray-100 text-gray-900">
Request Body
</h3>
</div>
<div>
<pre class="p-6 prettyprint break-all whitespace-pre-wrap">@{ currentLog.request.body }</pre>
</div>
</div>
<div v-if="view === 'response'">
<div class="px-4 py-5 border-b dark:border-gray-700 border-gray-200 sm:px-6">
<h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">
Headers
</h3>
</div>
<div v-for="(value, header) in currentLog.response.headers"
:key="header"
class="even:bg-gray-50 odd:bg-gray-50 dark:even:bg-gray-700 dark:odd:bg-gray-800 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm leading-5 font-medium dark:text-gray-200 text-gray-700">
@{ header }
</dt>
<dd class="mt-1 text-sm leading-5 dark:text-gray-100 text-gray-900 sm:mt-0 sm:col-span-2 break-all">
@{ 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-700">
@{ 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 class="px-4 py-5 border-b border-t dark:border-gray-700 border-gray-200 sm:px-6">
<h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100 flex justify-between">
<span>Response</span>
<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>
</h3>
</div>
<div v-if="activeTab === 'raw'">
<pre class="p-6 text-sm whitespace-pre-wrap break-all">@{ currentLog.response.body }</pre>
</div>
<div v-if="activeTab === 'preview'">
<div v-if="responseIsJson()">
<pre><span id="jsonResponseBody" class="json"></span></pre>
</div>
<iframe v-else :srcdoc="currentLog.response.body" style="height: 500px;" class="w-full h-full"></iframe>
</div>
</div>
</div>
<div v-else class="flex-col bg-white dark:bg-gray-800 shadow overflow-hidden sm:rounded-lg justify-center items-center flex py-4">
<h1 class="text-lg">Waiting for connections...</h1>
<img src="https://chart.googleapis.com/chart?chs=500x500&cht=qr&chl={{ subdomains[0] | url_encode }}&choe=UTF-8&chf=bg,s,FFFFFF00" />
<a class="text-sm" href="{{ subdomains[0] }}" target="_blank">{{ subdomains[0] }}</a>
</div>
</div>
</div>
</div>
<script>
new Vue({
el: '#app',
delimiters: ['@{', '}'],
data: {
search: '',
currentLog: null,
view: 'request',
activeTab: 'raw',
logs: [],
maxLogs: {{ max_logs }},
},
computed: {
filteredLogs: function() {
if (this.search === '') {
return this.logs;
}
return this.logs.filter(log => {
return log.request.uri.indexOf(this.search) !== -1;
});
},
},
methods: {
setActiveTab: function(tab) {
this.activeTab = tab;
this.$nextTick(function () {
this.formatJsonResponse();
});
},
clearLogs: function() {
fetch('/api/logs/clear');
this.logs = []
this.currentLog = null;
},
formatJsonResponse: function () {
if (this.view === 'response' && this.activeTab === 'preview' && this.responseIsJson()) {
const target = document.getElementById('jsonResponseBody');
target.innerText = JSON.stringify(JSON.parse(this.currentLog.response.body), null, 2)
hljs.highlightBlock(target);
}
},
responseIsJson: function() {
if (! this.currentLog || ! this.currentLog.response || ! this.currentLog.response.headers) {
return false;
}
return /application\/json/g.test(this.currentLog.response.headers['Content-Type']);
},
toPhpArray: function(rows, variableName) {
let output = `$${variableName} = [\n`;
for (let key in rows) {
let value = rows[key];
if (typeof value.value !== 'undefined') {
value = value.value;
}
output += ` '${key}' => '${value}',\n`;
}
output += `];`;
return output;
},
setLog: function (log) {
this.currentLog = log;
this.formatJsonResponse();
this.$nextTick(function () {
this.formatJsonResponse()
new ClipboardJS('.clipboard');
new ClipboardJS('.clipboard-query', {
text: (trigger) => {
return this.toPhpArray(this.currentLog.request.query, 'queryParameters');
}
});
new ClipboardJS('.clipboard-post', {
text: (trigger) => {
return this.toPhpArray(this.currentLog.request.post, 'postParameters');
}
});
new ClipboardJS('.clipboard-headers', {
text: (trigger) => {
return this.toPhpArray(this.currentLog.request.headers, 'headers');
}
});
});
},
setView: function (view) {
this.view = view;
},
replay: function (log) {
fetch('/api/replay/' + log.id);
},
connect: function () {
let conn = new ReconnectingWebSocket(`ws://${window.location.hostname}:${window.location.port}/socket`);
conn.onmessage = (e) => {
const request = JSON.parse(e.data);
const index = this.logs.findIndex(log => log.id === request.id);
if (index > -1) {
this.$set(this.logs, index, request)
} else {
this.logs.unshift(request);
}
this.logs = this.logs.splice(0, this.maxLogs);
};
},
loadLogs: function (log) {
fetch('/api/logs')
.then((response) => {
return response.json();
})
.then((data) => {
this.logs = data;
});
},
},
mounted: function () {
this.connect();
this.loadLogs();
}
});
</script>
</body>
</html>