feat: add page displaying all events

This commit is contained in:
Damien Arrachequesne 2022-06-16 00:37:34 +02:00
parent 77ee068318
commit 481ef22b3a
No known key found for this signature in database
GPG Key ID: 544D14663E7F7CF0
11 changed files with 280 additions and 32 deletions

View File

@ -334,7 +334,11 @@ const registerListeners = (
socket.data[key] = createProxy(data[key]);
}
adminNamespace.emit("socket_connected", serialize(socket, nsp.name));
adminNamespace.emit(
"socket_connected",
serialize(socket, nsp.name),
new Date()
);
socket.conn.on("upgrade", (transport: any) => {
socket.data._admin.transport = transport.name;
@ -346,17 +350,23 @@ const registerListeners = (
});
socket.on("disconnect", (reason: string) => {
adminNamespace.emit("socket_disconnected", nsp.name, socket.id, reason);
adminNamespace.emit(
"socket_disconnected",
nsp.name,
socket.id,
reason,
new Date()
);
});
});
nsp.adapter.on("join-room", (room: string, id: string) => {
adminNamespace.emit("room_joined", nsp.name, room, id);
adminNamespace.emit("room_joined", nsp.name, room, id, new Date());
});
nsp.adapter.on("leave-room", (room: string, id: string) => {
process.nextTick(() => {
adminNamespace.emit("room_left", nsp.name, room, id);
adminNamespace.emit("room_left", nsp.name, room, id, new Date());
});
});
};

View File

@ -36,11 +36,16 @@ export interface ServerEvents {
config: (config: Config) => void;
server_stats: (stats: ServerStats) => void;
all_sockets: (sockets: SerializedSocket[]) => void;
socket_connected: (socket: SerializedSocket) => void;
socket_connected: (socket: SerializedSocket, timestamp: Date) => void;
socket_updated: (socket: Partial<SerializedSocket>) => void;
socket_disconnected: (nsp: string, id: string, reason: string) => void;
room_joined: (nsp: string, room: string, id: string) => void;
room_left: (nsp: string, room: string, id: string) => void;
socket_disconnected: (
nsp: string,
id: string,
reason: string,
timestamp: Date
) => void;
room_joined: (nsp: string, room: string, id: string, timestamp: Date) => void;
room_left: (nsp: string, room: string, id: string, timestamp: Date) => void;
}
export interface ClientEvents {

View File

@ -264,7 +264,7 @@ describe("Socket.IO Admin (server instrumentation)", () => {
// connect
const serverSocket = await waitFor(io, "connection");
const socket = await waitFor(adminSocket, "socket_connected");
const [socket] = await waitFor(adminSocket, "socket_connected");
expect(socket.id).to.eql(serverSocket.id);
expect(socket.nsp).to.eql("/");
@ -329,7 +329,7 @@ describe("Socket.IO Admin (server instrumentation)", () => {
const serverSocket = await waitFor(io, "connection");
const socket = await waitFor(adminSocket, "socket_connected");
const [socket] = await waitFor(adminSocket, "socket_connected");
expect(socket.data).to.eql({ count: 1, array: [1] });
serverSocket.data.count++;
@ -401,7 +401,7 @@ describe("Socket.IO Admin (server instrumentation)", () => {
forceNew: true,
});
const socket = await waitFor(adminSocket, "socket_connected");
const [socket] = await waitFor(adminSocket, "socket_connected");
expect(socket.nsp).to.eql("/dynamic-101");
clientSocket.disconnect();

View File

@ -40,6 +40,11 @@ import {
VSlideYReverseTransition,
} from "vuetify/lib";
// created on the client side, for backward compatibility
function defaultTimestamp() {
return new Date().toISOString();
}
export default {
name: "App",
@ -153,25 +158,41 @@ export default {
socket.on("all_sockets", (sockets) => {
this.$store.commit("main/onAllSockets", sockets);
});
socket.on("socket_connected", (socket) => {
this.$store.commit("main/onSocketConnected", socket);
});
socket.on(
"socket_connected",
(socket, timestamp = defaultTimestamp()) => {
this.$store.commit("main/onSocketConnected", {
timestamp,
socket,
});
}
);
socket.on("socket_updated", (socket) => {
this.$store.commit("main/onSocketUpdated", socket);
});
socket.on("socket_disconnected", (nsp, id, reason) => {
this.$store.commit("main/onSocketDisconnected", {
nsp,
id,
reason,
});
});
socket.on("room_joined", (nsp, room, id) => {
this.$store.commit("main/onRoomJoined", { nsp, room, id });
});
socket.on("room_left", (nsp, room, id) => {
this.$store.commit("main/onRoomLeft", { nsp, room, id });
});
socket.on(
"socket_disconnected",
(nsp, id, reason, timestamp = defaultTimestamp()) => {
this.$store.commit("main/onSocketDisconnected", {
timestamp,
nsp,
id,
reason,
});
}
);
socket.on(
"room_joined",
(nsp, room, id, timestamp = defaultTimestamp()) => {
this.$store.commit("main/onRoomJoined", { timestamp, nsp, room, id });
}
);
socket.on(
"room_left",
(nsp, room, id, timestamp = defaultTimestamp()) => {
this.$store.commit("main/onRoomLeft", { timestamp, nsp, room, id });
}
);
},
onSubmit(form) {

View File

@ -0,0 +1,33 @@
<template>
<v-chip :color="color" outlined>
{{ $t("events.type." + type) }}
</v-chip>
</template>
<script>
import colors from "vuetify/lib/util/colors";
export default {
name: "EventType",
props: {
type: String,
},
computed: {
color() {
switch (this.type) {
case "connection":
return colors.green.base;
case "room_joined":
return colors.teal.base;
case "room_left":
return colors.amber.base;
case "disconnection":
return colors.red.base;
}
return colors.gray.base;
},
},
};
</script>

View File

@ -67,6 +67,11 @@ export default {
icon: "mdi-account-circle-outline",
to: { name: "clients" },
},
{
title: this.$t("events.title"),
icon: "mdi-calendar-text-outline",
to: { name: "events" },
},
{
title: this.$t("servers.title"),
icon: "mdi-server",

View File

@ -15,6 +15,8 @@
"connected": "connected",
"disconnected": "disconnected",
"data": "Data",
"timestamp": "Timestamp",
"args": "Arguments",
"connection": {
"title": "Connection",
"serverUrl": "Server URL",
@ -84,5 +86,14 @@
"language": "Language",
"readonly": "Read-only?",
"dark-theme": "Dark theme?"
},
"events": {
"title": "Events",
"type": {
"connection": "Connection",
"disconnection": "Disconnection",
"room_joined": "Room joined",
"room_left": "Room left"
}
}
}

View File

@ -14,6 +14,9 @@
"status": "Statut",
"connected": "connecté",
"disconnected": "déconnecté",
"data": "Données",
"timestamp": "Horodatage",
"args": "Arguments",
"connection": {
"title": "Connexion",
"serverUrl": "URL du serveur",
@ -80,5 +83,14 @@
"language": "Langue",
"readonly": "Lecture seule ?",
"dark-theme": "Mode sombre ?"
},
"events": {
"title": "Évènements",
"type": {
"connection": "Connexion",
"disconnection": "Déconnexion",
"room_joined": "Salle rejointe",
"room_left": "Salle quittée"
}
}
}

View File

@ -8,6 +8,7 @@ import Clients from "../views/Clients";
import Client from "../views/Client";
import Servers from "../views/Servers";
import Room from "../views/Room";
import Events from "@/views/Events";
Vue.use(VueRouter);
@ -72,13 +73,22 @@ const routes = [
topLevel: false,
},
},
{
path: "/events/",
name: "events",
component: Events,
meta: {
topLevel: true,
index: 4,
},
},
{
path: "/servers/",
name: "servers",
component: Servers,
meta: {
topLevel: true,
index: 4,
index: 5,
},
},
];

View File

@ -10,6 +10,7 @@ const getOrCreateNamespace = (namespaces, name) => {
name,
sockets: [],
rooms: [],
events: [],
};
namespaces.push(namespace);
return namespace;
@ -64,6 +65,17 @@ const addSocket = (state, socket) => {
}
};
const MAX_ARRAY_LENGTH = 1000;
let EVENT_COUNTER = 0;
const pushEvents = (array, event) => {
event.eventId = ++EVENT_COUNTER; // unique id
array.push(event);
if (array.length > MAX_ARRAY_LENGTH) {
array.shift();
}
};
export default {
namespaced: true,
state: {
@ -97,6 +109,9 @@ export default {
rooms: (state) => {
return state.selectedNamespace ? state.selectedNamespace.rooms : [];
},
events: (state) => {
return state.selectedNamespace ? state.selectedNamespace.events : [];
},
},
mutations: {
selectNamespace(state, namespace) {
@ -114,8 +129,14 @@ export default {
find(state.namespaces, { name: "/" }) || state.namespaces[0];
}
},
onSocketConnected(state, socket) {
onSocketConnected(state, { timestamp, socket }) {
addSocket(state, socket);
const namespace = getOrCreateNamespace(state.namespaces, socket.nsp);
pushEvents(namespace.events, {
type: "connection",
timestamp,
id: socket.id,
});
},
onSocketUpdated(state, socket) {
const namespace = getOrCreateNamespace(state.namespaces, socket.nsp);
@ -124,7 +145,7 @@ export default {
merge(existingSocket, socket);
}
},
onSocketDisconnected(state, { nsp, id }) {
onSocketDisconnected(state, { timestamp, nsp, id, reason }) {
const namespace = getOrCreateNamespace(state.namespaces, nsp);
const [socket] = remove(namespace.sockets, { id });
if (socket) {
@ -137,8 +158,14 @@ export default {
remove(state.clients, { id: socket.clientId });
}
}
pushEvents(namespace.events, {
type: "disconnection",
timestamp,
id,
args: reason,
});
},
onRoomJoined(state, { nsp, room, id }) {
onRoomJoined(state, { nsp, room, id, timestamp }) {
const namespace = getOrCreateNamespace(state.namespaces, nsp);
const socket = find(namespace.sockets, { id });
if (socket) {
@ -146,8 +173,14 @@ export default {
const _room = getOrCreateRoom(namespace, room);
_room.sockets.push(socket);
}
pushEvents(namespace.events, {
type: "room_joined",
timestamp,
id,
args: room,
});
},
onRoomLeft(state, { nsp, room, id }) {
onRoomLeft(state, { timestamp, nsp, room, id }) {
const namespace = getOrCreateNamespace(state.namespaces, nsp);
const socket = find(namespace.sockets, { id });
if (socket) {
@ -159,6 +192,12 @@ export default {
_room.active = false;
remove(namespace.rooms, { name: room });
}
pushEvents(namespace.events, {
type: "room_left",
timestamp,
id,
args: room,
});
},
},
};

102
ui/src/views/Events.vue Normal file
View File

@ -0,0 +1,102 @@
<template>
<div>
<v-breadcrumbs :items="breadcrumbItems" />
<v-card>
<v-card-text>
<NamespaceSelector />
</v-card-text>
<v-data-table
:headers="headers"
:items="events"
:footer-props="footerProps"
item-key="eventId"
:sort-by="['timestamp', 'eventId']"
:sort-desc="[true, true]"
>
<template #item.type="{ value }">
<EventType :type="value" />
</template>
<template #item.id="{ value }">
<router-link class="link" :to="socketDetailsRoute(value)">{{
value
}}</router-link>
</template>
</v-data-table>
</v-card>
</div>
</template>
<script>
import { mapGetters, mapState } from "vuex";
import NamespaceSelector from "../components/NamespaceSelector";
import EventType from "@/components/EventType";
export default {
name: "Events",
components: { EventType, NamespaceSelector },
data() {
return {
footerProps: {
"items-per-page-options": [20, 100, -1],
},
};
},
computed: {
breadcrumbItems() {
return [
{
text: this.$t("events.title"),
disabled: true,
},
];
},
headers() {
return [
{
text: this.$t("timestamp"),
value: "timestamp",
},
{
text: this.$t("sockets.socket"),
value: "id",
sortable: false,
},
{
text: this.$t("type"),
value: "type",
sortable: false,
},
{
text: this.$t("args"),
value: "args",
sortable: false,
},
];
},
...mapGetters("main", ["events"]),
...mapState({
selectedNamespace: (state) => state.main.selectedNamespace,
}),
},
methods: {
socketDetailsRoute(sid) {
return {
name: "socket",
params: { nsp: this.selectedNamespace.name, id: sid },
};
},
},
};
</script>
<style scoped>
.link {
color: inherit;
}
</style>