server connections handling

This commit is contained in:
Jan Prochazka
2020-05-01 11:41:18 +02:00
parent 44c19ad277
commit ca7eea8a05
11 changed files with 284 additions and 71 deletions

View File

@@ -1,24 +1,31 @@
const connections = require('./connections'); const connections = require('./connections');
const socket = require('../utility/socket'); const socket = require('../utility/socket');
const { fork } = require('child_process'); const { fork } = require('child_process');
const _ = require('lodash');
module.exports = { module.exports = {
opened: [], opened: [],
closed: [],
handle_databases(conid, { databases }) { handle_databases(conid, { databases }) {
const existing = this.opened.find(x => x.conid == conid); const existing = this.opened.find((x) => x.conid == conid);
if (!existing) return; if (!existing) return;
existing.databases = databases; existing.databases = databases;
socket.emitChanged(`database-list-changed-${conid}`); socket.emitChanged(`database-list-changed-${conid}`);
}, },
handle_status(conid, { status }) {
const existing = this.opened.find((x) => x.conid == conid);
if (!existing) return;
existing.status = status;
socket.emitChanged(`server-status-changed`);
},
handle_error(conid, { error }) { handle_error(conid, { error }) {
console.log(`Error in server connection ${conid}: ${error}`); console.log(`Error in server connection ${conid}: ${error}`);
}, },
handle_ping() { handle_ping() {},
},
async ensureOpened(conid) { async ensureOpened(conid) {
const existing = this.opened.find(x => x.conid == conid); const existing = this.opened.find((x) => x.conid == conid);
if (existing) return existing; if (existing) return existing;
const connection = await connections.get({ conid }); const connection = await connections.get({ conid });
const subprocess = fork(process.argv[1], ['serverConnectionProcess']); const subprocess = fork(process.argv[1], ['serverConnectionProcess']);
@@ -27,19 +34,75 @@ module.exports = {
subprocess, subprocess,
databases: [], databases: [],
connection, connection,
status: {
name: 'pending',
},
disconnected: false,
}; };
this.opened.push(newOpened); this.opened.push(newOpened);
this.closed = this.closed.filter((x) => x != conid);
socket.emitChanged(`server-status-changed`);
// @ts-ignore // @ts-ignore
subprocess.on('message', ({ msgtype, ...message }) => { subprocess.on('message', ({ msgtype, ...message }) => {
if (newOpened.disconnected) return;
this[`handle_${msgtype}`](conid, message); this[`handle_${msgtype}`](conid, message);
}); });
subprocess.on('exit', () => {
if (newOpened.disconnected) return;
this.opened = this.opened.filter((x) => x.conid != conid);
this.closed.push(conid);
socket.emitChanged(`server-status-changed`);
});
subprocess.send({ msgtype: 'connect', ...connection }); subprocess.send({ msgtype: 'connect', ...connection });
return newOpened; return newOpened;
}, },
close(conid) {
const existing = this.opened.find((x) => x.conid == conid);
if (existing) {
existing.disconnected = true;
existing.subprocess.kill();
this.opened = this.opened.filter((x) => x.conid != conid);
this.closed.push(conid);
}
},
listDatabases_meta: 'get', listDatabases_meta: 'get',
async listDatabases({ conid }) { async listDatabases({ conid }) {
const opened = await this.ensureOpened(conid); const opened = await this.ensureOpened(conid);
return opened.databases; return opened.databases;
}, },
serverStatus_meta: 'get',
async serverStatus() {
return {
...this.closed.reduce(
(res, conid) => ({
...res,
[conid]: {
name: 'error',
},
}),
{}
),
..._.mapValues(_.keyBy(this.opened, 'conid'), 'status'),
};
},
ping_meta: 'post',
async ping({ connections }) {
for (const conid of connections) {
const opened = await this.ensureOpened(conid);
opened.subprocess.send({ msgtype: 'ping' });
}
return { status: 'ok' };
},
refresh_meta: 'post',
async refresh({ conid }) {
this.close(conid);
await this.ensureOpened(conid);
return { status: 'ok' };
},
}; };

View File

@@ -1,27 +1,67 @@
const engines = require('@dbgate/engines'); const engines = require('@dbgate/engines');
const driverConnect = require('../utility/driverConnect'); const driverConnect = require('../utility/driverConnect');
const childProcessChecker = require('../utility/childProcessChecker'); const childProcessChecker = require('../utility/childProcessChecker');
const stableStringify = require('json-stable-stringify');
let systemConnection; let systemConnection;
let storedConnection; let storedConnection;
let lastDatabases = null;
let lastStatus = null;
let lastPing = null;
async function handleRefreshDatabases() { async function handleRefresh() {
const driver = engines(storedConnection); const driver = engines(storedConnection);
try {
const databases = await driver.listDatabases(systemConnection); const databases = await driver.listDatabases(systemConnection);
setStatusName('ok');
const databasesString = stableStringify(databases);
if (lastDatabases != databasesString) {
process.send({ msgtype: 'databases', databases }); process.send({ msgtype: 'databases', databases });
lastDatabases = databasesString;
}
} catch (err) {
setStatusName('error');
console.error(err);
process.exit(1);
}
}
function setStatus(status) {
const statusString = stableStringify(status);
if (lastStatus != statusString) {
process.send({ msgtype: 'status', status });
lastStatus = statusString;
}
}
function setStatusName(name) {
setStatus({ name });
} }
async function handleConnect(connection) { async function handleConnect(connection) {
storedConnection = connection; storedConnection = connection;
setStatusName('pending');
lastPing = new Date().getTime();
const driver = engines(storedConnection); const driver = engines(storedConnection);
try {
systemConnection = await driverConnect(driver, storedConnection); systemConnection = await driverConnect(driver, storedConnection);
handleRefreshDatabases(); handleRefresh();
setInterval(handleRefreshDatabases, 30 * 1000); setInterval(handleRefresh, 30 * 1000);
} catch (err) {
setStatusName('error');
console.error(err);
process.exit(1);
}
}
function handlePing() {
lastPing = new Date().getTime();
} }
const messageHandlers = { const messageHandlers = {
connect: handleConnect, connect: handleConnect,
ping: handlePing,
}; };
async function handleMessage({ msgtype, ...other }) { async function handleMessage({ msgtype, ...other }) {
@@ -31,6 +71,14 @@ async function handleMessage({ msgtype, ...other }) {
function start() { function start() {
childProcessChecker(); childProcessChecker();
setInterval(() => {
const time = new Date().getTime();
if (time - lastPing > 60 * 1000) {
process.exit(0);
}
}, 60 * 1000);
process.on('message', async (message) => { process.on('message', async (message) => {
try { try {
await handleMessage(message); await handleMessage(message);

View File

@@ -6,8 +6,10 @@ import {
CurrentDatabaseProvider, CurrentDatabaseProvider,
OpenedTabsProvider, OpenedTabsProvider,
SavedSqlFilesProvider, SavedSqlFilesProvider,
OpenedConnectionsProvider,
} from './utility/globalState'; } from './utility/globalState';
import { SocketProvider } from './utility/SocketProvider'; import { SocketProvider } from './utility/SocketProvider';
import OpenedConnectionsPinger from './utility/OpnedConnectionsPinger';
function App() { function App() {
return ( return (
@@ -16,7 +18,11 @@ function App() {
<SocketProvider> <SocketProvider>
<OpenedTabsProvider> <OpenedTabsProvider>
<SavedSqlFilesProvider> <SavedSqlFilesProvider>
<OpenedConnectionsProvider>
<OpenedConnectionsPinger>
<Screen /> <Screen />
</OpenedConnectionsPinger>
</OpenedConnectionsProvider>
</SavedSqlFilesProvider> </SavedSqlFilesProvider>
</OpenedTabsProvider> </OpenedTabsProvider>
</SocketProvider> </SocketProvider>

View File

@@ -38,7 +38,11 @@ function AppObjectListItem({ makeAppObj, data, filter, appobj, onObjectClick, Su
// if (matcher && !matcher(filter)) return null; // if (matcher && !matcher(filter)) return null;
if (onObjectClick) appobj.onClick = onObjectClick; if (onObjectClick) appobj.onClick = onObjectClick;
if (SubItems) { if (SubItems) {
appobj.onClick = () => setIsExpanded(!isExpanded); const oldClick = appobj.onClick;
appobj.onClick = () => {
if (oldClick) oldClick();
setIsExpanded(!isExpanded);
};
} }
let res = ( let res = (
@@ -51,7 +55,11 @@ function AppObjectListItem({ makeAppObj, data, filter, appobj, onObjectClick, Su
prefix={ prefix={
SubItems ? ( SubItems ? (
<ExpandIconHolder2> <ExpandIconHolder2>
{appobj.isExpandable ? (
<ExpandIcon isSelected={isHover} isExpanded={isExpanded} /> <ExpandIcon isSelected={isHover} isExpanded={isExpanded} />
) : (
<ExpandIcon isSelected={isHover} isBlank blankColor="#ccc" />
)}
</ExpandIconHolder2> </ExpandIconHolder2>
) : null ) : null
} }
@@ -83,9 +91,9 @@ function AppObjectGroup({ group, items }) {
<ExpandIconHolder> <ExpandIconHolder>
<ExpandIcon isSelected={isHover} isExpanded={isExpanded} /> <ExpandIcon isSelected={isHover} isExpanded={isExpanded} />
</ExpandIconHolder> </ExpandIconHolder>
{group} {items && `(${items.filter(x => x.component).length})`} {group} {items && `(${items.filter((x) => x.component).length})`}
</GroupDiv> </GroupDiv>
{isExpanded && items.map(x => x.component)} {isExpanded && items.map((x) => x.component)}
</> </>
); );
} }
@@ -115,7 +123,7 @@ export function AppObjectList({
if (groupFunc) { if (groupFunc) {
const listGrouped = _.compact( const listGrouped = _.compact(
(list || []).map(data => { (list || []).map((data) => {
const appobj = makeAppObj(data, appObjectParams); const appobj = makeAppObj(data, appObjectParams);
const { matcher } = appobj; const { matcher } = appobj;
if (matcher && !matcher(filter)) return null; if (matcher && !matcher(filter)) return null;
@@ -125,12 +133,12 @@ export function AppObjectList({
}) })
); );
const groups = _.groupBy(listGrouped, 'group'); const groups = _.groupBy(listGrouped, 'group');
return (groupOrdered || _.keys(groups)).map(group => ( return (groupOrdered || _.keys(groups)).map((group) => (
<AppObjectGroup key={group} group={group} items={groups[group]} /> <AppObjectGroup key={group} group={group} items={groups[group]} />
)); ));
} }
return (list || []).map(data => { return (list || []).map((data) => {
const appobj = makeAppObj(data, appObjectParams); const appobj = makeAppObj(data, appObjectParams);
const { matcher } = appobj; const { matcher } = appobj;
if (matcher && !matcher(filter)) return null; if (matcher && !matcher(filter)) return null;

View File

@@ -5,6 +5,7 @@ import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { showMenu } from '../modals/DropDownMenu'; import { showMenu } from '../modals/DropDownMenu';
import { useSetOpenedTabs, useAppObjectParams } from '../utility/globalState'; import { useSetOpenedTabs, useAppObjectParams } from '../utility/globalState';
import { FontIcon } from '../icons';
const AppObjectDiv = styled.div` const AppObjectDiv = styled.div`
padding: 5px; padding: 5px;
@@ -25,6 +26,10 @@ const IconWrap = styled.span`
margin-right: 10px; margin-right: 10px;
`; `;
const StatusIconWrap = styled.span`
margin-left: 5px;
`;
export function AppObjectCore({ export function AppObjectCore({
title, title,
Icon, Icon,
@@ -36,6 +41,7 @@ export function AppObjectCore({
isBusy, isBusy,
component = 'div', component = 'div',
prefix = null, prefix = null,
statusIcon,
...other ...other
}) { }) {
const appObjectParams = useAppObjectParams(); const appObjectParams = useAppObjectParams();
@@ -63,6 +69,11 @@ export function AppObjectCore({
{prefix} {prefix}
<IconWrap>{isBusy ? <i className="fas fa-spinner fa-spin"></i> : <Icon />}</IconWrap> <IconWrap>{isBusy ? <i className="fas fa-spinner fa-spin"></i> : <Icon />}</IconWrap>
{title} {title}
{statusIcon && (
<StatusIconWrap>
<FontIcon icon={statusIcon} />
</StatusIconWrap>
)}
</Component> </Component>
); );
} }

View File

@@ -7,34 +7,67 @@ import ConnectionModal from '../modals/ConnectionModal';
import axios from '../utility/axios'; import axios from '../utility/axios';
import { filterName } from '@dbgate/datalib'; import { filterName } from '@dbgate/datalib';
function Menu({ data, makeAppObj }) { function Menu({ data, setOpenedConnections }) {
const handleEdit = () => { const handleEdit = () => {
showModal(modalState => <ConnectionModal modalState={modalState} connection={data} />); showModal((modalState) => <ConnectionModal modalState={modalState} connection={data} />);
}; };
const handleDelete = () => { const handleDelete = () => {
axios.post('connections/delete', data); axios.post('connections/delete', data);
}; };
const handleRefresh = () => {
axios.post('server-connections/refresh', { conid: data._id });
};
const handleDisconnect = () => {
setOpenedConnections((list) => list.filter((x) => x != data._id));
};
return ( return (
<> <>
<DropDownMenuItem onClick={handleEdit}>Edit</DropDownMenuItem> <DropDownMenuItem onClick={handleEdit}>Edit</DropDownMenuItem>
<DropDownMenuItem onClick={handleDelete}>Delete</DropDownMenuItem> <DropDownMenuItem onClick={handleDelete}>Delete</DropDownMenuItem>
<DropDownMenuItem onClick={handleRefresh}>Refresh</DropDownMenuItem>
<DropDownMenuItem onClick={handleDisconnect}>Disconnect</DropDownMenuItem>
</> </>
); );
} }
const connectionAppObject = flags => ({ _id, server, displayName, engine }) => { const connectionAppObject = (flags) => (
{ _id, server, displayName, engine, status },
{ openedConnections, setOpenedConnections }
) => {
const title = displayName || server; const title = displayName || server;
const key = _id; const key = _id;
const isExpandable = openedConnections.includes(_id);
const Icon = getEngineIcon(engine); const Icon = getEngineIcon(engine);
const matcher = filter => filterName(filter, displayName, server); const matcher = (filter) => filterName(filter, displayName, server);
const { boldCurrentDatabase } = flags || {}; const { boldCurrentDatabase } = flags || {};
const isBold = boldCurrentDatabase const isBold = boldCurrentDatabase
? ({ currentDatabase }) => { ? ({ currentDatabase }) => {
return _.get(currentDatabase, 'connection._id') == _id; return _.get(currentDatabase, 'connection._id') == _id;
} }
: null; : null;
const onClick = () => setOpenedConnections((c) => [...c, _id]);
return { title, key, Icon, Menu, matcher, isBold }; // let isBusy = false;
let statusIcon = null;
if (openedConnections.includes(_id)) {
if (!status) statusIcon = 'fas fa-spinner fa-spin';
else if (status.name == 'pending') statusIcon = 'fas fa-spinner fa-spin';
else if (status.name == 'ok') statusIcon = 'fas fa-check-circle green';
else statusIcon = 'fas fa-times-circle red';
}
return {
title,
key,
Icon,
Menu,
matcher,
isBold,
isExpandable,
onClick,
// isBusy,
statusIcon,
};
}; };
export default connectionAppObject; export default connectionAppObject;

View File

@@ -27,7 +27,7 @@ export function FontIcon({ icon, ...props }) {
let className = props.className || ''; let className = props.className || '';
// if (_.startsWith(name, 'bs-')) className += ` glyphicon glyphicon-${name.substr(3)}`; // if (_.startsWith(name, 'bs-')) className += ` glyphicon glyphicon-${name.substr(3)}`;
if (type == 'fas' || type == 'far') className += ` ${type} ${name}`; if (type == 'fas' || type == 'far') className += ` ${type} ${name} ${parts.join(' ')}`;
if (_.includes(parts, 'spin')) className += ' fa-spin'; if (_.includes(parts, 'spin')) className += ' fa-spin';
@@ -41,67 +41,73 @@ export function FontIcon({ icon, ...props }) {
return <i {...props} className={className} style={style} title={props.title} />; return <i {...props} className={className} style={style} title={props.title} />;
} }
export function ExpandIcon({ isBlank = false, isExpanded = false, isSelected = false, ...other }) { export function ExpandIcon({
isBlank = false,
isExpanded = false,
isSelected = false,
blankColor = 'white',
...other
}) {
if (isBlank) { if (isBlank) {
return <FontIcon icon={`fas fa-square ${isSelected ? 'lightblue' : 'white'}`} {...other} />; return <FontIcon icon={`fas fa-square ${isSelected ? 'lightblue' : blankColor}`} {...other} />;
} }
return <FontIcon icon={`far ${isExpanded ? 'fa-minus-square' : 'fa-plus-square'} `} {...other} />; return <FontIcon icon={`far ${isExpanded ? 'fa-minus-square' : 'fa-plus-square'} `} {...other} />;
} }
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 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);
export const MicrosoftIcon = props => getIconImage('microsoft.svg', props); export const MicrosoftIcon = (props) => getIconImage('microsoft.svg', props);
export const MySqlIcon = props => getIconImage('mysql.svg', props); export const MySqlIcon = (props) => getIconImage('mysql.svg', props);
export const PostgreSqlIcon = props => getIconImage('postgresql.svg', props); export const PostgreSqlIcon = (props) => getIconImage('postgresql.svg', props);
export const SqliteIcon = props => getIconImage('sqlite.svg', props); export const SqliteIcon = (props) => getIconImage('sqlite.svg', props);
export const ProcedureIcon = props => getIconImage('procedure2.svg', props); export const ProcedureIcon = (props) => getIconImage('procedure2.svg', props);
export const FunctionIcon = props => getIconImage('function.svg', props); export const FunctionIcon = (props) => getIconImage('function.svg', props);
export const TriggerIcon = props => getIconImage('trigger.svg', props); export const TriggerIcon = (props) => getIconImage('trigger.svg', props);
export const HomeIcon = props => getIconImage('home.svg', props); export const HomeIcon = (props) => getIconImage('home.svg', props);
export const PrimaryKeyIcon = props => getIconImage('primarykey.svg', props); export const PrimaryKeyIcon = (props) => getIconImage('primarykey.svg', props);
export const ForeignKeyIcon = props => getIconImage('foreignkey.svg', props); export const ForeignKeyIcon = (props) => getIconImage('foreignkey.svg', props);
export const ComplexKeyIcon = props => getIconImage('complexkey.svg', props); export const ComplexKeyIcon = (props) => getIconImage('complexkey.svg', props);
export const VariableIcon = props => getIconImage('variable.svg', props); export const VariableIcon = (props) => getIconImage('variable.svg', props);
export const UniqueIcon = props => getIconImage('unique.svg', props); export const UniqueIcon = (props) => getIconImage('unique.svg', props);
export const IndexIcon = props => getIconImage('index.svg', props); export const IndexIcon = (props) => getIconImage('index.svg', props);
export const StartIcon = props => getIconImage('start.svg', props); export const StartIcon = (props) => getIconImage('start.svg', props);
export const DownCircleIcon = props => getIconImage('down_circle.svg', props); export const DownCircleIcon = (props) => getIconImage('down_circle.svg', props);
export const ColumnIcon = props => getIconImage('column.svg', props); export const ColumnIcon = (props) => getIconImage('column.svg', props);
export const SqlIcon = props => getIconImage('sql.svg', props); export const SqlIcon = (props) => getIconImage('sql.svg', props);
export const ExcelIcon = props => getIconImage('excel.svg', props); export const ExcelIcon = (props) => getIconImage('excel.svg', props);
export const DiagramIcon = props => getIconImage('diagram.svg', props); export const DiagramIcon = (props) => getIconImage('diagram.svg', props);
export const QueryDesignIcon = props => getIconImage('querydesign.svg', props); export const QueryDesignIcon = (props) => getIconImage('querydesign.svg', props);
export const LocalDbIcon = props => getIconImage('localdb.svg', props); export const LocalDbIcon = (props) => getIconImage('localdb.svg', props);
export const CsvIcon = props => getIconImage('csv.svg', props); export const CsvIcon = (props) => getIconImage('csv.svg', props);
export const ChangeSetIcon = props => getIconImage('changeset.svg', props); export const ChangeSetIcon = (props) => getIconImage('changeset.svg', props);
export const BinaryFileIcon = props => getIconImage('binaryfile.svg', props); export const BinaryFileIcon = (props) => getIconImage('binaryfile.svg', props);
export const ReferenceIcon = props => getIconImage('reference.svg', props); export const ReferenceIcon = (props) => getIconImage('reference.svg', props);
export const LinkIcon = props => getIconImage('link.svg', props); export const LinkIcon = (props) => getIconImage('link.svg', props);
export const SequenceIcon = props => getIconImage('sequence.svg', props); export const SequenceIcon = (props) => getIconImage('sequence.svg', props);
export const CheckIcon = props => getIconImage('check.svg', props); export const CheckIcon = (props) => getIconImage('check.svg', props);
export const LinkedServerIcon = props => getIconImage('linkedserver.svg', props); export const LinkedServerIcon = (props) => getIconImage('linkedserver.svg', props);
export const EmptyIcon = props => getIconImage('data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=', props); export const EmptyIcon = (props) => getIconImage('data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=', props);
export const TimesRedIcon = props => <FontIcon name="fas fa-times red" {...props} />; export const TimesRedIcon = (props) => <FontIcon name="fas fa-times red" {...props} />;
export const TimesGreenCircleIcon = props => <FontIcon icon="fas fa-times-circle green" {...props} />; export const TimesGreenCircleIcon = (props) => <FontIcon icon="fas fa-times-circle green" {...props} />;
export const GrayFilterIcon = props => <FontIcon icon="fas fa-filter lightgray" {...props} />; export const GrayFilterIcon = (props) => <FontIcon icon="fas fa-filter lightgray" {...props} />;
export const ExclamationTriangleIcon = props => <FontIcon icon="fas fa-exclamation-triangle" {...props} />; export const ExclamationTriangleIcon = (props) => <FontIcon icon="fas fa-exclamation-triangle" {...props} />;
export const HourGlassIcon = props => <FontIcon icon="fas fa-hourglass" {...props} />; export const HourGlassIcon = (props) => <FontIcon icon="fas fa-hourglass" {...props} />;
export const InfoBlueCircleIcon = props => <FontIcon icon="fas fa-info-circle blue" {...props} />; export const InfoBlueCircleIcon = (props) => <FontIcon icon="fas fa-info-circle blue" {...props} />;
export const SpinnerIcon = props => <FontIcon icon="fas fa-spinner spin" {...props} />; export const SpinnerIcon = (props) => <FontIcon icon="fas fa-spinner spin" {...props} />;
export function getEngineIcon(engine) { export function getEngineIcon(engine) {
switch (engine) { switch (engine) {

View File

@@ -0,0 +1,14 @@
import React from 'react';
import { useOpenedConnections } from './globalState';
import axios from './axios';
export default function OpenedConnectionsPinger({ children }) {
const openedConnections = useOpenedConnections();
React.useEffect(() => {
const handle = window.setInterval(() => {
axios.post('server-connections/ping', { connections: openedConnections });
}, 30 * 1000);
return () => window.clearInterval(handle);
}, [openedConnections]);
return children;
}

View File

@@ -84,6 +84,8 @@ export function useAppObjectParams() {
const newQuery = useNewQuery(); const newQuery = useNewQuery();
const openedTabs = useOpenedTabs(); const openedTabs = useOpenedTabs();
const setSavedSqlFiles = useSetSavedSqlFiles(); const setSavedSqlFiles = useSetSavedSqlFiles();
const openedConnections = useOpenedConnections();
const setOpenedConnections = useSetOpenedConnections();
return { return {
setOpenedTabs, setOpenedTabs,
@@ -91,8 +93,14 @@ export function useAppObjectParams() {
newQuery, newQuery,
openedTabs, openedTabs,
setSavedSqlFiles, setSavedSqlFiles,
openedConnections,
setOpenedConnections,
}; };
} }
const [SavedSqlFilesProvider, useSavedSqlFiles, useSetSavedSqlFiles] = createStorageState('savedSqlFiles', []); const [SavedSqlFilesProvider, useSavedSqlFiles, useSetSavedSqlFiles] = createStorageState('savedSqlFiles', []);
export { SavedSqlFilesProvider, useSavedSqlFiles, useSetSavedSqlFiles }; export { SavedSqlFilesProvider, useSavedSqlFiles, useSetSavedSqlFiles };
const [OpenedConnectionsProvider, useOpenedConnections, useSetOpenedConnections] = createGlobalState([]);
export { OpenedConnectionsProvider, useOpenedConnections, useSetOpenedConnections };

View File

@@ -33,6 +33,12 @@ const databaseListLoader = ({ conid }) => ({
reloadTrigger: `database-list-changed-${conid}`, reloadTrigger: `database-list-changed-${conid}`,
}); });
const serverStatusLoader = () => ({
url: 'server-connections/server-status',
params: {},
reloadTrigger: `server-status-changed`,
});
const connectionListLoader = () => ({ const connectionListLoader = () => ({
url: 'connections/list', url: 'connections/list',
params: {}, params: {},
@@ -126,6 +132,13 @@ export function useDatabaseList(args) {
return useCore(databaseListLoader, args); return useCore(databaseListLoader, args);
} }
export function getServerStatus() {
return getCore(serverStatusLoader, {});
}
export function useServerStatus() {
return useCore(serverStatusLoader, {});
}
export function getConnectionList() { export function getConnectionList() {
return getCore(connectionListLoader, {}); return getCore(connectionListLoader, {});
} }

View File

@@ -7,7 +7,7 @@ import databaseAppObject from '../appobj/databaseAppObject';
import { useSetCurrentDatabase, useCurrentDatabase } from '../utility/globalState'; import { useSetCurrentDatabase, useCurrentDatabase } from '../utility/globalState';
import InlineButton from './InlineButton'; import InlineButton from './InlineButton';
import databaseObjectAppObject from '../appobj/databaseObjectAppObject'; import databaseObjectAppObject from '../appobj/databaseObjectAppObject';
import { useSqlObjectList, useDatabaseList, useConnectionList } from '../utility/metadataLoaders'; import { useSqlObjectList, useDatabaseList, useConnectionList, useServerStatus } from '../utility/metadataLoaders';
import { SearchBoxWrapper, InnerContainer, Input, MainContainer, OuterContainer, WidgetTitle } from './WidgetStyles'; import { SearchBoxWrapper, InnerContainer, Input, MainContainer, OuterContainer, WidgetTitle } from './WidgetStyles';
function SubDatabaseList({ data }) { function SubDatabaseList({ data }) {
@@ -31,6 +31,9 @@ function SubDatabaseList({ data }) {
function ConnectionList() { function ConnectionList() {
const connections = useConnectionList(); const connections = useConnectionList();
const serverStatus = useServerStatus();
const connectionsWithStatus =
connections && serverStatus && connections.map((conn) => ({ ...conn, status: serverStatus[conn._id] }));
const [filter, setFilter] = React.useState(''); const [filter, setFilter] = React.useState('');
return ( return (
@@ -43,7 +46,7 @@ function ConnectionList() {
<InnerContainer> <InnerContainer>
<AppObjectList <AppObjectList
list={connections} list={connectionsWithStatus}
makeAppObj={connectionAppObject({ boldCurrentDatabase: true })} makeAppObj={connectionAppObject({ boldCurrentDatabase: true })}
SubItems={SubDatabaseList} SubItems={SubDatabaseList}
filter={filter} filter={filter}