SystemRestoreContent.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. <script setup lang="ts">
  2. import type { RestoreOptions, RestoreResponse } from '@/api/backup'
  3. import type { UploadFile } from 'ant-design-vue'
  4. import backup from '@/api/backup'
  5. import { InboxOutlined } from '@ant-design/icons-vue'
  6. import { message, Modal } from 'ant-design-vue'
  7. // Define props using TypeScript interface
  8. interface SystemRestoreProps {
  9. showTitle?: boolean
  10. showNginxOptions?: boolean
  11. onRestoreSuccess?: (data: RestoreResponse) => void
  12. }
  13. // Define emits using TypeScript interface
  14. interface SystemRestoreEmits {
  15. (e: 'restoreSuccess', data: RestoreResponse): void
  16. (e: 'restoreError', error: Error): void
  17. }
  18. const props = withDefaults(defineProps<SystemRestoreProps>(), {
  19. showTitle: true,
  20. showNginxOptions: true,
  21. onRestoreSuccess: () => null,
  22. })
  23. const emit = defineEmits<SystemRestoreEmits>()
  24. // Use UploadFile from ant-design-vue
  25. const uploadFiles = ref<UploadFile[]>([])
  26. const isRestoring = ref(false)
  27. const formModel = reactive({
  28. securityToken: '',
  29. restoreNginx: true,
  30. restoreNginxUI: true,
  31. verifyHash: true,
  32. })
  33. function handleBeforeUpload(file: File) {
  34. // Check if file type is zip
  35. const isZip = file.name.toLowerCase().endsWith('.zip')
  36. if (!isZip) {
  37. message.error($gettext('Only zip files are allowed'))
  38. uploadFiles.value = []
  39. return
  40. }
  41. // Create UploadFile object and directly manage uploadFiles
  42. const uploadFile = {
  43. uid: Date.now().toString(),
  44. name: file.name,
  45. status: 'done',
  46. size: file.size,
  47. type: file.type,
  48. originFileObj: file,
  49. } as UploadFile
  50. // Keep only the current file
  51. uploadFiles.value = [uploadFile]
  52. // Prevent default upload behavior
  53. return false
  54. }
  55. // Handle file removal
  56. function handleRemove() {
  57. uploadFiles.value = []
  58. }
  59. async function doRestore() {
  60. if (uploadFiles.value.length === 0) {
  61. message.warning($gettext('Please select a backup file'))
  62. return
  63. }
  64. if (!formModel.securityToken) {
  65. message.warning($gettext('Please enter the security token'))
  66. return
  67. }
  68. try {
  69. isRestoring.value = true
  70. const uploadedFile = uploadFiles.value[0]
  71. if (!uploadedFile.originFileObj) {
  72. message.error($gettext('Invalid file object'))
  73. return
  74. }
  75. const options: RestoreOptions = {
  76. backup_file: uploadedFile.originFileObj,
  77. security_token: formModel.securityToken,
  78. restore_nginx: formModel.restoreNginx,
  79. restore_nginx_ui: formModel.restoreNginxUI,
  80. verify_hash: formModel.verifyHash,
  81. }
  82. const data = await backup.restoreBackup(options) as RestoreResponse
  83. message.success($gettext('Restore completed successfully'))
  84. if (data.nginx_restored) {
  85. message.info($gettext('Nginx configuration has been restored'))
  86. }
  87. if (data.nginx_ui_restored) {
  88. message.info($gettext('Nginx UI configuration has been restored'))
  89. // Show warning modal about restart
  90. Modal.warning({
  91. title: $gettext('Automatic Restart'),
  92. content: $gettext('Nginx UI configuration has been restored and will restart automatically in a few seconds.'),
  93. okText: $gettext('OK'),
  94. maskClosable: false,
  95. })
  96. }
  97. if (data.hash_match === false && formModel.verifyHash) {
  98. message.warning($gettext('Backup file integrity check failed, it may have been tampered with'))
  99. }
  100. // Reset form after successful restore
  101. uploadFiles.value = []
  102. formModel.securityToken = ''
  103. // Emit success event
  104. emit('restoreSuccess', data)
  105. // Call the callback function if provided
  106. if (props.onRestoreSuccess) {
  107. props.onRestoreSuccess(data)
  108. }
  109. }
  110. catch (error) {
  111. console.error('Restore failed:', error)
  112. emit('restoreError', error instanceof Error ? error : new Error(String(error)))
  113. }
  114. finally {
  115. isRestoring.value = false
  116. }
  117. }
  118. </script>
  119. <template>
  120. <div>
  121. <ACard v-if="showTitle" :title="$gettext('System Restore')" :bordered="false">
  122. <AAlert
  123. show-icon
  124. type="warning"
  125. :message="$gettext('Warning: Restore operation will overwrite current configurations. Make sure you have a valid backup file and security token, and carefully select what to restore.')"
  126. class="mb-4"
  127. />
  128. <AUploadDragger
  129. :file-list="uploadFiles"
  130. :multiple="false"
  131. :max-count="1"
  132. accept=".zip"
  133. :before-upload="handleBeforeUpload"
  134. @remove="handleRemove"
  135. >
  136. <p class="ant-upload-drag-icon">
  137. <InboxOutlined />
  138. </p>
  139. <p class="ant-upload-text">
  140. {{ $gettext('Click or drag backup file to this area to upload') }}
  141. </p>
  142. <p class="ant-upload-hint">
  143. {{ $gettext('Supported file type: .zip') }}
  144. </p>
  145. </AUploadDragger>
  146. <AForm
  147. v-if="uploadFiles.length > 0"
  148. :model="formModel"
  149. layout="vertical"
  150. class="mt-4"
  151. >
  152. <AFormItem :label="$gettext('Security Token')">
  153. <AInput
  154. v-model:value="formModel.securityToken"
  155. :placeholder="$gettext('Please enter the security token received during backup')"
  156. />
  157. </AFormItem>
  158. <AFormItem>
  159. <ACheckbox v-model:checked="formModel.verifyHash" :disabled="true">
  160. {{ $gettext('Verify Backup File Integrity') }}
  161. </ACheckbox>
  162. </AFormItem>
  163. <template v-if="showNginxOptions">
  164. <AFormItem>
  165. <ACheckbox v-model:checked="formModel.restoreNginx">
  166. {{ $gettext('Restore Nginx Configuration') }}
  167. </ACheckbox>
  168. <div class="text-gray-500 ml-6 mt-1 text-sm">
  169. <p class="mb-0">
  170. {{ $gettext('This will restore all Nginx configuration files. Nginx will restart after the restoration is complete.') }}
  171. </p>
  172. </div>
  173. </AFormItem>
  174. <AFormItem>
  175. <ACheckbox v-model:checked="formModel.restoreNginxUI">
  176. {{ $gettext('Restore Nginx UI Configuration') }}
  177. </ACheckbox>
  178. <div class="text-gray-500 ml-6 mt-1 text-sm">
  179. <p class="mb-0">
  180. {{ $gettext('This will restore configuration files and database. Nginx UI will restart after the restoration is complete.') }}
  181. </p>
  182. </div>
  183. </AFormItem>
  184. </template>
  185. <AFormItem>
  186. <AButton type="primary" :loading="isRestoring" @click="doRestore">
  187. {{ $gettext('Start Restore') }}
  188. </AButton>
  189. </AFormItem>
  190. </AForm>
  191. </ACard>
  192. <div v-else>
  193. <AAlert
  194. show-icon
  195. type="warning"
  196. :message="$gettext('Warning: Restore operation will overwrite current configurations. Make sure you have a valid backup file and security token, and carefully select what to restore.')"
  197. class="mb-4"
  198. />
  199. <AUploadDragger
  200. :file-list="uploadFiles"
  201. :multiple="false"
  202. :max-count="1"
  203. accept=".zip"
  204. :before-upload="handleBeforeUpload"
  205. @remove="handleRemove"
  206. >
  207. <p class="ant-upload-drag-icon">
  208. <InboxOutlined />
  209. </p>
  210. <p class="ant-upload-text">
  211. {{ $gettext('Click or drag backup file to this area to upload') }}
  212. </p>
  213. <p class="ant-upload-hint">
  214. {{ $gettext('Supported file type: .zip') }}
  215. </p>
  216. </AUploadDragger>
  217. <AForm
  218. v-if="uploadFiles.length > 0"
  219. :model="formModel"
  220. layout="vertical"
  221. class="mt-4"
  222. >
  223. <AFormItem :label="$gettext('Security Token')">
  224. <AInput
  225. v-model:value="formModel.securityToken"
  226. :placeholder="$gettext('Please enter the security token received during backup')"
  227. />
  228. </AFormItem>
  229. <AFormItem>
  230. <ACheckbox v-model:checked="formModel.verifyHash" :disabled="true">
  231. {{ $gettext('Verify Backup File Integrity') }}
  232. </ACheckbox>
  233. </AFormItem>
  234. <template v-if="showNginxOptions">
  235. <AFormItem>
  236. <ACheckbox v-model:checked="formModel.restoreNginx">
  237. {{ $gettext('Restore Nginx Configuration') }}
  238. </ACheckbox>
  239. <div class="text-gray-500 ml-6 mt-1 text-sm">
  240. <p class="mb-0">
  241. {{ $gettext('This will restore all Nginx configuration files. Nginx will restart after the restoration is complete.') }}
  242. </p>
  243. </div>
  244. </AFormItem>
  245. <AFormItem>
  246. <ACheckbox v-model:checked="formModel.restoreNginxUI">
  247. {{ $gettext('Restore Nginx UI Configuration') }}
  248. </ACheckbox>
  249. <div class="text-gray-500 ml-6 mt-1 text-sm">
  250. <p class="mb-0">
  251. {{ $gettext('This will restore configuration files and database. Nginx UI will restart after the restoration is complete.') }}
  252. </p>
  253. </div>
  254. </AFormItem>
  255. </template>
  256. <AFormItem>
  257. <AButton type="primary" :loading="isRestoring" @click="doRestore">
  258. {{ $gettext('Start Restore') }}
  259. </AButton>
  260. </AFormItem>
  261. </AForm>
  262. </div>
  263. </div>
  264. </template>