mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-28 15:27:43 +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",
|
"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": {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
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.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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue