mejoras UI 4
This commit is contained in:
@@ -62,10 +62,19 @@
|
||||
</div>
|
||||
<div class="scroll">
|
||||
<div v-if="loading.users" class="muted">Cargando usuarios…</div>
|
||||
<div v-else class="grid">
|
||||
<UserCard v-for="u in filteredUsers" :key="u.username" :item="u" :mode="layoutMode"
|
||||
@toggleDisable="toggleDisable" @remove="removeUser" @edit="openEditUser" />
|
||||
</div>
|
||||
<template v-else>
|
||||
<div v-if="layoutMode==='user'" class="grid">
|
||||
<UserCard v-for="u in filteredUsers" :key="u.username" :item="u" :devicesById="devicesById"
|
||||
:expanded="!!userExpanded[u.username]"
|
||||
@toggle-expand="userExpanded[u.username] = !userExpanded[u.username]"
|
||||
@toggleDisable="toggleDisable" @remove="removeUser" @edit="openEditUser" />
|
||||
</div>
|
||||
<div v-else class="grid">
|
||||
<DispositivoCard v-for="d in devices" :key="d.id" :device="d" :users="usersForDevice(d.id)" :devicesById="devicesById"
|
||||
:expanded="!!deviceExpanded[d.id]"
|
||||
@toggle-expand="deviceExpanded[d.id] = !deviceExpanded[d.id]" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</main>
|
||||
</section>
|
||||
@@ -111,9 +120,10 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref, computed } from 'vue';
|
||||
import { onMounted, reactive, ref, computed, watch } from 'vue';
|
||||
import EventCard from './components/EventCard.js';
|
||||
import UserCard from './components/UserCard.js';
|
||||
import UserCard from './components/UserCard.vue';
|
||||
import DispositivoCard from './components/DispositivoCard.vue';
|
||||
import Modal from './components/Modal.vue';
|
||||
import UserForm from './components/UserForm.vue';
|
||||
import RawDbViewer from './components/RawDbViewer.vue';
|
||||
@@ -122,6 +132,9 @@ import VlanForm from './components/VlanForm.vue';
|
||||
const users = ref([]);
|
||||
const requests = ref([]);
|
||||
const loading = reactive({ users: false, requests: false });
|
||||
const devices = ref([]);
|
||||
const userExpanded = reactive({});
|
||||
const deviceExpanded = reactive({});
|
||||
// formulario inline removido: se usa modal con UserForm
|
||||
|
||||
const showEventFilters = ref(false);
|
||||
@@ -153,6 +166,14 @@ async function fetchRequests() {
|
||||
} finally { loading.requests = false; }
|
||||
}
|
||||
|
||||
async function fetchDevices() {
|
||||
try {
|
||||
const res = await fetch('/api/devices');
|
||||
const data = await res.json();
|
||||
devices.value = data.items || [];
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function toggleDisable(u) {
|
||||
await fetch(`/api/users/${encodeURIComponent(u.username)}`, {
|
||||
method: 'PATCH',
|
||||
@@ -181,17 +202,39 @@ async function selfTest() {
|
||||
|
||||
function setupSse() {
|
||||
const ev = new EventSource('/api/events');
|
||||
let refreshTimer = null;
|
||||
function scheduleRefresh() {
|
||||
if (refreshTimer) clearTimeout(refreshTimer);
|
||||
refreshTimer = setTimeout(async () => {
|
||||
await Promise.all([fetchUsers(), fetchDevices()]);
|
||||
refreshTimer = null;
|
||||
}, 1000);
|
||||
}
|
||||
ev.addEventListener('message', (e) => {
|
||||
try {
|
||||
const data = JSON.parse(e.data);
|
||||
if (data && data.ts) requests.value.push(data);
|
||||
const t = data && data.type;
|
||||
if (t === 'authorize' || t === 'post-auth' || t === 'accounting' || t === 'coa-disconnect') {
|
||||
scheduleRefresh();
|
||||
}
|
||||
} catch {}
|
||||
});
|
||||
ev.addEventListener('clear', () => { requests.value = []; });
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
// Load persisted expand state
|
||||
try {
|
||||
const ue = JSON.parse(localStorage.getItem('ui_userExpanded') || '{}');
|
||||
Object.assign(userExpanded, ue && typeof ue === 'object' ? ue : {});
|
||||
} catch {}
|
||||
try {
|
||||
const de = JSON.parse(localStorage.getItem('ui_deviceExpanded') || '{}');
|
||||
Object.assign(deviceExpanded, de && typeof de === 'object' ? de : {});
|
||||
} catch {}
|
||||
await fetchUsers();
|
||||
await fetchDevices();
|
||||
await fetchRequests();
|
||||
setupSse();
|
||||
applyTheme();
|
||||
@@ -218,6 +261,24 @@ const filteredUsers = computed(() => {
|
||||
});
|
||||
});
|
||||
|
||||
const devicesById = computed(() => {
|
||||
const m = {};
|
||||
for (const d of devices.value) m[d.id] = d;
|
||||
return m;
|
||||
});
|
||||
|
||||
function usersForDevice(id) {
|
||||
return users.value.filter(u => Array.isArray(u.dispositivos_utilizados) && u.dispositivos_utilizados.includes(id));
|
||||
}
|
||||
|
||||
// Persist expansion state
|
||||
watch(userExpanded, (v) => {
|
||||
try { localStorage.setItem('ui_userExpanded', JSON.stringify(v)); } catch {}
|
||||
}, { deep: true });
|
||||
watch(deviceExpanded, (v) => {
|
||||
try { localStorage.setItem('ui_deviceExpanded', JSON.stringify(v)); } catch {}
|
||||
}, { deep: true });
|
||||
|
||||
function copyRequests() {
|
||||
const txt = JSON.stringify(requests.value, null, 2);
|
||||
navigator.clipboard?.writeText(txt);
|
||||
|
||||
Reference in New Issue
Block a user