main
vue 204 lines 5.11 KB
Raw
1 <template>
2 <div class="flex flex-col gap-4">
3 <div class="flex items-center justify-between">
4 <h2 class="text-lg font-semibold">Snapshots</h2>
5 <div class="flex items-center gap-2">
6 <n-select
7 v-model:value="selectedRepository"
8 :options="repositoryOptions"
9 placeholder="Select Repository"
10 style="width: 200px"
11 @update:value="fetchSnapshots"
12 />
13 <n-button type="primary" :disabled="!selectedRepository" @click="showCreateModal = true">
14 <template #icon>
15 <Icon :name="AddIcon" :size="16" />
16 </template>
17 Create Snapshot
18 </n-button>
19 </div>
20 </div>
21
22 <n-spin :show="loading">
23 <n-card>
24 <n-data-table
25 :columns
26 :data="snapshots"
27 :bordered="false"
28 :single-line="false"
29 size="small"
30 :row-key="(row: SnapshotInfo) => row.snapshot"
31 />
32 </n-card>
33 </n-spin>
34
35 <n-empty v-if="!loading && !selectedRepository" description="Select a repository to view snapshots" />
36 <n-empty v-else-if="!loading && snapshots.length === 0" description="No snapshots found in this repository" />
37
38 <!-- Create Snapshot Modal -->
39 <n-modal v-model:show="showCreateModal" preset="dialog" title="Create Snapshot">
40 <CreateSnapshotForm
41 :repository="selectedRepository"
42 @success="onSnapshotCreated"
43 @cancel="showCreateModal = false"
44 />
45 </n-modal>
46
47 <!-- Restore Snapshot Modal -->
48 <n-modal v-model:show="showRestoreModal" preset="dialog" title="Restore Snapshot">
49 <RestoreSnapshotForm
50 :repository="selectedRepository"
51 :snapshot="selectedSnapshot"
52 @success="onSnapshotRestored"
53 @cancel="showRestoreModal = false"
54 />
55 </n-modal>
56 </div>
57 </template>
58
59 <script setup lang="ts">
60 // TODO-FE: refactor
61 import type { DataTableColumns, SelectOption } from "naive-ui"
62 import type { SnapshotInfo, SnapshotRepository } from "@/types/snapshots.d"
63 import { NButton, NCard, NDataTable, NEmpty, NModal, NSelect, NSpin, NTag, useMessage } from "naive-ui"
64 import { computed, h, onBeforeMount, ref } from "vue"
65 import Api from "@/api"
66 import Icon from "@/components/common/Icon.vue"
67 import CreateSnapshotForm from "./CreateSnapshotForm.vue"
68 import RestoreSnapshotForm from "./RestoreSnapshotForm.vue"
69
70 const AddIcon = "carbon:add"
71
72 const message = useMessage()
73 const loading = ref(false)
74 const repositories = ref<SnapshotRepository[]>([])
75 const selectedRepository = ref<string | null>(null)
76 const snapshots = ref<SnapshotInfo[]>([])
77 const showCreateModal = ref(false)
78 const showRestoreModal = ref(false)
79 const selectedSnapshot = ref<SnapshotInfo | null>(null)
80
81 const repositoryOptions = computed<SelectOption[]>(() =>
82 repositories.value.map(repo => ({
83 label: repo.name,
84 value: repo.name
85 }))
86 )
87
88 const columns: DataTableColumns<SnapshotInfo> = [
89 {
90 title: "Snapshot",
91 key: "snapshot",
92 sorter: "default"
93 },
94 {
95 title: "State",
96 key: "state",
97 render(row) {
98 const typeMap: Record<string, "success" | "warning" | "error" | "info"> = {
99 SUCCESS: "success",
100 IN_PROGRESS: "warning",
101 PARTIAL: "warning",
102 FAILED: "error"
103 }
104 return h(NTag, { type: typeMap[row.state] || "info", size: "small" }, () => row.state)
105 }
106 },
107 {
108 title: "Indices",
109 key: "indices",
110 render(row) {
111 return h("span", {}, `${row.indices.length} indices`)
112 }
113 },
114 {
115 title: "Start Time",
116 key: "start_time",
117 render(row) {
118 return row.start_time ? new Date(row.start_time).toLocaleString() : "-"
119 }
120 },
121 {
122 title: "End Time",
123 key: "end_time",
124 render(row) {
125 return row.end_time ? new Date(row.end_time).toLocaleString() : "-"
126 }
127 },
128 {
129 title: "Duration",
130 key: "duration_in_millis",
131 render(row) {
132 if (!row.duration_in_millis) return "-"
133 const seconds = Math.floor(row.duration_in_millis / 1000)
134 if (seconds < 60) return `${seconds}s`
135 const minutes = Math.floor(seconds / 60)
136 return `${minutes}m ${seconds % 60}s`
137 }
138 },
139 {
140 title: "Actions",
141 key: "actions",
142 render(row) {
143 return h(
144 NButton,
145 {
146 size: "small",
147 type: "primary",
148 onClick: () => openRestoreModal(row)
149 },
150 () => "Restore"
151 )
152 }
153 }
154 ]
155
156 function openRestoreModal(snapshot: SnapshotInfo) {
157 selectedSnapshot.value = snapshot
158 showRestoreModal.value = true
159 }
160
161 async function fetchRepositories() {
162 try {
163 const response = await Api.snapshots.getRepositories()
164 if (response.data.success) {
165 repositories.value = response.data.repositories
166 }
167 } catch (error: any) {
168 message.error(error.message || "Failed to fetch repositories")
169 }
170 }
171
172 async function fetchSnapshots() {
173 if (!selectedRepository.value) return
174
175 loading.value = true
176 try {
177 const response = await Api.snapshots.listSnapshots(selectedRepository.value)
178 if (response.data.success) {
179 snapshots.value = response.data.snapshots
180 } else {
181 message.error(response.data.message)
182 }
183 } catch (error: any) {
184 message.error(error.message || "Failed to fetch snapshots")
185 } finally {
186 loading.value = false
187 }
188 }
189
190 function onSnapshotCreated() {
191 showCreateModal.value = false
192 fetchSnapshots()
193 message.success("Snapshot creation initiated")
194 }
195
196 function onSnapshotRestored() {
197 showRestoreModal.value = false
198 message.success("Snapshot restoration initiated")
199 }
200
201 onBeforeMount(() => {
202 fetchRepositories()
203 })
204 </script>