mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-23 21:56:00 +00:00
archive, export into archive
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
SavedSqlFilesProvider,
|
||||
OpenedConnectionsProvider,
|
||||
LeftPanelWidthProvider,
|
||||
CurrentArchiveProvider,
|
||||
} from './utility/globalState';
|
||||
import { SocketProvider } from './utility/SocketProvider';
|
||||
import ConnectionsPinger from './utility/ConnectionsPinger';
|
||||
@@ -24,7 +25,9 @@ function App() {
|
||||
<LeftPanelWidthProvider>
|
||||
<ConnectionsPinger>
|
||||
<ModalLayerProvider>
|
||||
<Screen />
|
||||
<CurrentArchiveProvider>
|
||||
<Screen />
|
||||
</CurrentArchiveProvider>
|
||||
</ModalLayerProvider>
|
||||
</ConnectionsPinger>
|
||||
</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 ViewIcon = (props) => getIconImage('view2.svg', props);
|
||||
export const ArchiveTableIcon = (props) => getIconImage('archtable.svg', props);
|
||||
export const DatabaseIcon = (props) => getIconImage('database.svg', props);
|
||||
export const ServerIcon = (props) => getIconImage('server.svg', props);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
FormDatabaseSelect,
|
||||
FormTablesSelect,
|
||||
FormSchemaSelect,
|
||||
FormArchiveFolderSelect,
|
||||
} from '../utility/forms';
|
||||
import { useConnectionInfo, useDatabaseInfo } from '../utility/metadataLoaders';
|
||||
import TableControl, { TableColumn } from '../utility/TableControl';
|
||||
@@ -147,6 +148,7 @@ function SourceTargetConfig({
|
||||
storageTypeField,
|
||||
connectionIdField,
|
||||
databaseNameField,
|
||||
archiveFolderField,
|
||||
schemaNameField,
|
||||
tablesField = undefined,
|
||||
engine = undefined,
|
||||
@@ -161,6 +163,7 @@ function SourceTargetConfig({
|
||||
{ value: 'jsonl', label: 'JSON lines file(s)', directions: ['source', 'target'] },
|
||||
{ value: 'excel', label: 'MS Excel file(s)', directions: ['source'] },
|
||||
{ value: 'query', label: 'SQL Query', directions: ['source'] },
|
||||
{ value: 'archive', label: 'Archive', directions: ['source', 'target'] },
|
||||
];
|
||||
const storageType = values[storageTypeField];
|
||||
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 />}
|
||||
</Column>
|
||||
);
|
||||
@@ -270,6 +280,7 @@ export default function ImportExportConfigurator() {
|
||||
storageTypeField="sourceStorageType"
|
||||
connectionIdField="sourceConnectionId"
|
||||
databaseNameField="sourceDatabaseName"
|
||||
archiveFolderField="sourceArchiveFolder"
|
||||
schemaNameField="sourceSchemaName"
|
||||
tablesField="sourceList"
|
||||
engine={sourceEngine}
|
||||
@@ -279,6 +290,7 @@ export default function ImportExportConfigurator() {
|
||||
storageTypeField="targetStorageType"
|
||||
connectionIdField="targetConnectionId"
|
||||
databaseNameField="targetDatabaseName"
|
||||
archiveFolderField="targetArchiveFolder"
|
||||
schemaNameField="targetSchemaName"
|
||||
/>
|
||||
</Wrapper>
|
||||
|
||||
@@ -68,7 +68,7 @@ function getSourceExpr(sourceName, values, sourceConnection, sourceDriver) {
|
||||
if (sourceStorageType == 'jsldata') {
|
||||
return ['jslDataReader', { jslid: values.sourceJslId }];
|
||||
}
|
||||
throw new Error(`Unknown storage type: ${sourceStorageType}`);
|
||||
throw new Error(`Unknown source storage type: ${sourceStorageType}`);
|
||||
}
|
||||
|
||||
function getFlagsFroAction(action) {
|
||||
@@ -91,7 +91,8 @@ function getFlagsFroAction(action) {
|
||||
}
|
||||
|
||||
function getTargetExpr(sourceName, values, targetConnection, targetDriver) {
|
||||
if (values.targetStorageType == 'csv') {
|
||||
const { targetStorageType } = values;
|
||||
if (targetStorageType == 'csv') {
|
||||
return [
|
||||
'csvWriter',
|
||||
{
|
||||
@@ -99,7 +100,7 @@ function getTargetExpr(sourceName, values, targetConnection, targetDriver) {
|
||||
},
|
||||
];
|
||||
}
|
||||
if (values.targetStorageType == 'jsonl') {
|
||||
if (targetStorageType == 'jsonl') {
|
||||
return [
|
||||
'jsonLinesWriter',
|
||||
{
|
||||
@@ -107,7 +108,7 @@ function getTargetExpr(sourceName, values, targetConnection, targetDriver) {
|
||||
},
|
||||
];
|
||||
}
|
||||
if (values.targetStorageType == 'database') {
|
||||
if (targetStorageType == 'database') {
|
||||
return [
|
||||
'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) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import ModalContent from './ModalContent';
|
||||
import ImportExportConfigurator from '../impexp/ImportExportConfigurator';
|
||||
import createImpExpScript from '../impexp/createImpExpScript';
|
||||
import { openNewTab } from '../utility/common';
|
||||
import { useSetOpenedTabs } from '../utility/globalState';
|
||||
import { useCurrentArchive, useSetOpenedTabs } from '../utility/globalState';
|
||||
import RunnerOutputPane from '../query/RunnerOutputPane';
|
||||
import axios from '../utility/axios';
|
||||
|
||||
@@ -41,6 +41,7 @@ function GenerateSctriptButton({ modalState }) {
|
||||
export default function ImportExportModal({ modalState, initialValues }) {
|
||||
const [executeNumber, setExecuteNumber] = React.useState(0);
|
||||
const [runnerId, setRunnerId] = React.useState(null);
|
||||
const archive = useCurrentArchive();
|
||||
|
||||
const handleExecute = async (values) => {
|
||||
const script = await createImpExpScript(values);
|
||||
@@ -57,7 +58,13 @@ export default function ImportExportModal({ modalState, initialValues }) {
|
||||
<ModalBase modalState={modalState}>
|
||||
<Formik
|
||||
onSubmit={handleExecute}
|
||||
initialValues={{ sourceStorageType: 'database', targetStorageType: 'csv', ...initialValues }}
|
||||
initialValues={{
|
||||
sourceStorageType: 'database',
|
||||
targetStorageType: 'csv',
|
||||
sourceArchiveFolder: archive,
|
||||
targetArchiveFolder: archive,
|
||||
...initialValues,
|
||||
}}
|
||||
>
|
||||
<Form>
|
||||
<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 ShellTab from './ShellTab';
|
||||
import InfoPageTab from './InfoPageTab';
|
||||
import ArchiveFileTab from './ArchiveFileTab';
|
||||
|
||||
export default {
|
||||
TableDataTab,
|
||||
@@ -12,4 +13,5 @@ export default {
|
||||
QueryTab,
|
||||
InfoPageTab,
|
||||
ShellTab,
|
||||
ArchiveFileTab,
|
||||
};
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import Select from 'react-select';
|
||||
import Creatable from 'react-select/creatable';
|
||||
import { TextField, SelectField } from './inputs';
|
||||
import { Field, useFormikContext } from 'formik';
|
||||
import FormStyledButton from '../widgets/FormStyledButton';
|
||||
import { useConnectionList, useDatabaseList, useDatabaseInfo } from './metadataLoaders';
|
||||
import { useConnectionList, useDatabaseList, useDatabaseInfo, useArchiveFolders } from './metadataLoaders';
|
||||
import useSocket from './SocketProvider';
|
||||
import getAsArray from './getAsArray';
|
||||
import axios from './axios';
|
||||
|
||||
export const FormRow = styled.div`
|
||||
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();
|
||||
|
||||
return (
|
||||
<Select
|
||||
<Component
|
||||
options={options}
|
||||
value={
|
||||
isMulti
|
||||
@@ -102,6 +104,7 @@ export function FormReactSelect({ options, name, isMulti = false }) {
|
||||
menuPortalTarget={document.body}
|
||||
isMulti={isMulti}
|
||||
closeMenuOnSelect={!isMulti}
|
||||
{...other}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -170,3 +173,25 @@ export function FormTablesSelect({ conidName, databaseName, schemaName, name })
|
||||
if (tablesOptions.length == 0) return <div>Not available</div>;
|
||||
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 openedConnections = useOpenedConnections();
|
||||
const setOpenedConnections = useSetOpenedConnections();
|
||||
const currentArchive = useCurrentArchive();
|
||||
const showModal = useShowModal();
|
||||
const config = useConfig();
|
||||
|
||||
return {
|
||||
setOpenedTabs,
|
||||
currentDatabase,
|
||||
currentArchive,
|
||||
newQuery,
|
||||
openedTabs,
|
||||
setSavedSqlFiles,
|
||||
@@ -113,3 +115,7 @@ export { OpenedConnectionsProvider, useOpenedConnections, useSetOpenedConnection
|
||||
const [LeftPanelWidthProvider, useLeftPanelWidth, useSetLeftPanelWidth] = createGlobalState(300);
|
||||
|
||||
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}`,
|
||||
});
|
||||
|
||||
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 = () => ({
|
||||
url: 'server-connections/server-status',
|
||||
params: {},
|
||||
@@ -217,3 +229,17 @@ export function getConfig() {
|
||||
export function useConfig() {
|
||||
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 { useCurrentWidget } from '../utility/globalState';
|
||||
import ArchiveWidget from './ArchiveWidget';
|
||||
import DatabaseWidget from './DatabaseWidget';
|
||||
import FilesWidget from './FilesWidget';
|
||||
|
||||
@@ -7,5 +8,6 @@ export default function WidgetContainer() {
|
||||
const currentWidget = useCurrentWidget();
|
||||
if (currentWidget === 'database') return <DatabaseWidget />;
|
||||
if (currentWidget === 'file') return <FilesWidget />;
|
||||
if (currentWidget === 'archive') return <ArchiveWidget />;
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ const IconWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: ${props =>
|
||||
background-color: ${(props) =>
|
||||
// @ts-ignore
|
||||
props.isSelected ? theme.widgetMenu.backgroundSelected : 'inherit'};
|
||||
&:hover {
|
||||
@@ -23,6 +23,7 @@ export default function WidgetIconPanel() {
|
||||
{
|
||||
icon: 'fa-database',
|
||||
name: 'database',
|
||||
title: 'Database connections',
|
||||
},
|
||||
// {
|
||||
// icon: 'fa-table',
|
||||
@@ -31,6 +32,12 @@ export default function WidgetIconPanel() {
|
||||
{
|
||||
icon: 'fa-file-alt',
|
||||
name: 'file',
|
||||
title: 'Closed tabs & Saved SQL files',
|
||||
},
|
||||
{
|
||||
icon: 'fa-archive',
|
||||
name: 'archive',
|
||||
title: 'Archive (saved tabular data)',
|
||||
},
|
||||
// {
|
||||
// icon: 'fa-cog',
|
||||
@@ -47,14 +54,15 @@ export default function WidgetIconPanel() {
|
||||
|
||||
return (
|
||||
<>
|
||||
{widgets.map(({ icon, name }) => (
|
||||
{widgets.map(({ icon, name, title }) => (
|
||||
<IconWrapper
|
||||
key={icon}
|
||||
// @ts-ignore
|
||||
isSelected={name === currentWidget}
|
||||
onClick={() => setCurrentWidget(name === currentWidget ? null : name)}
|
||||
title={title}
|
||||
>
|
||||
<i className={`fas ${icon}`}/>
|
||||
<i className={`fas ${icon}`} />
|
||||
</IconWrapper>
|
||||
))}
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user