Delete.vue 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. <script setup lang="ts">
  2. import { message } from 'ant-design-vue'
  3. import config from '@/api/config'
  4. import NodeSelector from '@/components/NodeSelector'
  5. import use2FAModal from '@/components/TwoFA/use2FAModal'
  6. import { urlJoin } from '@/lib/helper'
  7. import { isProtectedPath } from '@/views/config/configUtils'
  8. const emit = defineEmits(['deleted'])
  9. const visible = ref(false)
  10. const confirmText = ref('')
  11. const data = ref({
  12. basePath: '',
  13. name: '',
  14. isDir: false,
  15. sync_node_ids: [] as number[],
  16. fullPath: '',
  17. })
  18. async function open(basePath: string, name: string, isDir: boolean) {
  19. visible.value = true
  20. confirmText.value = ''
  21. data.value.basePath = basePath
  22. data.value.name = name
  23. data.value.isDir = isDir
  24. data.value.sync_node_ids = []
  25. const { base_path: configBasePath } = await config.get_base_path()
  26. // Build full path
  27. const relativePath = urlJoin(basePath, name)
  28. data.value.fullPath = urlJoin(configBasePath, relativePath)
  29. // Load config details to get sync nodes
  30. try {
  31. // For files, try to get their specific sync configuration
  32. if (!isDir) {
  33. const configDetail = await config.getItem(relativePath)
  34. if (configDetail?.sync_node_ids && configDetail.sync_node_ids.length > 0) {
  35. data.value.sync_node_ids = [...configDetail.sync_node_ids]
  36. }
  37. }
  38. // For directories, we could potentially get sync nodes from any file within
  39. // but for simplicity, we'll leave it empty and let user choose
  40. }
  41. catch (error) {
  42. // Silently ignore errors if config details cannot be loaded
  43. // This might happen for files that are not tracked in the database
  44. console.error('Config details not available for sync nodes:', error)
  45. }
  46. }
  47. defineExpose({
  48. open,
  49. })
  50. // Check if the item is protected
  51. const isProtected = computed(() => {
  52. return isProtectedPath(data.value.name)
  53. })
  54. // Expected confirmation text
  55. const expectedConfirmText = computed(() => {
  56. return $gettext('Delete')
  57. })
  58. function ok() {
  59. if (confirmText.value !== expectedConfirmText.value) {
  60. message.error($gettext('Please type the exact confirmation text'))
  61. return
  62. }
  63. const { basePath, name, sync_node_ids } = data.value
  64. const otpModal = use2FAModal()
  65. otpModal.open().then(() => {
  66. config.delete(basePath, name, sync_node_ids).then(() => {
  67. visible.value = false
  68. message.success($gettext('Deleted successfully'))
  69. emit('deleted')
  70. })
  71. })
  72. }
  73. function cancel() {
  74. visible.value = false
  75. confirmText.value = ''
  76. }
  77. </script>
  78. <template>
  79. <AModal
  80. v-model:open="visible"
  81. :mask="false"
  82. :title="$gettext('Delete Confirmation')"
  83. :ok-text="$gettext('Delete')"
  84. :cancel-text="$gettext('Cancel')"
  85. :ok-button-props="{ danger: true, disabled: confirmText !== expectedConfirmText || isProtected }"
  86. @ok="ok"
  87. @cancel="cancel"
  88. >
  89. <AForm layout="vertical" class="delete-modal-content">
  90. <AAlert
  91. v-if="isProtected"
  92. type="error"
  93. :message="$gettext('Protected Directory')"
  94. :description="$gettext('This directory is protected and cannot be deleted for system safety.')"
  95. show-icon
  96. class="mb-4"
  97. />
  98. <AAlert
  99. v-else
  100. type="warning"
  101. :message="$gettext('This will permanently delete the %{type}.', { type: data.isDir ? $gettext('folder') : $gettext('file') })"
  102. show-icon
  103. class="mb-4"
  104. />
  105. <div class="item-info mb-4">
  106. <p><strong>{{ $gettext('Type') }}:</strong> {{ data.isDir ? $gettext('Folder') : $gettext('File') }}</p>
  107. <p><strong>{{ $gettext('Name') }}:</strong> {{ data.name }}</p>
  108. <p><strong>{{ $gettext('Path') }}:</strong> {{ data.fullPath }}</p>
  109. </div>
  110. <AFormItem
  111. v-if="!isProtected"
  112. :label="$gettext('Type %{delete} to confirm', { delete: expectedConfirmText })"
  113. class="mb-4"
  114. >
  115. <AInput
  116. v-model:value="confirmText"
  117. :placeholder="expectedConfirmText"
  118. :disabled="isProtected"
  119. />
  120. </AFormItem>
  121. <AFormItem
  122. v-if="!isProtected"
  123. :label="$gettext('Sync')"
  124. >
  125. <NodeSelector
  126. v-model:target="data.sync_node_ids"
  127. hidden-local
  128. />
  129. </AFormItem>
  130. </AForm>
  131. </AModal>
  132. </template>
  133. <style scoped lang="less">
  134. .delete-modal-content {
  135. .item-info {
  136. background-color: #fafafa;
  137. padding: 12px;
  138. border-radius: 6px;
  139. border: 1px solid #e8e8e8;
  140. p {
  141. margin: 0;
  142. line-height: 1.5;
  143. &:not(:last-child) {
  144. margin-bottom: 8px;
  145. }
  146. }
  147. }
  148. }
  149. </style>