- Updated the WebSocketLogDialog to use enriched log entries with resolved usernames for connection and target users. - Implemented batch retrieval of user information from the API to improve user display in logs. - Added error handling for user data fetching and fallback logic for missing usernames.
304 lines
8.2 KiB
Vue
304 lines
8.2 KiB
Vue
<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 enrichedLogEntries" :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_display || '-' }}</td>
|
|
<td>{{ entry.target_user_display || '-' }}</td>
|
|
<td>{{ entry.event || '-' }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</DialogWidget>
|
|
</template>
|
|
|
|
<script>
|
|
import DialogWidget from '@/components/DialogWidget.vue';
|
|
import { mapState } from 'vuex';
|
|
import apiClient from '@/utils/axios.js';
|
|
|
|
export default {
|
|
name: 'WebSocketLogDialog',
|
|
components: { DialogWidget },
|
|
data() {
|
|
return {
|
|
logEntries: [],
|
|
loading: false,
|
|
error: null,
|
|
userMap: {}, // Map von User-ID zu Benutzername
|
|
buttons: [
|
|
{ text: 'admin.servicesStatus.daemon.websocketLog.close', action: () => this.closeDialog() }
|
|
]
|
|
};
|
|
},
|
|
computed: {
|
|
...mapState(['daemonSocket']),
|
|
enrichedLogEntries() {
|
|
// Erstelle Log-Einträge mit aufgelösten Benutzernamen
|
|
return this.logEntries.map(entry => ({
|
|
...entry,
|
|
conn_user_display: this.getUsername(entry.conn_user),
|
|
target_user_display: this.getUsername(entry.target_user)
|
|
}));
|
|
}
|
|
},
|
|
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;
|
|
}
|
|
},
|
|
async handleDaemonMessage(event) {
|
|
try {
|
|
const data = JSON.parse(event.data);
|
|
if (data.event === 'getWebsocketLogResponse') {
|
|
this.logEntries = data.entries || [];
|
|
|
|
// Sammle alle eindeutigen User-IDs aus den Log-Einträgen
|
|
const userIds = new Set();
|
|
this.logEntries.forEach(entry => {
|
|
if (entry.conn_user) userIds.add(entry.conn_user);
|
|
if (entry.target_user) userIds.add(entry.target_user);
|
|
});
|
|
|
|
// Lade User-Informationen für alle User-IDs
|
|
if (userIds.size > 0) {
|
|
try {
|
|
const response = await apiClient.get('/api/admin/users/batch', {
|
|
params: { ids: Array.from(userIds) }
|
|
});
|
|
this.userMap = response.data;
|
|
} catch (err) {
|
|
console.error('Fehler beim Abrufen der User-Informationen:', err);
|
|
// Fallback: Verwende User-IDs, wenn Abruf fehlschlägt
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
},
|
|
getUsername(userId) {
|
|
if (!userId) return null;
|
|
const userInfo = this.userMap[userId];
|
|
if (userInfo && userInfo.username) {
|
|
return userInfo.username;
|
|
}
|
|
// Fallback: Zeige ersten Teil der ID, wenn User nicht gefunden wurde
|
|
return userId.length > 8 ? userId.substring(0, 8) + '...' : userId;
|
|
},
|
|
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>
|
|
|