Files
biiproject-kit-v1/resources/views/pages/system_settings/session-manager.blade.php
T

247 lines
14 KiB
PHP

<x-app-layout>
<div id="session-manager-root" x-data="{
sessionDetail: { session: { user_name: '', user_email: '', ip_address: '', id: '' }, device: { browser: '', browser_icon: '', os: '', os_icon: '' }, time: '', is_current: false },
openSession(btn) {
try {
this.sessionDetail = JSON.parse(btn.dataset.activity);
const modalEl = document.getElementById('sessionDetailModal');
let modal = bootstrap.Modal.getInstance(modalEl);
if (!modal) {
modal = new bootstrap.Modal(modalEl);
}
modal.show();
} catch (e) {
console.error('Error opening session detail:', e);
window.StandardSwal.fire({ title: 'Error', text: 'Could not parse session data.', icon: 'error' });
}
},
async terminate() {
const sid = this.sessionDetail.session.id;
const url = `{{ route('session-manager.terminate', ':id') }}`.replace(':id', sid);
const r = await window.StandardSwal.fire({
title: 'Terminate Connection?',
text: 'Sever all encrypted links for this digital session?',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Yes, Terminate'
});
if (r.isConfirmed) {
const res = await fetch(url, {
method: 'DELETE',
headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}', 'Accept': 'application/json' }
});
const data = await res.json();
if (data.success) {
bootstrap.Modal.getInstance(document.getElementById('sessionDetailModal')).hide();
window.reloadDataTable?.();
window.StandardSwal.fire({ title: 'Severed', text: data.message, icon: 'success', timer: 1500, showConfirmButton: false });
}
}
},
copyMetadata() {
navigator.clipboard.writeText(JSON.stringify(this.sessionDetail.session, null, 2));
window.StandardSwal.fire({ title: 'Copied!', text: 'Session metadata context copied.', icon: 'success', timer: 1500, showConfirmButton: false });
}
}">
<div class="container-fluid" id="main-content">
<div class="row gx-3 gx-lg-4">
<div class="col-12">
<div class="card adminuiux-card">
<div class="card-body p-0">
{{-- Header Section --}}
<div class="p-4 pb-0">
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<h5 class="mb-0 fw-bold">{{ __('Session Manager') }}</h5>
<small class="text-muted">
{{ __('Real-time monitoring and management of authenticated user sessions.') }}
</small>
</div>
<button @click="window.reloadDataTable?.()" class="btn btn-outline-secondary btn-sm rounded-pill px-3">
<i class="bi bi-arrow-clockwise me-1"></i> {{ __('Refresh') }}
</button>
</div>
</div>
{{-- Table Section --}}
<div class="p-4">
<div class="table-responsive">
<table id="datatables" class="table table-hover table-bordered w-100 nowrap mb-0"
data-server-side="true" data-ajax-url="{{ route('session-manager') }}"
data-order='@json([[4, "desc"]])'>
<thead>
<tr>
<th class="text-wrap">{{ __('Status') }}</th>
<th class="text-wrap">{{ __('Identity Profile') }}</th>
<th class="text-wrap">{{ __('Device') }}</th>
<th class="text-wrap">{{ __('IP Address') }}</th>
<th class="text-wrap">{{ __('Last Activity') }}</th>
<th class="text-wrap text-end" data-orderable="false" data-searchable="false">{{ __('Action') }}</th>
</tr>
{{-- Filter Row --}}
<tr class="filter-row">
<th>
<select class="form-select form-select-sm">
<option value="">{{ __('All') }}</option>
<option value="active">{{ __('Active') }}</option>
<option value="ended">{{ __('Idle') }}</option>
</select>
</th>
<th><input class="form-control form-control-sm" placeholder="{{ __('Search Identity') }}"></th>
<th><input class="form-control form-control-sm" placeholder="{{ __('Search Device') }}"></th>
<th><input class="form-control form-control-sm" placeholder="{{ __('IP Trace') }}"></th>
<th><input class="form-control form-control-sm" placeholder="{{ __('Search Time') }}"></th>
<th></th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{{-- Session Diagnostic Modal (Bootstrap) --}}
<div class="modal fade" id="sessionDetailModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content rounded-4 border-0 shadow-lg">
<div class="modal-header border-bottom-0 p-4">
<div class="d-flex align-items-center gap-3">
<div>
<h5 class="modal-title fw-bold mb-0">{{ __('Session Diagnostics') }}</h5>
<small class="text-uppercase text-muted fw-black ls-1" style="font-size: 10px;">{{ __('End-to-End Security Audit') }}</small>
</div>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body p-4 pt-0">
<div class="row g-4">
<div class="col-md-6">
<label class="ls-1 text-uppercase text-muted fw-bold mb-2" style="font-size: 10px;">{{ __('Identity Context') }}</label>
<div class="p-3 bg-light rounded-3 border">
<div class="fw-bold text-dark" x-text="sessionDetail.session.user_name || 'Guest Observer'"></div>
<div class="small text-muted" x-text="sessionDetail.session.user_email || 'anonymous'"></div>
</div>
</div>
<div class="col-md-6">
<label class="ls-1 text-uppercase text-muted fw-bold mb-2 d-block text-md-end" style="font-size: 10px;">{{ __('Temporal Sync') }}</label>
<div class="p-3 bg-light rounded-3 border text-md-end">
<div class="fw-bold text-dark" x-text="sessionDetail.time"></div>
<div class="small text-muted text-uppercase ls-1" style="font-size: 10px;">Last Pulse Detected</div>
</div>
</div>
<div class="col-12">
<label class="ls-1 text-uppercase text-muted fw-bold mb-2" style="font-size: 10px;">{{ __('Connected Node Details') }}</label>
<div class="row g-3">
<div class="col-md-4">
<div class="card card-body text-center py-4 rounded-4 border">
<i class="bi fs-2 mb-2" :class="sessionDetail.device.browser_icon"></i>
<div class="fw-black text-uppercase ls-1" style="font-size: 10px;" x-text="sessionDetail.device.browser"></div>
</div>
</div>
<div class="col-md-4">
<div class="card card-body text-center py-4 rounded-4 border">
<i class="bi fs-2 mb-2" :class="sessionDetail.device.os_icon"></i>
<div class="fw-black text-uppercase ls-1" style="font-size: 10px;" x-text="sessionDetail.device.os"></div>
</div>
</div>
<div class="col-md-4">
<div class="card card-body text-center py-4 rounded-4 border">
<i class="bi bi-broadcast fs-2 mb-2"></i>
<div class="fw-black text-uppercase ls-1" style="font-size: 10px;" x-text="sessionDetail.session.ip_address"></div>
</div>
</div>
</div>
</div>
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-2">
<label class="ls-1 text-uppercase text-muted fw-bold" style="font-size: 10px;">{{ __('Raw Metadata Payload') }}</label>
<a href="javascript:void(0)" @click="copyMetadata" class="text-decoration-none fw-bold ls-1 text-uppercase" style="font-size: 10px;">Copy manifest</a>
</div>
<div class="bg-dark rounded-4 p-4 shadow-inner">
<pre class="text-success m-0 fw-mono small overflow-auto no-scrollbar" style="max-height: 200px;" x-text="JSON.stringify(sessionDetail.session, null, 2)"></pre>
</div>
</div>
</div>
</div>
<div class="modal-footer border-top-0 p-4">
<button type="button" class="btn btn-outline-dark rounded-pill px-4" data-bs-dismiss="modal">Close</button>
@can('manage active sessions')
<template x-if="!sessionDetail.is_current">
<button type="button" @click="terminate" class="btn btn-danger rounded-pill px-4 shadow-sm">Terminate Session</button>
</template>
@endcan
</div>
</div>
</div>
</div>
</div>
@push('scripts')
<script>
document.addEventListener("DOMContentLoaded", () => {
const tableElement = $('#datatables');
window.reloadDataTable = () => tableElement.DataTable().ajax.reload(null, false);
$(document).on("click", ".btn-detail-session", function() {
const root = document.getElementById('session-manager-root');
if (root && window.Alpine) {
const alpine = window.Alpine.$data(root);
alpine.openSession(this);
}
});
$(document).on("click", ".btn-terminate-session", async function() {
const id = $(this).data('id');
const url = $(this).data('url');
const r = await window.StandardSwal.fire({
title: 'Terminate Connection?',
text: 'Sever all encrypted links for this digital session?',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Yes, Terminate'
});
if (r.isConfirmed) {
const res = await fetch(url, { method: 'DELETE', headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}', 'Accept': 'application/json' } });
const data = await res.json();
if (data.success) {
window.reloadDataTable();
window.StandardSwal.fire({ title: 'Severed', text: data.message, icon: 'success', timer: 1500, showConfirmButton: false });
}
}
});
tableElement.on('draw.dt', function() {
tableElement.find('tbody tr').each(function() {
const rowData = tableElement.DataTable().row(this).data();
if (rowData && rowData[6] === true) {
$(this).addClass('table-success-light');
}
});
});
});
</script>
<style>
.table-success-light { background-color: rgba(25, 135, 84, 0.05) !important; border-left: 4px solid #198754 !important; }
.ls-1 { letter-spacing: 0.1em; }
.fw-black { font-weight: 900; }
.extra-small { font-size: 10px; }
pre::-webkit-scrollbar { width: 4px; height: 4px; }
pre::-webkit-scrollbar-thumb { background: rgba(25, 135, 84, 0.3); border-radius: 4px; }
</style>
@endpush
</x-app-layout>