192 lines
5.7 KiB
Vue
192 lines
5.7 KiB
Vue
<script setup>
|
|
import { onMounted, ref } from 'vue'
|
|
import { useAuth } from '~/composables/useAuth.js'
|
|
|
|
const {
|
|
isAuthenticated,
|
|
isInitialized,
|
|
userProfile,
|
|
initializeAuth,
|
|
logout
|
|
} = useAuth()
|
|
|
|
const route = useRoute()
|
|
|
|
const recentActions = ref([])
|
|
const isLoadingActions = ref(true)
|
|
|
|
const FIELD_LABELS = {
|
|
account_level: 'Access Level',
|
|
country: 'Country',
|
|
language: 'Language',
|
|
timezone: 'Timezone',
|
|
gender: 'Gender',
|
|
email: 'Email',
|
|
marketing_allowed: 'Marketing Allowed',
|
|
off_device_allowed: 'Off-Device Allowed'
|
|
}
|
|
|
|
const formatDate = (isoStr) => {
|
|
if (!isoStr) return 'N/A'
|
|
try {
|
|
return new Date(isoStr).toLocaleString('en-US', {
|
|
dateStyle: 'short',
|
|
timeStyle: 'short'
|
|
})
|
|
} catch {
|
|
return isoStr
|
|
}
|
|
}
|
|
|
|
const parseLogChanges = (rawChanges) => {
|
|
if (!rawChanges) return { changes: [] }
|
|
if (typeof rawChanges === 'string') {
|
|
try {
|
|
return JSON.parse(rawChanges)
|
|
} catch {
|
|
return { changes: [] }
|
|
}
|
|
}
|
|
return rawChanges
|
|
}
|
|
|
|
const fetchRecentActions = async () => {
|
|
isLoadingActions.value = true
|
|
try {
|
|
const response = await $fetch('/api/admin/audit-logs?limit=3&page=1')
|
|
recentActions.value = response.logs || []
|
|
} catch (err) {
|
|
console.error('Failed to fetch recent actions:', err)
|
|
} finally {
|
|
isLoadingActions.value = false
|
|
}
|
|
}
|
|
|
|
const handleLogout = () => {
|
|
logout()
|
|
}
|
|
|
|
const handleNavigate = (path) => {
|
|
navigateTo(path)
|
|
}
|
|
|
|
onMounted(async () => {
|
|
await initializeAuth({ code: route.query.code, redirectTo: '/oauth' })
|
|
if (isAuthenticated.value) {
|
|
await fetchRecentActions()
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<div v-if="!isInitialized" class="global-loader-wrapper">
|
|
<div class="spinner lg"></div>
|
|
<p style="color: #8b949e; margin-top: 1rem; font-size: 0.95rem;">
|
|
Connecting to Keycloak...
|
|
</p>
|
|
</div>
|
|
|
|
<div v-else-if="isAuthenticated" class="settings-container">
|
|
<Header
|
|
:user-profile="userProfile"
|
|
active-tab="welcome"
|
|
@logout="handleLogout"
|
|
@navigate="handleNavigate"
|
|
/>
|
|
|
|
<main class="settings-main-content">
|
|
<section class="settings-panel">
|
|
<div class="panel-header archivo-black">
|
|
<span>PORTAL OVERVIEW</span>
|
|
</div>
|
|
|
|
<div class="panel-body grid-layout">
|
|
<div class="welcome-left-col">
|
|
<div class="image-wrapper">
|
|
<img
|
|
src="/welcome7.png"
|
|
alt="Welcome to Mod Portal"
|
|
class="welcome-image"
|
|
/>
|
|
</div>
|
|
<h2 class="welcome-text">
|
|
Welcome to the <em>new</em> mod portal!
|
|
</h2>
|
|
</div>
|
|
|
|
<div class="logins-right-col">
|
|
<div class="logins-card">
|
|
<div class="logins-header archivo-black" style="display: flex; justify-content: space-between; align-items: center;">
|
|
<span>RECENT ACTIONS</span>
|
|
<NuxtLink to="/audit/log" class="view-all-link">View All →</NuxtLink>
|
|
</div>
|
|
|
|
<div v-if="isLoadingActions" class="loading-state">
|
|
<div class="spinner sm"></div>
|
|
<span>Loading recent actions...</span>
|
|
</div>
|
|
|
|
<ul v-else-if="recentActions.length > 0" class="logins-list">
|
|
<li
|
|
v-for="log in recentActions"
|
|
:key="log.log_id || log.id"
|
|
class="login-item action-item"
|
|
@click="handleNavigate(`/audit/log/${log.log_id || log.id}`)"
|
|
>
|
|
<div class="action-top">
|
|
<div class="user-block">
|
|
<img
|
|
:src="log.editor_pid ? `https://mii.spbr.net/${log.editor_pid}/main.png` : '/img_unknown_MiiIcon.png'"
|
|
class="avatar-xs"
|
|
@error="(e) => e.target.src = '/img_unknown_MiiIcon.png'"
|
|
/>
|
|
<span class="editor-name">{{ log.editor_sfid || 'System' }}</span>
|
|
</div>
|
|
<span class="action-time">{{ formatDate(log.created_at) }}</span>
|
|
</div>
|
|
|
|
<div class="action-summary">
|
|
<span class="target-name">Modified {{ log.target_name }}</span>
|
|
|
|
<span v-if="log.ban_level !== undefined && log.ban_level !== null" class="badge-ban">
|
|
Banned
|
|
</span>
|
|
<span v-else class="changes-tags">
|
|
<span
|
|
v-for="change in parseLogChanges(log.changes).changes.slice(0, 2)"
|
|
:key="change.field"
|
|
class="change-chip"
|
|
>
|
|
{{ FIELD_LABELS[change.field] || change.field }}
|
|
</span>
|
|
</span>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
|
|
<div v-else class="empty-state">
|
|
<p>No recent actions logged.</p>
|
|
</div>
|
|
|
|
<div class="card-footer">
|
|
<button
|
|
type="button"
|
|
class="btn-action secondary sm"
|
|
style="width: 100%;"
|
|
@click="handleNavigate('/audit/log')"
|
|
>
|
|
Go to Audit Log Center
|
|
</button>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</section>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|