Merge branch 'dashboard-modifications' into share-files

This commit is contained in:
Marcel Pociot
2021-05-19 11:57:25 +02:00
8 changed files with 170 additions and 114 deletions

View File

@@ -140,7 +140,7 @@ class Factory
$this->router->post('/api/logs/{request_id}/data', AttachDataToLogController::class); $this->router->post('/api/logs/{request_id}/data', AttachDataToLogController::class);
$this->router->get('/api/logs/clear', ClearLogsController::class); $this->router->get('/api/logs/clear', ClearLogsController::class);
$this->app->route('/socket', new WsServer(new Socket()), ['*']); $this->app->route('/socket', new WsServer(new Socket()), ['*'], "");
foreach ($this->router->getRoutes()->all() as $name => $route) { foreach ($this->router->getRoutes()->all() as $name => $route) {
$this->app->routes->add($name, $route); $this->app->routes->add($name, $route);
@@ -162,7 +162,7 @@ class Factory
config()->set('expose.dashboard_port', $dashboardPort); config()->set('expose.dashboard_port', $dashboardPort);
$this->app = new App('127.0.0.1', $dashboardPort, '0.0.0.0', $this->loop); $this->app = new App('0.0.0.0', $dashboardPort, '0.0.0.0', $this->loop);
$this->addRoutes(); $this->addRoutes();

View File

@@ -4,8 +4,12 @@ namespace App\Client\Http\Controllers;
use App\Client\Http\HttpClient; use App\Client\Http\HttpClient;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Logger\LoggedRequest;
use App\Logger\RequestLogger; use App\Logger\RequestLogger;
use GuzzleHttp\Psr7\Message;
use GuzzleHttp\Psr7\Response; use GuzzleHttp\Psr7\Response;
use Illuminate\Support\Str;
use Laminas\Http\Header\GenericHeader;
use function GuzzleHttp\Psr7\str; use function GuzzleHttp\Psr7\str;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Ratchet\ConnectionInterface; use Ratchet\ConnectionInterface;
@@ -29,13 +33,15 @@ class ReplayLogController extends Controller
$loggedRequest = $this->requestLogger->findLoggedRequest($request->get('log')); $loggedRequest = $this->requestLogger->findLoggedRequest($request->get('log'));
if (is_null($loggedRequest)) { if (is_null($loggedRequest)) {
$httpConnection->send(str(new Response(404))); $httpConnection->send(Message::toString(new Response(404)));
return; return;
} }
$this->httpClient->performRequest($loggedRequest->getRequestData()); $loggedRequest->refreshId();
$httpConnection->send(str(new Response(200))); $this->httpClient->performRequest($loggedRequest->getRequest()->toString());
$httpConnection->send(Message::toString(new Response(200)));
} }
} }

View File

@@ -10,7 +10,7 @@ use Symfony\Component\Console\Output\ConsoleOutput;
class ShareCommand extends Command class ShareCommand extends Command
{ {
protected $signature = 'share {host} {--subdomain=} {--auth=} {--server-host=} {--server-port=}'; protected $signature = 'share {host} {--subdomain=} {--auth=} {--server-host=} {--server-port=} {--dns=}';
protected $description = 'Share a local url with a remote expose server'; protected $description = 'Share a local url with a remote expose server';
@@ -31,6 +31,14 @@ class ShareCommand extends Command
$serverPort = $this->option('server-port') ?? config('expose.port', 8080); $serverPort = $this->option('server-port') ?? config('expose.port', 8080);
$auth = $this->option('auth') ?? config('expose.auth_token', ''); $auth = $this->option('auth') ?? config('expose.auth_token', '');
if (strstr($this->argument('host'), 'host.docker.internal')) {
config(['expose.dns' => true]);
}
if ($this->option('dns') !== null) {
config(['expose.dns' => empty($this->option('dns')) ? true : $this->option('dns')]);
}
(new Factory()) (new Factory())
->setLoop(app(LoopInterface::class)) ->setLoop(app(LoopInterface::class))
->setHost($serverHost) ->setHost($serverHost)

View File

@@ -3,6 +3,7 @@
namespace App\Logger; namespace App\Logger;
use Carbon\Carbon; use Carbon\Carbon;
use Laminas\Http\Header\GenericHeader;
use function GuzzleHttp\Psr7\parse_request; use function GuzzleHttp\Psr7\parse_request;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@@ -211,4 +212,17 @@ class LoggedRequest implements \JsonSerializable
return ''; return '';
} }
} }
public function refreshId()
{
$requestId = (string) Str::uuid();
$this->getRequest()->getHeaders()->removeHeader(
$this->getRequest()->getHeader('x-expose-request-id')
);
$this->getRequest()->getHeaders()->addHeader(new GenericHeader('x-expose-request-id', $requestId));
$this->id = $requestId;
}
} }

View File

@@ -29,6 +29,18 @@ return [
*/ */
'port' => 443, 'port' => 443,
/*
|--------------------------------------------------------------------------
| DNS
|--------------------------------------------------------------------------
|
| The DNS server to use when resolving the shared URLs.
| When Expose is running from within Docker containers, you should set this to
| `true` to fall-back to the system default DNS servers.
|
*/
'dns' => '127.0.0.1',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Auth Token | Auth Token

View File

@@ -2,7 +2,19 @@
<head> <head>
<title>Expose Dashboard :: {{ subdomains|join(", ") }}</title> <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://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tailwindcss/ui@latest/dist/tailwind-ui.min.css"> <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> <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"> <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 src="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.1.0/build/highlight.min.js" async></script>
@@ -88,21 +100,19 @@
} }
</style> </style>
</head> </head>
<body> <body class="dark:bg-gray-900 dark:text-white">
<div id="app" class=""> <div id="app" class="container px-8 mx-auto dark:bg-gray-900">
<div class="relative bg-indigo-600" style="marign-left: -1px"> <header class="text-pink-500 font-semibold pt-12 pb-6 flex items-center justify-center">
<div class="max-w-screen-xl mx-auto py-3 px-3 sm:px-6 lg:px-8"> <a href="https://beyondco.de" target="_blank" class="inline-flex items-center self-start">
<div class="pr-16 sm:text-center sm:px-16"> <img src="https://beyondco.de/apps/icons/expose.png" class="h-12">
<p class="font-medium text-white flex justify-center"> <p class="ml-4 font-headline text-lg">Expose</p>
<span class="inline-block">Waiting for requests on: </a>
{% for subdomain in subdomains %} <span class="inline-block justify-self-stretch text-center w-full">Waiting for requests on:
<a class="underline" target="_blank" href="{{ subdomain }}">{{ subdomain }}</a> {% for subdomain in subdomains %}
{% endfor %} <a class="underline" target="_blank" href="{{ subdomain }}">{{ subdomain }}</a>
</span> {% endfor %}
</p> </span>
</div> </header>
</div>
</div>
<div class="p-5 flex flex-col md:flex-row"> <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="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="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
@@ -110,44 +120,51 @@
<span class="h-8 inline-flex rounded-md shadow-sm"> <span class="h-8 inline-flex rounded-md shadow-sm">
<button @click.prevent="clearLogs" <button @click.prevent="clearLogs"
type="button" 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"> 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 Clear
</button> </button>
</span> </span>
<div class="ml-4 flex-grow relative rounded-md shadow-sm"> <div class="ml-4 flex-grow relative rounded-md shadow-sm">
<input class="h-8 form-input block w-full sm:text-sm sm:leading-5" v-model="search" placeholder="Search" /> <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> </div>
<div <div
class="align-middle inline-block min-w-full shadow overflow-hidden sm:rounded-lg border-b border-gray-200"> 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"> <table class="min-w-full">
<thead> <thead>
<tr> <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"> <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 URL
</th> </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"> <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 Response
</th> </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"> <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 Duration
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody class="bg-white"> <tbody class="bg-white dark:bg-gray-700">
<tr v-for="log in filteredLogs" <tr v-for="log in filteredLogs"
:class="{'bg-gray-100': currentLog === log}" :class="{'dark:bg-gray-800 bg-gray-100': currentLog === log}"
@click="setLog(log)"> @click="setLog(log)">
<td class="cursor-pointer px-6 py-4 whitespace-normal border-b border-gray-200 text-sm leading-5 font-medium text-gray-900"> <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> <p class="flex">
@{ log.request.method } <span class="text-xs">@{ log.request.method }</span>
@{ log.request.uri } <span
style="max-width: 150px"
:title="log.request.uri"
class="font-mono pl-1 text-xs truncate">@{ log.request.uri }</span>
</p> </p>
<span class="text-xs">@{ log.subdomain }</span> {% if subdomains|length > 1 %}
<span class="text-xs text-gray-600">@{ log.performed_at }</span> <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>
<td class="cursor-pointer px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500"> <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"> <div v-if="log.response">
<span <span
v-if="log.response.status >= 200 && log.response.status < 300" v-if="log.response.status >= 200 && log.response.status < 300"
@@ -174,7 +191,7 @@
... ...
</div> </div>
</td> </td>
<td class="cursor-pointer px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500"> <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"> <div v-if="log.response">
@{ log.duration }ms @{ log.duration }ms
</div> </div>
@@ -186,15 +203,17 @@
</div> </div>
</div> </div>
<div class="w-full md:w-2/3 mt-5 md:mt-0 md:ml-5"> <div class="w-full md:w-2/3 mt-5 md:mt-0 md:ml-5">
<div v-if="currentLog" class="bg-white shadow overflow-hidden sm:rounded-lg"> <div v-if="currentLog" class="bg-white dark:bg-gray-800 shadow overflow-hidden sm:rounded-lg dark:border-gray-700 border">
<div class="px-4 py-5 border-b border-gray-200 sm:px-6"> <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 text-gray-900 flex"> <h3 class="text-lg leading-6 font-medium dark:text-gray-50 text-gray-900 flex">
@{ currentLog.request.method } @{ currentLog.request.uri } <span>@{ currentLog.request.method } @{ currentLog.request.uri }</span>
<div class="flex-grow"></div> <div class="flex-grow"></div>
<span class="inline-flex rounded-md shadow-sm"> <span class="inline-flex rounded-md shadow-sm">
<button @click.prevent="replay(currentLog)" <button @click.prevent="replay(currentLog)"
type="button" 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"> 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 Replay
</button> </button>
</span> </span>
@@ -202,42 +221,41 @@
<button <button
:data-clipboard-text="currentLog.request.curl" :data-clipboard-text="currentLog.request.curl"
type="button" 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"> 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 Copy as curl
</button> </button>
</span> </span>
</h3> </h3>
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500"> <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 class="text-xs text-gray-600">Received at: @{ currentLog.performed_at }</span> <span
<div v-if="currentLog.response"> v-if="currentLog.response.status >= 200 && currentLog.response.status < 300"
<span class="inline-flex items-center px-3 py-0.5 rounded-full text-xs font-medium leading-5 bg-green-100 text-green-800">
v-if="currentLog.response.status >= 200 && currentLog.response.status < 300" @{ currentLog.response.status } - @{ currentLog.response.reason }
class="inline-flex items-center px-3 py-0.5 rounded-full text-xs font-medium leading-5 bg-green-100 text-green-800"> </span>
@{ currentLog.response.status } - @{ currentLog.response.reason }
</span>
<span <span
v-if="currentLog.response.status >= 400 && currentLog.response.status < 500" 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"> 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 } @{ currentLog.response.status } - @{ currentLog.response.reason }
</span> </span>
<span <span
v-if="currentLog.response.status >= 500" 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"> 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 } @{ currentLog.response.status } - @{ currentLog.response.reason }
</span> </span>
</div> </div>
</p> <span class="text-xs text-gray-600 dark:text-gray-200">Received at: @{ currentLog.performed_at }</span>
</div>
</div> </div>
<div> <div>
<div class="hidden sm:block"> <div class="hidden sm:block">
<div class="border-b border-gray-200"> <div class="border-b border-gray-200 dark:border-gray-700">
<nav class="-mb-px flex"> <nav class="-mb-px flex">
<a href="#" <a href="#"
@click.prevent="setView('request')" @click.prevent="setView('request')"
:class="{ :class="{
'border-indigo-500 text-indigo-600 text-indigo-600 focus:text-indigo-800 focus:border-indigo-700': view === 'request', '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 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:text-gray-700 focus:border-gray-300': 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 === 'response',
}" }"
class="w-1/2 py-4 px-1 text-center border-b-2 font-medium text-sm leading-5 focus:outline-none"> class="w-1/2 py-4 px-1 text-center border-b-2 font-medium text-sm leading-5 focus:outline-none">
Request Request
@@ -245,8 +263,8 @@
<a href="#" <a href="#"
@click.prevent="setView('response')" @click.prevent="setView('response')"
:class="{ :class="{
'border-indigo-500 text-indigo-600': view === 'response', '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 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:text-gray-700 focus:border-gray-300': 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 === 'request',
}" }"
class="w-1/2 py-4 px-1 text-center border-b-2 font-medium text-sm leading-5 focus:outline-none"> class="w-1/2 py-4 px-1 text-center border-b-2 font-medium text-sm leading-5 focus:outline-none">
Response Response
@@ -257,37 +275,37 @@
</div> </div>
<div v-if="view === 'request'"> <div v-if="view === 'request'">
<div class="px-4 py-5 border-b border-gray-200 sm:px-6 flex justify-between" v-if="Object.keys(currentLog.request.query).length > 0"> <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"> <h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">
Query Parameters Query Parameters
</h3> </h3>
<span class="inline-flex rounded-md shadow-sm ml-4"> <span class="inline-flex rounded-md shadow-sm ml-4">
<button <button
type="button" type="button"
class="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"> 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 Copy as PHP array
</button> </button>
</span> </span>
</div> </div>
<div v-for="(value, name) in currentLog.request.query" <div v-for="(value, name) in currentLog.request.query"
:key="'query_' + name" :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"> 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 text-gray-700"> <dt class="text-sm leading-5 font-medium dark:text-gray-200 text-gray-700">
@{ name } @{ name }
</dt> </dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2 break-all"> <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 } @{ value }
</dd> </dd>
</div> </div>
<div class="px-4 py-5 border-b border-t border-gray-200 sm:px-6 flex justify-between" v-if="Object.keys(currentLog.request.post).length > 0"> <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"> <h3 class="text-lg leading-6 font-medium text-gray-900">
Post Parameters Post Parameters
</h3> </h3>
<span class="inline-flex rounded-md shadow-sm ml-4"> <span class="inline-flex rounded-md shadow-sm ml-4">
<button <button
type="button" type="button"
class="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"> 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 Copy as PHP array
</button> </button>
</span> </span>
@@ -305,31 +323,31 @@
</dd> </dd>
</div> </div>
<div class="px-4 py-5 border-b border-t border-gray-200 sm:px-6 flex justify-between"> <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 text-gray-900"> <h3 class="text-lg leading-6 font-medium dark:text-gray-100 text-gray-900">
Headers Headers
</h3> </h3>
<span class="inline-flex rounded-md shadow-sm ml-4"> <span class="inline-flex rounded-md shadow-sm ml-4">
<button <button
type="button" type="button"
class="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"> 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 Copy as PHP array
</button> </button>
</span> </span>
</div> </div>
<div v-for="(value, header) in currentLog.request.headers" <div v-for="(value, header) in currentLog.request.headers"
:key="header" :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"> 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 text-gray-700"> <dt class="text-sm leading-5 font-medium dark:text-gray-200 text-gray-700">
@{ header } @{ header }
</dt> </dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2"> <dd class="mt-1 text-sm leading-5 dark:text-gray-200 text-gray-900 sm:mt-0 sm:col-span-2">
@{ value } @{ value }
</dd> </dd>
</div> </div>
<div class="px-4 py-5 border-b border-t border-gray-200 sm:px-6"> <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"> <h3 class="text-lg leading-6 font-medium dark:text-gray-100 text-gray-900">
Request Body Request Body
</h3> </h3>
</div> </div>
@@ -339,18 +357,18 @@
</div> </div>
<div v-if="view === 'response'"> <div v-if="view === 'response'">
<div class="px-4 py-5 border-b border-gray-200 sm:px-6"> <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"> <h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">
Headers Headers
</h3> </h3>
</div> </div>
<div v-for="(value, header) in currentLog.response.headers" <div v-for="(value, header) in currentLog.response.headers"
:key="header" :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"> 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 text-gray-700"> <dt class="text-sm leading-5 font-medium dark:text-gray-200 text-gray-700">
@{ header } @{ header }
</dt> </dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2 break-all"> <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 } @{ value }
</dd> </dd>
</div> </div>
@@ -372,34 +390,25 @@
</div> </div>
</div> </div>
<div class="px-4 py-5 border-b border-t border-gray-200 sm:px-6"> <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"> <h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100 flex justify-between">
Response <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> </h3>
</div> </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">
</dt>
<div>
<div>
<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'"> <div v-if="activeTab === 'raw'">
<pre class="p-6 text-sm whitespace-pre-wrap break-all">@{ currentLog.response.body }</pre> <pre class="p-6 text-sm whitespace-pre-wrap break-all">@{ currentLog.response.body }</pre>
</div> </div>
@@ -411,9 +420,9 @@
</div> </div>
</div> </div>
</div> </div>
<div v-else class="flex-col bg-white shadow overflow-hidden sm:rounded-lg justify-center items-center flex py-4"> <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> <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" /> <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> <a class="text-sm" href="{{ subdomains[0] }}" target="_blank">{{ subdomains[0] }}</a>
</div> </div>
</div> </div>

View File

@@ -9,7 +9,9 @@ use App\Logger\LoggedRequest;
use App\Logger\RequestLogger; use App\Logger\RequestLogger;
use Clue\React\Buzz\Browser; use Clue\React\Buzz\Browser;
use Clue\React\Buzz\Message\ResponseException; use Clue\React\Buzz\Message\ResponseException;
use GuzzleHttp\Psr7\Message;
use GuzzleHttp\Psr7\Request; use GuzzleHttp\Psr7\Request;
use Illuminate\Support\Arr;
use function GuzzleHttp\Psr7\str; use function GuzzleHttp\Psr7\str;
use Mockery as m; use Mockery as m;
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\RequestInterface;
@@ -63,7 +65,10 @@ class DashboardTest extends TestCase
$httpClient = m::mock(HttpClient::class); $httpClient = m::mock(HttpClient::class);
$httpClient->shouldReceive('performRequest') $httpClient->shouldReceive('performRequest')
->once() ->once()
->with(str($request)); ->withArgs(function ($arg) {
$sentRequest = Message::parseMessage($arg);
return Arr::get($sentRequest, 'start-line') === 'GET /example HTTP/1.1';
});
app()->instance(HttpClient::class, $httpClient); app()->instance(HttpClient::class, $httpClient);

View File

@@ -59,7 +59,7 @@ class TunnelTest extends TestCase
$this->expectException(ResponseException::class); $this->expectException(ResponseException::class);
$this->expectExceptionMessage(404); $this->expectExceptionMessage(404);
$response = $this->await($this->browser->get('http://127.0.0.1:8080/', [ $this->await($this->browser->get('http://127.0.0.1:8080/', [
'Host' => 'tunnel.localhost', 'Host' => 'tunnel.localhost',
])); ]));
} }
@@ -67,6 +67,8 @@ class TunnelTest extends TestCase
/** @test */ /** @test */
public function it_sends_incoming_requests_to_the_connected_client() public function it_sends_incoming_requests_to_the_connected_client()
{ {
$this->app['config']['expose.admin.validate_auth_tokens'] = false;
$this->createTestHttpServer(); $this->createTestHttpServer();
$this->app['config']['expose.admin.validate_auth_tokens'] = false; $this->app['config']['expose.admin.validate_auth_tokens'] = false;