mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-29 14:33:59 +00:00
table structure tab WIP
This commit is contained in:
@@ -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;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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',
|
||||
})),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
13
api/src/engines/mssql/columns.sql
Normal file
13
api/src/engines/mssql/columns.sql
Normal 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
|
||||
@@ -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
29
lib/src/dbinfo.ts
Normal 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
11
lib/src/engines.ts
Normal 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>;
|
||||
}
|
||||
|
||||
@@ -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
5
lib/src/query.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { ChildProcess } from "child_process";
|
||||
|
||||
export interface QueryResult {
|
||||
rows: any[];
|
||||
}
|
||||
@@ -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": {
|
||||
|
||||
@@ -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 (
|
||||
|
||||
17
web/src/appobj/columnAppObject.js
Normal file
17
web/src/appobj/columnAppObject.js
Normal 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 };
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
78
web/src/tabs/TableStructureTab.js
Normal file
78
web/src/tabs/TableStructureTab.js
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import TableDataTab from './TableDataTab';
|
||||
import TableStructureTab from './TableStructureTab';
|
||||
|
||||
export default {
|
||||
TableDataTab,
|
||||
TableStructureTab,
|
||||
};
|
||||
|
||||
41
web/src/utility/ObjectListControl.js
Normal file
41
web/src/utility/ObjectListControl.js
Normal 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>
|
||||
);
|
||||
}
|
||||
59
web/src/utility/TableControl.js
Normal file
59
web/src/utility/TableControl.js
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -15,12 +15,12 @@
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
"esnext",
|
||||
],
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true
|
||||
"isolatedModules": true,
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
|
||||
Reference in New Issue
Block a user