feat(redesign): re-skin admin-ops/platform-admin/public views to DS (Phase D batch 4 — panel re-skin complete)
All checks were successful
Test Asgard Runner / test (push) Successful in 3s

Final re-skin batch: admin ops (Console/FileManager[VueFinder preserved]/WipeCalendar/WipeHistory/Changelog/Migration), platform-admin (Dashboard/Licenses/Servers/Subscriptions/Users), public product pages (ServerInfo/StatusPage/StoreView) + PublicLayout, WarpEditor, ErrorBoundary. All logic/store/router/WebSocket/handlers preserved. Marketing views (Landing/Pricing/FAQ/HowItWorks/Roadmap/EarlyAccess + MarketingLayout) intentionally deferred to the dedicated marketing-site redesign. Build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Vantz Stockwell
2026-06-11 02:55:02 -04:00
parent 376ed9a98d
commit 29615cb4f3
17 changed files with 2843 additions and 1301 deletions

View File

@@ -1,7 +1,11 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useWipeStore } from '@/stores/wipe'
import { Calendar, ChevronLeft, ChevronRight } from 'lucide-vue-next'
import Panel from '@/components/ds/data/Panel.vue'
import Button from '@/components/ds/core/Button.vue'
import Badge from '@/components/ds/core/Badge.vue'
import Icon from '@/components/ds/core/Icon.vue'
import EmptyState from '@/components/ds/feedback/EmptyState.vue'
const wipeStore = useWipeStore()
@@ -53,6 +57,8 @@ function nextMonth() {
currentMonth.value = d
}
const activeSchedules = computed(() => wipeStore.schedules.filter(s => s.is_active))
onMounted(() => {
wipeStore.fetchHistory()
wipeStore.fetchSchedules()
@@ -60,79 +66,177 @@ onMounted(() => {
</script>
<template>
<div class="p-6 space-y-6">
<!-- Header -->
<div class="flex items-center gap-3">
<Calendar class="w-5 h-5 text-oxide-500" />
<h1 class="text-2xl font-bold text-neutral-100">Wipe Calendar</h1>
<div class="wc">
<!-- Page head -->
<div class="wc__head">
<div class="wc__head-id">
<div class="wc__head-chip">
<Icon name="calendar" :size="20" :stroke-width="2" />
</div>
<div>
<div class="t-eyebrow">Auto-wiper</div>
<h1 class="wc__title">Wipe calendar</h1>
</div>
</div>
</div>
<!-- Month navigation -->
<div class="flex items-center justify-between">
<button @click="prevMonth" class="p-2 text-neutral-400 hover:text-neutral-200 transition-colors">
<ChevronLeft class="w-5 h-5" />
</button>
<h2 class="text-lg font-semibold text-neutral-200">{{ monthLabel }}</h2>
<button @click="nextMonth" class="p-2 text-neutral-400 hover:text-neutral-200 transition-colors">
<ChevronRight class="w-5 h-5" />
</button>
</div>
<!-- Calendar panel -->
<Panel :flush-body="true" title="Wipe calendar">
<template #actions>
<Button variant="ghost" size="sm" icon="chevron-left" @click="prevMonth" />
<span class="wc__month-label">{{ monthLabel }}</span>
<Button variant="ghost" size="sm" icon="chevron-right" @click="nextMonth" />
</template>
<!-- Calendar grid -->
<div class="bg-neutral-900 border border-neutral-800 rounded-lg overflow-hidden">
<!-- Day headers -->
<div class="grid grid-cols-7 border-b border-neutral-800">
<!-- Day-of-week headers -->
<div class="wc__dow-row">
<div
v-for="day in ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']"
:key="day"
class="py-2 text-center text-xs font-medium text-neutral-500 uppercase"
>
{{ day }}
</div>
class="wc__dow"
>{{ day }}</div>
</div>
<!-- Day cells -->
<div class="grid grid-cols-7">
<div class="wc__grid">
<div
v-for="(day, i) in calendarDays"
:key="i"
class="min-h-20 p-2 border-b border-r border-neutral-800 last:border-r-0"
:class="{ 'bg-neutral-800/30': !day.inMonth }"
class="wc__cell"
:class="{ 'wc__cell--out': !day.inMonth }"
>
<template v-if="day.inMonth">
<p class="text-sm" :class="day.hasWipe ? 'text-oxide-400 font-bold' : 'text-neutral-400'">
{{ day.date }}
</p>
<div v-if="day.hasWipe" class="mt-1">
<span class="text-xs bg-oxide-500/15 text-oxide-400 px-1.5 py-0.5 rounded">
{{ day.wipeType }}
</span>
</div>
<span class="wc__date" :class="{ 'wc__date--wipe': day.hasWipe }">{{ day.date }}</span>
<Badge
v-if="day.hasWipe"
tone="warn"
size="md"
class="wc__wipe-badge"
>{{ day.wipeType }}</Badge>
</template>
</div>
</div>
</div>
</Panel>
<!-- Upcoming schedules -->
<div class="bg-neutral-900 border border-neutral-800 rounded-lg p-5">
<h2 class="text-sm font-medium text-neutral-400 uppercase tracking-wider mb-3">Active Schedules</h2>
<div v-if="wipeStore.schedules.length === 0" class="text-sm text-neutral-500 text-center py-4">
No active schedules.
</div>
<div v-else class="space-y-2">
<!-- Active schedules panel -->
<Panel title="Active schedules" subtitle="Cron schedules currently enabled">
<EmptyState
v-if="activeSchedules.length === 0"
icon="calendar-clock"
title="No active schedules"
description="Activate a schedule in the auto-wiper to see it here."
/>
<div v-else class="wc__sched-list">
<div
v-for="schedule in wipeStore.schedules.filter(s => s.is_active)"
v-for="schedule in activeSchedules"
:key="schedule.id"
class="flex items-center justify-between p-3 bg-neutral-800/50 rounded-lg"
class="wc__sched-row"
>
<div>
<p class="text-sm text-neutral-200">{{ schedule.schedule_name }}</p>
<p class="text-xs text-neutral-500 font-mono">{{ schedule.cron_expression }}</p>
<div class="wc__sched-info">
<div class="wc__sched-name">{{ schedule.schedule_name }}</div>
<div class="wc__sched-meta">
<span class="wc__mono">{{ schedule.cron_expression }}</span>
&middot; {{ schedule.timezone }}
</div>
</div>
<div class="wc__sched-next">
Next:
<span class="wc__mono">
{{ schedule.next_scheduled_run ? new Date(schedule.next_scheduled_run).toLocaleDateString() : 'TBD' }}
</span>
</div>
<p class="text-xs text-neutral-400">
Next: {{ schedule.next_scheduled_run ? new Date(schedule.next_scheduled_run).toLocaleDateString() : 'TBD' }}
</p>
</div>
</div>
</div>
</Panel>
</div>
</template>
<style scoped>
.wc {
max-width: 900px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 16px;
}
/* Page head */
.wc__head { display: flex; align-items: center; }
.wc__head-id { display: flex; align-items: center; gap: 12px; }
.wc__head-chip {
width: 40px; height: 40px; flex: none; border-radius: var(--radius-md);
display: flex; align-items: center; justify-content: center;
color: var(--accent); background: var(--accent-soft);
box-shadow: inset 0 0 0 1px var(--accent-border);
}
.wc__title {
font-size: var(--text-2xl); font-weight: 700; letter-spacing: -0.02em;
color: var(--text-primary); margin-top: 3px;
}
.wc__month-label {
font-size: var(--text-sm); font-weight: 600; color: var(--text-primary);
padding: 0 4px; white-space: nowrap;
}
/* Calendar grid */
.wc__dow-row {
display: grid;
grid-template-columns: repeat(7, 1fr);
border-bottom: 1px solid var(--border-subtle);
background: var(--surface-inset);
}
.wc__dow {
padding: 8px 0;
text-align: center;
font-size: var(--text-xs);
font-weight: 600;
color: var(--text-tertiary);
text-transform: uppercase;
letter-spacing: var(--tracking-wider);
}
.wc__grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
}
.wc__cell {
min-height: 80px;
padding: 8px;
border-bottom: 1px solid var(--border-subtle);
border-right: 1px solid var(--border-subtle);
display: flex;
flex-direction: column;
gap: 4px;
}
.wc__cell:nth-child(7n) { border-right: 0; }
.wc__cell--out { background: var(--surface-inset); }
.wc__date {
font-size: var(--text-sm);
color: var(--text-tertiary);
font-variant-numeric: tabular-nums;
}
.wc__date--wipe { color: var(--accent-text); font-weight: 700; }
.wc__wipe-badge { align-self: flex-start; }
/* Active schedules list */
.wc__sched-list { display: flex; flex-direction: column; }
.wc__sched-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 12px 2px;
border-bottom: 1px solid var(--border-subtle);
}
.wc__sched-row:last-child { border-bottom: 0; }
.wc__sched-info { flex: 1; min-width: 0; }
.wc__sched-name { font-size: var(--text-sm); font-weight: 500; color: var(--text-primary); }
.wc__sched-meta { font-size: var(--text-xs); color: var(--text-tertiary); margin-top: 2px; }
.wc__sched-next { font-size: var(--text-xs); color: var(--text-tertiary); flex: none; }
.wc__mono { font-family: var(--font-mono); }
@media (max-width: 600px) {
.wc__cell { min-height: 52px; padding: 4px; }
}
</style>