table structure tab WIP

This commit is contained in:
Jan Prochazka
2020-02-02 11:31:41 +01:00
parent d22058382f
commit af80a2799f
18 changed files with 318 additions and 57 deletions

View File

@@ -8,4 +8,11 @@ module.exports = {
const res = await databaseConnections.sendRequest(opened, { msgtype: 'tableData', schemaName, pureName });
return res;
},
tableInfo_meta: 'get',
async tableInfo({ conid, database, schemaName, pureName }) {
const opened = await databaseConnections.ensureOpened(conid, database);
const table = opened.structure.tables.find(x => x.pureName == pureName && x.schemaName == schemaName);
return table;
},
};

View File

@@ -1,5 +1,6 @@
const fs = require('fs-extra');
const path = require('path');
const _ = require('lodash');
const DatabaseAnalayser = require('../default/DatabaseAnalyser');
@@ -27,13 +28,18 @@ class MsSqlAnalyser extends DatabaseAnalayser {
}
async runAnalysis() {
const tables = await this.driver.query(this.pool, await this.createQuery('tables.sql'));
// for (const table of tables) {
// table.name = {
// schema: table.schemaName,
// name: table.tableName,
// };
// }
this.result.tables = tables.rows;
const columns = await this.driver.query(this.pool, await this.createQuery('columns.sql'));
this.result.tables = tables.rows.map(table => ({
...table,
columns: columns.rows
.filter(col => col.objectId == table.objectId)
.map(({ isNullable, isIdentity, ...col }) => ({
...col,
notNull: isNullable != 'True',
autoIncrement: isIdentity == 'True',
})),
}));
}
}

View File

@@ -0,0 +1,13 @@
select c.name as columnName, t.name as dataType, c.object_id as objectId, c.is_identity as isIdentity,
c.max_length as maxLength, c.precision, c.scale, c.is_nullable as isNullable,
d.definition as defaultValue, d.name as defaultConstraint,
m.definition as computedExpression, m.is_persisted as isPersisted, c.column_id as columnId,
-- TODO only if version >= 2008
c.is_sparse as isSparse
from sys.columns c
inner join sys.types t on c.system_type_id = t.system_type_id and c.user_type_id = t.user_type_id
inner join sys.objects o on c.object_id = o.object_id
left join sys.default_constraints d on c.default_object_id = d.object_id
left join sys.computed_columns m on m.object_id = c.object_id and m.column_id = c.column_id
where o.type = 'U' and o.object_id =[OBJECT_ID_CONDITION]
order by c.column_id

View File

@@ -1,5 +1,5 @@
select
o.name as pureName, s.name as schemaName, o.object_id,
o.name as pureName, s.name as schemaName, o.object_id as objectId,
o.create_date, o.modify_date
from sys.tables o
inner join sys.schemas s on o.schema_id = s.schema_id

29
lib/src/dbinfo.ts Normal file
View File

@@ -0,0 +1,29 @@
import { ChildProcess } from "child_process";
export interface NamedObjectInfo {
pureName: string;
schemaName: string;
}
export interface ColumnInfo {
columnName: string;
notNull: boolean;
autoIncrement: boolean;
dataType: string;
precision: number;
scale: number;
length: number;
computedExpression: string;
isPersisted: boolean;
isSparse: boolean;
defaultValue: string;
defaultConstraint: string;
}
export interface TableInfo extends NamedObjectInfo {
columns: ColumnInfo[];
}
export interface DatabaseInfo {
tables: TableInfo[];
}

11
lib/src/engines.ts Normal file
View File

@@ -0,0 +1,11 @@
import { QueryResult } from "./query";
export interface EngineDriver {
connect({ server, port, user, password });
query(pool, sql: string): Promise<QueryResult>;
getVersion(pool): Promise<string>;
listDatabases(pool): Promise<{ name: string }[]>;
analyseFull(pool): Promise<void>;
analyseIncremental(pool): Promise<void>;
}

View File

@@ -1,28 +1,5 @@
import { ChildProcess } from "child_process";
export interface QueryResult {
rows: any[];
}
export interface EngineDriver {
connect({ server, port, user, password });
query(pool, sql: string): Promise<QueryResult>;
getVersion(pool): Promise<string>;
listDatabases(pool): Promise<{ name: string }[]>;
analyseFull(pool): Promise<void>;
analyseIncremental(pool): Promise<void>;
}
export interface NamedObjectInfo {
pureName: string;
schemaName: string;
}
export interface TableInfo extends NamedObjectInfo {}
export interface DatabaseInfo {
tables: TableInfo[];
}
import { DatabaseInfo } from "./dbinfo";
export interface OpenedDatabaseConnection {
conid: string;
@@ -31,6 +8,6 @@ export interface OpenedDatabaseConnection {
subprocess: ChildProcess;
}
export function sum(a: number, b: number) {
return a + b;
}
export * from "./engines";
export * from "./dbinfo";
export * from "./query";

5
lib/src/query.ts Normal file
View File

@@ -0,0 +1,5 @@
import { ChildProcess } from "child_process";
export interface QueryResult {
rows: any[];
}

View File

@@ -19,6 +19,7 @@
"resize-observer-polyfill": "^1.5.1",
"socket.io-client": "^2.3.0",
"styled-components": "^4.4.1",
"@dbgate/lib": "file:../lib",
"uuid": "^3.4.0"
},
"scripts": {

View File

@@ -17,11 +17,13 @@ const IconWrap = styled.span`
`;
export function AppObjectCore({ title, Icon, Menu, data, makeAppObj, onClick }) {
const setOpenedTabs = useSetOpenedTabs();
const handleContextMenu = event => {
if (!Menu) return;
event.preventDefault();
showMenu(event.pageX, event.pageY, <Menu data={data} makeAppObj={makeAppObj} />);
showMenu(event.pageX, event.pageY, <Menu data={data} makeAppObj={makeAppObj} setOpenedTabs={setOpenedTabs} />);
};
return (

View File

@@ -0,0 +1,17 @@
import React from 'react';
import { TableIcon } from '../icons';
import { DropDownMenuItem } from '../modals/DropDownMenu';
import showModal from '../modals/showModal';
import ConnectionModal from '../modals/ConnectionModal';
import axios from '../utility/axios';
import { openNewTab } from '../utility/common';
import { useSetOpenedTabs } from '../utility/globalState';
/** @param columnProps {import('@dbgate/lib').ColumnInfo} */
export default function columnAppObject(columnProps, { setOpenedTabs }) {
const title = columnProps.columnName;
const key = title;
const Icon = TableIcon;
return { title, key, Icon };
}

View File

@@ -5,18 +5,33 @@ import showModal from '../modals/showModal';
import ConnectionModal from '../modals/ConnectionModal';
import axios from '../utility/axios';
import { openNewTab } from '../utility/common';
import { useSetOpenedTabs } from '../utility/globalState';
function Menu({ data, makeAppObj }) {
const handleEdit = () => {
showModal(modalState => <ConnectionModal modalState={modalState} connection={data} />);
function openTableDetail(setOpenedTabs, tabComponent, { schemaName, pureName, conid, database }) {
openNewTab(setOpenedTabs, {
title: pureName,
icon: 'table2.svg',
tabComponent,
props: {
schemaName,
pureName,
conid,
database,
},
});
}
function Menu({ data, makeAppObj, setOpenedTabs }) {
const handleOpenData = () => {
openTableDetail(setOpenedTabs, 'TableDataTab', data);
};
const handleDelete = () => {
axios.post('connections/delete', data);
const handleOpenStructure = () => {
openTableDetail(setOpenedTabs, 'TableStructureTab', data);
};
return (
<>
<DropDownMenuItem onClick={handleEdit}>Edit</DropDownMenuItem>
<DropDownMenuItem onClick={handleDelete}>Delete</DropDownMenuItem>
<DropDownMenuItem onClick={handleOpenData}>Open data</DropDownMenuItem>
<DropDownMenuItem onClick={handleOpenStructure}>Open structure</DropDownMenuItem>
</>
);
}
@@ -26,16 +41,11 @@ export default function tableAppObject({ conid, database, pureName, schemaName }
const key = title;
const Icon = TableIcon;
const onClick = ({ schemaName, pureName }) => {
openNewTab(setOpenedTabs, {
title: pureName,
icon: 'table2.svg',
tabComponent: 'TableDataTab',
props: {
schemaName,
pureName,
conid,
database,
},
openTableDetail(setOpenedTabs, 'TableDataTab', {
schemaName,
pureName,
conid,
database,
});
};

View File

@@ -1,7 +1,10 @@
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
font-family: -apple-system,BlinkMacSystemFont,Segoe WPC,Segoe UI,HelveticaNeue-Light,Ubuntu,Droid Sans,sans-serif;
font-size: 14px;
/* font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
sans-serif;
*/
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

View File

@@ -0,0 +1,78 @@
import React from 'react';
import useFetch from '../utility/useFetch';
import styled from 'styled-components';
import theme from '../theme';
import ObjectListControl from '../utility/ObjectListControl';
import { TableColumn } from '../utility/TableControl';
const WhitePage = styled.div`
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: white;
`;
export default function TableStructureTab({ conid, database, schemaName, pureName }) {
/** @type {import('@dbgate/lib').TableInfo} */
const tableInfo = useFetch({
url: 'tables/table-info',
params: { conid, database, schemaName, pureName },
});
if (!tableInfo) return null;
return (
<WhitePage>
<ObjectListControl
collection={tableInfo.columns.map((x, index) => ({ ...x, ordinal: index + 1 }))}
title="Columns"
>
<TableColumn
fieldName="notNull"
header="Not NULL"
sortable={true}
formatter={row => (row.notNull ? 'YES' : 'NO')}
/>
<TableColumn fieldName="dataType" header="Data Type" sortable={true} />
<TableColumn fieldName="defaultValue" header="Default value" sortable={true} />
<TableColumn
fieldName="isSparse"
header="Is Sparse"
sortable={true}
formatter={row => (row.isSparse ? 'YES' : 'NO')}
/>
<TableColumn fieldName="computedExpression" header="Computed Expression" sortable={true} />
<TableColumn
fieldName="isPersisted"
header="Is Persisted"
sortable={true}
formatter={row => (row.isPersisted ? 'YES' : 'NO')}
/>
{/* {_.includes(dbCaps.columnListOptionalColumns, 'referencedTableNamesFormatted') && (
<TableColumn fieldName="referencedTableNamesFormatted" header="References" sortable={true} />
)}
<TableColumn
fieldName="actions"
header=""
formatter={row => (
<span>
<Link
linkElementId={encodeHtmlId(`button_delete_column_${row.column.name}`)}
onClick={() => this.deleteColumn(row)}
>
Delete
</Link>{' '}
|{' '}
<Link
linkElementId={encodeHtmlId(`button_edit_column__${row.column.name}`)}
onClick={() => this.editColumn(row)}
>
Edit
</Link>
</span>
)}
/> */}
</ObjectListControl>
</WhitePage>
);
}

View File

@@ -1,5 +1,7 @@
import TableDataTab from './TableDataTab';
import TableStructureTab from './TableStructureTab';
export default {
TableDataTab,
TableStructureTab,
};

View File

@@ -0,0 +1,41 @@
import React from 'react';
import useFetch from '../utility/useFetch';
import styled from 'styled-components';
import theme from '../theme';
import TableControl from './TableControl';
const ObjectListWrapper = styled.div`
margin-bottom: 20px;
`;
const ObjectListHeader = styled.div`
background-color: #ebedef;
padding: 5px;
`;
const ObjectListHeaderTitle = styled.span`
font-weight: bold;
margin-left: 5px;
`;
const ObjectListBody = styled.div`
margin: 20px;
// margin-left: 20px;
// margin-right: 20px;
// margin-top: 3px;
`;
export default function ObjectListControl({ collection = [], title, showIfEmpty = false, children }) {
if (collection.length == 0 && !showIfEmpty) return null;
return (
<ObjectListWrapper>
<ObjectListHeader>
<ObjectListHeaderTitle>{title}</ObjectListHeaderTitle>
</ObjectListHeader>
<ObjectListBody>
<TableControl rows={collection}>{children}</TableControl>
</ObjectListBody>
</ObjectListWrapper>
);
}

View File

@@ -0,0 +1,59 @@
import React from 'react';
import useFetch from '../utility/useFetch';
import styled from 'styled-components';
import theme from '../theme';
const Table = styled.table`
border-collapse: collapse;
width: 100%;
`;
const TableHead = styled.thead``;
const TableBody = styled.tbody``;
const TableHeaderRow = styled.tr``;
const TableBodyRow = styled.tr``;
const TableHeaderCell = styled.td`
border: 1px solid #e8eef4;
background-color: #e8eef4;
padding: 5px;
`;
const TableBodyCell = styled.td`
border: 1px solid #e8eef4;
padding: 5px;
`;
export function TableColumn({ fieldName, header, sortable, formatter = undefined }) {
return <></>;
}
function format(row, col) {
const { formatter, fieldName } = col;
if (formatter) return formatter(row);
return row[fieldName];
}
export default function TableControl({ rows = [], children }) {
const columns = (children instanceof Array ? children : [children])
.filter(child => child != null)
.map(child => child.props);
return (
<Table>
<TableHead>
<TableHeaderRow>
{columns.map(x => (
<TableHeaderCell key={x.fieldName}>{x.header}</TableHeaderCell>
))}
</TableHeaderRow>
</TableHead>
<TableBody>
{rows.map((row, index) => (
<TableBodyRow key={index}>
{columns.map(col => (
<TableBodyCell key={col.fieldName}>{format(row, col)}</TableBodyCell>
))}
</TableBodyRow>
))}
</TableBody>
</Table>
);
}

View File

@@ -15,12 +15,12 @@
"lib": [
"dom",
"dom.iterable",
"esnext"
"esnext",
],
"strict": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true
"isolatedModules": true,
},
"include": [
"src"