mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-28 05:15:54 +00:00
4.18.6
* 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:
parent
e0d2eb8d81
commit
aedfcecc14
6 changed files with 404 additions and 2 deletions
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "frankerfacez",
|
||||
"author": "Dan Salvato LLC",
|
||||
"version": "4.18.5",
|
||||
"version": "4.18.6",
|
||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
|
|
|
@ -122,7 +122,7 @@
|
|||
</header>
|
||||
<section class="tw-pd-b-1">
|
||||
{{ 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>
|
||||
|
||||
|
|
339
src/modules/main_menu/components/socket-info.vue
Normal file
339
src/modules/main_menu/components/socket-info.vue
Normal 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>
|
|
@ -25,6 +25,15 @@ export default class SocketClient extends Module {
|
|||
|
||||
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', {
|
||||
default: 'Production',
|
||||
|
||||
|
@ -412,6 +421,7 @@ export default class SocketClient extends Module {
|
|||
|
||||
this._socket = null;
|
||||
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.emit(':pong');
|
||||
}
|
||||
|
||||
|
||||
|
@ -476,6 +488,8 @@ export default class SocketClient extends Module {
|
|||
send(command, ...args) {
|
||||
if ( args.length === 1 )
|
||||
args = args[0];
|
||||
else if ( ! args.length )
|
||||
args = undefined;
|
||||
|
||||
if ( ! this.connected )
|
||||
this._pending.push([command, args]);
|
||||
|
@ -487,6 +501,8 @@ export default class SocketClient extends Module {
|
|||
call(command, ...args) {
|
||||
if ( args.length === 1 )
|
||||
args = args[0];
|
||||
else if ( ! args.length )
|
||||
args = undefined;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if ( ! this.connected )
|
||||
|
@ -503,22 +519,28 @@ export default class SocketClient extends Module {
|
|||
|
||||
subscribe(referrer, ...topics) {
|
||||
const t = this._topics;
|
||||
let changed = false;
|
||||
for(const topic of topics) {
|
||||
if ( ! t.has(topic) ) {
|
||||
if ( this.connected )
|
||||
this._send('sub', topic);
|
||||
|
||||
t.set(topic, new Set);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
const tp = t.get(topic);
|
||||
tp.add(referrer);
|
||||
}
|
||||
|
||||
if ( changed )
|
||||
this.emit(':sub-change');
|
||||
}
|
||||
|
||||
|
||||
unsubscribe(referrer, ...topics) {
|
||||
const t = this._topics;
|
||||
let changed = false;
|
||||
for(const topic of topics) {
|
||||
if ( ! t.has(topic) )
|
||||
continue;
|
||||
|
@ -527,11 +549,15 @@ export default class SocketClient extends Module {
|
|||
tp.delete(referrer);
|
||||
|
||||
if ( ! tp.size ) {
|
||||
changed = true;
|
||||
t.delete(topic);
|
||||
if ( this.connected )
|
||||
this._send('unsub', topic);
|
||||
}
|
||||
}
|
||||
|
||||
if ( changed )
|
||||
this.emit(':sub-change');
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -82,6 +82,26 @@ export class Vue extends Module {
|
|||
},
|
||||
|
||||
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) {
|
||||
this.locale && this.phrases[key];
|
||||
return t.i18n.t(key, phrase, options);
|
||||
|
@ -182,6 +202,18 @@ export class Vue extends Module {
|
|||
},
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -206,6 +206,11 @@ textarea.tw-input {
|
|||
}
|
||||
|
||||
|
||||
.ffz--cursor {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
.ffz--home {
|
||||
h2, p {
|
||||
margin-bottom: 1rem;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue