Initial commit - Aurora Admin v1.0

Co-Authored-By: NixReaper <[email protected]>
This commit is contained in:
WPAuroraAdmin
2026-07-26 16:15:24 -05:00
co-authored by NixReaper
commit 7728104a7c
150 changed files with 24005 additions and 0 deletions
@@ -0,0 +1,35 @@
<script setup>
import AuroraCard from '../components/AuroraCard.vue';
defineProps({ count: { type: Number, default: 0 } });
</script>
<template>
<AuroraCard title="Active Users" subtitle="Visitors in the last 5 minutes">
<div class="au">
<span class="au__pulse" />
<div class="au__num">{{ count }}</div>
<div class="au__label">active now</div>
</div>
</AuroraCard>
</template>
<style scoped>
.au {
display: flex; flex-direction: column; align-items: center; justify-content: center;
text-align: center; padding: 24px 0; height: 100%;
}
.au__pulse {
width: 10px; height: 10px; border-radius: 50%;
background: #34d399; margin-bottom: 14px;
box-shadow: 0 0 0 0 rgba(52, 211, 153, 0.6);
animation: au-pulse 1.8s infinite;
}
@keyframes au-pulse {
0% { box-shadow: 0 0 0 0 rgba(52,211,153,0.5); }
70% { box-shadow: 0 0 0 12px rgba(52,211,153,0); }
100% { box-shadow: 0 0 0 0 rgba(52,211,153,0); }
}
.au__num { font-size: 2.5rem; font-weight: 700; color: var(--aurora-text); line-height: 1; }
.au__label { margin-top: 6px; font-size: 0.8125rem; color: var(--aurora-text-muted); }
</style>
@@ -0,0 +1,18 @@
<script setup>
import { computed } from 'vue';
import AuroraCard from '../components/AuroraCard.vue';
import BarList from '../components/BarList.vue';
const props = defineProps({ items: { type: Array, default: () => [] } });
const cap = (s) => (s ? s.charAt(0).toUpperCase() + s.slice(1) : s);
const list = computed(() =>
props.items.map((d) => ({ label: `${cap(d.device)} · ${d.pct}%`, count: d.count }))
);
</script>
<template>
<AuroraCard title="Device Usage" subtitle="How visitors browse">
<BarList :items="list" empty-text="No visits in this range yet." />
</AuroraCard>
</template>
@@ -0,0 +1,18 @@
<script setup>
import AuroraCard from '../components/AuroraCard.vue';
import StatRow from '../components/StatRow.vue';
defineProps({ bounce: { type: Object, default: () => ({}) } });
</script>
<template>
<AuroraCard title="Engagement" subtitle="Bounce rate & time on site">
<StatRow
:stats="[
{ value: (bounce.rate || 0) + '%', label: 'Bounce Rate' },
{ value: bounce.avgTime || '0s', label: 'Avg. Time' },
{ value: bounce.sessions || 0, label: 'Sessions' },
]"
/>
</AuroraCard>
</template>
@@ -0,0 +1,61 @@
<script setup>
import AuroraCard from '../components/AuroraCard.vue';
defineProps({ data: { type: Object, default: () => ({}) } });
</script>
<template>
<AuroraCard title="Media Analytics" subtitle="Media library overview">
<div class="ma__top">
<div class="ma__big">
<div class="ma__big-num">{{ data.totalFiles || 0 }}</div>
<div class="ma__big-label">Total Files</div>
</div>
<div class="ma__big">
<div class="ma__big-num">{{ data.totalSize || '0 B' }}</div>
<div class="ma__big-label">Total Size</div>
</div>
</div>
<div class="ma__grid">
<div class="ma__box">
<div class="ma__box-num">{{ data.recent30d || 0 }}</div>
<div class="ma__box-label">Recent (30d)</div>
</div>
<div class="ma__box">
<div class="ma__box-num">{{ data.unused || 0 }}</div>
<div class="ma__box-label">Unused</div>
</div>
<div class="ma__box">
<div class="ma__box-num">{{ data.largeFiles || 0 }}</div>
<div class="ma__box-label">Large Files</div>
</div>
<div class="ma__box">
<div class="ma__box-num">{{ data.diskUsage || '0 B' }}</div>
<div class="ma__box-label">Disk Usage</div>
</div>
</div>
<template #footer>
<a :href="data.manageUrl" class="viewall">Manage Media ›</a>
</template>
</AuroraCard>
</template>
<style scoped>
.ma__top {
display: grid; grid-template-columns: 1fr 1fr; text-align: center;
margin-bottom: 18px;
}
.ma__big-num { font-size: 1.75rem; font-weight: 700; color: var(--aurora-text); }
.ma__big-label { margin-top: 4px; font-size: 0.75rem; color: var(--aurora-text-muted); }
.ma__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.ma__box {
background: var(--aurora-bg-subtle);
border-radius: var(--aurora-radius-md);
padding: 14px;
text-align: center;
}
.ma__box-num { font-size: 1.25rem; font-weight: 700; color: var(--aurora-text); }
.ma__box-label { margin-top: 2px; font-size: 0.6875rem; color: var(--aurora-text-muted); }
</style>
@@ -0,0 +1,26 @@
<script setup>
import AuroraCard from '../components/AuroraCard.vue';
import StatRow from '../components/StatRow.vue';
import AreaChart from '../components/AreaChart.vue';
defineProps({ data: { type: Object, default: () => ({}) } });
</script>
<template>
<AuroraCard title="Page Views" subtitle="Traffic over the selected range">
<StatRow
:boxed="false"
:stats="[
{ value: data.total || 0, label: 'Total Views' },
{ value: data.unique || 0, label: 'Unique Visitors' },
]"
/>
<div class="pv__chart">
<AreaChart :series="data.series || []" :height="150" />
</div>
</AuroraCard>
</template>
<style scoped>
.pv__chart { margin-top: 20px; }
</style>
@@ -0,0 +1,43 @@
<script setup>
import AuroraCard from '../components/AuroraCard.vue';
import StatRow from '../components/StatRow.vue';
defineProps({ data: { type: Object, default: () => ({}) } });
</script>
<template>
<AuroraCard title="Recent Comments" subtitle="Latest comments and discussions">
<template #head-right>
<span class="dot dot--accent" /> {{ data.total || 0 }} total
</template>
<StatRow
:stats="[
{ value: data.approved || 0, label: 'Approved' },
{ value: data.pending || 0, label: 'Pending' },
{ value: data.spam || 0, label: 'Spam' },
]"
/>
<ul class="cm__list">
<li v-for="c in data.latest" :key="c.id" class="cm__item">
<p class="cm__excerpt">{{ c.excerpt }}</p>
<div class="cm__meta">on <span class="cm__post">{{ c.postTitle }}</span> · {{ c.date }}</div>
</li>
<li v-if="!data.latest || !data.latest.length" class="cm__empty">No comments yet.</li>
</ul>
<template #footer>
<a :href="data.listUrl" class="viewall">View all ›</a>
</template>
</AuroraCard>
</template>
<style scoped>
.cm__list { list-style: none; margin: 16px 0 0; padding: 0; }
.cm__item { padding: 8px 0; }
.cm__excerpt { margin: 0; font-size: 0.8125rem; color: var(--aurora-text); }
.cm__meta { margin-top: 4px; font-size: 0.75rem; color: var(--aurora-text-muted); }
.cm__post { color: var(--aurora-accent); }
.cm__empty { font-size: 0.8125rem; color: var(--aurora-text-muted); padding: 12px 0; }
</style>
@@ -0,0 +1,72 @@
<script setup>
import AuroraCard from '../components/AuroraCard.vue';
import StatRow from '../components/StatRow.vue';
const props = defineProps({ data: { type: Object, default: () => ({}) } });
const statusLabel = (s) =>
({ publish: 'Published', draft: 'Draft', future: 'Scheduled' }[s] || s);
</script>
<template>
<AuroraCard title="Recent content" subtitle="Latest content activity">
<template #head-right>
<span class="dot dot--accent" /> {{ data.total || 0 }} total
</template>
<StatRow
:stats="[
{ value: data.published || 0, label: 'Published' },
{ value: data.draft || 0, label: 'Draft' },
{ value: data.scheduled || 0, label: 'Scheduled' },
]"
/>
<ul class="rc__list">
<li v-for="p in data.latest" :key="p.id" class="rc__item">
<div class="rc__main">
<a :href="p.editUrl" class="rc__title">{{ p.title || '(no title)' }}</a>
<div class="rc__meta">{{ p.author }} · {{ p.date }}</div>
</div>
<span class="rc__badge" :class="`rc__badge--${p.status}`">
{{ statusLabel(p.status) }}
</span>
</li>
<li v-if="!data.latest || !data.latest.length" class="rc__empty">No content yet.</li>
</ul>
<template #footer>
<a :href="data.listUrl" class="viewall">View all ›</a>
</template>
</AuroraCard>
</template>
<style scoped>
.rc__list { list-style: none; margin: 16px 0 0; padding: 0; }
.rc__item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 8px 0;
}
.rc__title {
color: var(--aurora-text);
text-decoration: none;
font-size: 0.875rem;
font-weight: 500;
}
.rc__title:hover { color: var(--aurora-accent); }
.rc__meta { font-size: 0.75rem; color: var(--aurora-text-muted); margin-top: 2px; }
.rc__badge {
font-size: 0.6875rem;
font-weight: 600;
padding: 2px 8px;
border-radius: 999px;
flex-shrink: 0;
}
.rc__badge--publish { color: #34d399; }
.rc__badge--future { color: var(--aurora-accent); }
.rc__badge--draft { color: var(--aurora-text-muted); }
.rc__empty { font-size: 0.8125rem; color: var(--aurora-text-muted); padding: 12px 0; }
</style>
@@ -0,0 +1,169 @@
<script setup>
import { ref, computed } from 'vue';
import AuroraCard from '../components/AuroraCard.vue';
const props = defineProps({ data: { type: Object, default: () => ({}) } });
const view = ref(new Date());
const monthLabel = computed(() =>
view.value.toLocaleDateString(undefined, { month: 'long', year: 'numeric' })
);
const weekdays = ['M', 'T', 'W', 'T', 'F', 'S', 'S'];
// Build a calendar grid (Mon-first) for the viewed month.
const grid = computed(() => {
const year = view.value.getFullYear();
const month = view.value.getMonth();
const first = new Date(year, month, 1);
const startOffset = (first.getDay() + 6) % 7; // Mon = 0
const daysInMonth = new Date(year, month + 1, 0).getDate();
const cells = [];
for (let i = 0; i < startOffset; i++) cells.push(null);
const today = new Date();
for (let d = 1; d <= daysInMonth; d++) {
const key = `${year}-${String(month + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
const scheduled = props.data.days && props.data.days[key] ? props.data.days[key].length : 0;
const isToday =
d === today.getDate() && month === today.getMonth() && year === today.getFullYear();
cells.push({ d, key, scheduled, isToday });
}
return cells;
});
const move = (delta) => {
const next = new Date(view.value);
next.setMonth(next.getMonth() + delta);
view.value = next;
};
// Clicking a day: with scheduled content, open the posts list filtered to
// that day; otherwise show a transient popup on the dashboard.
const toast = ref('');
let toastTimer;
const onDayClick = (cell) => {
if (!cell) return;
if (cell.scheduled > 0) {
// Relative to /wp-admin/ (the dashboard is at index.php).
window.location.href = `edit.php?post_status=future&aurora_day=${cell.key}`;
} else {
toast.value = 'No scheduled content for this day.';
clearTimeout(toastTimer);
toastTimer = setTimeout(() => (toast.value = ''), 3000);
}
};
</script>
<template>
<AuroraCard title="Scheduled Content" :subtitle="monthLabel">
<template #head-right>
<div class="sc__nav">
<button type="button" @click="move(-1)" aria-label="Previous month">‹</button>
<button type="button" @click="move(1)" aria-label="Next month">›</button>
</div>
</template>
<div class="sc__count">
<span class="sc__count-num">{{ data.count || 0 }}</span> Scheduled Content
</div>
<div class="sc__weekdays">
<span v-for="(w, i) in weekdays" :key="i">{{ w }}</span>
</div>
<div class="sc__grid">
<div v-for="(cell, i) in grid" :key="i" class="sc__cell">
<button
v-if="cell"
type="button"
class="sc__day"
:class="{ 'sc__day--has': cell.scheduled > 0, 'sc__day--today': cell.isToday }"
:title="cell.scheduled > 0 ? `${cell.scheduled} scheduled` : 'No scheduled content'"
@click="onDayClick(cell)"
>
<span class="sc__date">{{ cell.d }}</span>
<span v-if="cell.scheduled > 0" class="sc__count">{{ cell.scheduled }}</span>
</button>
</div>
</div>
<Teleport to="body">
<Transition name="sc-toast">
<div v-if="toast" class="sc__toast">{{ toast }}</div>
</Transition>
</Teleport>
</AuroraCard>
</template>
<style scoped>
.sc__nav { display: flex; gap: 4px; }
.sc__nav button {
width: 26px; height: 26px;
border: 1px solid var(--aurora-card-border);
background: var(--aurora-bg-subtle);
color: var(--aurora-text-muted);
border-radius: var(--aurora-radius-sm);
cursor: pointer;
line-height: 1;
}
.sc__nav button:hover { color: var(--aurora-text); }
.sc__count { font-size: 0.875rem; color: var(--aurora-text-muted); margin-bottom: 16px; }
.sc__count-num { font-size: 1.5rem; font-weight: 700; color: var(--aurora-text); margin-right: 6px; }
.sc__weekdays {
display: grid; grid-template-columns: repeat(7, 1fr);
font-size: 0.6875rem; color: var(--aurora-text-muted); margin-bottom: 8px;
}
.sc__weekdays span { text-align: center; }
.sc__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; }
.sc__cell { aspect-ratio: 1; }
.sc__day {
position: relative;
width: 100%; height: 100%;
display: flex; align-items: center; justify-content: center;
border-radius: var(--aurora-radius-sm);
border: 1px solid var(--aurora-card-border);
background: transparent;
padding: 0;
cursor: pointer;
font-family: inherit;
transition: border-color 0.12s ease, background-color 0.12s ease;
}
.sc__day:hover { border-color: var(--aurora-accent); }
.sc__day--has:hover { filter: brightness(1.08); }
.sc__date {
position: absolute; top: 5px; left: 6px;
font-size: 0.7rem; line-height: 1; color: var(--aurora-text-muted);
}
.sc__count {
font-size: 1.05rem; font-weight: 700; color: var(--aurora-text);
}
.sc__day--has { background: var(--aurora-accent); border-color: var(--aurora-accent); }
.sc__day--has .sc__date { color: rgba(255, 255, 255, 0.85); }
.sc__day--has .sc__count { color: #fff; }
.sc__day--today { border-color: var(--aurora-accent); }
.sc__day--today .sc__date { color: var(--aurora-accent); }
</style>
<style>
/* Toast is teleported to <body>, so these rules are global (not scoped).
They rely on the global --aurora-* theme variables. */
.sc__toast {
position: fixed;
bottom: 24px;
left: 50%;
transform: translateX(-50%);
z-index: 100001;
background: var(--aurora-card-bg, #1f1f23);
color: var(--aurora-text, #f4f4f5);
border: 1px solid var(--aurora-border, #2e2e33);
border-radius: var(--aurora-radius-md);
padding: 12px 18px;
font-size: 0.875rem;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
box-shadow: 0 8px 28px rgba(0, 0, 0, 0.35);
}
.sc-toast-enter-active,
.sc-toast-leave-active { transition: opacity 0.2s ease, transform 0.2s ease; }
.sc-toast-enter-from,
.sc-toast-leave-to { opacity: 0; transform: translate(-50%, 10px); }
</style>
@@ -0,0 +1,71 @@
<script setup>
import AuroraCard from '../components/AuroraCard.vue';
import StatRow from '../components/StatRow.vue';
import ProgressBar from '../components/ProgressBar.vue';
defineProps({ data: { type: Object, default: () => ({}) } });
</script>
<template>
<AuroraCard title="Server Health" subtitle="System performance and updates">
<template #head-right>
<span class="dot dot--green" /> {{ data.updates || 0 }} updates
</template>
<div class="sh__versions">
<div class="sh__ver">
<div class="sh__ver-label">‹› WordPress</div>
<div class="sh__ver-num">{{ data.wp || '—' }}</div>
</div>
<div class="sh__ver">
<div class="sh__ver-label">⚙ PHP</div>
<div class="sh__ver-num">{{ data.php || '—' }}</div>
</div>
</div>
<div class="sh__bars">
<ProgressBar
label="Memory Usage"
icon="◍"
:pct="data.memory ? data.memory.pct : 0"
:detail="data.memory ? `${data.memory.used} / ${data.memory.total}` : ''"
/>
<ProgressBar
label="Disk Space"
icon="▤"
gradient
:pct="data.disk ? data.disk.pct : 0"
:detail="data.disk ? `${data.disk.used} / ${data.disk.total}` : ''"
/>
</div>
<StatRow
:stats="[
{ value: data.plugins || 0, label: 'Plugins' },
{ value: data.themes || 0, label: 'Themes' },
{ value: data.core || 0, label: 'Core' },
]"
/>
<div class="sh__foot">
<span>{{ data.ssl ? '🔒 SSL enabled' : '⚠ SSL disabled' }}</span>
<span>🕓 Timezone: {{ data.timezone || 'UTC' }}</span>
</div>
</AuroraCard>
</template>
<style scoped>
.sh__versions { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 14px; }
.sh__ver {
background: var(--aurora-bg-subtle);
border-radius: var(--aurora-radius-md);
padding: 12px 14px;
}
.sh__ver-label { font-size: 0.6875rem; color: var(--aurora-text-muted); }
.sh__ver-num { font-size: 1rem; font-weight: 600; color: var(--aurora-text); margin-top: 2px; }
.sh__bars { display: flex; flex-direction: column; gap: 12px; margin-bottom: 16px; }
.sh__foot {
display: flex; justify-content: space-between;
margin-top: 16px; font-size: 0.6875rem; color: var(--aurora-text-muted);
}
</style>
+17
View File
@@ -0,0 +1,17 @@
<script setup>
import AuroraCard from '../components/AuroraCard.vue';
import BarList from '../components/BarList.vue';
defineProps({
title: { type: String, required: true },
subtitle: { type: String, default: '' },
items: { type: Array, default: () => [] },
emptyText: { type: String, default: 'No data in this range yet.' },
});
</script>
<template>
<AuroraCard :title="title" :subtitle="subtitle">
<BarList :items="items" :empty-text="emptyText" />
</AuroraCard>
</template>
@@ -0,0 +1,27 @@
<script setup>
import AuroraCard from '../components/AuroraCard.vue';
import StatRow from '../components/StatRow.vue';
import AreaChart from '../components/AreaChart.vue';
defineProps({ data: { type: Object, default: () => ({}) } });
</script>
<template>
<AuroraCard title="User Analytics" subtitle="User registration trends">
<StatRow
:boxed="false"
:stats="[
{ value: data.total || 0, label: 'Total Users' },
{ value: data.inRange || 0, label: 'In Range' },
{ value: data.recent7d || 0, label: 'Recent (7d)' },
]"
/>
<div class="ua__chart">
<AreaChart :series="data.series || []" :height="150" />
</div>
</AuroraCard>
</template>
<style scoped>
.ua__chart { margin-top: 20px; }
</style>