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
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:
@@ -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>
|
||||
· {{ 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>
|
||||
|
||||
Reference in New Issue
Block a user