1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-28 15:27:43 +00:00
* Added: Socket debugging information, including if the client is connected, latency, ping, etc.
* Changed: Make the text for profile rules more clear about what happens if you have multiple rules for a profile.
This commit is contained in:
SirStendec 2020-02-10 19:43:35 -05:00
parent e0d2eb8d81
commit aedfcecc14
6 changed files with 404 additions and 2 deletions

View file

@ -1,7 +1,7 @@
{ {
"name": "frankerfacez", "name": "frankerfacez",
"author": "Dan Salvato LLC", "author": "Dan Salvato LLC",
"version": "4.18.5", "version": "4.18.6",
"description": "FrankerFaceZ is a Twitch enhancement suite.", "description": "FrankerFaceZ is a Twitch enhancement suite.",
"license": "Apache-2.0", "license": "Apache-2.0",
"scripts": { "scripts": {

View file

@ -122,7 +122,7 @@
</header> </header>
<section class="tw-pd-b-1"> <section class="tw-pd-b-1">
{{ t('setting.data_management.profiles.edit.rules.description', {{ t('setting.data_management.profiles.edit.rules.description',
'Rules allows you to define a series of conditions under which this profile will be active.') 'Rules allows you to define a series of conditions under which this profile will be active. When there are multiple rules, they must all match for the profile to activate. Please use an `Or` rule to create a profile that activates by matching one of several rules.')
}} }}
</section> </section>

View file

@ -0,0 +1,339 @@
<template lang="html">
<div>
<div v-if="! page" class="tw-flex tw-flex-wrap">
<div
v-for="info in info_blocks"
:key="info.key"
class="tw-flex tw-flex-column tw-justify-content-center tw-pd-x-1 tw-pd-y-05 tw-c-background-base tw-border-radius-large tw-mg-r-1 tw-mg-b-1"
>
<p class="tw-c-text-base tw-font-size-4">
{{ get(info.key, info.format) }}
</p>
<p class="tw-c-text-alt-2 tw-font-size-6">
{{ info.i18n_key ? t(info.i18n_key, info.title) : info.title }}
</p>
</div>
</div>
<div v-if="! page" class="tw-border-t tw-pd-t-1 tw-flex tw-flex-wrap">
<div
v-for="info in stat_blocks"
:key="info.key"
:class="info.click ? 'ffz--cursor' : ''"
class="tw-flex tw-flex-column tw-justify-content-center tw-pd-x-1 tw-pd-y-05 tw-c-background-base tw-border-radius-large tw-mg-r-1 tw-mg-b-1"
@click="onClick(info, $event)"
>
<p class="tw-c-text-base tw-font-size-4">
{{ get(info.key, info.format) }}
</p>
<p class="tw-c-text-alt-2 tw-font-size-6">
{{ info.i18n_key ? t(info.i18n_key, info.title) : info.title }}
</p>
</div>
</div>
<button
v-if="page"
class="tw-mg-b-1 tw-button tw-button--text"
@click="page = null"
>
<span class="tw-button__text">
{{ t('settings.back', 'Back') }}
</span>
</button>
<div v-if="page == 'versions'">
<table>
<thead class="tw-border-b tw-pd-b-05 tw-mg-b-05 tw-strong">
<th class="tw-pd-r-1">{{ t('socket.info.version', 'Version') }}</th>
<th>{{ t('socket.info.count', 'Count') }}</th>
</thead>
<tbody>
<tr v-for="entry in version_list" :key="entry[0]">
<td class="tw-pd-r-1">{{ entry[0] }}</td>
<td>{{ tNumber(entry[1]) }}</td>
</tr>
</tbody>
</table>
</div>
<div v-if="page == 'commands'">
<table>
<thead class="tw-border-b tw-pd-b-05 tw-mg-b-05 tw-strong">
<th class="tw-pd-r-1">{{ t('socket.info.command', 'Command') }}</th>
<th>{{ t('socket.info.count', 'Count') }}</th>
</thead>
<tbody>
<tr v-for="entry in command_list" :key="entry[0]">
<td class="tw-pd-r-1">{{ entry[0] }}</td>
<td>{{ tNumber(entry[1]) }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
import {get} from 'utilities/object';
const INFO_BLOCKS = [
{
key: 'state',
title: 'State'
},
{
key: 'server',
title: 'Server'
},
{
key: 'stats.authenticated',
title: 'Authenticated'
},
{
key: 'topics',
title: 'Topics'
},
{
key: 'ping',
title: 'Ping',
format(val) {
if ( val == null )
return null;
return `${Math.round(val)} ms`;
}
},
{
key: 'time_offset',
title: 'Time Offset',
format(val) {
if ( val == null )
return null;
return `${Math.round(val)} ms`;
}
},
{
key: 'local_time',
title: 'Local Time',
format(val) {
if ( val == null )
return null;
return val.toISOString();
}
},
{
key: 'server_time',
title: 'Server Time',
format(val) {
if ( val == null )
return null;
return val.toISOString();
}
}
];
function formatNumber(val) {
return val == null ? null : this.tNumber(val); // eslint-disable-line no-invalid-this
}
const STAT_BLOCKS = [
{
key: 'stats.connected_clients',
title: 'Connected Clients',
format: formatNumber
},
{
key: 'stats.live_clients',
title: 'Live Clients',
format: formatNumber
},
{
key: 'stats.total_commands',
title: 'Commands',
click() {
this.page = 'commands';
},
format: formatNumber
},
{
key: 'stats.total_messages',
title: 'Messages',
format: formatNumber
},
{
key: 'stats.versions',
title: 'Client Versions',
click() {
this.page = 'versions';
},
format(val) {
if ( val == null )
return null;
return Object.keys(val).length;
}
}
];
export default {
props: ['item', 'context'],
data() {
return {
connected: false,
connecting: false,
server: null,
ping: null,
local_time: new Date(),
time_offset: null,
stats: null,
topics: null,
page: null
}
},
computed: {
info_blocks() {
const out = [];
for(const info of INFO_BLOCKS) {
const copy = Object.assign({}, info);
if ( ! copy.i18n_key )
copy.i18n_key = `socket.info.${copy.key}`;
out.push(copy);
}
return out;
},
stat_blocks() {
const out = [];
for(const info of STAT_BLOCKS) {
const copy = Object.assign({}, info);
if ( ! copy.i18n_key )
copy.i18n_key = `socket.info.${copy.key}`;
out.push(copy);
}
return out;
},
version_list() {
if ( ! this.stats?.versions )
return [];
const out = Object.entries(this.stats.versions);
out.sort((a,b) => b[1] - a[1]);
return out;
},
command_list() {
if ( ! this.stats?.commands )
return [];
const out = Object.entries(this.stats.commands);
out.sort((a,b) => b[1] - a[1]);
return out;
},
state() {
if ( this.connected )
return this.t('socket.info.connected', 'connected');
else if ( this.connecting )
return this.t('socket.info.connecting', 'connecting');
return this.t('socket.info.disconnected', 'disconnected');
},
server_time() {
if ( this.time_offset == null || this.local_time == null )
return null;
return new Date(this.local_time.getTime() - this.time_offset);
}
},
created() {
this.time_interval = setInterval(this.updateTime.bind(this), 1000);
this.info_interval = setInterval(this.updateInfo.bind(this), 5000);
this.updateInfo();
this.update();
const socket = this.item.getSocket();
socket.on(':pong', this.update, this);
socket.on(':sub-change', this.update, this);
socket.on(':connected', this.update, this);
socket.on(':closed', this.update, this);
socket.on(':disconnected', this.update, this);
},
destroyed() {
clearInterval(this.time_interval);
clearInterval(this.info_interval);
this.time_interval = null;
this.info_interval = null;
const socket = this.item.getSocket();
socket.off(':pong', this.update, this);
socket.off(':sub-change', this.update, this);
socket.off(':connected', this.update, this);
socket.off(':closed', this.update, this);
socket.off(':disconnected', this.update, this);
},
methods: {
onClick(info, e) {
if ( info.click )
info.click.call(this, e);
},
get(key, fmt) {
let val = get(key, this);
if ( fmt )
val = fmt.call(this, val);
if ( val == null )
return '---';
return val;
},
updateTime() {
const socket = this.item.getSocket();
socket.ping(true);
this.local_time = new Date();
},
async updateInfo() {
if ( this.updating_info )
return;
this.updating_info = true;
const socket = this.item.getSocket();
this.stats = socket.connected ? (await socket.call('get_server_status')) : null;
this.updating_info = false;
},
update() {
const socket = this.item.getSocket();
this.connected = socket.connected;
this.connecting = socket.connecting;
this.server = socket._host;
this.ping = this.connected ? socket._last_ping : null;
this.time_offset = this.connected ? socket._time_drift : null;
this.topics = socket.topics?.length ?? 0;
}
}
}
</script>

View file

@ -25,6 +25,15 @@ export default class SocketClient extends Module {
this.inject('settings'); this.inject('settings');
this.settings.addUI('socket.info', {
path: 'Debugging > Socket >> Info @{"sort": -1000}',
force_seen: true,
no_filter: true,
component: 'socket-info',
getSocket: () => this,
});
this.settings.add('socket.use-cluster', { this.settings.add('socket.use-cluster', {
default: 'Production', default: 'Production',
@ -412,6 +421,7 @@ export default class SocketClient extends Module {
this._socket = null; this._socket = null;
this._state = State.DISCONNECTED; this._state = State.DISCONNECTED;
this.emit(':disconnected');
} }
@ -445,6 +455,8 @@ export default class SocketClient extends Module {
this.log.warn('Local time differs from server time by more than 5 minutes.'); this.log.warn('Local time differs from server time by more than 5 minutes.');
} }
} }
this.emit(':pong');
} }
@ -476,6 +488,8 @@ export default class SocketClient extends Module {
send(command, ...args) { send(command, ...args) {
if ( args.length === 1 ) if ( args.length === 1 )
args = args[0]; args = args[0];
else if ( ! args.length )
args = undefined;
if ( ! this.connected ) if ( ! this.connected )
this._pending.push([command, args]); this._pending.push([command, args]);
@ -487,6 +501,8 @@ export default class SocketClient extends Module {
call(command, ...args) { call(command, ...args) {
if ( args.length === 1 ) if ( args.length === 1 )
args = args[0]; args = args[0];
else if ( ! args.length )
args = undefined;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if ( ! this.connected ) if ( ! this.connected )
@ -503,22 +519,28 @@ export default class SocketClient extends Module {
subscribe(referrer, ...topics) { subscribe(referrer, ...topics) {
const t = this._topics; const t = this._topics;
let changed = false;
for(const topic of topics) { for(const topic of topics) {
if ( ! t.has(topic) ) { if ( ! t.has(topic) ) {
if ( this.connected ) if ( this.connected )
this._send('sub', topic); this._send('sub', topic);
t.set(topic, new Set); t.set(topic, new Set);
changed = true;
} }
const tp = t.get(topic); const tp = t.get(topic);
tp.add(referrer); tp.add(referrer);
} }
if ( changed )
this.emit(':sub-change');
} }
unsubscribe(referrer, ...topics) { unsubscribe(referrer, ...topics) {
const t = this._topics; const t = this._topics;
let changed = false;
for(const topic of topics) { for(const topic of topics) {
if ( ! t.has(topic) ) if ( ! t.has(topic) )
continue; continue;
@ -527,11 +549,15 @@ export default class SocketClient extends Module {
tp.delete(referrer); tp.delete(referrer);
if ( ! tp.size ) { if ( ! tp.size ) {
changed = true;
t.delete(topic); t.delete(topic);
if ( this.connected ) if ( this.connected )
this._send('unsub', topic); this._send('unsub', topic);
} }
} }
if ( changed )
this.emit(':sub-change');
} }

View file

@ -82,6 +82,26 @@ export class Vue extends Module {
}, },
methods: { methods: {
tNumber_(val, format) {
this.locale;
return t.i18n.formatNumber(val, format);
},
tDate_(val, format) {
this.locale;
return t.i18n.formatDate(val, format);
},
tTime_(val, format) {
this.locale;
return t.i18n.formatTime(val, format);
},
tDateTime_(val, format) {
this.locale;
return t.i18n.formatDateTime(val, format);
},
t_(key, phrase, options) { t_(key, phrase, options) {
this.locale && this.phrases[key]; this.locale && this.phrases[key];
return t.i18n.t(key, phrase, options); return t.i18n.t(key, phrase, options);
@ -182,6 +202,18 @@ export class Vue extends Module {
}, },
tNode(node, data) { tNode(node, data) {
return this.$i18n.tNode_(node, data); return this.$i18n.tNode_(node, data);
},
tNumber(val, format) {
return this.$i18n.tNumber_(val, format);
},
tDate(val, format) {
return this.$i18n.tDate_(val, format);
},
tTime(val, format) {
return this.$i18n.tTime_(val, format);
},
tDateTime(val, format) {
return this.$i18n.tDateTime_(val, format);
} }
} }
}); });

View file

@ -206,6 +206,11 @@ textarea.tw-input {
} }
.ffz--cursor {
cursor: pointer;
}
.ffz--home { .ffz--home {
h2, p { h2, p {
margin-bottom: 1rem; margin-bottom: 1rem;