ilk
This commit is contained in:
341
ui/src/pages/ActivityLogs.vue
Normal file
341
ui/src/pages/ActivityLogs.vue
Normal file
@@ -0,0 +1,341 @@
|
||||
<template>
|
||||
<q-page 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
|
||||
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"
|
||||
binary-state-sort
|
||||
>
|
||||
|
||||
|
||||
<!-- ================= 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>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script setup>
|
||||
import { ref,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'
|
||||
|
||||
const { canRead, canWrite, canUpdate } = usePermission()
|
||||
|
||||
const canReadOrder = canRead('order')
|
||||
const canWriteOrder = canWrite('order')
|
||||
const canUpdateOrder = canUpdate('order')
|
||||
|
||||
const diffDialog = ref(false)
|
||||
|
||||
const selectedDiff = ref({
|
||||
before: '',
|
||||
after: ''
|
||||
})
|
||||
const store = useActivityLogStore()
|
||||
const auth = useAuthStore()
|
||||
|
||||
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>
|
||||
Reference in New Issue
Block a user