代码拉取完成,页面将自动刷新
import Redis from 'ioredis';
import { createTunnel } from 'tunnel-ssh';
import vue from '@/main.js';
import { remote } from 'electron';
import { writeCMD } from '@/commands.js';
const fs = require('fs');
const { sendCommand } = Redis.prototype;
// redis command log
Redis.prototype.sendCommand = function (...options) {
const command = options[0];
// readonly mode
if (this.options.connectionReadOnly && writeCMD[command.name.toUpperCase()]) {
command.reject(new Error('You are in readonly mode! Unable to execute write command!'));
return command.promise;
}
// exec directly, without logs
if (this.withoutLogging === true) {
// invalid in next calling
this.withoutLogging = false;
return sendCommand.apply(this, options);
}
const start = performance.now();
const response = sendCommand.apply(this, options);
const cost = performance.now() - start;
const record = {
time: new Date(), connectionName: this.options.connectionName, command, cost,
};
vue.$bus.$emit('commandLog', record);
return response;
};
// fix ioredis hgetall key has been toString()
Redis.Command.setReplyTransformer('hgetall', (result) => {
const arr = [];
for (let i = 0; i < result.length; i += 2) {
arr.push([result[i], result[i + 1]]);
}
return arr;
});
export default {
createConnection(host, port, auth, config, promise = true, forceStandalone = false, removeDb = false) {
const options = this.getRedisOptions(host, port, auth, config);
let client = null;
if (removeDb) {
delete options.db;
}
if (forceStandalone) {
client = new Redis(options);
}
// sentinel redis
else if (config.sentinelOptions) {
const sentinelOptions = this.getSentinelOptions(host, port, auth, config);
client = new Redis(sentinelOptions);
}
// cluster redis
else if (config.cluster) {
const clusterOptions = this.getClusterOptions(options, config.natMap ? config.natMap : {});
client = new Redis.Cluster([{ port, host }], clusterOptions);
}
// standalone redis
else {
client = new Redis(options);
}
if (promise) {
return new Promise((resolve, reject) => {
resolve(client);
});
}
return client;
},
createSSHConnection(sshOptions, host, port, auth, config) {
const sshOptionsDict = this.getSSHOptions(sshOptions, host, port);
const configRaw = JSON.parse(JSON.stringify(config));
const sshConfigRaw = JSON.parse(JSON.stringify(sshOptionsDict));
const sshPromise = new Promise((resolve, reject) => {
createTunnel(...Object.values(sshOptionsDict)).then(([server, connection]) => {
const listenAddress = server.address();
// sentinel mode
if (configRaw.sentinelOptions) {
// this is a sentinel connection, remove db
const client = this.createConnection(listenAddress.address, listenAddress.port, auth, configRaw, false, true, true);
client.on('ready', () => {
client.call('sentinel', 'get-master-addr-by-name', configRaw.sentinelOptions.masterName).then((reply) => {
if (!reply) {
return reject(new Error(`Master name "${configRaw.sentinelOptions.masterName}" not exists!`));
}
// connect to the master node via ssh
this.createClusterSSHTunnels(sshConfigRaw, [{ host: reply[0], port: reply[1] }]).then((tunnels) => {
const sentinelClient = this.createConnection(
tunnels[0].localHost, tunnels[0].localPort, configRaw.sentinelOptions.nodePassword, configRaw, false, true,
);
return resolve(sentinelClient);
});
}).catch((e) => { reject(e); }); // sentinel exec failed
});
client.on('error', (e) => { reject(e); });
}
// ssh cluster mode
else if (configRaw.cluster) {
const client = this.createConnection(listenAddress.address, listenAddress.port, auth, configRaw, false, true);
client.on('ready', () => {
// get all cluster nodes info
client.call('cluster', 'nodes').then((reply) => {
const nodes = this.getClusterNodes(reply);
// create ssh tunnel for each node
this.createClusterSSHTunnels(sshConfigRaw, nodes).then((tunnels) => {
configRaw.natMap = this.initNatMap(tunnels);
// select first line of tunnels to connect
const clusterClient = this.createConnection(tunnels[0].localHost, tunnels[0].localPort, auth, configRaw, false);
resolve(clusterClient);
});
}).catch((e) => { reject(e); });
});
client.on('error', (e) => { reject(e); });
}
// ssh standalone redis
else {
const client = this.createConnection(listenAddress.address, listenAddress.port, auth, configRaw, false);
return resolve(client);
}
// create SSH tunnel failed
}).catch((e) => {
// vue.$message.error('SSH errror: ' + e.message);
// vue.$bus.$emit('closeConnection');
reject(e);
});
});
return sshPromise;
},
getSSHOptions(options, host, port) {
const tunnelOptions = {
autoClose: false,
};
// where your localTCP Server is listening
const serverOptions = {
// if port set to 0, the serverOptions will be null
// which means automatic assign by OS
// host: '127.0.0.1',
// port: 0,
};
// ssh server
const sshOptions = {
host: options.host,
port: options.port,
username: options.username,
password: options.password,
privateKey: this.getFileContent(options.privatekey, options.privatekeybookmark),
passphrase: options.passphrase ? options.passphrase : undefined,
readyTimeout: (options.timeout) > 0 ? (options.timeout * 1000) : 30000,
keepaliveInterval: 10000,
};
// forward link in ssh server
const forwardOptions = {
// set srcAddr/srcPort undefined to use server.address() automatically
// srcAddr: '127.0.0.1',
// srcPort: 0,
dstAddr: host,
dstPort: port,
};
// Tips: small dict is ordered, should replace to Map if dict is large
return {
tunnelOptions, serverOptions, sshOptions, forwardOptions,
};
},
getRedisOptions(host, port, auth, config) {
return {
// add additional host+port to options for "::1"
host,
port,
family: 0,
connectTimeout: 30000,
retryStrategy: times => this.retryStragety(times, { host, port }),
enableReadyCheck: false,
connectionName: config.connectionName ? config.connectionName : null,
password: auth,
db: config.db ? config.db : undefined,
// ACL support
username: config.username ? config.username : undefined,
tls: config.sslOptions ? this.getTLSOptions(config.sslOptions) : undefined,
connectionReadOnly: config.connectionReadOnly ? true : undefined,
// return int as string to avoid big number issues
stringNumbers: true,
};
},
getSentinelOptions(host, port, auth, config) {
return {
sentinels: [{ host, port }],
sentinelPassword: auth,
password: config.sentinelOptions.nodePassword,
name: config.sentinelOptions.masterName,
connectTimeout: 30000,
retryStrategy: times => this.retryStragety(times, { host, port }),
enableReadyCheck: false,
connectionName: config.connectionName ? config.connectionName : null,
db: config.db ? config.db : undefined,
// ACL support
username: config.username ? config.username : undefined,
tls: config.sslOptions ? this.getTLSOptions(config.sslOptions) : undefined,
};
},
getClusterOptions(redisOptions, natMap = {}) {
return {
connectionName: redisOptions.connectionName,
enableReadyCheck: false,
slotsRefreshTimeout: 30000,
redisOptions,
natMap,
};
},
getClusterNodes(nodes, type = 'master') {
const result = [];
nodes = nodes.split('\n');
for (let node of nodes) {
if (!node) {
continue;
}
node = node.trim().split(' ');
if (node[2].includes(type)) {
const dsn = node[1].split('@')[0];
const lastIndex = dsn.lastIndexOf(':');
const host = dsn.substr(0, lastIndex);
const port = dsn.substr(lastIndex + 1);
result.push({ host, port });
}
}
return result;
},
createClusterSSHTunnels(sshConfig, nodes) {
const sshTunnelStack = [];
for (const node of nodes) {
// tunnelssh will change 'config' param, so just copy it
const sshConfigCopy = JSON.parse(JSON.stringify(sshConfig));
// revocery the buffer after json.parse
if (sshConfigCopy.sshOptions.privateKey) {
sshConfigCopy.sshOptions.privateKey = Buffer.from(sshConfigCopy.sshOptions.privateKey);
}
sshConfigCopy.forwardOptions.dstHost = node.host;
sshConfigCopy.forwardOptions.dstPort = node.port;
const promise = new Promise((resolve, reject) => {
const sshPromise = createTunnel(...Object.values(sshConfigCopy));
sshPromise.then(([server, connection]) => {
const addr = server.address();
const line = {
localHost: addr.address,
localPort: addr.port,
dstHost: node.host,
dstPort: node.port,
};
resolve(line);
}).catch((e) => {
reject(e);
});
});
sshTunnelStack.push(promise);
}
return Promise.all(sshTunnelStack);
},
initNatMap(tunnels) {
const natMap = {};
for (const line of tunnels) {
natMap[`${line.dstHost}:${line.dstPort}`] = { host: line.localHost, port: line.localPort };
}
return natMap;
},
getTLSOptions(options) {
return {
// ca: options.ca ? fs.readFileSync(options.ca) : '',
// key: options.key ? fs.readFileSync(options.key) : '',
// cert: options.cert ? fs.readFileSync(options.cert) : '',
ca: this.getFileContent(options.ca, options.cabookmark),
key: this.getFileContent(options.key, options.keybookmark),
cert: this.getFileContent(options.cert, options.certbookmark),
checkServerIdentity: (servername, cert) =>
// skip certificate hostname validation
undefined,
rejectUnauthorized: false,
};
},
retryStragety(times, connection) {
const maxRetryTimes = 3;
if (times >= maxRetryTimes) {
vue.$message.error('Too Many Attempts To Reconnect. Please Check The Server Status!');
vue.$bus.$emit('closeConnection');
return false;
}
// reconnect after
return Math.min(times * 200, 1000);
},
getFileContent(file, bookmark = '') {
if (!file) {
return undefined;
}
try {
// mac app store version, read through bookmark
if (bookmark) {
const bookmarkClose = remote.app.startAccessingSecurityScopedResource(bookmark);
}
const content = fs.readFileSync(file);
(typeof bookmarkClose === 'function') && bookmarkClose();
return content;
} catch (e) {
// force alert
alert(`${vue.$t('message.key_no_permission')}\n[${e.message}]`);
vue.$bus.$emit('closeConnection');
return undefined;
}
},
};
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。