mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-23 23:05:59 +00:00
SYNC: Merge branch 'feature/backup-restore'
This commit is contained in:
@@ -132,7 +132,6 @@
|
||||
import { getDatabaseList, useUsedApps } from '../utility/metadataLoaders';
|
||||
import { getLocalStorage } from '../utility/storageCache';
|
||||
import { apiCall, removeVolatileMapping } from '../utility/api';
|
||||
import ImportDatabaseDumpModal from '../modals/ImportDatabaseDumpModal.svelte';
|
||||
import { closeMultipleTabs } from '../tabpanel/TabsPanel.svelte';
|
||||
import AboutModal from '../modals/AboutModal.svelte';
|
||||
import { tick } from 'svelte';
|
||||
@@ -141,6 +140,7 @@
|
||||
import { switchCurrentDatabase } from '../utility/common';
|
||||
import { getConnectionClickActionSetting } from '../settings/settingsTools';
|
||||
import { _t } from '../translations';
|
||||
import { isProApp } from '../utility/proTools';
|
||||
|
||||
export let data;
|
||||
export let passProps;
|
||||
@@ -231,9 +231,14 @@
|
||||
});
|
||||
};
|
||||
|
||||
const handleSqlRestore = () => {
|
||||
showModal(ImportDatabaseDumpModal, {
|
||||
connection: data,
|
||||
const handleRestoreDatabase = () => {
|
||||
openNewTab({
|
||||
title: 'Restore #',
|
||||
icon: 'img db-restore',
|
||||
tabComponent: 'RestoreDatabaseTab',
|
||||
props: {
|
||||
conid: data._id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -364,11 +369,10 @@
|
||||
),
|
||||
],
|
||||
|
||||
driver?.databaseEngineTypes?.includes('sql') &&
|
||||
!data.isReadOnly && {
|
||||
onClick: handleSqlRestore,
|
||||
text: _t('connection.sqlRestore', { defaultMessage: 'Restore/import SQL dump' }),
|
||||
},
|
||||
driver?.supportsDatabaseRestore &&
|
||||
isProApp() &&
|
||||
hasPermission(`dbops/sql-dump/import`) &&
|
||||
!data.isReadOnly && { onClick: handleRestoreDatabase, text: 'Restore database backup' },
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
@@ -126,16 +126,27 @@
|
||||
});
|
||||
};
|
||||
|
||||
const handleSqlDump = () => {
|
||||
showModal(ExportDatabaseDumpModal, {
|
||||
connection: { ...connection, database: name },
|
||||
const handleBackupDatabase = () => {
|
||||
openNewTab({
|
||||
title: 'Backup #',
|
||||
icon: 'img db-backup',
|
||||
tabComponent: 'BackupDatabaseTab',
|
||||
props: {
|
||||
conid: connection._id,
|
||||
database: name,
|
||||
},
|
||||
});
|
||||
// exportSqlDump(connection, name);
|
||||
};
|
||||
|
||||
const handleSqlRestore = () => {
|
||||
showModal(ImportDatabaseDumpModal, {
|
||||
connection: { ...connection, database: name },
|
||||
const handleRestoreDatabase = () => {
|
||||
openNewTab({
|
||||
title: 'Restore #',
|
||||
icon: 'img db-restore',
|
||||
tabComponent: 'RestoreDatabaseTab',
|
||||
props: {
|
||||
conid: connection._id,
|
||||
database: name,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -376,11 +387,13 @@ await dbgateApi.dropAllDbObjects(${JSON.stringify(
|
||||
!connection.isReadOnly &&
|
||||
hasPermission(`dbops/import`) && { onClick: handleImport, text: 'Import' },
|
||||
isSqlOrDoc && hasPermission(`dbops/export`) && { onClick: handleExport, text: 'Export' },
|
||||
driver?.databaseEngineTypes?.includes('sql') &&
|
||||
driver?.supportsDatabaseRestore &&
|
||||
isProApp() &&
|
||||
hasPermission(`dbops/sql-dump/import`) &&
|
||||
!connection.isReadOnly && { onClick: handleSqlRestore, text: 'Restore/import SQL dump' },
|
||||
driver?.supportsDatabaseDump &&
|
||||
hasPermission(`dbops/sql-dump/export`) && { onClick: handleSqlDump, text: 'Backup/export SQL dump' },
|
||||
!connection.isReadOnly && { onClick: handleRestoreDatabase, text: 'Restore database backup' },
|
||||
driver?.supportsDatabaseBackup &&
|
||||
isProApp() &&
|
||||
hasPermission(`dbops/sql-dump/export`) && { onClick: handleBackupDatabase, text: 'Create database backup' },
|
||||
isSqlOrDoc &&
|
||||
!connection.isReadOnly &&
|
||||
!connection.singleDatabase &&
|
||||
@@ -491,8 +504,6 @@ await dbgateApi.dropAllDbObjects(${JSON.stringify(
|
||||
import ConfirmSqlModal, { runOperationOnDatabase, saveScriptToDatabase } from '../modals/ConfirmSqlModal.svelte';
|
||||
import { filterAppsForDatabase } from '../utility/appTools';
|
||||
import newQuery from '../query/newQuery';
|
||||
import ImportDatabaseDumpModal from '../modals/ImportDatabaseDumpModal.svelte';
|
||||
import ExportDatabaseDumpModal from '../modals/ExportDatabaseDumpModal.svelte';
|
||||
import ConfirmModal from '../modals/ConfirmModal.svelte';
|
||||
import { closeMultipleTabs } from '../tabpanel/TabsPanel.svelte';
|
||||
import NewCollectionModal from '../modals/NewCollectionModal.svelte';
|
||||
|
||||
@@ -1086,8 +1086,12 @@
|
||||
icon={databaseObjectIcons[data.objectTypeField]}
|
||||
menu={createMenu}
|
||||
showPinnedInsteadOfUnpin={passProps?.showPinnedInsteadOfUnpin}
|
||||
onPin={isPinned ? null : () => pinnedTables.update(list => [...list, data])}
|
||||
onUnpin={isPinned ? () => pinnedTables.update(list => list.filter(x => !testEqual(x, data))) : null}
|
||||
onPin={passProps?.ingorePin ? null : isPinned ? null : () => pinnedTables.update(list => [...list, data])}
|
||||
onUnpin={passProps?.ingorePin
|
||||
? null
|
||||
: isPinned
|
||||
? () => pinnedTables.update(list => list.filter(x => !testEqual(x, data)))
|
||||
: null}
|
||||
extInfo={getExtInfo(data)}
|
||||
isChoosed={matchDatabaseObjectAppObject($selectedDatabaseObjectAppObject, data)}
|
||||
on:click={() => handleObjectClick(data, 'leftClick')}
|
||||
|
||||
74
packages/web/src/buttons/InlineButtonLabel.svelte
Normal file
74
packages/web/src/buttons/InlineButtonLabel.svelte
Normal file
@@ -0,0 +1,74 @@
|
||||
<script lang="ts">
|
||||
export let disabled = false;
|
||||
export let square = false;
|
||||
export let narrow = false;
|
||||
export let title = null;
|
||||
|
||||
let domButton;
|
||||
|
||||
export function getBoundingClientRect() {
|
||||
return domButton.getBoundingClientRect();
|
||||
}
|
||||
</script>
|
||||
|
||||
<label
|
||||
class="outer buttonLike"
|
||||
{title}
|
||||
class:disabled
|
||||
class:square
|
||||
class:narrow
|
||||
on:click
|
||||
bind:this={domButton}
|
||||
{...$$restProps}
|
||||
>
|
||||
<div class="inner">
|
||||
<slot />
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<style>
|
||||
.outer {
|
||||
--bg-1: var(--theme-bg-1);
|
||||
--bg-2: var(--theme-bg-3);
|
||||
|
||||
background: linear-gradient(to bottom, var(--bg-1) 5%, var(--bg-2) 100%);
|
||||
background-color: var(--bg-1);
|
||||
border: 1px solid var(--bg-2);
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
color: var(--theme-font-1);
|
||||
font-size: 12px;
|
||||
padding: 3px;
|
||||
margin: 0;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.narrow {
|
||||
padding: 3px 1px;
|
||||
}
|
||||
|
||||
.outer.disabled {
|
||||
color: var(--theme-font-3);
|
||||
}
|
||||
|
||||
.outer:hover:not(.disabled) {
|
||||
border: 1px solid var(--theme-font-1);
|
||||
}
|
||||
|
||||
.outer:active:not(.disabled) {
|
||||
background: linear-gradient(to bottom, var(--bg-2) 5%, var(--bg-1) 100%);
|
||||
background-color: var(--bg-2);
|
||||
}
|
||||
|
||||
.inner {
|
||||
margin: auto;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.square {
|
||||
width: 18px;
|
||||
}
|
||||
</style>
|
||||
@@ -33,10 +33,13 @@
|
||||
let collapsed2 = false;
|
||||
|
||||
export let size = 0;
|
||||
export let onChangeSize = null;
|
||||
let clientWidth;
|
||||
let customRatio = null;
|
||||
|
||||
$: size = computeSplitterSize(initialValue, clientWidth, customRatio, initialSizeRight);
|
||||
|
||||
$: if (onChangeSize) onChangeSize(size);
|
||||
</script>
|
||||
|
||||
<div class="container" bind:clientWidth>
|
||||
|
||||
@@ -52,6 +52,8 @@
|
||||
{:else if tab.slot == 5}<slot name="5" />
|
||||
{:else if tab.slot == 6}<slot name="6" />
|
||||
{:else if tab.slot == 7}<slot name="7" />
|
||||
{:else if tab.slot == 8}<slot name="8" />
|
||||
{:else if tab.slot == 9}<slot name="9" />
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
$: name = `${namePrefix}${arg.name}`;
|
||||
|
||||
const { setFieldValue } = getFormContext();
|
||||
const { setFieldValue, values } = getFormContext();
|
||||
</script>
|
||||
|
||||
{#if arg.type == 'text'}
|
||||
@@ -23,7 +23,7 @@
|
||||
defaultValue={arg.default}
|
||||
focused={arg.focused}
|
||||
placeholder={arg.placeholder}
|
||||
disabled={arg.disabled}
|
||||
disabled={arg.disabledFn ? arg.disabledFn($values) : arg.disabled}
|
||||
/>
|
||||
{:else if arg.type == 'stringlist'}
|
||||
<FormStringList label={arg.label} addButtonLabel={arg.addButtonLabel} {name} placeholder={arg.placeholder} />
|
||||
@@ -35,9 +35,15 @@
|
||||
defaultValue={arg.default}
|
||||
focused={arg.focused}
|
||||
placeholder={arg.placeholder}
|
||||
disabled={arg.disabledFn ? arg.disabledFn($values) : arg.disabled}
|
||||
/>
|
||||
{:else if arg.type == 'checkbox'}
|
||||
<FormCheckboxField label={arg.label} {name} defaultValue={arg.default} />
|
||||
<FormCheckboxField
|
||||
label={arg.label}
|
||||
{name}
|
||||
defaultValue={arg.default}
|
||||
disabled={arg.disabledFn ? arg.disabledFn($values) : arg.disabled}
|
||||
/>
|
||||
{:else if arg.type == 'select'}
|
||||
<FormSelectField
|
||||
label={arg.label}
|
||||
@@ -47,6 +53,7 @@
|
||||
options={arg.options.map(opt =>
|
||||
_.isString(opt) ? { label: opt, value: opt } : { label: opt.name, value: opt.value }
|
||||
)}
|
||||
disabled={arg.disabledFn ? arg.disabledFn($values) : arg.disabled}
|
||||
/>
|
||||
{:else if arg.type == 'dropdowntext'}
|
||||
<FormDropDownTextField
|
||||
@@ -59,5 +66,6 @@
|
||||
onClick: () => setFieldValue(name, _.isString(opt) ? opt : opt.value),
|
||||
}));
|
||||
}}
|
||||
disabled={arg.disabledFn ? arg.disabledFn($values) : arg.disabled}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -306,6 +306,9 @@
|
||||
'img tip': 'mdi mdi-lightbulb-on color-icon-yellow',
|
||||
|
||||
'img filter-active': 'mdi mdi-filter-cog color-icon-blue',
|
||||
|
||||
'img db-backup': 'mdi mdi-database-export color-icon-yellow',
|
||||
'img db-restore': 'mdi mdi-database-import color-icon-red',
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { onMount, tick } from 'svelte';
|
||||
import { format as dateFormat } from 'date-fns';
|
||||
|
||||
import FormStyledButton from '../buttons/FormStyledButton.svelte';
|
||||
import UploadButton from '../buttons/UploadButton.svelte';
|
||||
|
||||
import FormProvider from '../forms/FormProvider.svelte';
|
||||
import FormSubmit from '../forms/FormSubmit.svelte';
|
||||
import { exportSqlDump, importSqlDump } from '../utility/exportFileTools';
|
||||
import getElectron from '../utility/getElectron';
|
||||
import { setUploadListener } from '../utility/uploadFiles';
|
||||
import ChangeDownloadUrlModal from './ChangeDownloadUrlModal.svelte';
|
||||
import ModalBase from './ModalBase.svelte';
|
||||
import { closeCurrentModal, showModal } from './modalTools';
|
||||
import InputTextModal from './InputTextModal.svelte';
|
||||
import { apiCall } from '../utility/api';
|
||||
import { getConnectionLabel } from 'dbgate-tools';
|
||||
|
||||
export let connection;
|
||||
|
||||
let outputLabel;
|
||||
let outputFile;
|
||||
let pureFileName = null;
|
||||
|
||||
function getDefaultFileName() {
|
||||
return `${connection.database}-${dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss')}.sql`;
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
const file = getDefaultFileName();
|
||||
setFilesFolderResult(file);
|
||||
});
|
||||
|
||||
const setFilesFolderResult = async file => {
|
||||
const resp = await apiCall('files/get-file-real-path', { folder: 'sql', file });
|
||||
if (!resp) return;
|
||||
outputLabel = `SQL Files folder: ${file}`;
|
||||
outputFile = resp;
|
||||
pureFileName = null;
|
||||
};
|
||||
|
||||
const handleSubmit = async values => {
|
||||
const { value } = values;
|
||||
closeCurrentModal();
|
||||
exportSqlDump(outputFile, connection, connection.database, pureFileName);
|
||||
};
|
||||
|
||||
const electron = getElectron();
|
||||
|
||||
const handleFilesFolder = () => {
|
||||
showModal(InputTextModal, {
|
||||
value: getDefaultFileName(),
|
||||
label: 'New file name',
|
||||
header: 'Backup/dump database',
|
||||
|
||||
onConfirm: async file => {
|
||||
await tick();
|
||||
setFilesFolderResult(file);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleBrowse = async () => {
|
||||
const electron = getElectron();
|
||||
const file = await electron.showSaveDialog({
|
||||
properties: ['showOverwriteConfirmation'],
|
||||
filters: [
|
||||
{ name: 'SQL Files', extensions: ['sql'] },
|
||||
{ name: 'All Files', extensions: ['*'] },
|
||||
],
|
||||
defaultPath: outputFile,
|
||||
});
|
||||
if (file) {
|
||||
const path = window.require('path');
|
||||
outputFile = file;
|
||||
outputLabel = path.parse(outputFile).name;
|
||||
pureFileName = null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
const resp = await apiCall('files/generate-uploads-file', { extension: 'sql' });
|
||||
outputFile = resp.filePath;
|
||||
outputLabel = 'Download';
|
||||
pureFileName = resp.fileName;
|
||||
};
|
||||
</script>
|
||||
|
||||
<FormProvider>
|
||||
<ModalBase {...$$restProps}>
|
||||
<svelte:fragment slot="header">Export database dump</svelte:fragment>
|
||||
|
||||
<div class="m-3">
|
||||
<strong>Source:</strong>
|
||||
{getConnectionLabel(connection)}
|
||||
{#if connection.database}
|
||||
({connection.database})
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="ml-3 mr-3 mt-3"><strong>Target:</strong> {outputLabel}</div>
|
||||
<div class="flex ml-3">
|
||||
{#if electron}
|
||||
<FormStyledButton type="button" value="Browse" on:click={handleBrowse} />
|
||||
{:else}
|
||||
<FormStyledButton type="button" value="Set download" on:click={handleDownload} />
|
||||
{/if}
|
||||
<FormStyledButton type="button" value="Files folder" on:click={handleFilesFolder} />
|
||||
</div>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<FormSubmit value="Run export" on:click={e => handleSubmit(e.detail)} disabled={!outputFile} />
|
||||
<FormStyledButton type="button" value="Cancel" on:click={closeCurrentModal} />
|
||||
</svelte:fragment>
|
||||
</ModalBase>
|
||||
</FormProvider>
|
||||
@@ -1,130 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { onMount, tick } from 'svelte';
|
||||
|
||||
import FormStyledButton from '../buttons/FormStyledButton.svelte';
|
||||
import UploadButton from '../buttons/UploadButton.svelte';
|
||||
|
||||
import FormProvider from '../forms/FormProvider.svelte';
|
||||
import FormSubmit from '../forms/FormSubmit.svelte';
|
||||
import { currentDropDownMenu } from '../stores';
|
||||
import { apiCall } from '../utility/api';
|
||||
import { importSqlDump } from '../utility/exportFileTools';
|
||||
import getElectron from '../utility/getElectron';
|
||||
import { setUploadListener } from '../utility/uploadFiles';
|
||||
import ChangeDownloadUrlModal from './ChangeDownloadUrlModal.svelte';
|
||||
import ModalBase from './ModalBase.svelte';
|
||||
import { closeCurrentModal, showModal } from './modalTools';
|
||||
import { getConnectionLabel } from 'dbgate-tools';
|
||||
|
||||
export let connection;
|
||||
|
||||
let inputLabel = '(not selected)';
|
||||
let inputFile = null;
|
||||
let domButton;
|
||||
|
||||
const handleSubmit = async values => {
|
||||
const { value } = values;
|
||||
closeCurrentModal();
|
||||
importSqlDump(inputFile, connection);
|
||||
};
|
||||
|
||||
const electron = getElectron();
|
||||
|
||||
const handleUpload = file => {
|
||||
inputLabel = `uploaded: ${file.shortName}`;
|
||||
inputFile = file.filePath;
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
setUploadListener(handleUpload);
|
||||
|
||||
return () => {
|
||||
setUploadListener(null);
|
||||
};
|
||||
});
|
||||
|
||||
const handleAddUrl = () => {
|
||||
showModal(ChangeDownloadUrlModal, {
|
||||
onConfirm: async url => {
|
||||
await tick();
|
||||
inputLabel = url;
|
||||
inputFile = url;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleBrowse = async () => {
|
||||
const electron = getElectron();
|
||||
const files = await electron.showOpenDialog({
|
||||
properties: ['openFile'],
|
||||
filters: [
|
||||
{ name: 'SQL Files', extensions: ['sql'] },
|
||||
{ name: 'All Files', extensions: ['*'] },
|
||||
],
|
||||
});
|
||||
if (files && files[0]) {
|
||||
const path = window.require('path');
|
||||
inputFile = files[0];
|
||||
inputLabel = path.parse(inputFile).name;
|
||||
}
|
||||
};
|
||||
|
||||
async function handleFilesClick() {
|
||||
const rect = domButton.getBoundingClientRect();
|
||||
const left = rect.left;
|
||||
const top = rect.bottom;
|
||||
const files = await apiCall('files/list', { folder: 'sql' });
|
||||
const menu = files.map(({ file }) => ({
|
||||
label: file,
|
||||
onClick: async () => {
|
||||
inputFile = await apiCall('files/get-file-real-path', { folder: 'sql', file });
|
||||
if (inputFile) {
|
||||
inputLabel = file;
|
||||
}
|
||||
},
|
||||
}));
|
||||
currentDropDownMenu.set({ left, top, items: menu });
|
||||
}
|
||||
</script>
|
||||
|
||||
<FormProvider>
|
||||
<ModalBase {...$$restProps}>
|
||||
<svelte:fragment slot="header">Import database dump</svelte:fragment>
|
||||
|
||||
<div class="ml-3 mr-3 mt-3"><strong>Source:</strong> {inputLabel}</div>
|
||||
|
||||
<div class="flex ml-3 mr-3 mb-3">
|
||||
{#if electron}
|
||||
<FormStyledButton type="button" value="Browse" on:click={handleBrowse} />
|
||||
{:else}
|
||||
<UploadButton />
|
||||
{/if}
|
||||
|
||||
<FormStyledButton value="Web URL" on:click={handleAddUrl} />
|
||||
<FormStyledButton value="From files" on:click={handleFilesClick} bind:this={domButton} />
|
||||
</div>
|
||||
|
||||
<div class="m-3">
|
||||
<strong>Target:</strong>
|
||||
{getConnectionLabel(connection)}
|
||||
{#if connection.database}
|
||||
({connection.database})
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<FormSubmit
|
||||
value="Run import"
|
||||
on:click={e => handleSubmit(e.detail)}
|
||||
disabled={!inputFile}
|
||||
data-testid="ImportDatabaseDumpModal_runImport"
|
||||
/>
|
||||
<FormStyledButton
|
||||
type="button"
|
||||
value="Cancel"
|
||||
on:click={closeCurrentModal}
|
||||
data-testid="ImportDatabaseDumpModal_cancel"
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</ModalBase>
|
||||
</FormProvider>
|
||||
@@ -160,6 +160,7 @@
|
||||
filter={objectsFilter}
|
||||
disableContextMenu
|
||||
{checkedObjectsStore}
|
||||
passProps={{ ingorePin: true }}
|
||||
/>
|
||||
</WidgetsInnerContainer>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import getElectron from '../utility/getElectron';
|
||||
import { downloadFromApi } from '../utility/exportFileTools';
|
||||
import useEffect from '../utility/useEffect';
|
||||
import Link from '../elements/Link.svelte';
|
||||
|
||||
export let runnerId;
|
||||
export let executeNumber;
|
||||
@@ -63,41 +64,38 @@
|
||||
},
|
||||
]}
|
||||
>
|
||||
<a
|
||||
slot="0"
|
||||
let:row
|
||||
href="#"
|
||||
on:click={() => {
|
||||
downloadFromApi(`runners/data/${runnerId}/${row.name}`, row.name);
|
||||
}}
|
||||
>
|
||||
download
|
||||
</a>
|
||||
<svelte:fragment slot="0" let:row>
|
||||
<Link
|
||||
onClick={() => {
|
||||
downloadFromApi(`runners/data/${runnerId}/${row.name}`, row.name);
|
||||
}}
|
||||
>
|
||||
download
|
||||
</Link>
|
||||
</svelte:fragment>
|
||||
|
||||
<a
|
||||
slot="1"
|
||||
let:row
|
||||
href="#"
|
||||
on:click={async () => {
|
||||
const file = await electron.showSaveDialog({});
|
||||
if (file) {
|
||||
const fs = window.require('fs');
|
||||
fs.copyFile(row.path, file, () => {});
|
||||
}
|
||||
}}
|
||||
>
|
||||
save
|
||||
</a>
|
||||
<svelte:fragment slot="1" let:row>
|
||||
<Link
|
||||
onClick={async () => {
|
||||
const file = await electron.showSaveDialog({});
|
||||
if (file) {
|
||||
const fs = window.require('fs');
|
||||
fs.copyFile(row.path, file, () => {});
|
||||
}
|
||||
}}
|
||||
>
|
||||
save
|
||||
</Link>
|
||||
</svelte:fragment>
|
||||
|
||||
<a
|
||||
slot="2"
|
||||
let:row
|
||||
href="#"
|
||||
on:click={() => {
|
||||
electron.showItemInFolder(row.path);
|
||||
}}
|
||||
>
|
||||
show
|
||||
</a>
|
||||
<svelte:fragment slot="2" let:row>
|
||||
<Link
|
||||
onClick={() => {
|
||||
electron.showItemInFolder(row.path);
|
||||
}}
|
||||
>
|
||||
show
|
||||
</Link>
|
||||
</svelte:fragment>
|
||||
</TableControl>
|
||||
{/if}
|
||||
|
||||
@@ -104,6 +104,7 @@ ORDER BY
|
||||
{ label: 'Themes', slot: 3 },
|
||||
{ label: 'Default Actions', slot: 4 },
|
||||
{ label: 'Behaviour', slot: 5 },
|
||||
{ label: 'External tools', slot: 8 },
|
||||
{ label: 'Other', slot: 6 },
|
||||
]}
|
||||
>
|
||||
@@ -480,6 +481,31 @@ ORDER BY
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="8">
|
||||
<div class="heading">External tools</div>
|
||||
<FormTextField
|
||||
name="externalTools.mysqldump"
|
||||
label="mysqldump (backup MySQL database)"
|
||||
defaultValue="mysqldump"
|
||||
/>
|
||||
<FormTextField name="externalTools.mysql" label="mysql (restore MySQL database)" defaultValue="mysql" />
|
||||
<FormTextField
|
||||
name="externalTools.mysqlPlugins"
|
||||
label="Folder with mysql plugins (for example for authentication). Set only in case of problems"
|
||||
defaultValue=""
|
||||
/>
|
||||
<FormTextField
|
||||
name="externalTools.pg_dump"
|
||||
label="pg_dump (backup PostgreSQL database)"
|
||||
defaultValue="pg_dump"
|
||||
/>
|
||||
<FormTextField
|
||||
name="externalTools.psql"
|
||||
label="psql (restore PostgreSQL database)"
|
||||
defaultValue="psql"
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</TabControl>
|
||||
</FormValues>
|
||||
|
||||
|
||||
@@ -182,26 +182,6 @@ export async function exportQuickExportFile(dataName, reader, format: QuickExpor
|
||||
}
|
||||
}
|
||||
|
||||
// export async function exportSqlDump(connection, databaseName) {
|
||||
// await saveExportedFile(
|
||||
// [{ name: 'SQL files', extensions: ['sql'] }],
|
||||
// `${databaseName}.sql`,
|
||||
// 'sql',
|
||||
// `${databaseName}-dump`,
|
||||
// filePath => {
|
||||
// const script = getCurrentConfig().allowShellScripting ? new ScriptWriter() : new ScriptWriterJson();
|
||||
|
||||
// script.dumpDatabase({
|
||||
// connection,
|
||||
// databaseName,
|
||||
// outputFile: filePath,
|
||||
// });
|
||||
|
||||
// return script.getScript();
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
|
||||
export async function saveFileToDisk(
|
||||
filePathFunc,
|
||||
options: any = { formatLabel: 'HTML page', formatExtension: 'html' }
|
||||
|
||||
25
packages/web/src/utility/openWebFile.ts
Normal file
25
packages/web/src/utility/openWebFile.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import newQuery from '../query/newQuery';
|
||||
import _ from 'lodash';
|
||||
|
||||
export function canOpenByWeb(file, extensions) {
|
||||
if (!file) return false;
|
||||
const nameLower = file.toLowerCase();
|
||||
if (nameLower.endsWith('.sql')) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function openWebFileCore(file, extensions) {
|
||||
const nameLower = file.path.toLowerCase();
|
||||
|
||||
if (nameLower.endsWith('.sql')) {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = function (e) {
|
||||
newQuery({
|
||||
initialData: e.target.result,
|
||||
});
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { showModal } from '../modals/modalTools';
|
||||
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
|
||||
import openNewTab from './openNewTab';
|
||||
import { openImportExportTab } from './importExportTools';
|
||||
import { canOpenByWeb, openWebFileCore } from './openWebFile';
|
||||
|
||||
let uploadListener;
|
||||
|
||||
@@ -28,6 +29,11 @@ export default function uploadFiles(files) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!electron && canOpenByWeb(file.path, ext)) {
|
||||
openWebFileCore(file, ext);
|
||||
return;
|
||||
}
|
||||
|
||||
const maxSize = 32 * 1024 * 1024;
|
||||
if (parseInt(file.size, 10) >= maxSize) {
|
||||
showModal(ErrorMessageModal, {
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
import { apiCall } from '../utility/api';
|
||||
import { useFiles } from '../utility/metadataLoaders';
|
||||
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
|
||||
import getElectron from '../utility/getElectron';
|
||||
import InlineButtonLabel from '../buttons/InlineButtonLabel.svelte';
|
||||
import resolveApi, { resolveApiHeaders } from '../utility/resolveApi';
|
||||
|
||||
let filter = '';
|
||||
|
||||
@@ -24,6 +27,8 @@
|
||||
const perspectiveFiles = useFiles({ folder: 'perspectives' });
|
||||
const modelTransformFiles = useFiles({ folder: 'modtrans' });
|
||||
|
||||
const electron = getElectron();
|
||||
|
||||
$: files = [
|
||||
...($sqlFiles || []),
|
||||
...($shellFiles || []),
|
||||
@@ -58,16 +63,67 @@
|
||||
if (folder == 'modtrans') return 'Model transforms';
|
||||
return _.startCase(folder);
|
||||
}
|
||||
|
||||
async function handleUploadedFile(e) {
|
||||
const files = [...e.target.files];
|
||||
|
||||
for (const file of files) {
|
||||
const formData = new FormData();
|
||||
formData.append('name', file.name);
|
||||
formData.append('data', file);
|
||||
|
||||
const fetchOptions = {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: resolveApiHeaders(),
|
||||
};
|
||||
|
||||
const apiBase = resolveApi();
|
||||
const resp = await fetch(`${apiBase}/uploads/upload-data-file`, fetchOptions);
|
||||
const fileData = await resp.json();
|
||||
}
|
||||
}
|
||||
|
||||
async function handleOpenElectronFile() {
|
||||
const filePaths = await electron.showOpenDialog({
|
||||
filters: [
|
||||
{
|
||||
name: `All supported files`,
|
||||
extensions: ['sql'],
|
||||
},
|
||||
{ name: `SQL files`, extensions: ['sql'] },
|
||||
],
|
||||
properties: ['showHiddenFiles', 'openFile'],
|
||||
});
|
||||
const filePath = filePaths && filePaths[0];
|
||||
await apiCall('uploads/save-data-file', { filePath });
|
||||
}
|
||||
</script>
|
||||
|
||||
<WidgetsInnerContainer>
|
||||
<SearchBoxWrapper>
|
||||
<SearchInput placeholder="Search saved files" bind:value={filter} />
|
||||
<CloseSearchButton bind:filter />
|
||||
<InlineButton on:click={handleRefreshFiles} title="Refresh files">
|
||||
{#if electron}
|
||||
<InlineButton on:click={handleOpenElectronFile} title="Add file" data-testid="SavedFileList_buttonAddFile">
|
||||
<FontIcon icon="icon plus-thick" />
|
||||
</InlineButton>
|
||||
{:else}
|
||||
<InlineButtonLabel
|
||||
on:click={() => {}}
|
||||
title="Add file"
|
||||
data-testid="SavedFileList_buttonAddFile"
|
||||
htmlFor="uploadSavedFileButton"
|
||||
>
|
||||
<FontIcon icon="icon plus-thick" />
|
||||
</InlineButtonLabel>
|
||||
{/if}
|
||||
<InlineButton on:click={handleRefreshFiles} title="Refresh files" data-testid="SavedFileList_buttonRefresh">
|
||||
<FontIcon icon="icon refresh" />
|
||||
</InlineButton>
|
||||
</SearchBoxWrapper>
|
||||
|
||||
<input type="file" id="uploadSavedFileButton" hidden on:change={handleUploadedFile} />
|
||||
|
||||
<AppObjectList list={files} module={savedFileAppObject} groupFunc={data => dataFolderTitle(data.folder)} {filter} />
|
||||
</WidgetsInnerContainer>
|
||||
|
||||
@@ -2,23 +2,36 @@
|
||||
let domDiv;
|
||||
|
||||
export let hideContent = false;
|
||||
export let fixedWidth = 0;
|
||||
|
||||
export function scrollTop() {
|
||||
domDiv.scrollTop = 0;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div on:drop bind:this={domDiv} class:hideContent data-testid={$$props['data-testid']}><slot /></div>
|
||||
<div
|
||||
on:drop
|
||||
bind:this={domDiv}
|
||||
class:hideContent
|
||||
class:leftFixedWidth={!fixedWidth}
|
||||
data-testid={$$props['data-testid']}
|
||||
style:width={fixedWidth ? `${fixedWidth}px` : undefined}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
flex: 1 1;
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
div.leftFixedWidth {
|
||||
width: var(--dim-left-panel-width);
|
||||
}
|
||||
|
||||
.hideContent {
|
||||
div.hideContent {
|
||||
visibility: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user