Files
corrosion-admin-panel/frontend/src/views/admin/WipeCalendarView.vue
Vantz Stockwell 29615cb4f3
All checks were successful
Test Asgard Runner / test (push) Successful in 3s
feat(redesign): re-skin admin-ops/platform-admin/public views to DS (Phase D batch 4 — panel re-skin complete)
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>
2026-06-11 02:55:02 -04:00

243 lines
7.0 KiB
Vue

<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useWipeStore } from '@/stores/wipe'
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()
const currentMonth = ref(new Date())
const monthLabel = computed(() => {
return currentMonth.value.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })
})
const calendarDays = computed(() => {
const year = currentMonth.value.getFullYear()
const month = currentMonth.value.getMonth()
const firstDay = new Date(year, month, 1).getDay()
const daysInMonth = new Date(year, month + 1, 0).getDate()
const days: { date: number; inMonth: boolean; hasWipe: boolean; wipeType?: string }[] = []
// Padding before
for (let i = 0; i < firstDay; i++) {
days.push({ date: 0, inMonth: false, hasWipe: false })
}
// Days of month
for (let d = 1; d <= daysInMonth; d++) {
const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}`
const wipe = wipeStore.history.find(w =>
w.started_at?.startsWith(dateStr) || w.completed_at?.startsWith(dateStr)
)
days.push({
date: d,
inMonth: true,
hasWipe: !!wipe,
wipeType: wipe?.wipe_type,
})
}
return days
})
function prevMonth() {
const d = new Date(currentMonth.value)
d.setMonth(d.getMonth() - 1)
currentMonth.value = d
}
function nextMonth() {
const d = new Date(currentMonth.value)
d.setMonth(d.getMonth() + 1)
currentMonth.value = d
}
const activeSchedules = computed(() => wipeStore.schedules.filter(s => s.is_active))
onMounted(() => {
wipeStore.fetchHistory()
wipeStore.fetchSchedules()
})
</script>
<template>
<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>
<!-- 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>
<!-- Day-of-week headers -->
<div class="wc__dow-row">
<div
v-for="day in ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']"
:key="day"
class="wc__dow"
>{{ day }}</div>
</div>
<!-- Day cells -->
<div class="wc__grid">
<div
v-for="(day, i) in calendarDays"
:key="i"
class="wc__cell"
:class="{ 'wc__cell--out': !day.inMonth }"
>
<template v-if="day.inMonth">
<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>
</Panel>
<!-- 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 activeSchedules"
:key="schedule.id"
class="wc__sched-row"
>
<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>
</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>