mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-29 14:33: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 fs = require('fs');
|
||||
const lineReader = require('line-reader');
|
||||
const { jsldir } = require('../utility/directories');
|
||||
const { jsldir, archivedir } = require('../utility/directories');
|
||||
const socket = require('../utility/socket');
|
||||
|
||||
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 = {
|
||||
openedReaders: {},
|
||||
|
||||
@@ -57,7 +65,7 @@ module.exports = {
|
||||
// 'OPENING READER, LINES=',
|
||||
// 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) =>
|
||||
lineReader.open(file, (err, reader) => {
|
||||
if (err) reject(err);
|
||||
@@ -93,7 +101,7 @@ module.exports = {
|
||||
|
||||
getInfo_meta: 'get',
|
||||
async getInfo({ jslid }) {
|
||||
const file = path.join(jsldir(), `${jslid}.jsonl`);
|
||||
const file = getJslFileName(jslid);
|
||||
const firstLine = await readFirstLine(file);
|
||||
if (firstLine) return JSON.parse(firstLine);
|
||||
return null;
|
||||
@@ -119,8 +127,9 @@ module.exports = {
|
||||
|
||||
getStats_meta: 'get',
|
||||
getStats({ jslid }) {
|
||||
const file = path.join(jsldir(), `${jslid}.jsonl.stats`);
|
||||
return JSON.parse(fs.readFileSync(file, 'utf-8'));
|
||||
const file = `${getJslFileName(jslid)}.stats`;
|
||||
if (fs.existsSync(file)) return JSON.parse(fs.readFileSync(file, 'utf-8'));
|
||||
return {};
|
||||
},
|
||||
|
||||
async notifyChangedStats(stats) {
|
||||
|
||||
@@ -20,6 +20,7 @@ const runners = require('./controllers/runners');
|
||||
const jsldata = require('./controllers/jsldata');
|
||||
const config = require('./controllers/config');
|
||||
const files = require('./controllers/files');
|
||||
const archive = require('./controllers/archive');
|
||||
|
||||
const { rundir } = require('./utility/directories');
|
||||
|
||||
@@ -55,6 +56,7 @@ function start(argument = null) {
|
||||
useController(app, '/jsldata', jsldata);
|
||||
useController(app, '/config', config);
|
||||
useController(app, '/files', files);
|
||||
useController(app, '/archive', archive);
|
||||
|
||||
if (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 jsonLinesReader = require('./jsonLinesReader');
|
||||
const jslDataReader = require('./jslDataReader');
|
||||
const archiveWriter = require('./archiveWriter');
|
||||
|
||||
module.exports = {
|
||||
queryReader,
|
||||
@@ -26,4 +27,5 @@ module.exports = {
|
||||
fakeObjectReader,
|
||||
consoleObjectWriter,
|
||||
jslDataReader,
|
||||
archiveWriter,
|
||||
};
|
||||
|
||||
@@ -2,31 +2,28 @@ const os = require('os');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
let createdDatadir = false;
|
||||
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() {
|
||||
const dir = path.join(os.homedir(), 'dbgate-data');
|
||||
if (!createdDatadir) {
|
||||
if (!fs.existsSync(dir)) {
|
||||
console.log(`Creating data directory ${dir}`);
|
||||
fs.mkdirSync(dir);
|
||||
}
|
||||
createdDatadir = true;
|
||||
}
|
||||
ensureDirectory(dir);
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
const dirFunc = (dirname) => () => {
|
||||
const dir = path.join(datadir(), dirname);
|
||||
if (!createDirectories[dirname]) {
|
||||
if (!fs.existsSync(dir)) {
|
||||
console.log(`Creating jsl directory ${dir}`);
|
||||
fs.mkdirSync(dir);
|
||||
}
|
||||
createDirectories[dirname] = true;
|
||||
}
|
||||
ensureDirectory(dir);
|
||||
|
||||
return dir;
|
||||
};
|
||||
@@ -34,10 +31,13 @@ const dirFunc = (dirname) => () => {
|
||||
const jsldir = dirFunc('jsl');
|
||||
const rundir = dirFunc('run');
|
||||
const uploadsdir = dirFunc('uploads');
|
||||
const archivedir = dirFunc('archive');
|
||||
|
||||
module.exports = {
|
||||
datadir,
|
||||
jsldir,
|
||||
rundir,
|
||||
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,
|
||||
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