2018-11-23 23:06:28 +00:00
|
|
|
<html>
|
|
|
|
|
<head>
|
2020-08-18 06:35:09 +00:00
|
|
|
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
|
|
|
|
|
|
|
|
|
<title>WebSockets Dashboard</title>
|
|
|
|
|
|
|
|
|
|
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
|
|
|
|
|
<link href="https://cdn.jsdelivr.net/npm/vue-json-editor@1.4.2/assets/jsoneditor.min.css" rel="stylesheet">
|
|
|
|
|
|
|
|
|
|
<script src="https://js.pusher.com/7.0/pusher.min.js"></script>
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js"></script>
|
|
|
|
|
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/v-jsoneditor@1.4.1/dist/v-jsoneditor.min.js"></script>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
window.baseURL = '{{ url(request()->path()) }}';
|
|
|
|
|
axios.defaults.baseURL = baseURL;
|
|
|
|
|
</script>
|
2018-11-23 23:06:28 +00:00
|
|
|
</head>
|
|
|
|
|
|
2020-08-18 06:35:09 +00:00
|
|
|
<body class="px-6">
|
|
|
|
|
<div
|
|
|
|
|
id="app"
|
|
|
|
|
class="mx-auto"
|
|
|
|
|
:class="{
|
|
|
|
|
'max-w-xl': ! connected,
|
|
|
|
|
'max-w-6xl': connected,
|
|
|
|
|
}"
|
|
|
|
|
>
|
|
|
|
|
<div class="w-full my-6 rounded-lg bg-gray-100 p-6">
|
|
|
|
|
<div class="font-semibold uppercase text-gray-700 mb-6">
|
|
|
|
|
Connect to app
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="flex flex-row justify-between">
|
|
|
|
|
<div class="relative">
|
|
|
|
|
<select
|
|
|
|
|
v-model="app"
|
|
|
|
|
class="block appearance-none w-full bg-gray-200 border border-gray-200 text-gray-700 py-2 px-6 pr-12 rounded-lg focus:outline-none focus:bg-white focus:border-gray-500"
|
|
|
|
|
id="grid-state"
|
|
|
|
|
>
|
|
|
|
|
<option
|
|
|
|
|
v-for="app in apps"
|
|
|
|
|
:value="app"
|
|
|
|
|
>
|
|
|
|
|
@{{ app.name }}
|
|
|
|
|
</option>
|
|
|
|
|
</select>
|
|
|
|
|
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
|
|
|
|
|
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/></svg>
|
|
|
|
|
</div>
|
2018-11-23 23:06:28 +00:00
|
|
|
</div>
|
2020-08-18 06:35:09 +00:00
|
|
|
<div>
|
|
|
|
|
<button
|
|
|
|
|
v-if="connected"
|
|
|
|
|
@click.prevent="disconnect"
|
|
|
|
|
class="bg-red-500 hover:bg-red-600 rounded-full px-3 py-2 text-white focus:outline-none"
|
|
|
|
|
>
|
|
|
|
|
Disconnect
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
v-else
|
|
|
|
|
@click.prevent="connect"
|
|
|
|
|
class="rounded-full px-3 py-2 text-white focus:outline-none"
|
|
|
|
|
:class="{
|
|
|
|
|
'bg-green-500 hover:bg-green-600': ! connecting,
|
|
|
|
|
'bg-gray-500 cursor-not-allowed': connecting,
|
|
|
|
|
}"
|
|
|
|
|
>
|
|
|
|
|
<template v-if="connecting">
|
|
|
|
|
Connecting...
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else>
|
|
|
|
|
Connect
|
|
|
|
|
</template>
|
|
|
|
|
</button>
|
2018-11-23 23:06:28 +00:00
|
|
|
</div>
|
2020-08-18 06:35:09 +00:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
v-if="connected && app.statisticsEnabled"
|
|
|
|
|
class="w-full my-6 px-6"
|
|
|
|
|
>
|
2020-08-20 08:27:55 +00:00
|
|
|
<div class="flex justify-between items-center">
|
|
|
|
|
<span class="font-semibold uppercase text-gray-700">
|
|
|
|
|
Live statistics
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
<div class="space-x-3 flex items-center">
|
|
|
|
|
<div>
|
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
|
|
|
|
v-model="autoRefresh"
|
|
|
|
|
class="mr-2"
|
|
|
|
|
/>
|
|
|
|
|
Refresh automatically
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
@click="loadChart"
|
|
|
|
|
class="rounded-full bg-blue-500 hover:bg-blue-600 focus:outline-none text-white px-3 py-1"
|
|
|
|
|
>
|
|
|
|
|
Refresh
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2020-08-18 06:35:09 +00:00
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
id="statisticsChart"
|
|
|
|
|
style="width: 100%; height: 250px;"
|
|
|
|
|
></div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
v-if="connected"
|
|
|
|
|
class="flex flex-col rounded-lg bg-gray-100 p-6 my-6 space-y-6"
|
|
|
|
|
>
|
|
|
|
|
<div class="font-semibold uppercase text-gray-700">
|
|
|
|
|
Send payload event to channel
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="flex flex-col space-y-6 md:flex-row md:space-x-6 md:space-y-0">
|
|
|
|
|
<div class="w-full md:w-1/2">
|
|
|
|
|
<label
|
|
|
|
|
class="block text-gray-700 text-sm font-bold mb-2"
|
|
|
|
|
for="channel"
|
|
|
|
|
>
|
|
|
|
|
Channel name
|
|
|
|
|
</label>
|
|
|
|
|
<input
|
|
|
|
|
v-model="form.channel"
|
|
|
|
|
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
|
|
|
|
id="channel"
|
|
|
|
|
type="text"
|
|
|
|
|
placeholder="ex: orders"
|
|
|
|
|
>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="w-full md:w-1/2">
|
|
|
|
|
<label
|
|
|
|
|
class="block text-gray-700 text-sm font-bold mb-2"
|
|
|
|
|
for="event"
|
|
|
|
|
>
|
|
|
|
|
Event name
|
|
|
|
|
</label>
|
|
|
|
|
<input
|
|
|
|
|
v-model="form.event"
|
|
|
|
|
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
|
|
|
|
id="event"
|
|
|
|
|
type="text"
|
|
|
|
|
placeholder="ex: OrderShipped"
|
|
|
|
|
>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<label class="block text-gray-700 text-sm font-bold mb-2">
|
|
|
|
|
Payload
|
|
|
|
|
</label>
|
|
|
|
|
|
|
|
|
|
<v-jsoneditor
|
|
|
|
|
v-model="form.data"
|
|
|
|
|
:options="{ mode: 'code' }"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<button
|
|
|
|
|
@click.prevent="sendEvent"
|
|
|
|
|
class="rounded-full px-3 py-2 text-white focus:outline-none"
|
|
|
|
|
:class="{
|
|
|
|
|
'bg-blue-500 hover:bg-blue-600': ! sendingEvent,
|
|
|
|
|
'bg-gray-500 cursor-not-allowed': sendingEvent,
|
|
|
|
|
}"
|
|
|
|
|
>
|
|
|
|
|
<template v-if="sendingEvent">
|
|
|
|
|
Sending...
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else>
|
|
|
|
|
Send event
|
|
|
|
|
</template>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
v-if="connected"
|
|
|
|
|
class="flex flex-col my-6"
|
|
|
|
|
>
|
|
|
|
|
<div class="font-semibold uppercase text-gray-700 mb-6">
|
|
|
|
|
Server activity
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="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 divide-y divide-gray-200">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-100 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
|
|
|
|
Type
|
|
|
|
|
</th>
|
|
|
|
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-100 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
|
|
|
|
Details
|
|
|
|
|
</th>
|
|
|
|
|
<th class="px-6 py-3 border-b border-gray-200 bg-gray-100 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
|
|
|
|
Time
|
|
|
|
|
</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody class="bg-white divide-y divide-gray-200">
|
|
|
|
|
<tr
|
|
|
|
|
v-for="(log, index) in logs.slice().reverse()"
|
|
|
|
|
:key="index"
|
|
|
|
|
>
|
|
|
|
|
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200">
|
|
|
|
|
<div
|
|
|
|
|
:class="[getBadgeClass(log)]"
|
|
|
|
|
class="rounded-full px-3 py-1 inline-block text-sm"
|
|
|
|
|
>
|
|
|
|
|
@{{ log.type }}
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200">
|
|
|
|
|
<pre class="text-xs">@{{ log.details }}</pre>
|
|
|
|
|
</td>
|
|
|
|
|
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200">
|
|
|
|
|
@{{ log.time }}
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2018-11-23 23:06:28 +00:00
|
|
|
<script>
|
2020-08-18 06:35:09 +00:00
|
|
|
new Vue({
|
|
|
|
|
el: '#app',
|
|
|
|
|
data: {
|
|
|
|
|
connected: false,
|
|
|
|
|
connecting: false,
|
|
|
|
|
sendingEvent: false,
|
2020-08-20 08:27:55 +00:00
|
|
|
autoRefresh: true,
|
|
|
|
|
refreshInterval: {{ $refreshInterval }},
|
|
|
|
|
refreshTicker: null,
|
2020-08-18 06:35:09 +00:00
|
|
|
chart: null,
|
|
|
|
|
pusher: null,
|
|
|
|
|
app: null,
|
|
|
|
|
apps: @json($apps),
|
|
|
|
|
form: {
|
|
|
|
|
channel: null,
|
|
|
|
|
event: null,
|
2020-09-15 20:46:37 +00:00
|
|
|
data: {},
|
2020-08-18 06:35:09 +00:00
|
|
|
},
|
|
|
|
|
logs: [],
|
|
|
|
|
},
|
|
|
|
|
mounted () {
|
|
|
|
|
this.app = this.apps[0] || null;
|
|
|
|
|
},
|
2020-08-20 08:27:55 +00:00
|
|
|
destroyed () {
|
|
|
|
|
if (this.refreshTicker) {
|
2020-09-16 04:15:12 +00:00
|
|
|
this.stopRefreshInterval();
|
2020-08-20 08:27:55 +00:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
watch: {
|
|
|
|
|
connected (newVal) {
|
2020-09-16 04:15:12 +00:00
|
|
|
newVal ? this.startRefreshInterval() : this.stopRefreshInterval();
|
2020-08-20 08:27:55 +00:00
|
|
|
},
|
|
|
|
|
autoRefresh (newVal) {
|
2020-09-16 04:15:12 +00:00
|
|
|
newVal ? this.startRefreshInterval() : this.stopRefreshInterval();
|
2020-08-20 08:27:55 +00:00
|
|
|
},
|
|
|
|
|
},
|
2020-08-18 06:35:09 +00:00
|
|
|
methods: {
|
|
|
|
|
connect () {
|
|
|
|
|
this.connecting = true;
|
2018-11-25 21:24:31 +00:00
|
|
|
|
2020-08-18 06:35:09 +00:00
|
|
|
this.pusher = new Pusher(this.app.key, {
|
|
|
|
|
wsHost: this.app.host === null ? window.location.hostname : this.app.host,
|
2020-08-18 11:22:09 +00:00
|
|
|
wsPort: {{ $port }},
|
|
|
|
|
wssPort: {{ $port }},
|
2020-08-18 06:35:09 +00:00
|
|
|
wsPath: this.app.path === null ? '' : this.app.path,
|
|
|
|
|
disableStats: true,
|
|
|
|
|
authEndpoint: `${window.baseURL}/auth`,
|
|
|
|
|
auth: {
|
|
|
|
|
headers: {
|
|
|
|
|
'X-CSRF-Token': "{{ csrf_token() }}",
|
|
|
|
|
'X-App-ID': this.app.id,
|
2018-11-25 21:24:31 +00:00
|
|
|
},
|
2020-08-18 06:35:09 +00:00
|
|
|
},
|
|
|
|
|
enabledTransports: ['ws', 'wss'],
|
|
|
|
|
forceTLS: false,
|
|
|
|
|
});
|
2018-11-25 21:24:31 +00:00
|
|
|
|
2020-08-18 06:35:09 +00:00
|
|
|
this.pusher.connection.bind('state_change', states => {
|
|
|
|
|
this.connecting = false;
|
|
|
|
|
});
|
2018-12-03 21:59:50 +00:00
|
|
|
|
2020-08-18 06:35:09 +00:00
|
|
|
this.pusher.connection.bind('connected', () => {
|
|
|
|
|
this.connected = true;
|
|
|
|
|
this.connecting = false;
|
2018-11-26 07:51:59 +00:00
|
|
|
|
2020-08-18 06:35:09 +00:00
|
|
|
if (this.app.statisticsEnabled) {
|
|
|
|
|
this.loadChart();
|
|
|
|
|
}
|
|
|
|
|
});
|
2018-11-25 21:24:31 +00:00
|
|
|
|
2020-08-18 06:35:09 +00:00
|
|
|
this.pusher.connection.bind('disconnected', () => {
|
|
|
|
|
this.connected = false;
|
|
|
|
|
this.connecting = false;
|
|
|
|
|
this.logs = [];
|
2020-08-20 08:27:55 +00:00
|
|
|
this.chart = null;
|
2020-08-18 06:35:09 +00:00
|
|
|
});
|
2018-12-04 00:03:47 +00:00
|
|
|
|
2020-08-18 06:35:09 +00:00
|
|
|
this.pusher.connection.bind('error', event => {
|
2020-09-16 04:03:37 +00:00
|
|
|
if (event.data.code === 4100) {
|
2020-08-18 06:35:09 +00:00
|
|
|
this.connected = false;
|
|
|
|
|
this.logs = [];
|
2020-08-20 08:27:55 +00:00
|
|
|
this.chart = null;
|
2018-12-03 22:19:46 +00:00
|
|
|
|
2020-08-18 06:35:09 +00:00
|
|
|
throw new Error("Over capacity");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.connecting = false;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.subscribeToAllChannels();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
disconnect () {
|
|
|
|
|
this.pusher.disconnect();
|
|
|
|
|
this.connecting = false;
|
2020-08-20 08:27:55 +00:00
|
|
|
this.chart = null;
|
2020-08-18 06:35:09 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
loadChart () {
|
|
|
|
|
axios.get(`/api/${this.app.id}/statistics`)
|
|
|
|
|
.then(res => {
|
|
|
|
|
let data = res.data;
|
|
|
|
|
|
|
|
|
|
let chartData = [
|
|
|
|
|
{
|
|
|
|
|
x: data.peak_connections.x,
|
|
|
|
|
y: data.peak_connections.y,
|
|
|
|
|
type: 'lines',
|
|
|
|
|
name: '# Peak Connections'
|
|
|
|
|
},
|
|
|
|
|
{
|
2020-09-11 06:07:57 +00:00
|
|
|
x: data.websocket_messages_count.x,
|
|
|
|
|
y: data.websocket_messages_count.y,
|
2020-08-18 06:35:09 +00:00
|
|
|
type: 'bar',
|
|
|
|
|
name: '# Websocket Messages'
|
|
|
|
|
},
|
|
|
|
|
{
|
2020-09-11 06:07:57 +00:00
|
|
|
x: data.api_messages_count.x,
|
|
|
|
|
y: data.api_messages_count.y,
|
2020-08-18 06:35:09 +00:00
|
|
|
type: 'bar',
|
|
|
|
|
name: '# API Messages'
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
let layout = {
|
|
|
|
|
margin: {
|
|
|
|
|
l: 50,
|
|
|
|
|
r: 0,
|
|
|
|
|
b: 50,
|
|
|
|
|
t: 50,
|
|
|
|
|
pad: 4,
|
|
|
|
|
},
|
|
|
|
|
autosize: true,
|
|
|
|
|
};
|
|
|
|
|
|
2020-08-20 08:27:55 +00:00
|
|
|
this.chart = this.chart
|
|
|
|
|
? Plotly.react('statisticsChart', chartData, layout)
|
|
|
|
|
: Plotly.newPlot('statisticsChart', chartData, layout);
|
|
|
|
|
|
2020-08-18 06:35:09 +00:00
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
subscribeToAllChannels () {
|
|
|
|
|
@json($channels).forEach(channelName => this.subscribeToChannel(channelName))
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
subscribeToChannel (channel) {
|
|
|
|
|
this.pusher.subscribe(`{{ $logPrefix }}${channel}`)
|
|
|
|
|
.bind('log-message', (data) => {
|
|
|
|
|
this.logs.push(data);
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
sendEvent () {
|
|
|
|
|
if (! this.sendingEvent) {
|
|
|
|
|
this.sendingEvent = true;
|
2018-11-25 21:24:31 +00:00
|
|
|
|
2020-08-18 06:35:09 +00:00
|
|
|
let payload = {
|
|
|
|
|
_token: '{{ csrf_token() }}',
|
|
|
|
|
appId: this.app.id,
|
2020-09-15 20:46:37 +00:00
|
|
|
key: this.app.key,
|
|
|
|
|
secret: this.app.secret,
|
2020-08-18 06:35:09 +00:00
|
|
|
channel: this.form.channel,
|
|
|
|
|
event: this.form.event,
|
|
|
|
|
data: JSON.stringify(this.form.data),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
axios
|
|
|
|
|
.post('/event', payload)
|
2020-08-27 10:39:52 +00:00
|
|
|
.then(() => {})
|
2020-08-18 06:35:09 +00:00
|
|
|
.catch(err => {
|
|
|
|
|
alert('Error sending event.');
|
|
|
|
|
})
|
|
|
|
|
.then(() => {
|
|
|
|
|
this.sendingEvent = false;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getBadgeClass (log) {
|
|
|
|
|
if (['connection', 'subscribed'].includes(log.type)) {
|
|
|
|
|
return 'bg-green-500 text-white';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (['replicator-subscribed', 'replicator-joined'].includes(log.type)) {
|
|
|
|
|
return 'bg-green-700 text-white';
|
2018-11-25 21:24:31 +00:00
|
|
|
}
|
2020-08-18 06:35:09 +00:00
|
|
|
|
|
|
|
|
if (['disconnection', 'occupied', 'replicator-unsubscribed', 'replicator-left'].includes(log.type)) {
|
|
|
|
|
return 'bg-red-700 text-white';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (['api_message', 'replicator-message-published', 'replicator-message-received'].includes(log.type)) {
|
|
|
|
|
return 'bg-black text-white';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 'bg-gray-700 text-white';
|
|
|
|
|
},
|
2020-08-20 08:27:55 +00:00
|
|
|
|
|
|
|
|
startRefreshInterval () {
|
|
|
|
|
this.refreshTicker = setInterval(function () {
|
|
|
|
|
this.loadChart();
|
|
|
|
|
}.bind(this), this.refreshInterval * 1000);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
stopRefreshInterval () {
|
|
|
|
|
clearInterval(this.refreshTicker);
|
|
|
|
|
this.refreshTicker = null;
|
|
|
|
|
},
|
2020-08-18 06:35:09 +00:00
|
|
|
},
|
|
|
|
|
});
|
2018-11-23 23:06:28 +00:00
|
|
|
</script>
|
|
|
|
|
</body>
|
2018-12-05 13:22:16 +00:00
|
|
|
</html>
|