diff --git a/dashboard/src/components/AppImportDialog.vue b/dashboard/src/components/AppImportDialog.vue index 03aaec96e..f4efc42ee 100644 --- a/dashboard/src/components/AppImportDialog.vue +++ b/dashboard/src/components/AppImportDialog.vue @@ -2,7 +2,7 @@ import { ref, useTemplateRef } from 'vue'; import { Dialog, FormGroup, TextInput, PasswordInput, Checkbox } from '@cloudron/pankow'; -import { s3like, mountlike } from '../utils.js'; +import { s3like, mountlike, parseFullBackupPath } from '../utils.js'; import BackupProviderForm from './BackupProviderForm.vue'; import AppsModel from '../models/AppsModel.js'; import { REGIONS_CONTABO, REGIONS_VULTR, REGIONS_IONOS, REGIONS_OVH, REGIONS_LINODE, REGIONS_SCALEWAY, REGIONS_WASABI } from '../constants.js'; @@ -17,7 +17,7 @@ const busy = ref(false); const formError = ref({}); const providerConfig = ref({}); const provider = ref(''); -const remotePath = ref(''); +const fullPath = ref(''); const format = ref(''); const encrypted = ref(false); const encryptionPasswordHint = ref(''); @@ -30,15 +30,17 @@ async function onSubmit() { formError.value = {}; busy.value = true; - let backupPath = remotePath.value; const config = {}; + const { prefix, remotePath } = parseFullBackupPath(fullPath.value); + // only set provider specific fields, this will clear them in the db if (s3like(provider.value)) { config.bucket = providerConfig.value.bucket; config.prefix = providerConfig.value.prefix; config.accessKeyId = providerConfig.value.accessKeyId; config.secretAccessKey = providerConfig.value.secretAccessKey; + config.prefix = prefix; if (providerConfig.value.endpoint) config.endpoint = providerConfig.value.endpoint; @@ -85,7 +87,7 @@ async function onSubmit() { config.signatureVersion = 'v4'; } } else if (mountlike(provider.value)) { - config.prefix = providerConfig.value.prefix; + config.prefix = prefix; config.noHardlinks = !providerConfig.value.useHardlinks; config.mountOptions = {}; @@ -113,21 +115,19 @@ async function onSubmit() { config.preserveAttributes = !!providerConfig.value.preserveAttributes; } } else if (provider.value === 'filesystem') { - const parts = remotePath.value.split('/'); - backupPath = parts.pop() || parts.pop(); // removes any trailing slash. this is basename() - config.backupDir = parts.join('/'); // this is dirname() + config.backupDir = prefix; } else if (provider.value === 'gcs') { config.bucket = providerConfig.value.bucket; - config.prefix = providerConfig.value.prefix; config.projectId = providerConfig.value.projectId; config.credentials = providerConfig.value.credentials; + config.prefix = prefix; } const data = { format: format.value, provider: provider.value, - config: config, - remotePath: backupPath + config, + remotePath }; if (encrypted.value) { @@ -197,7 +197,7 @@ function onBackupConfigChanged(event) { } provider.value = data.provider; - remotePath.value = data.config.prefix ? `${data.config.prefix}/${data.remotePath}` : data.remotePath; + fullPath.value = data.config.prefix ? `${data.config.prefix}/${data.remotePath}` : data.remotePath; providerConfig.value = data.config; format.value = data.format; encrypted.value = !!data.encrypted; @@ -220,7 +220,7 @@ defineExpose({ formError.value = {}; provider.value = ''; providerConfig.value = {}; - remotePath.value = ''; + fullPath.value = ''; encrypted.value = false; encryptionPassword.value = ''; encryptedFilenames.value = false; @@ -265,7 +265,7 @@ defineExpose({ - + timestampRegex.test(p) || p === 'snapshot'); + + let remotePath, prefix; + if (idx === -1) { + remotePath = parts.pop() || parts.pop(); // if fs+rsync there may be a trailing slash, so this removes it. this is basename() + prefix = parts.join('/'); // this is dirname() + } else { + prefix = parts.slice(0, idx).join('/'); + remotePath = parts.slice(idx).join('/'); + } + + return { prefix, remotePath }; +} + // named exports export { prettyRelayProviderName, @@ -757,7 +776,8 @@ export { getColor, prettySchedule, parseSchedule, - prettySiteLocation + prettySiteLocation, + parseFullBackupPath }; // default export @@ -778,5 +798,6 @@ export default { getColor, prettySchedule, parseSchedule, - prettySiteLocation + prettySiteLocation, + parseFullBackupPath }; diff --git a/dashboard/src/views/RestoreView.vue b/dashboard/src/views/RestoreView.vue index ab1b0b959..b819d8000 100644 --- a/dashboard/src/views/RestoreView.vue +++ b/dashboard/src/views/RestoreView.vue @@ -4,7 +4,7 @@ import { ref, onMounted, useTemplateRef } from 'vue'; import { Notification, Button, SingleSelect, FormGroup, PasswordInput, TextInput, Checkbox } from '@cloudron/pankow'; import { copyToClipboard } from '@cloudron/pankow/utils'; import { REGIONS_CONTABO, REGIONS_VULTR, REGIONS_IONOS, REGIONS_OVH, REGIONS_LINODE, REGIONS_SCALEWAY, REGIONS_WASABI } from '../constants.js'; -import { redirectIfNeeded, mountlike, s3like } from '../utils.js'; +import { redirectIfNeeded, mountlike, s3like, parseFullBackupPath } from '../utils.js'; import ProvisionModel from '../models/ProvisionModel.js'; import BackupProviderForm from '../components/BackupProviderForm.vue'; import Whirlpool from '../components/Whirlpool.vue'; @@ -27,7 +27,7 @@ const progressMessage = ref(''); const taskMinutesActive = ref(0); const provider = ref(''); const providerConfig = ref({}); -const remotePath = ref(''); +const fullPath = ref(''); const format = ref(''); const encrypted = ref(false); const encryptionPasswordHint = ref(''); @@ -87,21 +87,21 @@ async function onSubmit() { busy.value = true; formError.value = {}; - if (remotePath.value.indexOf('/') === -1) { + if (fullPath.value.indexOf('/') === -1) { error.value.generic = 'Backup id must include the directory path'; error.value.remotePath = true; busy.value = false; return; } - if (remotePath.value.indexOf('box') === -1) { + if (fullPath.value.indexOf('box') === -1) { error.value.generic = 'Backup id must contain "box"'; error.value.remotePath = true; busy.value = false; return; } - const version = remotePath.value.match(/_v(\d+.\d+.\d+)/); + const version = fullPath.value.match(/_v(\d+.\d+.\d+)/); if (!version) { formError.value.generic = 'Backup id is missing version information'; formError.value.remotePath = true; @@ -109,11 +109,12 @@ async function onSubmit() { return; } - const config = {}; // filled below + const config = {}; + const { prefix, remotePath } = parseFullBackupPath(fullPath.value); if (s3like(provider.value)) { config.endpoint = providerConfig.value.endpoint; - config.prefix = providerConfig.value.prefix; + config.prefix = prefix; config.bucket = providerConfig.value.bucket; config.accessKeyId = providerConfig.value.accessKeyId; config.secretAccessKey = providerConfig.value.secretAccessKey; @@ -160,7 +161,7 @@ async function onSubmit() { config.signatureVersion = 'v4'; } } else if (mountlike(provider.value)) { - config.prefix = providerConfig.value.prefix; + config.prefix = prefix; config.noHardlinks = !providerConfig.value.useHardlinks; config.mountOptions = {}; @@ -188,12 +189,12 @@ async function onSubmit() { config.preserveAttributes = !!providerConfig.value.preserveAttributes; } } else if (provider.value === 'filesystem') { - config.backupDir = providerConfig.value.backupDir; + config.backupDir = prefix; config.noHardlinks = !providerConfig.value.useHardlinks; config.preserveAttributes = true; } else if (provider.value === 'gcs') { config.bucket = providerConfig.value.bucket; - config.prefix = providerConfig.value.prefix; + config.prefix = prefix; config.projectId = providerConfig.value.projectId; config.credentials = providerConfig.value.credentials; } @@ -204,7 +205,7 @@ async function onSubmit() { config, format: format.value, }, - remotePath: remotePath.value, + remotePath, version: version ? version[1] : '', ipv4Config: { provider: ipv4Provider.value, @@ -274,7 +275,7 @@ function onBackupConfigChanged(event) { } provider.value = data.provider; - remotePath.value = data.config.prefix ? `${data.config.prefix}/${data.remotePath}` : data.remotePath; + fullPath.value = data.config.prefix ? `${data.config.prefix}/${data.remotePath}` : data.remotePath; providerConfig.value = data.config; format.value = data.format; encrypted.value = !!data.encrypted; @@ -351,7 +352,7 @@ onMounted(async () => { - +