mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-05-02 16:03:59 +00:00
archive, export into archive
This commit is contained in:
41
packages/api/src/controllers/archive.js
Normal file
41
packages/api/src/controllers/archive.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
const fs = require('fs-extra');
|
||||||
|
const path = require('path');
|
||||||
|
const { archivedir } = require('../utility/directories');
|
||||||
|
const socket = require('../utility/socket');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
folders_meta: 'get',
|
||||||
|
async folders() {
|
||||||
|
const folders = await fs.readdir(archivedir());
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'default',
|
||||||
|
type: 'jsonl',
|
||||||
|
},
|
||||||
|
...folders
|
||||||
|
.filter((x) => x != 'default')
|
||||||
|
.map((name) => ({
|
||||||
|
name,
|
||||||
|
type: 'jsonl',
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
createFolder_meta: 'post',
|
||||||
|
async createFolder({ folder }) {
|
||||||
|
await fs.mkdir(path.join(archivedir(), folder));
|
||||||
|
socket.emitChanged('archive-folders-changed');
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
files_meta: 'get',
|
||||||
|
async files({ folder }) {
|
||||||
|
const files = await fs.readdir(path.join(archivedir(), folder));
|
||||||
|
return files
|
||||||
|
.filter((name) => name.endsWith('.jsonl'))
|
||||||
|
.map((name) => ({
|
||||||
|
name: name.slice(0, -'.jsonl'.length),
|
||||||
|
type: 'jsonl',
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const lineReader = require('line-reader');
|
const lineReader = require('line-reader');
|
||||||
const { jsldir } = require('../utility/directories');
|
const { jsldir, archivedir } = require('../utility/directories');
|
||||||
const socket = require('../utility/socket');
|
const socket = require('../utility/socket');
|
||||||
|
|
||||||
function readFirstLine(file) {
|
function readFirstLine(file) {
|
||||||
@@ -20,6 +20,14 @@ function readFirstLine(file) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getJslFileName(jslid) {
|
||||||
|
const archiveMatch = jslid.match(/^archive:\/\/([^/]+)\/(.*)$/);
|
||||||
|
if (archiveMatch) {
|
||||||
|
return path.join(archivedir(), archiveMatch[1], `${archiveMatch[2]}.jsonl`);
|
||||||
|
}
|
||||||
|
return path.join(jsldir(), `${jslid}.jsonl`);
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
openedReaders: {},
|
openedReaders: {},
|
||||||
|
|
||||||
@@ -57,7 +65,7 @@ module.exports = {
|
|||||||
// 'OPENING READER, LINES=',
|
// 'OPENING READER, LINES=',
|
||||||
// fs.readFileSync(path.join(jsldir(), `${jslid}.jsonl`), 'utf-8').split('\n').length
|
// fs.readFileSync(path.join(jsldir(), `${jslid}.jsonl`), 'utf-8').split('\n').length
|
||||||
// );
|
// );
|
||||||
const file = path.join(jsldir(), `${jslid}.jsonl`);
|
const file = getJslFileName(jslid);
|
||||||
return new Promise((resolve, reject) =>
|
return new Promise((resolve, reject) =>
|
||||||
lineReader.open(file, (err, reader) => {
|
lineReader.open(file, (err, reader) => {
|
||||||
if (err) reject(err);
|
if (err) reject(err);
|
||||||
@@ -93,7 +101,7 @@ module.exports = {
|
|||||||
|
|
||||||
getInfo_meta: 'get',
|
getInfo_meta: 'get',
|
||||||
async getInfo({ jslid }) {
|
async getInfo({ jslid }) {
|
||||||
const file = path.join(jsldir(), `${jslid}.jsonl`);
|
const file = getJslFileName(jslid);
|
||||||
const firstLine = await readFirstLine(file);
|
const firstLine = await readFirstLine(file);
|
||||||
if (firstLine) return JSON.parse(firstLine);
|
if (firstLine) return JSON.parse(firstLine);
|
||||||
return null;
|
return null;
|
||||||
@@ -119,8 +127,9 @@ module.exports = {
|
|||||||
|
|
||||||
getStats_meta: 'get',
|
getStats_meta: 'get',
|
||||||
getStats({ jslid }) {
|
getStats({ jslid }) {
|
||||||
const file = path.join(jsldir(), `${jslid}.jsonl.stats`);
|
const file = `${getJslFileName(jslid)}.stats`;
|
||||||
return JSON.parse(fs.readFileSync(file, 'utf-8'));
|
if (fs.existsSync(file)) return JSON.parse(fs.readFileSync(file, 'utf-8'));
|
||||||
|
return {};
|
||||||
},
|
},
|
||||||
|
|
||||||
async notifyChangedStats(stats) {
|
async notifyChangedStats(stats) {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const runners = require('./controllers/runners');
|
|||||||
const jsldata = require('./controllers/jsldata');
|
const jsldata = require('./controllers/jsldata');
|
||||||
const config = require('./controllers/config');
|
const config = require('./controllers/config');
|
||||||
const files = require('./controllers/files');
|
const files = require('./controllers/files');
|
||||||
|
const archive = require('./controllers/archive');
|
||||||
|
|
||||||
const { rundir } = require('./utility/directories');
|
const { rundir } = require('./utility/directories');
|
||||||
|
|
||||||
@@ -55,6 +56,7 @@ function start(argument = null) {
|
|||||||
useController(app, '/jsldata', jsldata);
|
useController(app, '/jsldata', jsldata);
|
||||||
useController(app, '/config', config);
|
useController(app, '/config', config);
|
||||||
useController(app, '/files', files);
|
useController(app, '/files', files);
|
||||||
|
useController(app, '/archive', archive);
|
||||||
|
|
||||||
if (process.env.PAGES_DIRECTORY) {
|
if (process.env.PAGES_DIRECTORY) {
|
||||||
app.use('/pages', express.static(process.env.PAGES_DIRECTORY));
|
app.use('/pages', express.static(process.env.PAGES_DIRECTORY));
|
||||||
|
|||||||
14
packages/api/src/shell/archiveWriter.js
Normal file
14
packages/api/src/shell/archiveWriter.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const { archivedir, ensureDirectory } = require('../utility/directories');
|
||||||
|
// const socket = require('../utility/socket');
|
||||||
|
const jsonLinesWriter = require('./jsonLinesWriter');
|
||||||
|
|
||||||
|
function archiveWriter({ folderName, fileName }) {
|
||||||
|
if (folderName == 'default') ensureDirectory(path.join(archivedir(), folderName));
|
||||||
|
const jsonlFile = path.join(archivedir(), folderName, `${fileName}.jsonl`);
|
||||||
|
const res = jsonLinesWriter({ fileName: jsonlFile });
|
||||||
|
// socket.emitChanged(`archive-files-changed-${folderName}`);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = archiveWriter;
|
||||||
@@ -11,6 +11,7 @@ const excelSheetReader = require('./excelSheetReader');
|
|||||||
const jsonLinesWriter = require('./jsonLinesWriter');
|
const jsonLinesWriter = require('./jsonLinesWriter');
|
||||||
const jsonLinesReader = require('./jsonLinesReader');
|
const jsonLinesReader = require('./jsonLinesReader');
|
||||||
const jslDataReader = require('./jslDataReader');
|
const jslDataReader = require('./jslDataReader');
|
||||||
|
const archiveWriter = require('./archiveWriter');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
queryReader,
|
queryReader,
|
||||||
@@ -26,4 +27,5 @@ module.exports = {
|
|||||||
fakeObjectReader,
|
fakeObjectReader,
|
||||||
consoleObjectWriter,
|
consoleObjectWriter,
|
||||||
jslDataReader,
|
jslDataReader,
|
||||||
|
archiveWriter,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,31 +2,28 @@ const os = require('os');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
let createdDatadir = false;
|
|
||||||
const createDirectories = {};
|
const createDirectories = {};
|
||||||
|
|
||||||
|
const ensureDirectory = (dir) => {
|
||||||
|
if (!createDirectories[dir]) {
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
console.log(`Creating directory ${dir}`);
|
||||||
|
fs.mkdirSync(dir);
|
||||||
|
}
|
||||||
|
createDirectories[dir] = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function datadir() {
|
function datadir() {
|
||||||
const dir = path.join(os.homedir(), 'dbgate-data');
|
const dir = path.join(os.homedir(), 'dbgate-data');
|
||||||
if (!createdDatadir) {
|
ensureDirectory(dir);
|
||||||
if (!fs.existsSync(dir)) {
|
|
||||||
console.log(`Creating data directory ${dir}`);
|
|
||||||
fs.mkdirSync(dir);
|
|
||||||
}
|
|
||||||
createdDatadir = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return dir;
|
return dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dirFunc = (dirname) => () => {
|
const dirFunc = (dirname) => () => {
|
||||||
const dir = path.join(datadir(), dirname);
|
const dir = path.join(datadir(), dirname);
|
||||||
if (!createDirectories[dirname]) {
|
ensureDirectory(dir);
|
||||||
if (!fs.existsSync(dir)) {
|
|
||||||
console.log(`Creating jsl directory ${dir}`);
|
|
||||||
fs.mkdirSync(dir);
|
|
||||||
}
|
|
||||||
createDirectories[dirname] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return dir;
|
return dir;
|
||||||
};
|
};
|
||||||
@@ -34,10 +31,13 @@ const dirFunc = (dirname) => () => {
|
|||||||
const jsldir = dirFunc('jsl');
|
const jsldir = dirFunc('jsl');
|
||||||
const rundir = dirFunc('run');
|
const rundir = dirFunc('run');
|
||||||
const uploadsdir = dirFunc('uploads');
|
const uploadsdir = dirFunc('uploads');
|
||||||
|
const archivedir = dirFunc('archive');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
datadir,
|
datadir,
|
||||||
jsldir,
|
jsldir,
|
||||||
rundir,
|
rundir,
|
||||||
uploadsdir,
|
uploadsdir,
|
||||||
|
archivedir,
|
||||||
|
ensureDirectory,
|
||||||
};
|
};
|
||||||
|
|||||||
10
packages/web/public/icons/archtable.svg
Normal file
10
packages/web/public/icons/archtable.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version='1.0' encoding='iso-8859-1'?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<g transform="translate(-326-532.36)">
|
||||||
|
<rect width="18" height="6" x="329" y="537.36" fill="#fdcc01" stroke="#2d2d2d" stroke-linejoin="round" stroke-linecap="round" stroke-width=".837" rx=".646"/>
|
||||||
|
|
||||||
|
<rect width="18" height="5" x="329" y="547.86" fill="#fdcc01" stroke="#2d2d2d" stroke-linejoin="round" stroke-linecap="round" stroke-width=".837" rx=".646"/>
|
||||||
|
|
||||||
|
<path d="m507.95 46.02c-1.367-16.368-14.588-30.13-31.21-30.13h-444.96c-16.622 0-29.844 13.762-31.24 30.13h-.54v414.82c0 17.544 14.239 31.782 31.782 31.782h444.95c17.544 0 31.782-14.239 31.782-31.782v-414.82h-.571m-349.04 414.82h-127.13v-95.35h127.13v95.35m0-124.49h-127.13v-97.99h127.13v97.99m0-129.77h-127.13v-95.35h127.13v95.35m158.91 254.26h-127.13v-95.35h127.13v95.35m0-124.49h-127.13v-97.99h127.13v97.99m0-129.77h-127.13v-95.35h127.13v95.35m158.91 254.26h-127.13v-95.35h127.13v95.35m0-124.49h-127.13v-97.99h127.13v97.99m0-129.77h-127.13v-95.35h127.13v95.35" transform="matrix(.03776 0 0 .03776 328.4 534.76)" fill="#2d2d2d"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -8,6 +8,7 @@ import {
|
|||||||
SavedSqlFilesProvider,
|
SavedSqlFilesProvider,
|
||||||
OpenedConnectionsProvider,
|
OpenedConnectionsProvider,
|
||||||
LeftPanelWidthProvider,
|
LeftPanelWidthProvider,
|
||||||
|
CurrentArchiveProvider,
|
||||||
} from './utility/globalState';
|
} from './utility/globalState';
|
||||||
import { SocketProvider } from './utility/SocketProvider';
|
import { SocketProvider } from './utility/SocketProvider';
|
||||||
import ConnectionsPinger from './utility/ConnectionsPinger';
|
import ConnectionsPinger from './utility/ConnectionsPinger';
|
||||||
@@ -24,7 +25,9 @@ function App() {
|
|||||||
<LeftPanelWidthProvider>
|
<LeftPanelWidthProvider>
|
||||||
<ConnectionsPinger>
|
<ConnectionsPinger>
|
||||||
<ModalLayerProvider>
|
<ModalLayerProvider>
|
||||||
<Screen />
|
<CurrentArchiveProvider>
|
||||||
|
<Screen />
|
||||||
|
</CurrentArchiveProvider>
|
||||||
</ModalLayerProvider>
|
</ModalLayerProvider>
|
||||||
</ConnectionsPinger>
|
</ConnectionsPinger>
|
||||||
</LeftPanelWidthProvider>
|
</LeftPanelWidthProvider>
|
||||||
|
|||||||
39
packages/web/src/appobj/archiveFileAppObject.js
Normal file
39
packages/web/src/appobj/archiveFileAppObject.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { DatabaseIcon, getIconImage, ArchiveTableIcon } from '../icons';
|
||||||
|
import { DropDownMenuItem } from '../modals/DropDownMenu';
|
||||||
|
import { openNewTab } from '../utility/common';
|
||||||
|
|
||||||
|
function Menu({ data, setOpenedTabs }) {
|
||||||
|
const handleDelete = () => {
|
||||||
|
// setOpenedTabs((tabs) => tabs.filter((x) => x.tabid != data.tabid));
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DropDownMenuItem onClick={handleDelete}>Delete</DropDownMenuItem>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const archiveFileAppObject = () => ({ fileName, folderName }, { setOpenedTabs }) => {
|
||||||
|
const key = fileName;
|
||||||
|
// const Icon = (props) => <i className="fas fa-archive" />;
|
||||||
|
const Icon = ArchiveTableIcon;
|
||||||
|
const onClick = () => {
|
||||||
|
openNewTab(setOpenedTabs, {
|
||||||
|
title: fileName,
|
||||||
|
icon: 'archtable.svg',
|
||||||
|
tooltip: `${folderName}\n${fileName}`,
|
||||||
|
tabComponent: 'ArchiveFileTab',
|
||||||
|
props: {
|
||||||
|
fileName,
|
||||||
|
folderName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return { title: fileName, key, Icon, Menu, onClick };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default archiveFileAppObject;
|
||||||
27
packages/web/src/appobj/archiveFolderAppObject.js
Normal file
27
packages/web/src/appobj/archiveFolderAppObject.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { LocalDbIcon, getIconImage } from '../icons';
|
||||||
|
import { DropDownMenuItem } from '../modals/DropDownMenu';
|
||||||
|
|
||||||
|
function Menu({ data, setOpenedTabs }) {
|
||||||
|
const handleDelete = () => {
|
||||||
|
// setOpenedTabs((tabs) => tabs.filter((x) => x.tabid != data.tabid));
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DropDownMenuItem onClick={handleDelete}>Delete</DropDownMenuItem>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const archiveFolderAppObject = () => ({ name }, { setOpenedTabs, currentArchive }) => {
|
||||||
|
const key = name;
|
||||||
|
// const Icon = (props) => <i className="fas fa-archive" />;
|
||||||
|
const Icon = LocalDbIcon;
|
||||||
|
const isBold = name == currentArchive;
|
||||||
|
|
||||||
|
return { title: name, key, Icon, isBold, Menu };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default archiveFolderAppObject;
|
||||||
@@ -58,6 +58,7 @@ export function ExpandIcon({
|
|||||||
|
|
||||||
export const TableIcon = (props) => getIconImage('table2.svg', props);
|
export const TableIcon = (props) => getIconImage('table2.svg', props);
|
||||||
export const ViewIcon = (props) => getIconImage('view2.svg', props);
|
export const ViewIcon = (props) => getIconImage('view2.svg', props);
|
||||||
|
export const ArchiveTableIcon = (props) => getIconImage('archtable.svg', props);
|
||||||
export const DatabaseIcon = (props) => getIconImage('database.svg', props);
|
export const DatabaseIcon = (props) => getIconImage('database.svg', props);
|
||||||
export const ServerIcon = (props) => getIconImage('server.svg', props);
|
export const ServerIcon = (props) => getIconImage('server.svg', props);
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
FormDatabaseSelect,
|
FormDatabaseSelect,
|
||||||
FormTablesSelect,
|
FormTablesSelect,
|
||||||
FormSchemaSelect,
|
FormSchemaSelect,
|
||||||
|
FormArchiveFolderSelect,
|
||||||
} from '../utility/forms';
|
} from '../utility/forms';
|
||||||
import { useConnectionInfo, useDatabaseInfo } from '../utility/metadataLoaders';
|
import { useConnectionInfo, useDatabaseInfo } from '../utility/metadataLoaders';
|
||||||
import TableControl, { TableColumn } from '../utility/TableControl';
|
import TableControl, { TableColumn } from '../utility/TableControl';
|
||||||
@@ -147,6 +148,7 @@ function SourceTargetConfig({
|
|||||||
storageTypeField,
|
storageTypeField,
|
||||||
connectionIdField,
|
connectionIdField,
|
||||||
databaseNameField,
|
databaseNameField,
|
||||||
|
archiveFolderField,
|
||||||
schemaNameField,
|
schemaNameField,
|
||||||
tablesField = undefined,
|
tablesField = undefined,
|
||||||
engine = undefined,
|
engine = undefined,
|
||||||
@@ -161,6 +163,7 @@ function SourceTargetConfig({
|
|||||||
{ value: 'jsonl', label: 'JSON lines file(s)', directions: ['source', 'target'] },
|
{ value: 'jsonl', label: 'JSON lines file(s)', directions: ['source', 'target'] },
|
||||||
{ value: 'excel', label: 'MS Excel file(s)', directions: ['source'] },
|
{ value: 'excel', label: 'MS Excel file(s)', directions: ['source'] },
|
||||||
{ value: 'query', label: 'SQL Query', directions: ['source'] },
|
{ value: 'query', label: 'SQL Query', directions: ['source'] },
|
||||||
|
{ value: 'archive', label: 'Archive', directions: ['source', 'target'] },
|
||||||
];
|
];
|
||||||
const storageType = values[storageTypeField];
|
const storageType = values[storageTypeField];
|
||||||
const dbinfo = useDatabaseInfo({ conid: values[connectionIdField], database: values[databaseNameField] });
|
const dbinfo = useDatabaseInfo({ conid: values[connectionIdField], database: values[databaseNameField] });
|
||||||
@@ -231,6 +234,13 @@ function SourceTargetConfig({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{storageType == 'archive' && (
|
||||||
|
<>
|
||||||
|
<Label>Archive folder</Label>
|
||||||
|
<FormArchiveFolderSelect name={archiveFolderField} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{isFileStorage(storageType) && direction == 'source' && <FilesInput />}
|
{isFileStorage(storageType) && direction == 'source' && <FilesInput />}
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
@@ -270,6 +280,7 @@ export default function ImportExportConfigurator() {
|
|||||||
storageTypeField="sourceStorageType"
|
storageTypeField="sourceStorageType"
|
||||||
connectionIdField="sourceConnectionId"
|
connectionIdField="sourceConnectionId"
|
||||||
databaseNameField="sourceDatabaseName"
|
databaseNameField="sourceDatabaseName"
|
||||||
|
archiveFolderField="sourceArchiveFolder"
|
||||||
schemaNameField="sourceSchemaName"
|
schemaNameField="sourceSchemaName"
|
||||||
tablesField="sourceList"
|
tablesField="sourceList"
|
||||||
engine={sourceEngine}
|
engine={sourceEngine}
|
||||||
@@ -279,6 +290,7 @@ export default function ImportExportConfigurator() {
|
|||||||
storageTypeField="targetStorageType"
|
storageTypeField="targetStorageType"
|
||||||
connectionIdField="targetConnectionId"
|
connectionIdField="targetConnectionId"
|
||||||
databaseNameField="targetDatabaseName"
|
databaseNameField="targetDatabaseName"
|
||||||
|
archiveFolderField="targetArchiveFolder"
|
||||||
schemaNameField="targetSchemaName"
|
schemaNameField="targetSchemaName"
|
||||||
/>
|
/>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ function getSourceExpr(sourceName, values, sourceConnection, sourceDriver) {
|
|||||||
if (sourceStorageType == 'jsldata') {
|
if (sourceStorageType == 'jsldata') {
|
||||||
return ['jslDataReader', { jslid: values.sourceJslId }];
|
return ['jslDataReader', { jslid: values.sourceJslId }];
|
||||||
}
|
}
|
||||||
throw new Error(`Unknown storage type: ${sourceStorageType}`);
|
throw new Error(`Unknown source storage type: ${sourceStorageType}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFlagsFroAction(action) {
|
function getFlagsFroAction(action) {
|
||||||
@@ -91,7 +91,8 @@ function getFlagsFroAction(action) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getTargetExpr(sourceName, values, targetConnection, targetDriver) {
|
function getTargetExpr(sourceName, values, targetConnection, targetDriver) {
|
||||||
if (values.targetStorageType == 'csv') {
|
const { targetStorageType } = values;
|
||||||
|
if (targetStorageType == 'csv') {
|
||||||
return [
|
return [
|
||||||
'csvWriter',
|
'csvWriter',
|
||||||
{
|
{
|
||||||
@@ -99,7 +100,7 @@ function getTargetExpr(sourceName, values, targetConnection, targetDriver) {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
if (values.targetStorageType == 'jsonl') {
|
if (targetStorageType == 'jsonl') {
|
||||||
return [
|
return [
|
||||||
'jsonLinesWriter',
|
'jsonLinesWriter',
|
||||||
{
|
{
|
||||||
@@ -107,7 +108,7 @@ function getTargetExpr(sourceName, values, targetConnection, targetDriver) {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
if (values.targetStorageType == 'database') {
|
if (targetStorageType == 'database') {
|
||||||
return [
|
return [
|
||||||
'tableWriter',
|
'tableWriter',
|
||||||
{
|
{
|
||||||
@@ -118,6 +119,17 @@ function getTargetExpr(sourceName, values, targetConnection, targetDriver) {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
if (targetStorageType == 'archive') {
|
||||||
|
return [
|
||||||
|
'archiveWriter',
|
||||||
|
{
|
||||||
|
folderName: values.targetArchiveFolder,
|
||||||
|
fileName: getTargetName(sourceName, values),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unknown target storage type: ${targetStorageType}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function createImpExpScript(values, addEditorInfo = true) {
|
export default async function createImpExpScript(values, addEditorInfo = true) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import ModalContent from './ModalContent';
|
|||||||
import ImportExportConfigurator from '../impexp/ImportExportConfigurator';
|
import ImportExportConfigurator from '../impexp/ImportExportConfigurator';
|
||||||
import createImpExpScript from '../impexp/createImpExpScript';
|
import createImpExpScript from '../impexp/createImpExpScript';
|
||||||
import { openNewTab } from '../utility/common';
|
import { openNewTab } from '../utility/common';
|
||||||
import { useSetOpenedTabs } from '../utility/globalState';
|
import { useCurrentArchive, useSetOpenedTabs } from '../utility/globalState';
|
||||||
import RunnerOutputPane from '../query/RunnerOutputPane';
|
import RunnerOutputPane from '../query/RunnerOutputPane';
|
||||||
import axios from '../utility/axios';
|
import axios from '../utility/axios';
|
||||||
|
|
||||||
@@ -41,6 +41,7 @@ function GenerateSctriptButton({ modalState }) {
|
|||||||
export default function ImportExportModal({ modalState, initialValues }) {
|
export default function ImportExportModal({ modalState, initialValues }) {
|
||||||
const [executeNumber, setExecuteNumber] = React.useState(0);
|
const [executeNumber, setExecuteNumber] = React.useState(0);
|
||||||
const [runnerId, setRunnerId] = React.useState(null);
|
const [runnerId, setRunnerId] = React.useState(null);
|
||||||
|
const archive = useCurrentArchive();
|
||||||
|
|
||||||
const handleExecute = async (values) => {
|
const handleExecute = async (values) => {
|
||||||
const script = await createImpExpScript(values);
|
const script = await createImpExpScript(values);
|
||||||
@@ -57,7 +58,13 @@ export default function ImportExportModal({ modalState, initialValues }) {
|
|||||||
<ModalBase modalState={modalState}>
|
<ModalBase modalState={modalState}>
|
||||||
<Formik
|
<Formik
|
||||||
onSubmit={handleExecute}
|
onSubmit={handleExecute}
|
||||||
initialValues={{ sourceStorageType: 'database', targetStorageType: 'csv', ...initialValues }}
|
initialValues={{
|
||||||
|
sourceStorageType: 'database',
|
||||||
|
targetStorageType: 'csv',
|
||||||
|
sourceArchiveFolder: archive,
|
||||||
|
targetArchiveFolder: archive,
|
||||||
|
...initialValues,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Form>
|
<Form>
|
||||||
<ModalHeader modalState={modalState}>Import/Export</ModalHeader>
|
<ModalHeader modalState={modalState}>Import/Export</ModalHeader>
|
||||||
|
|||||||
6
packages/web/src/tabs/ArchiveFileTab.js
Normal file
6
packages/web/src/tabs/ArchiveFileTab.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import JslDataGrid from '../sqleditor/JslDataGrid';
|
||||||
|
|
||||||
|
export default function ArchiveFileTab({ folderName, fileName, tabVisible, toolbarPortalRef, tabid }) {
|
||||||
|
return <JslDataGrid jslid={`archive://${folderName}/${fileName}`} />;
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import TableStructureTab from './TableStructureTab';
|
|||||||
import QueryTab from './QueryTab';
|
import QueryTab from './QueryTab';
|
||||||
import ShellTab from './ShellTab';
|
import ShellTab from './ShellTab';
|
||||||
import InfoPageTab from './InfoPageTab';
|
import InfoPageTab from './InfoPageTab';
|
||||||
|
import ArchiveFileTab from './ArchiveFileTab';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
TableDataTab,
|
TableDataTab,
|
||||||
@@ -12,4 +13,5 @@ export default {
|
|||||||
QueryTab,
|
QueryTab,
|
||||||
InfoPageTab,
|
InfoPageTab,
|
||||||
ShellTab,
|
ShellTab,
|
||||||
|
ArchiveFileTab,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import Select from 'react-select';
|
import Select from 'react-select';
|
||||||
|
import Creatable from 'react-select/creatable';
|
||||||
import { TextField, SelectField } from './inputs';
|
import { TextField, SelectField } from './inputs';
|
||||||
import { Field, useFormikContext } from 'formik';
|
import { Field, useFormikContext } from 'formik';
|
||||||
import FormStyledButton from '../widgets/FormStyledButton';
|
import FormStyledButton from '../widgets/FormStyledButton';
|
||||||
import { useConnectionList, useDatabaseList, useDatabaseInfo } from './metadataLoaders';
|
import { useConnectionList, useDatabaseList, useDatabaseInfo, useArchiveFolders } from './metadataLoaders';
|
||||||
import useSocket from './SocketProvider';
|
import useSocket from './SocketProvider';
|
||||||
import getAsArray from './getAsArray';
|
import getAsArray from './getAsArray';
|
||||||
|
import axios from './axios';
|
||||||
|
|
||||||
export const FormRow = styled.div`
|
export const FormRow = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -85,11 +87,11 @@ export function FormRadioGroupItem({ name, text, value }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FormReactSelect({ options, name, isMulti = false }) {
|
export function FormReactSelect({ options, name, isMulti = false, Component = Select, ...other }) {
|
||||||
const { setFieldValue, values } = useFormikContext();
|
const { setFieldValue, values } = useFormikContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Component
|
||||||
options={options}
|
options={options}
|
||||||
value={
|
value={
|
||||||
isMulti
|
isMulti
|
||||||
@@ -102,6 +104,7 @@ export function FormReactSelect({ options, name, isMulti = false }) {
|
|||||||
menuPortalTarget={document.body}
|
menuPortalTarget={document.body}
|
||||||
isMulti={isMulti}
|
isMulti={isMulti}
|
||||||
closeMenuOnSelect={!isMulti}
|
closeMenuOnSelect={!isMulti}
|
||||||
|
{...other}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -170,3 +173,25 @@ export function FormTablesSelect({ conidName, databaseName, schemaName, name })
|
|||||||
if (tablesOptions.length == 0) return <div>Not available</div>;
|
if (tablesOptions.length == 0) return <div>Not available</div>;
|
||||||
return <FormReactSelect options={tablesOptions} name={name} isMulti />;
|
return <FormReactSelect options={tablesOptions} name={name} isMulti />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function FormArchiveFolderSelect({ name }) {
|
||||||
|
const { setFieldValue } = useFormikContext();
|
||||||
|
const folders = useArchiveFolders();
|
||||||
|
const folderOptions = React.useMemo(
|
||||||
|
() =>
|
||||||
|
(folders || []).map((folder) => ({
|
||||||
|
value: folder.name,
|
||||||
|
label: folder.name,
|
||||||
|
})),
|
||||||
|
[folders]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleCreateOption = (folder) => {
|
||||||
|
axios.post('archive/create-folder', { folder });
|
||||||
|
setFieldValue(name, folder);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormReactSelect options={folderOptions} name={name} Component={Creatable} onCreateOption={handleCreateOption} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -87,12 +87,14 @@ export function useAppObjectParams() {
|
|||||||
const setSavedSqlFiles = useSetSavedSqlFiles();
|
const setSavedSqlFiles = useSetSavedSqlFiles();
|
||||||
const openedConnections = useOpenedConnections();
|
const openedConnections = useOpenedConnections();
|
||||||
const setOpenedConnections = useSetOpenedConnections();
|
const setOpenedConnections = useSetOpenedConnections();
|
||||||
|
const currentArchive = useCurrentArchive();
|
||||||
const showModal = useShowModal();
|
const showModal = useShowModal();
|
||||||
const config = useConfig();
|
const config = useConfig();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
setOpenedTabs,
|
setOpenedTabs,
|
||||||
currentDatabase,
|
currentDatabase,
|
||||||
|
currentArchive,
|
||||||
newQuery,
|
newQuery,
|
||||||
openedTabs,
|
openedTabs,
|
||||||
setSavedSqlFiles,
|
setSavedSqlFiles,
|
||||||
@@ -113,3 +115,7 @@ export { OpenedConnectionsProvider, useOpenedConnections, useSetOpenedConnection
|
|||||||
const [LeftPanelWidthProvider, useLeftPanelWidth, useSetLeftPanelWidth] = createGlobalState(300);
|
const [LeftPanelWidthProvider, useLeftPanelWidth, useSetLeftPanelWidth] = createGlobalState(300);
|
||||||
|
|
||||||
export { LeftPanelWidthProvider, useLeftPanelWidth, useSetLeftPanelWidth };
|
export { LeftPanelWidthProvider, useLeftPanelWidth, useSetLeftPanelWidth };
|
||||||
|
|
||||||
|
const [CurrentArchiveProvider, useCurrentArchive, useSetCurrentArchive] = createGlobalState('default');
|
||||||
|
|
||||||
|
export { CurrentArchiveProvider, useCurrentArchive, useSetCurrentArchive };
|
||||||
|
|||||||
@@ -64,6 +64,18 @@ const databaseListLoader = ({ conid }) => ({
|
|||||||
reloadTrigger: `database-list-changed-${conid}`,
|
reloadTrigger: `database-list-changed-${conid}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const archiveFoldersLoader = () => ({
|
||||||
|
url: 'archive/folders',
|
||||||
|
params: {},
|
||||||
|
reloadTrigger: `archive-folders-changed`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const archiveFilesLoader = ({ folder }) => ({
|
||||||
|
url: 'archive/files',
|
||||||
|
params: { folder },
|
||||||
|
reloadTrigger: `archive-files-changed-${folder}`,
|
||||||
|
});
|
||||||
|
|
||||||
const serverStatusLoader = () => ({
|
const serverStatusLoader = () => ({
|
||||||
url: 'server-connections/server-status',
|
url: 'server-connections/server-status',
|
||||||
params: {},
|
params: {},
|
||||||
@@ -217,3 +229,17 @@ export function getConfig() {
|
|||||||
export function useConfig() {
|
export function useConfig() {
|
||||||
return useCore(configLoader, {}) || {};
|
return useCore(configLoader, {}) || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getArchiveFiles(args) {
|
||||||
|
return getCore(archiveFilesLoader, args);
|
||||||
|
}
|
||||||
|
export function useArchiveFiles(args) {
|
||||||
|
return useCore(archiveFilesLoader, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getArchiveFolders(args) {
|
||||||
|
return getCore(archiveFoldersLoader, args);
|
||||||
|
}
|
||||||
|
export function useArchiveFolders(args) {
|
||||||
|
return useCore(archiveFoldersLoader, args);
|
||||||
|
}
|
||||||
|
|||||||
70
packages/web/src/widgets/ArchiveWidget.js
Normal file
70
packages/web/src/widgets/ArchiveWidget.js
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import { AppObjectList } from '../appobj/AppObjectList';
|
||||||
|
import { useCurrentArchive, useOpenedTabs, useSavedSqlFiles, useSetCurrentArchive } from '../utility/globalState';
|
||||||
|
import closedTabAppObject from '../appobj/closedTabAppObject';
|
||||||
|
import {
|
||||||
|
SearchBoxWrapper,
|
||||||
|
WidgetsInnerContainer,
|
||||||
|
WidgetsMainContainer,
|
||||||
|
WidgetsOuterContainer,
|
||||||
|
WidgetTitle,
|
||||||
|
} from './WidgetStyles';
|
||||||
|
import savedSqlFileAppObject from '../appobj/savedSqlFileAppObject';
|
||||||
|
import { useArchiveFiles, useArchiveFolders } from '../utility/metadataLoaders';
|
||||||
|
import archiveFolderAppObject from '../appobj/archiveFolderAppObject';
|
||||||
|
import archiveFileAppObject from '../appobj/archiveFileAppObject';
|
||||||
|
|
||||||
|
function ArchiveFolderList() {
|
||||||
|
const folders = useArchiveFolders();
|
||||||
|
|
||||||
|
const setArchive = useSetCurrentArchive();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<WidgetTitle>Archive folder</WidgetTitle>
|
||||||
|
<WidgetsInnerContainer>
|
||||||
|
<AppObjectList
|
||||||
|
list={_.sortBy(folders, 'name')}
|
||||||
|
makeAppObj={archiveFolderAppObject()}
|
||||||
|
onObjectClick={(archive) => setArchive(archive.name)}
|
||||||
|
/>
|
||||||
|
</WidgetsInnerContainer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ArchiveFilesList() {
|
||||||
|
const folder = useCurrentArchive();
|
||||||
|
const files = useArchiveFiles({ folder });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<WidgetTitle>Archive files</WidgetTitle>
|
||||||
|
<WidgetsInnerContainer>
|
||||||
|
<AppObjectList
|
||||||
|
list={(files || []).map((file) => ({
|
||||||
|
fileName: file.name,
|
||||||
|
folderName: folder,
|
||||||
|
}))}
|
||||||
|
makeAppObj={archiveFileAppObject()}
|
||||||
|
/>
|
||||||
|
</WidgetsInnerContainer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ArchiveWidget() {
|
||||||
|
return (
|
||||||
|
<WidgetsMainContainer>
|
||||||
|
<WidgetsOuterContainer>
|
||||||
|
<ArchiveFolderList />
|
||||||
|
</WidgetsOuterContainer>
|
||||||
|
<WidgetsOuterContainer>
|
||||||
|
<ArchiveFilesList />
|
||||||
|
</WidgetsOuterContainer>
|
||||||
|
</WidgetsMainContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useCurrentWidget } from '../utility/globalState';
|
import { useCurrentWidget } from '../utility/globalState';
|
||||||
|
import ArchiveWidget from './ArchiveWidget';
|
||||||
import DatabaseWidget from './DatabaseWidget';
|
import DatabaseWidget from './DatabaseWidget';
|
||||||
import FilesWidget from './FilesWidget';
|
import FilesWidget from './FilesWidget';
|
||||||
|
|
||||||
@@ -7,5 +8,6 @@ export default function WidgetContainer() {
|
|||||||
const currentWidget = useCurrentWidget();
|
const currentWidget = useCurrentWidget();
|
||||||
if (currentWidget === 'database') return <DatabaseWidget />;
|
if (currentWidget === 'database') return <DatabaseWidget />;
|
||||||
if (currentWidget === 'file') return <FilesWidget />;
|
if (currentWidget === 'file') return <FilesWidget />;
|
||||||
|
if (currentWidget === 'archive') return <ArchiveWidget />;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const IconWrapper = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background-color: ${props =>
|
background-color: ${(props) =>
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
props.isSelected ? theme.widgetMenu.backgroundSelected : 'inherit'};
|
props.isSelected ? theme.widgetMenu.backgroundSelected : 'inherit'};
|
||||||
&:hover {
|
&:hover {
|
||||||
@@ -23,6 +23,7 @@ export default function WidgetIconPanel() {
|
|||||||
{
|
{
|
||||||
icon: 'fa-database',
|
icon: 'fa-database',
|
||||||
name: 'database',
|
name: 'database',
|
||||||
|
title: 'Database connections',
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// icon: 'fa-table',
|
// icon: 'fa-table',
|
||||||
@@ -31,6 +32,12 @@ export default function WidgetIconPanel() {
|
|||||||
{
|
{
|
||||||
icon: 'fa-file-alt',
|
icon: 'fa-file-alt',
|
||||||
name: 'file',
|
name: 'file',
|
||||||
|
title: 'Closed tabs & Saved SQL files',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'fa-archive',
|
||||||
|
name: 'archive',
|
||||||
|
title: 'Archive (saved tabular data)',
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// icon: 'fa-cog',
|
// icon: 'fa-cog',
|
||||||
@@ -47,14 +54,15 @@ export default function WidgetIconPanel() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{widgets.map(({ icon, name }) => (
|
{widgets.map(({ icon, name, title }) => (
|
||||||
<IconWrapper
|
<IconWrapper
|
||||||
key={icon}
|
key={icon}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
isSelected={name === currentWidget}
|
isSelected={name === currentWidget}
|
||||||
onClick={() => setCurrentWidget(name === currentWidget ? null : name)}
|
onClick={() => setCurrentWidget(name === currentWidget ? null : name)}
|
||||||
|
title={title}
|
||||||
>
|
>
|
||||||
<i className={`fas ${icon}`}/>
|
<i className={`fas ${icon}`} />
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user