Implement batch user retrieval in AdminController and update routes
- Added a new method `getUsers` in AdminController to handle batch retrieval of user information based on hashed IDs. - Updated adminRouter to include a new route for batch user retrieval. - Enhanced AdminService with a method to fetch user details by hashed IDs, ensuring proper access control. - Updated localization files to include the new "username" field for user connections in both German and English. - Modified ServicesStatusView to utilize the new batch user retrieval for displaying usernames alongside connection counts.
This commit is contained in:
@@ -27,6 +27,7 @@ class AdminController {
|
|||||||
// User administration
|
// User administration
|
||||||
this.searchUsers = this.searchUsers.bind(this);
|
this.searchUsers = this.searchUsers.bind(this);
|
||||||
this.getUser = this.getUser.bind(this);
|
this.getUser = this.getUser.bind(this);
|
||||||
|
this.getUsers = this.getUsers.bind(this);
|
||||||
this.updateUser = this.updateUser.bind(this);
|
this.updateUser = this.updateUser.bind(this);
|
||||||
|
|
||||||
// Rights
|
// Rights
|
||||||
@@ -74,6 +75,30 @@ class AdminController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getUsers(req, res) {
|
||||||
|
try {
|
||||||
|
const { userid: requester } = req.headers;
|
||||||
|
let { ids } = req.query;
|
||||||
|
if (!ids) {
|
||||||
|
return res.status(400).json({ error: 'ids query parameter is required' });
|
||||||
|
}
|
||||||
|
// Unterstütze sowohl Array-Format (ids[]=...) als auch komma-separierten String (ids=...)
|
||||||
|
let hashedIds;
|
||||||
|
if (Array.isArray(ids)) {
|
||||||
|
hashedIds = ids;
|
||||||
|
} else if (typeof ids === 'string') {
|
||||||
|
hashedIds = ids.split(',').map(id => id.trim()).filter(id => id.length > 0);
|
||||||
|
} else {
|
||||||
|
return res.status(400).json({ error: 'ids must be an array or comma-separated string' });
|
||||||
|
}
|
||||||
|
const result = await AdminService.getUsersByHashedIds(requester, hashedIds);
|
||||||
|
res.status(200).json(result);
|
||||||
|
} catch (error) {
|
||||||
|
const status = error.message === 'noaccess' ? 403 : 500;
|
||||||
|
res.status(status).json({ error: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async updateUser(req, res) {
|
async updateUser(req, res) {
|
||||||
try {
|
try {
|
||||||
const { userid: requester } = req.headers;
|
const { userid: requester } = req.headers;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ router.delete('/chat/rooms/:id', authenticate, adminController.deleteRoom);
|
|||||||
// --- Users Admin ---
|
// --- Users Admin ---
|
||||||
router.get('/users/search', authenticate, adminController.searchUsers);
|
router.get('/users/search', authenticate, adminController.searchUsers);
|
||||||
router.get('/users/statistics', authenticate, adminController.getUserStatistics);
|
router.get('/users/statistics', authenticate, adminController.getUserStatistics);
|
||||||
|
router.get('/users/batch', authenticate, adminController.getUsers);
|
||||||
router.get('/users/:id', authenticate, adminController.getUser);
|
router.get('/users/:id', authenticate, adminController.getUser);
|
||||||
router.put('/users/:id', authenticate, adminController.updateUser);
|
router.put('/users/:id', authenticate, adminController.updateUser);
|
||||||
|
|
||||||
|
|||||||
@@ -441,6 +441,30 @@ class AdminService {
|
|||||||
return { id: user.hashedId, username: user.username, active: user.active, registrationDate: user.registrationDate };
|
return { id: user.hashedId, username: user.username, active: user.active, registrationDate: user.registrationDate };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getUsersByHashedIds(requestingHashedUserId, targetHashedIds) {
|
||||||
|
if (!(await this.hasUserAccess(requestingHashedUserId, 'useradministration'))) {
|
||||||
|
throw new Error('noaccess');
|
||||||
|
}
|
||||||
|
if (!Array.isArray(targetHashedIds) || targetHashedIds.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const users = await User.findAll({
|
||||||
|
where: { hashedId: { [Op.in]: targetHashedIds } },
|
||||||
|
attributes: ['id', 'hashedId', 'username', 'active', 'registrationDate']
|
||||||
|
});
|
||||||
|
// Erstelle ein Map für schnellen Zugriff
|
||||||
|
const userMap = {};
|
||||||
|
users.forEach(user => {
|
||||||
|
userMap[user.hashedId] = {
|
||||||
|
id: user.hashedId,
|
||||||
|
username: user.username,
|
||||||
|
active: user.active,
|
||||||
|
registrationDate: user.registrationDate
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return userMap;
|
||||||
|
}
|
||||||
|
|
||||||
async updateUser(requestingHashedUserId, targetHashedId, data) {
|
async updateUser(requestingHashedUserId, targetHashedId, data) {
|
||||||
if (!(await this.hasUserAccess(requestingHashedUserId, 'useradministration'))) {
|
if (!(await this.hasUserAccess(requestingHashedUserId, 'useradministration'))) {
|
||||||
throw new Error('noaccess');
|
throw new Error('noaccess');
|
||||||
|
|||||||
@@ -256,6 +256,7 @@
|
|||||||
"title": "Aktive Verbindungen",
|
"title": "Aktive Verbindungen",
|
||||||
"none": "Keine aktiven Verbindungen",
|
"none": "Keine aktiven Verbindungen",
|
||||||
"userId": "Benutzer-ID",
|
"userId": "Benutzer-ID",
|
||||||
|
"username": "Benutzername",
|
||||||
"connections": "Verbindungen",
|
"connections": "Verbindungen",
|
||||||
"duration": "Verbindungsdauer",
|
"duration": "Verbindungsdauer",
|
||||||
"lastPong": "Zeit seit letztem Pong",
|
"lastPong": "Zeit seit letztem Pong",
|
||||||
|
|||||||
@@ -256,6 +256,7 @@
|
|||||||
"title": "Active Connections",
|
"title": "Active Connections",
|
||||||
"none": "No active connections",
|
"none": "No active connections",
|
||||||
"userId": "User ID",
|
"userId": "User ID",
|
||||||
|
"username": "Username",
|
||||||
"connections": "connections",
|
"connections": "connections",
|
||||||
"duration": "Connection Duration",
|
"duration": "Connection Duration",
|
||||||
"lastPong": "Time Since Last Pong",
|
"lastPong": "Time Since Last Pong",
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
<div v-else class="connections-list">
|
<div v-else class="connections-list">
|
||||||
<div v-for="(userConn, index) in connections" :key="index" class="connection-group">
|
<div v-for="(userConn, index) in connections" :key="index" class="connection-group">
|
||||||
<div class="connection-header">
|
<div class="connection-header">
|
||||||
<strong>{{ $t('admin.servicesStatus.daemon.connections.userId') }}:</strong> {{ userConn.userId }}
|
<strong>{{ $t('admin.servicesStatus.daemon.connections.username') }}:</strong> {{ userConn.username }}
|
||||||
<span class="connection-count">({{ userConn.connectionCount }} {{ $t('admin.servicesStatus.daemon.connections.connections') }})</span>
|
<span class="connection-count">({{ userConn.connectionCount }} {{ $t('admin.servicesStatus.daemon.connections.connections') }})</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-for="(conn, connIndex) in userConn.connections" :key="connIndex" class="connection-details">
|
<div v-for="(conn, connIndex) in userConn.connections" :key="connIndex" class="connection-details">
|
||||||
@@ -97,6 +97,7 @@
|
|||||||
<script>
|
<script>
|
||||||
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';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ServicesStatusView',
|
name: 'ServicesStatusView',
|
||||||
@@ -214,18 +215,39 @@ export default {
|
|||||||
this.error = this.$t('admin.servicesStatus.daemon.connections.sendError');
|
this.error = this.$t('admin.servicesStatus.daemon.connections.sendError');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleDaemonMessage(event) {
|
async handleDaemonMessage(event) {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
if (data.event === 'getConnectionsResponse') {
|
if (data.event === 'getConnectionsResponse') {
|
||||||
// Die Daemon-Antwort hat die Struktur: {event, total, unauthenticated, users: {userId: count}}
|
// Die Daemon-Antwort hat die Struktur: {event, total, unauthenticated, users: {userId: count}}
|
||||||
if (data.users && typeof data.users === 'object') {
|
if (data.users && typeof data.users === 'object') {
|
||||||
// Transformiere das users-Objekt in ein Array für das Template
|
// Transformiere das users-Objekt in ein Array für das Template
|
||||||
this.connections = Object.entries(data.users).map(([userId, connectionCount]) => ({
|
const userIds = Object.keys(data.users);
|
||||||
|
|
||||||
|
// Lade User-Informationen für alle User-IDs
|
||||||
|
let userMap = {};
|
||||||
|
if (userIds.length > 0) {
|
||||||
|
try {
|
||||||
|
const response = await apiClient.get('/api/admin/users/batch', {
|
||||||
|
params: { ids: userIds }
|
||||||
|
});
|
||||||
|
userMap = response.data;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Fehler beim Abrufen der User-Informationen:', err);
|
||||||
|
// Fallback: Verwende User-IDs, wenn Abruf fehlschlägt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transformiere das users-Objekt in ein Array für das Template
|
||||||
|
this.connections = Object.entries(data.users).map(([userId, connectionCount]) => {
|
||||||
|
const userInfo = userMap[userId];
|
||||||
|
return {
|
||||||
userId: userId,
|
userId: userId,
|
||||||
|
username: userInfo ? userInfo.username : userId.substring(0, 8) + '...', // Fallback: Zeige ersten Teil der ID
|
||||||
connectionCount: connectionCount,
|
connectionCount: connectionCount,
|
||||||
connections: [] // Detaillierte Verbindungsinfos sind in der aktuellen Antwort nicht enthalten
|
connections: [] // Detaillierte Verbindungsinfos sind in der aktuellen Antwort nicht enthalten
|
||||||
}));
|
};
|
||||||
|
});
|
||||||
this.error = null;
|
this.error = null;
|
||||||
} else {
|
} else {
|
||||||
this.connections = [];
|
this.connections = [];
|
||||||
|
|||||||
Reference in New Issue
Block a user