github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/static_source/admin/src/views/Backups/index.vue (about) 1 <script setup lang="ts"> 2 import {useI18n} from '@/hooks/web/useI18n' 3 import {Table} from '@/components/Table' 4 import {h, onMounted, onUnmounted, reactive, ref} from 'vue' 5 import {TableColumn} from '@/types/table' 6 import api from "@/api/api"; 7 import {ElButton, ElCol, ElMessage, ElPopconfirm, ElRow, ElUpload, UploadProps} from 'element-plus' 8 import {useForm} from "@/hooks/web/useForm"; 9 import {ContentWrap} from "@/components/ContentWrap"; 10 import {ApiBackup} from "@/api/stub"; 11 import {parseTime} from "@/utils"; 12 import {formatBytes} from "@/views/Dashboard/core"; 13 import {useCache} from "@/hooks/web/useCache"; 14 import {UUID} from "uuid-generator-ts"; 15 import stream from "@/api/stream"; 16 17 const {methods} = useForm() 18 const {wsCache} = useCache() 19 const {t} = useI18n() 20 21 interface TableObject { 22 tableList: ApiBackup[] 23 loading: boolean 24 } 25 26 interface Params { 27 page?: number; 28 limit?: number; 29 sort?: string; 30 } 31 32 const tableObject = reactive<TableObject>( 33 { 34 tableList: [], 35 loading: false, 36 } 37 ); 38 39 const currentID = ref('') 40 41 const columns: TableColumn[] = [ 42 { 43 field: 'name', 44 label: t('backup.name'), 45 sortable: true, 46 }, 47 { 48 field: 'size', 49 label: t('backup.size'), 50 width: "100px", 51 formatter: (row: ApiBackup) => { 52 return h( 53 'span', 54 formatBytes(row.size.toString(), 2) 55 ) 56 } 57 }, 58 { 59 field: 'operations', 60 label: t('backup.operations'), 61 width: "170px", 62 }, 63 { 64 field: 'modTime', 65 label: t('main.createdAt'), 66 type: 'time', 67 sortable: true, 68 width: "170px", 69 formatter: (row: ApiBackup) => { 70 return h( 71 'span', 72 parseTime(row.modTime) 73 ) 74 } 75 }, 76 ] 77 78 const getList = async () => { 79 tableObject.loading = true 80 const res = await api.v1.backupServiceGetBackupList() 81 .catch(() => { 82 }) 83 .finally(() => { 84 tableObject.loading = false 85 }) 86 if (res) { 87 const {items, meta} = res.data; 88 tableObject.tableList = items; 89 } else { 90 tableObject.tableList = []; 91 } 92 } 93 94 const addNew = async () => { 95 const res = await api.v1.backupServiceNewBackup({}) 96 .catch(() => { 97 }) 98 .finally(() => { 99 }) 100 if (res.status == 200) { 101 ElMessage({ 102 title: t('Success'), 103 message: t('message.createdSuccessfully'), 104 type: 'success', 105 duration: 2000 106 }); 107 } 108 } 109 110 const restore = async (backup: ApiBackup) => { 111 const res = await api.v1.backupServiceRestoreBackup(backup.name) 112 .catch(() => { 113 }) 114 .finally(() => { 115 }) 116 117 if (res.status == 200) { 118 ElMessage({ 119 title: t('Success'), 120 message: t('message.callSuccessful'), 121 type: 'success', 122 duration: 2000 123 }); 124 } 125 } 126 127 const remove = async (backup: ApiBackup) => { 128 const res = await api.v1.backupServiceDeleteBackup(backup.name) 129 .catch(() => { 130 }) 131 .finally(() => { 132 }) 133 134 if (res.status == 200) { 135 ElMessage({ 136 title: t('Success'), 137 message: t('message.callSuccessful'), 138 type: 'success', 139 duration: 2000 140 }); 141 } 142 } 143 144 const getUploadURL = () => { 145 let uri = import.meta.env.VITE_API_BASEPATH as string || window.location.origin; 146 const accessToken = wsCache.get("accessToken") 147 uri += '/v1/backup/upload?access_token=' + accessToken; 148 const serverId = wsCache.get('serverId') 149 if (serverId) { 150 uri += '&server_id=' + serverId; 151 } 152 return uri; 153 } 154 155 156 const getDownloadURL = (file: ApiBackup) => { 157 let uri = import.meta.env.VITE_API_BASEPATH as string || window.location.origin; 158 const accessToken = wsCache.get("accessToken") 159 uri += '/snapshots/' + file.name + '?access_token=' + accessToken; 160 const serverId = wsCache.get('serverId') 161 if (serverId) { 162 uri += '&server_id=' + serverId; 163 } 164 return uri; 165 } 166 167 const onSuccess: UploadProps['onSuccess'] = (file: ApiBackup, uploadFile) => { 168 ElMessage({ 169 message: t('message.uploadSuccessfully'), 170 type: 'success', 171 duration: 2000 172 }) 173 } 174 175 const onError: UploadProps['onError'] = (error, uploadFile, uploadFiles) => { 176 const body = JSON.parse(error.message) 177 const {message, code} = body.error; 178 ElMessage({ 179 message: message, 180 type: 'error', 181 duration: 0 182 }) 183 } 184 185 const forceFileDownload = (file: ApiBackup) => { 186 const link = document.createElement('a') 187 link.href = getDownloadURL(file) 188 link.setAttribute('download', file.name) 189 document.body.appendChild(link) 190 link.click() 191 } 192 193 const onEventhandler = () => { 194 getList() 195 } 196 197 const onEventStartedRestoreHandler = () => { 198 ElMessage({ 199 message: t('message.startedRestoreProcess'), 200 type: 'warning', 201 duration: 0 202 }) 203 } 204 205 onMounted(() => { 206 const uuid = new UUID() 207 currentID.value = uuid.getDashFreeUUID() 208 209 setTimeout(() => { 210 stream.subscribe('event_created_backup', currentID.value, onEventhandler); 211 stream.subscribe('event_removed_backup', currentID.value, onEventhandler); 212 stream.subscribe('event_uploaded_backup', currentID.value, onEventhandler); 213 stream.subscribe('event_started_restore', currentID.value, onEventStartedRestoreHandler); 214 }, 1000) 215 }) 216 217 onUnmounted(() => { 218 stream.unsubscribe('event_created_backup', currentID.value); 219 stream.unsubscribe('event_removed_backup', currentID.value); 220 stream.unsubscribe('event_uploaded_backup', currentID.value); 221 stream.unsubscribe('event_started_restore', currentID.value); 222 }) 223 224 getList() 225 226 </script> 227 228 <template> 229 <ContentWrap> 230 <ElRow class="file-manager-body mb-20px"> 231 <ElCol> 232 <ElButton class="flex mb-20px items-left" type="primary" @click="addNew()" plain> 233 <Icon icon="iconoir:database-restore" class="mr-5px"/> 234 {{ t('backup.addNew') }} 235 </ElButton> 236 237 <!-- <ElButton class="flex mb-20px items-left" type="primary" @click="addNew()" plain>--> 238 <!-- {{ t('backup.apply') }}--> 239 <!-- </ElButton>--> 240 241 <!-- <ElButton class="flex mb-20px items-left" type="primary" @click="addNew()" plain>--> 242 <!-- {{ t('backup.rollback') }}--> 243 <!-- </ElButton>--> 244 245 246 <ElUpload 247 class="upload-demo" 248 :action="getUploadURL()" 249 :multiple="true" 250 :on-success="onSuccess" 251 :on-error="onError" 252 :auto-upload="true" 253 > 254 <ElButton type="primary" plain> 255 <Icon icon="material-symbols:upload" class="mr-5px"/> 256 {{ $t('backup.uploadDump') }} 257 </ElButton> 258 </ElUpload> 259 </ElCol> 260 </ElRow> 261 262 263 <Table 264 :selection="false" 265 :columns="columns" 266 :data="tableObject.tableList" 267 :loading="tableObject.loading" 268 style="width: 100%" 269 > 270 <template #operations="{ row }"> 271 <ElPopconfirm 272 :confirm-button-text="$t('main.ok')" 273 :cancel-button-text="$t('main.no')" 274 width="auto" 275 style="margin-left: 10px;" 276 :title="$t('backup.restoreSnapshot')" 277 @confirm="restore(row)" 278 > 279 <template #reference> 280 <ElButton class="flex items-right" type="danger" link> 281 <Icon icon="ic:baseline-restore" class="mr-5px"/> 282 </ElButton> 283 </template> 284 </ElPopconfirm> 285 286 <ElButton class="flex items-right" link @click="forceFileDownload(row)"> 287 <Icon icon="material-symbols:download" class="mr-5px"/> 288 </ElButton> 289 290 <ElPopconfirm 291 :confirm-button-text="$t('main.ok')" 292 :cancel-button-text="$t('main.no')" 293 width="auto" 294 style="margin-left: 10px;" 295 :title="$t('backup.removeSnapshot')" 296 @confirm="remove(row)" 297 > 298 <template #reference> 299 <ElButton class="flex items-right" link> 300 <Icon icon="mdi:remove" class="mr-5px"/> 301 </ElButton> 302 </template> 303 </ElPopconfirm> 304 305 </template> 306 </Table> 307 </ContentWrap> 308 309 </template> 310 311 <style lang="less"> 312 313 .el-table__row { 314 cursor: pointer; 315 } 316 </style>