Files
bssapp/ui/src/pages/ActivityLogs.vue
2026-06-03 13:33:15 +03:00

351 lines
8.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<q-page v-if="isAllowed" class="act-page with-bg">
<!-- =======================================================
🔍 FILTER BAR
======================================================= -->
<div class="act-filter-bar">
<div class="act-filter-row">
<q-input
dense
filled
v-model="store.filters.username"
label="Kullanıcı"
clearable
class="act-filter-input"
@keyup.enter="store.fetchLogs()"
/>
<q-select
dense
filled
v-model="store.filters.actionCategory"
:options="categoryOptions"
label="Kategori"
clearable
emit-value
map-options
class="act-filter-input"
@update:model-value="store.fetchLogs()"
/>
<q-select
dense
filled
v-model="store.filters.actionType"
:options="actionTypeOptions"
label="Action"
clearable
emit-value
map-options
class="act-filter-input"
@update:model-value="store.fetchLogs()"
/>
<q-select
dense
filled
v-model="store.filters.success"
:options="successOptions"
label="Sonuç"
clearable
emit-value
map-options
class="act-filter-input"
@update:model-value="store.fetchLogs()"
/>
<q-input
dense
filled
type="date"
v-model="store.filters.dateFrom"
label="Başlangıç"
class="act-filter-input"
@update:model-value="store.fetchLogs()"
/>
<q-input
dense
filled
type="date"
v-model="store.filters.dateTo"
label="Bitiş"
class="act-filter-input"
@update:model-value="store.fetchLogs()"
/>
<div class="act-filter-actions">
<q-btn color="primary" unelevated label="Ara" @click="store.fetchLogs()" />
<q-btn flat label="Temizle" @click="store.resetFilters()" />
<!-- YENİ -->
<q-btn
v-if="canUpdateUser"
outline
color="secondary"
label="Rol Değişimleri"
@click="store.quickRoleChange()"
/>
</div>
</div>
</div>
<!-- =======================================================
📊 LOG TABLE
======================================================= -->
<q-table
class="act-table sticky-table"
row-key="created_at"
:rows="store.rows"
:columns="columns"
:loading="store.loading"
v-model:pagination="store.pagination"
:rows-per-page-options="[250, 500, 1000]"
binary-state-sort
@request="store.onTableRequest"
>
<!-- ================= CHANGE DIFF MODAL ================= -->
<q-dialog v-model="diffDialog">
<q-card style="min-width:700px">
<q-card-section class="text-h6">
Rol Değişiklik Detayı
</q-card-section>
<q-separator />
<q-card-section>
<div class="row q-col-gutter-md">
<div class="col-6">
<div class="text-bold q-mb-sm">Önce</div>
<q-banner class="bg-grey-2 text-black">
<pre>{{ selectedDiff.before }}</pre>
</q-banner>
</div>
<div class="col-6">
<div class="text-bold q-mb-sm">Sonra</div>
<q-banner class="bg-green-1 text-black">
<pre>{{ selectedDiff.after }}</pre>
</q-banner>
</div>
</div>
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="Kapat" v-close-popup />
</q-card-actions>
</q-card>
</q-dialog>
<!-- Zaman -->
<template #body-cell-created_at="props">
<q-td :props="props">
{{ formatDate(props.row.created_at) }}
</q-td>
</template>
<!-- Route -->
<template #body-cell-action_target="props">
<q-td :props="props" class="act-col-route">
{{ props.row.action_target }}
</q-td>
</template>
<!-- HTTP -->
<template #body-cell-http_status="props">
<q-td :props="props">
<q-badge
v-if="props.row.http_status"
:label="props.row.http_status"
:class="props.row.http_status < 300 ? 'act-badge-ok' : 'act-badge-fail'"
/>
</q-td>
</template>
<!-- Diff -->
<template #body-cell-diff="props">
<q-td :props="props">
<q-btn
v-if="props.row.change_before || props.row.change_after"
dense
flat
color="primary"
icon="compare_arrows"
@click="openDiff(props.row)"
/>
<span v-else>-</span>
</q-td>
</template>
<!-- Sonuç -->
<template #body-cell-is_success="props">
<q-td :props="props">
<q-badge
:label="props.row.is_success ? 'OK' : 'FAIL'"
:class="props.row.is_success ? 'act-badge-ok' : 'act-badge-fail'"
/>
</q-td>
</template>
<template #no-data>
<div class="full-width row flex-center q-pa-md text-grey-6">
Log kaydı bulunamadı.
</div>
</template>
</q-table>
</q-page>
<q-page v-else class="q-pa-md flex flex-center">
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
<script setup>
import { ref, computed, onMounted, watch } from 'vue'
import { date } from 'quasar'
import { useActivityLogStore } from 'src/stores/activityLogStore'
import { useAuthStore } from 'stores/authStore.js'
import { usePermission } from 'src/composables/usePermission'
import { isActivityLogsAllowedUser } from 'src/modules/activityLogs'
const { canUpdate } = usePermission()
const canUpdateUser = canUpdate('user')
const diffDialog = ref(false)
const selectedDiff = ref({
before: '',
after: ''
})
const store = useActivityLogStore()
const auth = useAuthStore()
const isAllowed = computed(() => isActivityLogsAllowedUser(auth.user))
const categoryOptions = [
{ label: 'Auth', value: 'auth' },
{ label: 'Navigation', value: 'nav' },
{ label: 'Yetkilendirme (User)', value: 'user_permission' },
{ label: 'Yetkilendirme (Role)', value: 'role_permission' },
{ label: 'Genel Yetki', value: 'permission' }
]
const actionTypeOptions = [
// USER
{
label: 'User Permission Change',
value: 'user_permission_change'
},
// ROLE
{
label: 'Role Permission Change',
value: 'permission_change'
},
{
label: 'Role + Dept Change',
value: 'role_department_permission_change'
},
// AUTH
{
label: 'Login',
value: 'login'
},
{
label: 'Logout',
value: 'logout'
}
]
const successOptions = [
{ label: 'Başarılı', value: true },
{ label: 'Hatalı', value: false }
]
const columns = [
{ name: 'created_at', label: 'Zaman', field: 'created_at', sortable: true },
{ name: 'username', label: 'İşlemi Yapan', field: 'username', sortable: true },
{ name: 'target_username', label: 'Hedef Kullanıcı', field: 'target_username' },
{ name: 'role_code', label: 'Rol', field: 'role_code' },
{ name: 'action_category', label: 'Kategori', field: 'action_category' },
{ name: 'action_type', label: 'Action', field: 'action_type' },
{ name: 'action_target', label: 'Route', field: 'action_target' },
{ name: 'http_status', label: 'HTTP', field: 'http_status' },
{ name: 'duration_ms', label: 'Süre (ms)', field: 'duration_ms' },
// 🔥 field önemli değil ama name = diff şart
{ name: 'diff', label: 'Değişiklik' },
{ name: 'is_success', label: 'Sonuç', field: 'is_success' }
]
function formatDate(v) {
if (!v) return '-'
return date.formatDate(v, 'YYYY-MM-DD HH:mm:ss')
}
/* =======================================================
🔐 TOKEN HAZIR OLDUĞUNDA LOG ÇEK
======================================================= */
function safeFetch() {
if (auth.isAuthenticated && auth.token) {
store.fetchLogs()
}
}
function openDiff (row) {
selectedDiff.value = {
before: pretty(row.change_before),
after: pretty(row.change_after)
}
diffDialog.value = true
}
function pretty (v) {
if (!v) return '-'
try {
return JSON.stringify(JSON.parse(v), null, 2)
} catch {
return v
}
}
onMounted(() => {
safeFetch()
})
watch(
() => auth.token,
(token) => {
if (token) {
safeFetch()
}
},
{ immediate: true }
)
</script>