Add WebSocket Log feature to Services Status View
- Introduced a WebSocket Log section in the Services Status View, allowing users to view real-time logs. - Updated localization files for both German and English to include WebSocket Log messages. - Enhanced the UI with a button to open the WebSocket Log dialog, improving user interaction and monitoring capabilities.
This commit is contained in:
263
frontend/src/dialogues/admin/WebSocketLogDialog.vue
Normal file
263
frontend/src/dialogues/admin/WebSocketLogDialog.vue
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
<template>
|
||||||
|
<DialogWidget ref="dialog" :title="$t('admin.servicesStatus.daemon.websocketLog.title')"
|
||||||
|
:show-close="true" :buttons="buttons" name="WebSocketLogDialog" :modal="true" :isTitleTranslated="true"
|
||||||
|
:width="'90%'" :height="'80%'">
|
||||||
|
<div class="websocket-log-container">
|
||||||
|
<div class="log-controls">
|
||||||
|
<button @click="fetchLog" class="refresh-btn" :disabled="loading">
|
||||||
|
{{ loading ? $t('admin.servicesStatus.daemon.websocketLog.loading') : $t('admin.servicesStatus.daemon.websocketLog.refresh') }}
|
||||||
|
</button>
|
||||||
|
<span v-if="logEntries.length > 0" class="entry-count">
|
||||||
|
{{ $t('admin.servicesStatus.daemon.websocketLog.entryCount', { count: logEntries.length }) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="error" class="error-message">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
<div v-if="logEntries.length === 0 && !loading" class="no-entries">
|
||||||
|
{{ $t('admin.servicesStatus.daemon.websocketLog.noEntries') }}
|
||||||
|
</div>
|
||||||
|
<div v-else class="log-entries">
|
||||||
|
<table class="log-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{ $t('admin.servicesStatus.daemon.websocketLog.timestamp') }}</th>
|
||||||
|
<th>{{ $t('admin.servicesStatus.daemon.websocketLog.direction') }}</th>
|
||||||
|
<th>{{ $t('admin.servicesStatus.daemon.websocketLog.peer') }}</th>
|
||||||
|
<th>{{ $t('admin.servicesStatus.daemon.websocketLog.connUser') }}</th>
|
||||||
|
<th>{{ $t('admin.servicesStatus.daemon.websocketLog.targetUser') }}</th>
|
||||||
|
<th>{{ $t('admin.servicesStatus.daemon.websocketLog.event') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(entry, index) in logEntries" :key="index" class="log-entry">
|
||||||
|
<td>{{ formatTimestamp(entry.timestamp) }}</td>
|
||||||
|
<td>
|
||||||
|
<span :class="['direction-badge', entry.direction === 'broker->client' ? 'incoming' : 'outgoing']">
|
||||||
|
{{ entry.direction }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>{{ entry.peer || '-' }}</td>
|
||||||
|
<td>{{ entry.conn_user || '-' }}</td>
|
||||||
|
<td>{{ entry.target_user || '-' }}</td>
|
||||||
|
<td>{{ entry.event || '-' }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogWidget>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import DialogWidget from '@/components/DialogWidget.vue';
|
||||||
|
import { mapState } from 'vuex';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'WebSocketLogDialog',
|
||||||
|
components: { DialogWidget },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
logEntries: [],
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
buttons: [
|
||||||
|
{ text: 'admin.servicesStatus.daemon.websocketLog.close', action: () => this.closeDialog() }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(['daemonSocket'])
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open() {
|
||||||
|
this.$refs.dialog.open();
|
||||||
|
this.fetchLog();
|
||||||
|
},
|
||||||
|
closeDialog() {
|
||||||
|
this.$refs.dialog.close();
|
||||||
|
},
|
||||||
|
async fetchLog() {
|
||||||
|
if (!this.daemonSocket || this.daemonSocket.readyState !== WebSocket.OPEN) {
|
||||||
|
this.error = this.$t('admin.servicesStatus.daemon.websocketLog.notConnected');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = true;
|
||||||
|
this.error = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const message = JSON.stringify({
|
||||||
|
event: 'getWebsocketLog'
|
||||||
|
});
|
||||||
|
this.daemonSocket.send(message);
|
||||||
|
// Die Antwort wird über handleDaemonMessage verarbeitet
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Fehler beim Senden von getWebsocketLog:', err);
|
||||||
|
this.error = this.$t('admin.servicesStatus.daemon.websocketLog.sendError');
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleDaemonMessage(event) {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
if (data.event === 'getWebsocketLogResponse') {
|
||||||
|
this.logEntries = data.entries || [];
|
||||||
|
this.loading = false;
|
||||||
|
this.error = null;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Fehler beim Verarbeiten der Daemon-Nachricht:', err);
|
||||||
|
this.error = this.$t('admin.servicesStatus.daemon.websocketLog.parseError');
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
formatTimestamp(timestamp) {
|
||||||
|
if (!timestamp) return '-';
|
||||||
|
const date = new Date(timestamp * 1000);
|
||||||
|
return date.toLocaleString('de-DE', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// Event-Listener für Daemon-Nachrichten registrieren
|
||||||
|
if (this.daemonSocket) {
|
||||||
|
this.daemonSocket.addEventListener('message', this.handleDaemonMessage);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
// Event-Listener entfernen
|
||||||
|
if (this.daemonSocket) {
|
||||||
|
this.daemonSocket.removeEventListener('message', this.handleDaemonMessage);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
daemonSocket(newSocket, oldSocket) {
|
||||||
|
// Event-Listener bei Socket-Änderung aktualisieren
|
||||||
|
if (oldSocket) {
|
||||||
|
oldSocket.removeEventListener('message', this.handleDaemonMessage);
|
||||||
|
}
|
||||||
|
if (newSocket) {
|
||||||
|
newSocket.addEventListener('message', this.handleDaemonMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.websocket-log-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-controls {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-btn {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: #1976d2;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-btn:hover:not(:disabled) {
|
||||||
|
background: #1565c0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-btn:disabled {
|
||||||
|
background: #ccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-count {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
padding: 10px;
|
||||||
|
background: #ffebee;
|
||||||
|
border: 1px solid #f44336;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #c62828;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-entries {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px;
|
||||||
|
color: #666;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-entries {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-table thead {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background: #f5f5f5;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-table th {
|
||||||
|
padding: 10px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 2px solid #ddd;
|
||||||
|
font-weight: bold;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-table td {
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-entry:hover {
|
||||||
|
background: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.direction-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.direction-badge.incoming {
|
||||||
|
background: #e3f2fd;
|
||||||
|
color: #1976d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.direction-badge.outgoing {
|
||||||
|
background: #f3e5f5;
|
||||||
|
color: #7b1fa2;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -267,6 +267,24 @@
|
|||||||
"notConnected": "Daemon nicht verbunden",
|
"notConnected": "Daemon nicht verbunden",
|
||||||
"sendError": "Fehler beim Senden der Anfrage",
|
"sendError": "Fehler beim Senden der Anfrage",
|
||||||
"error": "Fehler beim Abrufen der Verbindungen"
|
"error": "Fehler beim Abrufen der Verbindungen"
|
||||||
|
},
|
||||||
|
"websocketLog": {
|
||||||
|
"title": "WebSocket-Log",
|
||||||
|
"showLog": "WebSocket-Log anzeigen",
|
||||||
|
"refresh": "Aktualisieren",
|
||||||
|
"loading": "Lädt...",
|
||||||
|
"close": "Schließen",
|
||||||
|
"entryCount": "{count} Einträge",
|
||||||
|
"noEntries": "Keine Log-Einträge vorhanden",
|
||||||
|
"notConnected": "Daemon nicht verbunden",
|
||||||
|
"sendError": "Fehler beim Senden der Anfrage",
|
||||||
|
"parseError": "Fehler beim Verarbeiten der Antwort",
|
||||||
|
"timestamp": "Zeitstempel",
|
||||||
|
"direction": "Richtung",
|
||||||
|
"peer": "Peer",
|
||||||
|
"connUser": "Verbindungs-User",
|
||||||
|
"targetUser": "Ziel-User",
|
||||||
|
"event": "Event"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -267,6 +267,24 @@
|
|||||||
"notConnected": "Daemon not connected",
|
"notConnected": "Daemon not connected",
|
||||||
"sendError": "Error sending request",
|
"sendError": "Error sending request",
|
||||||
"error": "Error fetching connections"
|
"error": "Error fetching connections"
|
||||||
|
},
|
||||||
|
"websocketLog": {
|
||||||
|
"title": "WebSocket Log",
|
||||||
|
"showLog": "Show WebSocket Log",
|
||||||
|
"refresh": "Refresh",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"close": "Close",
|
||||||
|
"entryCount": "{count} entries",
|
||||||
|
"noEntries": "No log entries available",
|
||||||
|
"notConnected": "Daemon not connected",
|
||||||
|
"sendError": "Error sending request",
|
||||||
|
"parseError": "Error parsing response",
|
||||||
|
"timestamp": "Timestamp",
|
||||||
|
"direction": "Direction",
|
||||||
|
"peer": "Peer",
|
||||||
|
"connUser": "Connection User",
|
||||||
|
"targetUser": "Target User",
|
||||||
|
"event": "Event"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,12 @@
|
|||||||
|
|
||||||
<!-- Daemon Connections -->
|
<!-- Daemon Connections -->
|
||||||
<div v-if="daemonStatus === 'connected'" class="connections-section">
|
<div v-if="daemonStatus === 'connected'" class="connections-section">
|
||||||
<h3>{{ $t('admin.servicesStatus.daemon.connections.title') }}</h3>
|
<div class="section-header">
|
||||||
|
<h3>{{ $t('admin.servicesStatus.daemon.connections.title') }}</h3>
|
||||||
|
<button @click="openWebSocketLogDialog" class="log-button">
|
||||||
|
{{ $t('admin.servicesStatus.daemon.websocketLog.showLog') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div v-if="connections.length === 0" class="no-connections">
|
<div v-if="connections.length === 0" class="no-connections">
|
||||||
<p>{{ $t('admin.servicesStatus.daemon.connections.none') }}</p>
|
<p>{{ $t('admin.servicesStatus.daemon.connections.none') }}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -91,6 +96,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- WebSocket Log Dialog -->
|
||||||
|
<WebSocketLogDialog ref="webSocketLogDialog" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -98,11 +106,13 @@
|
|||||||
import { mapState, mapGetters } from 'vuex';
|
import { mapState, mapGetters } from 'vuex';
|
||||||
import SimpleTabs from '@/components/SimpleTabs.vue';
|
import SimpleTabs from '@/components/SimpleTabs.vue';
|
||||||
import apiClient from '@/utils/axios.js';
|
import apiClient from '@/utils/axios.js';
|
||||||
|
import WebSocketLogDialog from '@/dialogues/admin/WebSocketLogDialog.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ServicesStatusView',
|
name: 'ServicesStatusView',
|
||||||
components: {
|
components: {
|
||||||
SimpleTabs
|
SimpleTabs,
|
||||||
|
WebSocketLogDialog
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -271,6 +281,11 @@ export default {
|
|||||||
const minutes = Math.floor((seconds % 3600) / 60);
|
const minutes = Math.floor((seconds % 3600) / 60);
|
||||||
return `${hours}h ${minutes}m`;
|
return `${hours}h ${minutes}m`;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
openWebSocketLogDialog() {
|
||||||
|
if (this.$refs.webSocketLogDialog) {
|
||||||
|
this.$refs.webSocketLogDialog.open();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@@ -364,10 +379,31 @@ export default {
|
|||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.connections-section h3 {
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.section-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: #1976d2;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-button:hover {
|
||||||
|
background: #1565c0;
|
||||||
|
}
|
||||||
|
|
||||||
.no-connections {
|
.no-connections {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
Reference in New Issue
Block a user