mirror of
https://github.com/Fabio286/antares.git
synced 2025-02-16 19:50:37 +01:00
feat: ability to import connections
This commit is contained in:
parent
f5b86e59e7
commit
6df214558f
@ -16,6 +16,7 @@
|
||||
:id="`id_${id}`"
|
||||
class="file-uploader-input"
|
||||
type="file"
|
||||
:accept="accept"
|
||||
@change="$emit('change', $event)"
|
||||
>
|
||||
</form>
|
||||
@ -33,6 +34,10 @@ defineProps({
|
||||
default: 'Browse',
|
||||
type: String
|
||||
},
|
||||
accept: {
|
||||
default: '',
|
||||
type: String
|
||||
},
|
||||
modelValue: {
|
||||
default: '',
|
||||
type: String
|
||||
|
@ -38,41 +38,17 @@
|
||||
<ModalSettingsDataExport
|
||||
v-if="isExportModal"
|
||||
@close="isExportModal = false"
|
||||
>
|
||||
<template #header>
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-tray-arrow-up mr-1" /> {{ t('application.exportData') }}
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="mb-2">
|
||||
<!-- -->
|
||||
</div>
|
||||
</template>
|
||||
</ModalSettingsDataExport>
|
||||
|
||||
<!-- <ConfirmModal
|
||||
/>
|
||||
<ModalSettingsDataImport
|
||||
v-if="isImportModal"
|
||||
size="medium"
|
||||
@confirm="null"
|
||||
@hide="isImportModal = false"
|
||||
>
|
||||
<template #header>
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-tray-arrow-down mr-1" /> {{ t('application.importData') }}
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="mb-2">
|
||||
|
||||
</div>
|
||||
</template>
|
||||
</ConfirmModal> -->
|
||||
@close="isImportModal = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
// import { useApplicationStore } from '@/stores/application';
|
||||
import ModalSettingsDataExport from '@/components/ModalSettingsDataExport.vue';
|
||||
import ModalSettingsDataImport from '@/components/ModalSettingsDataImport.vue';
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
|
@ -140,11 +140,12 @@ import { computed, onBeforeUnmount, Ref, ref } from 'vue';
|
||||
import * as moment from 'moment';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useFocusTrap } from '@/composables/useFocusTrap';
|
||||
import { useConnectionsStore } from '@/stores/connections';
|
||||
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
|
||||
import { unproxify } from '@/libs/unproxify';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { encrypt } from 'common/libs/encrypter';
|
||||
import { ConnectionParams } from 'common/interfaces/antares';
|
||||
|
||||
const { t } = useI18n();
|
||||
const emit = defineEmits(['close']);
|
||||
@ -153,8 +154,8 @@ const { trapRef } = useFocusTrap();
|
||||
|
||||
const { getConnectionName } = useConnectionsStore();
|
||||
const { connectionsOrder, connections } = storeToRefs(useConnectionsStore());
|
||||
const localConnections = unproxify<typeof connections.value>(connections.value);
|
||||
const localConnectionsOrder = unproxify<typeof connectionsOrder.value>(connectionsOrder.value);
|
||||
const localConnections = unproxify<ConnectionParams[]>(connections.value);
|
||||
const localConnectionsOrder = unproxify<SidebarElement[]>(connectionsOrder.value);
|
||||
|
||||
const isPasswordVisible = ref(false);
|
||||
const isPasswordError = ref(false);
|
||||
@ -217,6 +218,7 @@ const exportData = () => {
|
||||
const foldersToInclude = unproxify(localConnectionsOrder).filter(f => (
|
||||
f.isFolder && oldConnUids.some(uid => f.connections.includes(uid))
|
||||
)).map(f => {
|
||||
f.uid = uidGen('F');
|
||||
f.connections = f.connections
|
||||
.map(fc => connectionsUidMap.get(fc))
|
||||
.filter(fc => newConnUids.includes(fc));
|
||||
@ -242,10 +244,12 @@ const exportData = () => {
|
||||
document.body.appendChild(downloadLink);
|
||||
downloadLink.click();
|
||||
downloadLink.remove();
|
||||
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
const closeModal = async () => {
|
||||
const closeModal = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
|
263
src/renderer/components/ModalSettingsDataImport.vue
Normal file
263
src/renderer/components/ModalSettingsDataImport.vue
Normal file
@ -0,0 +1,263 @@
|
||||
<template>
|
||||
<Teleport to="#window-content">
|
||||
<div class="modal modal-sm active">
|
||||
<a class="modal-overlay" @click.stop="closeModal" />
|
||||
<div ref="trapRef" class="modal-container p-0">
|
||||
<div class="modal-header pl-2">
|
||||
<div class="modal-title h6">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-tray-arrow-down mr-1" /> {{ t('application.importData') }}
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||
</div>
|
||||
<div class="modal-body pb-0">
|
||||
<div class="mb-2">
|
||||
<div class="h6 mb-2">
|
||||
{{ t('application.choseFile') }}
|
||||
</div>
|
||||
<BaseUploadInput
|
||||
:model-value="filePath"
|
||||
:message="t('general.browse')"
|
||||
accept=".antares"
|
||||
@clear="filePath = ''"
|
||||
@change="filesChange($event)"
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<div class="h6 mb-2">
|
||||
{{ t('application.password') }}
|
||||
</div>
|
||||
<fieldset class="form-group" :class="{'has-error': isPasswordError}">
|
||||
<div class="input-group">
|
||||
<input
|
||||
ref="passkey"
|
||||
v-model="options.passkey"
|
||||
:type="isPasswordVisible ? 'text' : 'password'"
|
||||
class="form-input"
|
||||
:placeholder="t('application.required')"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-link input-group-addon"
|
||||
@click="isPasswordVisible = !isPasswordVisible"
|
||||
>
|
||||
<i v-if="isPasswordVisible" class="mdi mdi-eye px-1" />
|
||||
<i v-else class="mdi mdi-eye-off px-1" />
|
||||
</button>
|
||||
</div>
|
||||
<span v-if="isPasswordError" class="form-input-hint">
|
||||
{{ t('application.encryptionPasswordError') }}
|
||||
</span>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-checkbox">
|
||||
<input v-model="options.ignoreDuplicates" type="checkbox">
|
||||
<i class="form-icon" />
|
||||
{{ t(`application.ignoreDuplicates`) }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
class="btn btn-link mr-2"
|
||||
@click.stop="closeModal"
|
||||
>
|
||||
{{ t('general.close') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-primary mr-2"
|
||||
:disabled="!filePath"
|
||||
@click.prevent="importData()"
|
||||
>
|
||||
{{ t('database.import') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import BaseUploadInput from '@/components/BaseUploadInput.vue';
|
||||
import { SidebarElement, useConnectionsStore } from '@/stores/connections';
|
||||
import { unproxify } from '@/libs/unproxify';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { decrypt } from 'common/libs/encrypter';
|
||||
import { useNotificationsStore } from '@/stores/notifications';
|
||||
import { ConnectionParams } from 'common/interfaces/antares';
|
||||
|
||||
const { t } = useI18n();
|
||||
const emit = defineEmits(['close']);
|
||||
|
||||
const { addNotification } = useNotificationsStore();
|
||||
|
||||
const connectionsStore = useConnectionsStore();
|
||||
const { importConnections } = connectionsStore;
|
||||
const { connections } = storeToRefs(connectionsStore);
|
||||
|
||||
const filePath = ref('');
|
||||
const fileContent = ref(null);
|
||||
const isPasswordVisible = ref(false);
|
||||
const isPasswordError = ref(false);
|
||||
const options = ref({
|
||||
passkey: '',
|
||||
ignoreDuplicates: true
|
||||
});
|
||||
|
||||
const closeModal = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const filesChange = ({ target } : {target: HTMLInputElement }) => {
|
||||
const { files } = target;
|
||||
if (!files.length) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.readAsText(files[0]);
|
||||
reader.onload = () => {
|
||||
fileContent.value = reader.result;
|
||||
filePath.value = files[0].path;
|
||||
};
|
||||
};
|
||||
|
||||
const importData = () => {
|
||||
if (options.value.passkey.length < 8)
|
||||
isPasswordError.value = true;
|
||||
else {
|
||||
try {
|
||||
const hash = JSON.parse(Buffer.from(fileContent.value, 'hex').toString('utf-8'));
|
||||
|
||||
try {
|
||||
const importObj: {
|
||||
connections: ConnectionParams[];
|
||||
connectionsOrder: SidebarElement[];
|
||||
} = JSON.parse(decrypt(hash, options.value.passkey));
|
||||
|
||||
if (options.value.ignoreDuplicates) {
|
||||
const actualConnections = unproxify(connections.value).map(c => {
|
||||
delete c.uid;
|
||||
|
||||
delete c.name;
|
||||
delete c.password;
|
||||
delete c.ask;
|
||||
|
||||
delete c.key;
|
||||
delete c.cert;
|
||||
delete c.ca;
|
||||
|
||||
delete c.sshKey;
|
||||
|
||||
return JSON.stringify(c);
|
||||
});
|
||||
|
||||
const incomingConnections = unproxify<ConnectionParams[]>(importObj.connections).map(c => {
|
||||
const uid = c.uid;
|
||||
delete c.uid;
|
||||
|
||||
delete c.name;
|
||||
delete c.password;
|
||||
delete c.ask;
|
||||
|
||||
delete c.key;
|
||||
delete c.cert;
|
||||
delete c.ca;
|
||||
|
||||
delete c.sshKey;
|
||||
|
||||
return { uid, jsonString: JSON.stringify(c) };
|
||||
});
|
||||
|
||||
const newConnectionsUid = incomingConnections
|
||||
.filter(c => !actualConnections.includes(c.jsonString))
|
||||
.reduce((acc, cur) => {
|
||||
acc.push(cur.uid);
|
||||
return acc;
|
||||
}, [] as string[]);
|
||||
|
||||
importObj.connections = importObj.connections.filter(c => newConnectionsUid.includes(c.uid));
|
||||
importObj.connectionsOrder = importObj.connectionsOrder
|
||||
.filter(c => newConnectionsUid
|
||||
.includes(c.uid) ||
|
||||
(c.isFolder && c.connections.every(c => newConnectionsUid.includes(c))));
|
||||
}
|
||||
|
||||
importConnections(importObj);
|
||||
|
||||
addNotification({
|
||||
status: 'success',
|
||||
message: t('application.dataImportSuccess')
|
||||
});
|
||||
closeModal();
|
||||
}
|
||||
catch (error) {
|
||||
addNotification({
|
||||
status: 'error',
|
||||
message: t('application.wrongImportPassword')
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
addNotification({
|
||||
status: 'error',
|
||||
message: t('application.wrongFileFormat')
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape')
|
||||
closeModal();
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', onKey);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKey);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.export-options {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.workspace-query-results {
|
||||
flex: 1 0 1px;
|
||||
|
||||
.table {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.form-checkbox {
|
||||
min-height: 0.8rem;
|
||||
padding: 0;
|
||||
|
||||
.form-icon {
|
||||
top: 0.1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal {
|
||||
.modal-body {
|
||||
max-height: 60vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
@ -295,6 +295,7 @@ export const enUS = {
|
||||
fileName: 'File name',
|
||||
choseFile: 'Chose file',
|
||||
data: 'Data',
|
||||
password: 'Password',
|
||||
required: 'Required',
|
||||
madeWithJS: 'Made with 💛 and JavaScript!',
|
||||
checkForUpdates: 'Check for updates',
|
||||
@ -369,7 +370,11 @@ export const enUS = {
|
||||
includeConnectionPasswords: 'Include connection passwords',
|
||||
includeFolders: 'Include folders',
|
||||
encryptionPassword: 'Encryption password',
|
||||
encryptionPasswordError: 'The encryption password must be at least 8 characters long.'
|
||||
encryptionPasswordError: 'The encryption password must be at least 8 characters long.',
|
||||
ignoreDuplicates: 'Ignore duplicates',
|
||||
wrongImportPassword: 'Wrong import password',
|
||||
wrongFileFormat: 'Wrong file format',
|
||||
dataImportSuccess: 'Data successfully imported'
|
||||
},
|
||||
faker: { // Faker.js methods, used in random generated content
|
||||
address: 'Address',
|
||||
|
@ -219,6 +219,16 @@ export const useConnectionsStore = defineStore('connections', {
|
||||
|
||||
this.connectionsOrder = (this.connectionsOrder as SidebarElement[]).filter(el => !emptyFolders.includes(el.uid));
|
||||
persistentStore.set('connectionsOrder', this.connectionsOrder);
|
||||
},
|
||||
importConnections (importObj: {
|
||||
connections: ConnectionParams[];
|
||||
connectionsOrder: SidebarElement[];
|
||||
}) {
|
||||
this.connections = [...this.connections, ...importObj.connections];
|
||||
this.connectionsOrder = [...this.connectionsOrder, ...importObj.connectionsOrder];
|
||||
|
||||
persistentStore.set('connections', this.connections);
|
||||
persistentStore.set('connectionsOrder', this.connectionsOrder);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user