268 lines
10 KiB
PHP
268 lines
10 KiB
PHP
<html>
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
|
<title>WebSockets Dashboard</title>
|
|
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet"
|
|
integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
|
|
<script
|
|
src="https://code.jquery.com/jquery-3.3.1.min.js"
|
|
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
|
|
crossorigin="anonymous"></script>
|
|
<script src="https://js.pusher.com/4.3/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>
|
|
</head>
|
|
|
|
<body>
|
|
<div class="container" id="app">
|
|
<div class="card col-xs-12 mt-4">
|
|
<div class="card-header">
|
|
<form id="connect" class="form-inline" role="form">
|
|
<label class="my-1 mr-2" for="app">App:</label>
|
|
<select class="form-control form-control-sm mr-2" name="app" id="app" v-model="app">
|
|
<option v-for="app in apps" :value="app">@{{ app.name }}</option>
|
|
</select>
|
|
<label class="my-1 mr-2" for="app">Port:</label>
|
|
<input class="form-control form-control-sm mr-2" v-model="port" placeholder="Port">
|
|
<button v-if="! connected" type="submit" @click.prevent="connect" class="mr-2 btn btn-sm btn-primary">
|
|
Connect
|
|
</button>
|
|
<button v-if="connected" type="submit" @click.prevent="disconnect" class="btn btn-sm btn-danger">
|
|
Disconnect
|
|
</button>
|
|
</form>
|
|
<div id="status"></div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div v-if="connected && app.statisticsEnabled">
|
|
<h4>Realtime Statistics</h4>
|
|
<div id="statisticsChart" style="width: 100%; height: 250px;"></div>
|
|
</div>
|
|
<div v-if="connected">
|
|
<h4>Event Creator</h4>
|
|
<form>
|
|
<div class="row">
|
|
<div class="col">
|
|
<input type="text" class="form-control" v-model="form.channel" placeholder="Channel">
|
|
</div>
|
|
<div class="col">
|
|
<input type="text" class="form-control" v-model="form.event" placeholder="Event">
|
|
</div>
|
|
</div>
|
|
<div class="row mt-3">
|
|
<div class="col">
|
|
<div class="form-group">
|
|
<textarea placeholder="Data" v-model="form.data" class="form-control" id="data"
|
|
rows="3"></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row text-right">
|
|
<div class="col">
|
|
<button type="submit" @click.prevent="sendEvent" class="btn btn-sm btn-primary">Send event
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<h4>Events</h4>
|
|
<table id="events" class="table table-striped table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>Type</th>
|
|
<th>Socket</th>
|
|
<th>Details</th>
|
|
<th>Time</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="log in logs.slice().reverse()">
|
|
<td><span class="badge" :class="getBadgeClass(log)">@{{ log.type }}</span></td>
|
|
<td>@{{ log.socketId }}</td>
|
|
<td>@{{ log.details }}</td>
|
|
<td>@{{ log.time }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
new Vue({
|
|
el: '#app',
|
|
|
|
data: {
|
|
connected: false,
|
|
chart: null,
|
|
pusher: null,
|
|
app: null,
|
|
port: {{ $port }},
|
|
apps: {!! json_encode($apps) !!},
|
|
form: {
|
|
channel: null,
|
|
event: null,
|
|
data: null
|
|
},
|
|
logs: [],
|
|
},
|
|
|
|
mounted() {
|
|
this.app = this.apps[0] || null;
|
|
},
|
|
|
|
methods: {
|
|
connect() {
|
|
this.pusher = new Pusher(this.app.key, {
|
|
wsHost: this.app.host === null ? window.location.hostname : this.app.host,
|
|
wsPort: this.port === null ? 6001 : this.port,
|
|
wssPort: this.port === null ? 6001 : this.port,
|
|
wsPath: this.app.path === null ? '' : this.app.path,
|
|
disableStats: true,
|
|
authEndpoint: '{{ url(request()->path().'/auth') }}',
|
|
auth: {
|
|
headers: {
|
|
'X-CSRF-Token': "{{ csrf_token() }}",
|
|
'X-App-ID': this.app.id
|
|
}
|
|
},
|
|
enabledTransports: ['ws', 'flash']
|
|
});
|
|
|
|
this.pusher.connection.bind('state_change', states => {
|
|
$('div#status').text("Channels current state is " + states.current);
|
|
});
|
|
|
|
this.pusher.connection.bind('connected', () => {
|
|
this.connected = true;
|
|
|
|
this.loadChart();
|
|
});
|
|
|
|
this.pusher.connection.bind('disconnected', () => {
|
|
this.connected = false;
|
|
this.logs = [];
|
|
});
|
|
|
|
this.pusher.connection.bind('error', event => {
|
|
if (event.error.data.code === 4100) {
|
|
$('div#status').text("Maximum connection limit exceeded!");
|
|
this.connected = false;
|
|
this.logs = [];
|
|
throw new Error("Over capacity");
|
|
}
|
|
});
|
|
|
|
this.subscribeToAllChannels();
|
|
|
|
this.subscribeToStatistics();
|
|
},
|
|
|
|
disconnect() {
|
|
this.pusher.disconnect();
|
|
},
|
|
|
|
loadChart() {
|
|
$.getJSON('{{ url(request()->path().'/api') }}/' + this.app.id + '/statistics', (data) => {
|
|
|
|
let chartData = [
|
|
{
|
|
x: data.peak_connections.x,
|
|
y: data.peak_connections.y,
|
|
type: 'lines',
|
|
name: '# Peak Connections'
|
|
},
|
|
{
|
|
x: data.websocket_message_count.x,
|
|
y: data.websocket_message_count.y,
|
|
type: 'bar',
|
|
name: '# Websocket Messages'
|
|
},
|
|
{
|
|
x: data.api_message_count.x,
|
|
y: data.api_message_count.y,
|
|
type: 'bar',
|
|
name: '# API Messages'
|
|
}
|
|
];
|
|
let layout = {
|
|
margin: {
|
|
l: 50,
|
|
r: 0,
|
|
b: 50,
|
|
t: 50,
|
|
pad: 4
|
|
}
|
|
};
|
|
|
|
this.chart = Plotly.newPlot('statisticsChart', chartData, layout);
|
|
});
|
|
},
|
|
|
|
subscribeToAllChannels() {
|
|
[
|
|
'disconnection',
|
|
'connection',
|
|
'vacated',
|
|
'occupied',
|
|
'subscribed',
|
|
'client-message',
|
|
'api-message',
|
|
'replicator-subscribed',
|
|
'replicator-unsubscribed',
|
|
].forEach(channelName => this.subscribeToChannel(channelName))
|
|
},
|
|
|
|
subscribeToChannel(channel) {
|
|
this.pusher.subscribe('{{ \BeyondCode\LaravelWebSockets\Dashboard\DashboardLogger::LOG_CHANNEL_PREFIX }}' + channel)
|
|
.bind('log-message', (data) => {
|
|
this.logs.push(data);
|
|
});
|
|
},
|
|
|
|
subscribeToStatistics() {
|
|
this.pusher.subscribe('{{ \BeyondCode\LaravelWebSockets\Dashboard\DashboardLogger::LOG_CHANNEL_PREFIX }}statistics')
|
|
.bind('statistics-updated', (data) => {
|
|
var update = {
|
|
x: [[data.time], [data.time], [data.time]],
|
|
y: [[data.peak_connection_count], [data.websocket_message_count], [data.api_message_count]]
|
|
};
|
|
|
|
Plotly.extendTraces('statisticsChart', update, [0, 1, 2]);
|
|
});
|
|
},
|
|
|
|
getBadgeClass(log) {
|
|
if (log.type === 'occupied' || log.type === 'connection') {
|
|
return 'badge-primary';
|
|
}
|
|
if (log.type === 'vacated') {
|
|
return 'badge-warning';
|
|
}
|
|
if (log.type === 'disconnection') {
|
|
return 'badge-error';
|
|
}
|
|
if (log.type === 'api_message') {
|
|
return 'badge-info';
|
|
}
|
|
return 'badge-secondary';
|
|
},
|
|
|
|
sendEvent() {
|
|
$.post('{{ url(request()->path().'/event') }}', {
|
|
_token: '{{ csrf_token() }}',
|
|
key: this.app.key,
|
|
secret: this.app.secret,
|
|
appId: this.app.id,
|
|
channel: this.form.channel,
|
|
event: this.form.event,
|
|
data: this.form.data,
|
|
}).fail(() => {
|
|
alert('Error sending event.');
|
|
});
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|