Initial commit: Aurora Admin plugin
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
<script setup>
|
||||
import { ref, computed, watch, onMounted } from 'vue';
|
||||
import AppListShell from '../_shared/AppListShell.vue';
|
||||
import AppListItem from '../_shared/AppListItem.vue';
|
||||
|
||||
const props = defineProps({
|
||||
serverData: { type: Object, required: true },
|
||||
});
|
||||
|
||||
const api = (path) =>
|
||||
fetch(`${props.serverData.restUrl}aurora-admin/v1${path}`, {
|
||||
headers: { 'X-WP-Nonce': props.serverData.restNonce },
|
||||
}).then((r) => {
|
||||
if (!r.ok) throw new Error('Request failed');
|
||||
return r.json();
|
||||
});
|
||||
|
||||
const ACTION_TYPES = [
|
||||
['', 'All Actions'],
|
||||
['post_created', 'Post created'],
|
||||
['post_updated', 'Post updated'],
|
||||
['post_deleted', 'Post deleted'],
|
||||
['user_registered', 'User registered'],
|
||||
['user_updated', 'User updated'],
|
||||
['login', 'Login'],
|
||||
['plugin_activated', 'Plugin activated'],
|
||||
['plugin_deactivated', 'Plugin deactivated'],
|
||||
['theme_switched', 'Theme switched'],
|
||||
['settings_updated', 'Settings updated'],
|
||||
];
|
||||
const OBJECT_TYPES = [
|
||||
['', 'All Types'],
|
||||
['post', 'Post'],
|
||||
['user', 'User'],
|
||||
['plugin', 'Plugin'],
|
||||
['theme', 'Theme'],
|
||||
['settings', 'Settings'],
|
||||
];
|
||||
|
||||
const items = ref([]);
|
||||
const loading = ref(true);
|
||||
const total = ref(0);
|
||||
const page = ref(1);
|
||||
const totalPages = ref(1);
|
||||
const actionFilter = ref('');
|
||||
const objectFilter = ref('');
|
||||
const search = ref('');
|
||||
const selectedId = ref(null);
|
||||
|
||||
const load = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (actionFilter.value) params.set('action_type', actionFilter.value);
|
||||
if (objectFilter.value) params.set('object_type', objectFilter.value);
|
||||
if (search.value.trim()) params.set('search', search.value.trim());
|
||||
params.set('page', page.value);
|
||||
const data = await api(`/activity-log?${params.toString()}`);
|
||||
items.value = data.items;
|
||||
total.value = data.total;
|
||||
totalPages.value = data.totalPages;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(load);
|
||||
watch([actionFilter, objectFilter, search], () => {
|
||||
page.value = 1;
|
||||
load();
|
||||
});
|
||||
watch(page, load);
|
||||
|
||||
const selected = computed(() => items.value.find((i) => i.id === selectedId.value) || null);
|
||||
|
||||
const formatDate = (s) => (s ? new Date(s.replace(' ', 'T')).toLocaleString() : '');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppListShell
|
||||
v-model:search="search"
|
||||
title="Activity Log"
|
||||
search-placeholder="Search activities…"
|
||||
:count-label="`${total} ITEMS`"
|
||||
:page="page"
|
||||
:has-prev="page > 1"
|
||||
:has-next="page < totalPages"
|
||||
detail-icon="dashicons-clock"
|
||||
detail-title="Activity Details"
|
||||
detail-text="Select an activity from the list to view detailed information."
|
||||
:has-selection="!!selected"
|
||||
@prev-page="page--"
|
||||
@next-page="page++"
|
||||
>
|
||||
<template #header-actions>
|
||||
<button type="button" @click="load">
|
||||
<span class="dashicons dashicons-update" />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<template #filters>
|
||||
<select v-model="actionFilter" class="activity-log__select">
|
||||
<option v-for="[v, l] in ACTION_TYPES" :key="v" :value="v">{{ l }}</option>
|
||||
</select>
|
||||
<select v-model="objectFilter" class="activity-log__select">
|
||||
<option v-for="[v, l] in OBJECT_TYPES" :key="v" :value="v">{{ l }}</option>
|
||||
</select>
|
||||
</template>
|
||||
|
||||
<template #list>
|
||||
<p v-if="!loading && !items.length" style="font-size: 0.8125rem; color: var(--aurora-text-muted); padding: 20px 4px; text-align: center;">
|
||||
No activities found
|
||||
</p>
|
||||
<AppListItem
|
||||
v-for="i in items"
|
||||
:key="i.id"
|
||||
icon="dashicons-clock"
|
||||
:title="i.objectLabel || i.actionType"
|
||||
:subtitle="`${i.userName || 'System'} · ${formatDate(i.createdAt)}`"
|
||||
:active="i.id === selectedId"
|
||||
@click="selectedId = i.id"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #detail>
|
||||
<div v-if="selected" class="activity-detail">
|
||||
<h2>{{ selected.objectLabel || selected.actionType }}</h2>
|
||||
<dl>
|
||||
<dt>Action</dt><dd>{{ selected.actionType }}</dd>
|
||||
<dt>Object type</dt><dd>{{ selected.objectType }}</dd>
|
||||
<dt>User</dt><dd>{{ selected.userName || 'System' }}</dd>
|
||||
<dt>Date</dt><dd>{{ formatDate(selected.createdAt) }}</dd>
|
||||
</dl>
|
||||
<pre v-if="selected.details">{{ JSON.stringify(selected.details, null, 2) }}</pre>
|
||||
</div>
|
||||
</template>
|
||||
</AppListShell>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.activity-log__select {
|
||||
padding: 5px 10px; border-radius: 8px; border: 1px solid var(--aurora-border);
|
||||
background: var(--aurora-bg-subtle); color: var(--aurora-text); font-size: 0.75rem;
|
||||
}
|
||||
.activity-detail h2 { margin: 0 0 16px; font-size: 1.1rem; color: var(--aurora-text); }
|
||||
.activity-detail dl { display: grid; grid-template-columns: 120px 1fr; gap: 8px 12px; font-size: 0.8125rem; margin: 0; }
|
||||
.activity-detail dt { color: var(--aurora-text-muted); }
|
||||
.activity-detail dd { margin: 0; color: var(--aurora-text); }
|
||||
.activity-detail pre {
|
||||
margin-top: 18px; padding: 12px; background: var(--aurora-bg-subtle);
|
||||
border-radius: 8px; font-size: 0.75rem; overflow-x: auto; color: var(--aurora-text);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user