351 lines
8.6 KiB
Vue
351 lines
8.6 KiB
Vue
<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>
|