mirror of
https://github.com/bitinflow/expose.git
synced 2026-03-13 13:35:54 +00:00
wip
This commit is contained in:
243
resources/views/index.html
Normal file
243
resources/views/index.html
Normal file
@@ -0,0 +1,243 @@
|
||||
<html>
|
||||
<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>
|
||||
!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>
|
||||
<div id="app" 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">
|
||||
<table class="min-w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||
URL
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||
Response
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||
Duration
|
||||
</th>
|
||||
</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>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-2/3 ml-5">
|
||||
<div v-if="currentLog" class="bg-white shadow overflow-hidden sm:rounded-lg">
|
||||
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900 flex">
|
||||
{{ 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>
|
||||
</h3>
|
||||
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500">
|
||||
Status code: {{ currentLog.response?.status}}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="hidden sm:block">
|
||||
<div class="border-b border-gray-200">
|
||||
<nav class="-mb-px flex">
|
||||
<a href="#"
|
||||
@click.prevent="setView('request')"
|
||||
:class="{
|
||||
'border-indigo-500 text-indigo-600 text-indigo-600 focus:text-indigo-800 focus:border-indigo-700': view === 'request',
|
||||
'border-border-transparent text-gray-500 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="{
|
||||
'border-indigo-500 text-indigo-600': view === 'response',
|
||||
'border-border-transparent text-gray-500 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="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">
|
||||
Query Parameters
|
||||
</dt>
|
||||
</div>
|
||||
<div v-for="(value, name) in currentLog.request.query"
|
||||
:key="'query_' + 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-500">
|
||||
{{ name }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ value }}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
Post Parameters
|
||||
</dt>
|
||||
</div>
|
||||
<div v-for="(parameter, name) in currentLog.request.post"
|
||||
:key="'post_' + 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-500">
|
||||
{{ 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-else>{{ parameter.value }}</span>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
Headers
|
||||
</dt>
|
||||
</div>
|
||||
<div v-for="(value, header) in currentLog.request.headers"
|
||||
:key="header"
|
||||
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">
|
||||
{{ header }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ value }}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<pre class="p-6 prettyprint">{{ currentLog.request.body }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="view === 'response'">
|
||||
<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">
|
||||
Headers
|
||||
</dt>
|
||||
</div>
|
||||
<div v-for="(value, header) in currentLog.response.headers"
|
||||
:key="header"
|
||||
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">
|
||||
{{ header }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ value }}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<pre class="p-6 prettyprint">{{ currentLog.response.body }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app',
|
||||
|
||||
data: {
|
||||
currentLog: null,
|
||||
view: 'request',
|
||||
logs: [],
|
||||
},
|
||||
|
||||
methods: {
|
||||
setLog: function(log) {
|
||||
this.currentLog = log;
|
||||
|
||||
this.$nextTick(function(){
|
||||
PR.prettyPrint();
|
||||
});
|
||||
},
|
||||
setView: function(view) {
|
||||
this.view = view;
|
||||
|
||||
this.$nextTick(function(){
|
||||
PR.prettyPrint();
|
||||
});
|
||||
},
|
||||
replay: function(log) {
|
||||
fetch('/replay/'+log.id);
|
||||
},
|
||||
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) {
|
||||
fetch('/logs')
|
||||
.then((response) => {
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
this.logs = data;
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
mounted: function() {
|
||||
this.connect();
|
||||
|
||||
this.loadLogs();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user