mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-17 23:45:59 +00:00
ssh tunnel - wking POC
This commit is contained in:
@@ -35,16 +35,18 @@
|
||||
"find-free-port": "^2.0.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"http": "^0.0.0",
|
||||
"json-stable-stringify": "^1.0.1",
|
||||
"line-reader": "^0.4.0",
|
||||
"lodash": "^4.17.15",
|
||||
"ncp": "^2.0.0",
|
||||
"nedb-promises": "^4.0.1",
|
||||
"node-cron": "^2.0.3",
|
||||
"node-ssh-forward": "^0.7.2",
|
||||
"portfinder": "^1.0.28",
|
||||
"simple-encryptor": "^4.0.0",
|
||||
"tar": "^6.0.5",
|
||||
"uuid": "^3.4.0",
|
||||
"socket.io": "^2.3.0",
|
||||
"json-stable-stringify": "^1.0.1"
|
||||
"tar": "^6.0.5",
|
||||
"uuid": "^3.4.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "nodemon src/index.js",
|
||||
@@ -66,4 +68,4 @@
|
||||
"optionalDependencies": {
|
||||
"msnodesqlv8": "^2.0.10"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
const childProcessChecker = require('../utility/childProcessChecker');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const { decryptConnection } = require('../utility/crypting');
|
||||
const connectUtility = require('../utility/connectUtility');
|
||||
|
||||
function start() {
|
||||
childProcessChecker();
|
||||
process.on('message', async connection => {
|
||||
try {
|
||||
const driver = requireEngineDriver(connection);
|
||||
const conn = await driver.connect(decryptConnection(connection));
|
||||
const conn = await connectUtility(driver, connection);
|
||||
const res = await driver.getVersion(conn);
|
||||
process.send({ msgtype: 'connected', ...res });
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const stableStringify = require('json-stable-stringify');
|
||||
const childProcessChecker = require('../utility/childProcessChecker');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const { decryptConnection } = require('../utility/crypting');
|
||||
const connectUtility = require('../utility/connectUtility');
|
||||
|
||||
let systemConnection;
|
||||
let storedConnection;
|
||||
@@ -60,7 +60,7 @@ async function handleConnect({ connection, structure }) {
|
||||
|
||||
if (!structure) setStatusName('pending');
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
systemConnection = await checkedAsyncCall(driver.connect(decryptConnection(storedConnection)));
|
||||
systemConnection = await checkedAsyncCall(connectUtility(driver, storedConnection));
|
||||
if (structure) {
|
||||
analysedStructure = structure;
|
||||
handleIncrementalRefresh();
|
||||
|
||||
@@ -2,6 +2,7 @@ const stableStringify = require('json-stable-stringify');
|
||||
const childProcessChecker = require('../utility/childProcessChecker');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const { decryptConnection } = require('../utility/crypting');
|
||||
const connectUtility = require('../utility/connectUtility');
|
||||
|
||||
let systemConnection;
|
||||
let storedConnection;
|
||||
@@ -48,7 +49,7 @@ async function handleConnect(connection) {
|
||||
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
try {
|
||||
systemConnection = await driver.connect(decryptConnection(storedConnection));
|
||||
systemConnection = await connectUtility(driver, storedConnection);
|
||||
handleRefresh();
|
||||
setInterval(handleRefresh, 30 * 1000);
|
||||
} catch (err) {
|
||||
@@ -67,7 +68,7 @@ function handlePing() {
|
||||
|
||||
async function handleCreateDatabase({ name }) {
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
systemConnection = await driver.connect(decryptConnection(storedConnection));
|
||||
systemConnection = await connectUtility(driver, storedConnection);
|
||||
console.log(`RUNNING SCRIPT: CREATE DATABASE ${driver.dialect.quoteIdentifier(name)}`);
|
||||
await driver.query(systemConnection, `CREATE DATABASE ${driver.dialect.quoteIdentifier(name)}`);
|
||||
await handleRefresh();
|
||||
|
||||
@@ -8,6 +8,7 @@ const goSplit = require('../utility/goSplit');
|
||||
const { jsldir } = require('../utility/directories');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const { decryptConnection } = require('../utility/crypting');
|
||||
const connectUtility = require('../utility/connectUtility');
|
||||
|
||||
let systemConnection;
|
||||
let storedConnection;
|
||||
@@ -131,7 +132,7 @@ async function handleConnect(connection) {
|
||||
storedConnection = connection;
|
||||
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
systemConnection = await driver.connect(decryptConnection(storedConnection));
|
||||
systemConnection = await connectUtility(driver, storedConnection);
|
||||
for (const [resolve] of afterConnectCallbacks) {
|
||||
resolve();
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
const goSplit = require('../utility/goSplit');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const { decryptConnection } = require('../utility/crypting');
|
||||
const connectUtility = require('../utility/connectUtility');
|
||||
|
||||
async function executeQuery({ connection, sql }) {
|
||||
console.log(`Execute query ${sql}`);
|
||||
|
||||
const driver = requireEngineDriver(connection);
|
||||
const pool = await driver.connect(decryptConnection(connection));
|
||||
const pool = await connectUtility(driver, connection);
|
||||
console.log(`Connected.`);
|
||||
|
||||
for (const sqlItem of goSplit(sql)) {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const { decryptConnection } = require('../utility/crypting');
|
||||
const connectUtility = require('../utility/connectUtility');
|
||||
|
||||
async function queryReader({ connection, sql }) {
|
||||
console.log(`Reading query ${sql}`);
|
||||
|
||||
const driver = requireEngineDriver(connection);
|
||||
const pool = await driver.connect(decryptConnection(connection));
|
||||
const pool = await connectUtility(driver, connection);
|
||||
console.log(`Connected.`);
|
||||
return await driver.readQuery(pool, sql);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
const { quoteFullName, fullNameToString } = require('dbgate-tools');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const { decryptConnection } = require('../utility/crypting');
|
||||
const connectUtility = require('../utility/connectUtility');
|
||||
|
||||
async function tableReader({ connection, pureName, schemaName }) {
|
||||
const driver = requireEngineDriver(connection);
|
||||
const pool = await driver.connect(decryptConnection(connection));
|
||||
const pool = await connectUtility(driver, connection);
|
||||
console.log(`Connected.`);
|
||||
|
||||
const fullName = { pureName, schemaName };
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
const { fullNameToString } = require('dbgate-tools');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const { decryptConnection } = require('../utility/crypting');
|
||||
const connectUtility = require('../utility/connectUtility');
|
||||
|
||||
async function tableWriter({ connection, schemaName, pureName, ...options }) {
|
||||
console.log(`Writing table ${fullNameToString({ schemaName, pureName })}`);
|
||||
|
||||
const driver = requireEngineDriver(connection);
|
||||
const pool = await driver.connect(decryptConnection(connection));
|
||||
const pool = await connectUtility(driver, connection);
|
||||
console.log(`Connected.`);
|
||||
return await driver.writeTable(pool, { schemaName, pureName }, options);
|
||||
}
|
||||
|
||||
43
packages/api/src/utility/connectUtility.js
Normal file
43
packages/api/src/utility/connectUtility.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const { SSHConnection } = require('node-ssh-forward');
|
||||
const portfinder = require('portfinder');
|
||||
const { decryptConnection } = require('./crypting');
|
||||
|
||||
async function connectUtility(driver, storedConnection) {
|
||||
let connection = decryptConnection(storedConnection);
|
||||
if (connection.useSshTunnel) {
|
||||
const sshConfig = {
|
||||
endHost: connection.sshHost || '',
|
||||
endPort: connection.sshPort || 22,
|
||||
bastionHost: '',
|
||||
agentForward: false,
|
||||
passphrase: undefined,
|
||||
username: connection.sshLogin,
|
||||
password: connection.sshPassword,
|
||||
skipAutoPrivateKey: true,
|
||||
noReadline: true,
|
||||
};
|
||||
|
||||
const sshConn = new SSHConnection(sshConfig);
|
||||
const localPort = await portfinder.getPortPromise({ port: 10000, stopPort: 60000 });
|
||||
// workaround for `getPortPromise` not releasing the port quickly enough
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
const tunnelConfig = {
|
||||
fromPort: localPort,
|
||||
toPort: connection.port,
|
||||
toHost: connection.server,
|
||||
};
|
||||
const tunnel = await sshConn.forward(tunnelConfig);
|
||||
console.log(`Created SSH tunnel to ${connection.sshHost}-${connection.server}:${connection.port}, using local port ${localPort}`)
|
||||
|
||||
connection = {
|
||||
...connection,
|
||||
server: '127.0.0.1',
|
||||
port: localPort,
|
||||
};
|
||||
}
|
||||
|
||||
const conn = await driver.connect(connection);
|
||||
return conn;
|
||||
}
|
||||
|
||||
module.exports = connectUtility;
|
||||
@@ -70,7 +70,7 @@ export default function AboutModal({ modalState }) {
|
||||
<Link label="Web" href="https://dbgate.org">
|
||||
dbgate.org
|
||||
</Link>
|
||||
<Link label="Source codes" href="https://github.com/dbshell/dbgate/">
|
||||
<Link label="Source codes" href="https://github.com/dbgate/dbgate/">
|
||||
github
|
||||
</Link>
|
||||
<Link label="Docker container" href="https://hub.docker.com/r/dbgate/dbgate">
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import React from 'react';
|
||||
import axios from '../utility/axios';
|
||||
import ModalBase from './ModalBase';
|
||||
import { FormButton, FormTextField, FormSelectField, FormSubmit, FormPasswordField } from '../utility/forms';
|
||||
import {
|
||||
FormButton,
|
||||
FormTextField,
|
||||
FormSelectField,
|
||||
FormSubmit,
|
||||
FormPasswordField,
|
||||
FormCheckboxField,
|
||||
} from '../utility/forms';
|
||||
import ModalHeader from './ModalHeader';
|
||||
import ModalFooter from './ModalFooter';
|
||||
import ModalContent from './ModalContent';
|
||||
@@ -9,6 +16,7 @@ import useExtensions from '../utility/useExtensions';
|
||||
import LoadingInfo from '../widgets/LoadingInfo';
|
||||
import { FontIcon } from '../icons';
|
||||
import { FormProvider, useForm } from '../utility/FormProvider';
|
||||
import { TabControl, TabPage } from '../widgets/TabControl';
|
||||
// import FormikForm from '../utility/FormikForm';
|
||||
|
||||
function DriverFields({ extensions }) {
|
||||
@@ -60,6 +68,20 @@ function DriverFields({ extensions }) {
|
||||
);
|
||||
}
|
||||
|
||||
function SshTunnelFields() {
|
||||
const { values } = useForm();
|
||||
const { useSshTunnel } = values;
|
||||
return (
|
||||
<>
|
||||
<FormCheckboxField label="Use SSH tunnel" name="useSshTunnel" />
|
||||
<FormTextField label="SSH Host" name="sshHost" disabled={!useSshTunnel} />
|
||||
<FormTextField label="SSH Port" name="sshPort" disabled={!useSshTunnel} />
|
||||
<FormTextField label="SSH Login" name="sshLogin" disabled={!useSshTunnel} />
|
||||
<FormPasswordField label="SSH Password" name="sshPassword" disabled={!useSshTunnel} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ConnectionModal({ modalState, connection = undefined }) {
|
||||
const [sqlConnectResult, setSqlConnectResult] = React.useState(null);
|
||||
const extensions = useExtensions();
|
||||
@@ -90,31 +112,38 @@ export default function ConnectionModal({ modalState, connection = undefined })
|
||||
<ModalBase modalState={modalState}>
|
||||
<ModalHeader modalState={modalState}>{connection ? 'Edit connection' : 'Add connection'}</ModalHeader>
|
||||
<FormProvider initialValues={connection || { server: 'localhost', engine: 'mssql@dbgate-plugin-mssql' }}>
|
||||
<ModalContent>
|
||||
<FormSelectField label="Database engine" name="engine">
|
||||
<option value="(select driver)"></option>
|
||||
{extensions.drivers.map(driver => (
|
||||
<option value={driver.engine} key={driver.engine}>
|
||||
{driver.title}
|
||||
</option>
|
||||
))}
|
||||
{/* <option value="mssql">Microsoft SQL Server</option>
|
||||
<ModalContent noPadding>
|
||||
<TabControl isInline>
|
||||
<TabPage label="Main" key="main">
|
||||
<FormSelectField label="Database engine" name="engine">
|
||||
<option value="(select driver)"></option>
|
||||
{extensions.drivers.map(driver => (
|
||||
<option value={driver.engine} key={driver.engine}>
|
||||
{driver.title}
|
||||
</option>
|
||||
))}
|
||||
{/* <option value="mssql">Microsoft SQL Server</option>
|
||||
<option value="mysql">MySQL</option>
|
||||
<option value="postgres">Postgre SQL</option> */}
|
||||
</FormSelectField>
|
||||
<DriverFields extensions={extensions} />
|
||||
<FormTextField label="Display name" name="displayName" />
|
||||
{!isTesting && sqlConnectResult && sqlConnectResult.msgtype == 'connected' && (
|
||||
<div>
|
||||
Connected: <FontIcon icon="img ok" /> {sqlConnectResult.version}
|
||||
</div>
|
||||
)}
|
||||
{!isTesting && sqlConnectResult && sqlConnectResult.msgtype == 'error' && (
|
||||
<div>
|
||||
Connect failed: <FontIcon icon="img error" /> {sqlConnectResult.error}
|
||||
</div>
|
||||
)}
|
||||
{isTesting && <LoadingInfo message="Testing connection" />}
|
||||
</FormSelectField>
|
||||
<DriverFields extensions={extensions} />
|
||||
<FormTextField label="Display name" name="displayName" />
|
||||
{!isTesting && sqlConnectResult && sqlConnectResult.msgtype == 'connected' && (
|
||||
<div>
|
||||
Connected: <FontIcon icon="img ok" /> {sqlConnectResult.version}
|
||||
</div>
|
||||
)}
|
||||
{!isTesting && sqlConnectResult && sqlConnectResult.msgtype == 'error' && (
|
||||
<div>
|
||||
Connect failed: <FontIcon icon="img error" /> {sqlConnectResult.error}
|
||||
</div>
|
||||
)}
|
||||
{isTesting && <LoadingInfo message="Testing connection" />}
|
||||
</TabPage>
|
||||
<TabPage label="SSH Tunnel" key="sshTunnel">
|
||||
<SshTunnelFields />
|
||||
</TabPage>
|
||||
</TabControl>
|
||||
</ModalContent>
|
||||
|
||||
<ModalFooter>
|
||||
|
||||
@@ -5,10 +5,23 @@ import useTheme from '../theme/useTheme';
|
||||
const Wrapper = styled.div`
|
||||
border-bottom: 1px solid ${props => props.theme.border};
|
||||
border-top: 1px solid ${props => props.theme.border};
|
||||
${props =>
|
||||
// @ts-ignore
|
||||
!props.noPadding &&
|
||||
`
|
||||
padding: 15px;
|
||||
`}
|
||||
`;
|
||||
|
||||
export default function ModalContent({ children }) {
|
||||
export default function ModalContent({ children, noPadding = false }) {
|
||||
const theme = useTheme();
|
||||
return <Wrapper theme={theme}>{children}</Wrapper>;
|
||||
return (
|
||||
<Wrapper
|
||||
theme={theme}
|
||||
// @ts-ignore
|
||||
noPadding={noPadding}
|
||||
>
|
||||
{children}
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,16 +28,21 @@ const TabNameWrapper = styled.span`
|
||||
// props.tabVisible ? 'visible' : 'none'};
|
||||
|
||||
const TabContainer = styled.div`
|
||||
${props =>
|
||||
// @ts-ignore
|
||||
!props.isInline &&
|
||||
`
|
||||
position: absolute;
|
||||
display: flex;
|
||||
left: 0;
|
||||
right: 0
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
bottom: 0;
|
||||
`}
|
||||
|
||||
${props =>
|
||||
// @ts-ignore
|
||||
!props.tabVisible && `visibility: hidden;`}
|
||||
!props.tabVisible && (props.isInline ? `display:none` : `visibility: hidden;`)}
|
||||
`;
|
||||
|
||||
const TabsContainer = styled.div`
|
||||
@@ -62,7 +67,7 @@ export function TabPage({ key, label, children }) {
|
||||
return children;
|
||||
}
|
||||
|
||||
export function TabControl({ children, activePageIndex = undefined, activePageLabel = undefined }) {
|
||||
export function TabControl({ children, activePageIndex = undefined, activePageLabel = undefined, isInline = false }) {
|
||||
const [value, setValue] = React.useState(0);
|
||||
|
||||
// const [mountedTabs, setMountedTabs] = React.useState({});
|
||||
@@ -112,6 +117,7 @@ export function TabControl({ children, activePageIndex = undefined, activePageLa
|
||||
<TabContainer
|
||||
// @ts-ignore
|
||||
tabVisible={tabVisible}
|
||||
isInline={isInline}
|
||||
key={tab.props.key}
|
||||
>
|
||||
{childrenArray[index] && childrenArray[index].props.children}
|
||||
|
||||
46
yarn.lock
46
yarn.lock
@@ -2128,7 +2128,7 @@ asn1.js@^4.0.0:
|
||||
inherits "^2.0.1"
|
||||
minimalistic-assert "^1.0.0"
|
||||
|
||||
asn1@~0.2.3:
|
||||
asn1@~0.2.0, asn1@~0.2.3:
|
||||
version "0.2.4"
|
||||
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
|
||||
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
|
||||
@@ -2488,7 +2488,7 @@ batch@0.6.1:
|
||||
resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
|
||||
integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=
|
||||
|
||||
bcrypt-pbkdf@^1.0.0:
|
||||
bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
|
||||
integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
|
||||
@@ -7759,6 +7759,13 @@ mkdirp@0.5.1, mkdirp@0.x, mkdirp@^0.5.1, mkdirp@~0.5.1:
|
||||
dependencies:
|
||||
minimist "0.0.8"
|
||||
|
||||
mkdirp@^0.5.5:
|
||||
version "0.5.5"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
|
||||
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
||||
mkdirp@^1.0.3:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||
@@ -8012,6 +8019,14 @@ node-releases@^1.1.47:
|
||||
dependencies:
|
||||
semver "^6.3.0"
|
||||
|
||||
node-ssh-forward@^0.7.2:
|
||||
version "0.7.2"
|
||||
resolved "https://registry.yarnpkg.com/node-ssh-forward/-/node-ssh-forward-0.7.2.tgz#a5103ba9ae0e044156b0568a5084304e7d5b4a2a"
|
||||
integrity sha512-dQGhwT9emJJ0PymZGXdwHue18oc+bnAeqSGUCi4+YufGLaTSfA4Ft04T97WGkVxmBUySy/O6lvKIfpJM0a8lgA==
|
||||
dependencies:
|
||||
debug "^4.1.1"
|
||||
ssh2 "^0.8.9"
|
||||
|
||||
nodemon@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.2.tgz#9c7efeaaf9b8259295a97e5d4585ba8f0cbe50b0"
|
||||
@@ -8751,6 +8766,15 @@ portfinder@^1.0.25:
|
||||
debug "^3.1.1"
|
||||
mkdirp "^0.5.1"
|
||||
|
||||
portfinder@^1.0.28:
|
||||
version "1.0.28"
|
||||
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778"
|
||||
integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==
|
||||
dependencies:
|
||||
async "^2.6.2"
|
||||
debug "^3.1.1"
|
||||
mkdirp "^0.5.5"
|
||||
|
||||
posix-character-classes@^0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
|
||||
@@ -11028,6 +11052,22 @@ sql-formatter@^2.3.3:
|
||||
dependencies:
|
||||
lodash "^4.16.0"
|
||||
|
||||
ssh2-streams@~0.4.10:
|
||||
version "0.4.10"
|
||||
resolved "https://registry.yarnpkg.com/ssh2-streams/-/ssh2-streams-0.4.10.tgz#48ef7e8a0e39d8f2921c30521d56dacb31d23a34"
|
||||
integrity sha512-8pnlMjvnIZJvmTzUIIA5nT4jr2ZWNNVHwyXfMGdRJbug9TpI3kd99ffglgfSWqujVv/0gxwMsDn9j9RVst8yhQ==
|
||||
dependencies:
|
||||
asn1 "~0.2.0"
|
||||
bcrypt-pbkdf "^1.0.2"
|
||||
streamsearch "~0.1.2"
|
||||
|
||||
ssh2@^0.8.9:
|
||||
version "0.8.9"
|
||||
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.8.9.tgz#54da3a6c4ba3daf0d8477a538a481326091815f3"
|
||||
integrity sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw==
|
||||
dependencies:
|
||||
ssh2-streams "~0.4.10"
|
||||
|
||||
sshpk@^1.7.0:
|
||||
version "1.16.1"
|
||||
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
|
||||
@@ -11118,7 +11158,7 @@ stream-shift@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d"
|
||||
integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==
|
||||
|
||||
streamsearch@0.1.2:
|
||||
streamsearch@0.1.2, streamsearch@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
|
||||
integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
|
||||
|
||||
Reference in New Issue
Block a user