Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TLS feature #238

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 77 additions & 35 deletions src/components/NewConnectionDialog.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
<template>
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" :append-to-body='true'>
<el-dialog
:title="dialogTitle"
:visible.sync="dialogVisible"
:append-to-body="true"
>
<!-- redis connection form -->
<el-form :label-position="labelPosition" label-width="80px">
<el-form-item label="Host">
<el-input v-model="connection.host" autocomplete="off" placeholder="127.0.0.1"></el-input>
<el-input
v-model="connection.host"
autocomplete="off"
placeholder="127.0.0.1"
></el-input>
</el-form-item>

<el-form-item label="Port">
<el-input v-model="connection.port" autocomplete="off" placeholder="6379"></el-input>
<el-input
v-model="connection.port"
autocomplete="off"
placeholder="6379"
></el-input>
</el-form-item>

<el-form-item label="Auth">
Expand All @@ -18,27 +30,40 @@
<el-input v-model="connection.name" autocomplete="off"></el-input>
</el-form-item>

<el-form-item label="SSL">
<el-checkbox v-model="connection.tls">Use SSL</el-checkbox>
</el-form-item>

<el-form-item label="">
<el-checkbox v-model="sshOptionsShow">SSH Tunnel</el-checkbox>
<el-checkbox v-model="connection.cluster">Cluster</el-checkbox>
<el-popover trigger="hover">
<i slot="reference" class="el-icon-question"></i>
{{ $t('message.cluster_faq') }}
{{ $t("message.cluster_faq") }}
</el-popover>
</el-form-item>

<!-- ssh connection form -->
<el-form v-if="sshOptionsShow" v-show="sshOptionsShow" label-width="80px">
<el-form-item label="Host">
<el-input v-model="connection.sshOptions.host" autocomplete="off"></el-input>
<el-input
v-model="connection.sshOptions.host"
autocomplete="off"
></el-input>
</el-form-item>

<el-form-item label="Port">
<el-input v-model="connection.sshOptions.port" autocomplete="off"></el-input>
<el-input
v-model="connection.sshOptions.port"
autocomplete="off"
></el-input>
</el-form-item>

<el-form-item label="Username">
<el-input v-model="connection.sshOptions.username" autocomplete="off"></el-input>
<el-input
v-model="connection.sshOptions.username"
autocomplete="off"
></el-input>
</el-form-item>

<el-form-item label="Password">
Expand All @@ -48,58 +73,74 @@
<el-form-item label="PrivateKey">
<el-tooltip effect="dark">
<div slot="content" v-html="$t('message.private_key_faq')"></div>
<el-input v-if='connection.sshOptions.privatekey' v-model='connection.sshOptions.privatekey' clearable autocomplete="off" ></el-input>
<el-input v-else id='private-key-path' type='file' @change='changePrivateKey'></el-input>
<el-input
v-if="connection.sshOptions.privatekey"
v-model="connection.sshOptions.privatekey"
clearable
autocomplete="off"
></el-input>
<el-input
v-else
id="private-key-path"
type="file"
@change="changePrivateKey"
></el-input>
</el-tooltip>
</el-form-item>
</el-form>
</el-form>

<div slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">{{ $t('el.messagebox.cancel') }}</el-button>
<el-button type="primary" @click="editConnection">{{ $t('el.messagebox.confirm') }}</el-button>
<el-button @click="dialogVisible = false">{{
$t("el.messagebox.cancel")
}}</el-button>
<el-button type="primary" @click="editConnection">{{
$t("el.messagebox.confirm")
}}</el-button>
</div>
</el-dialog>
</template>

<script type="text/javascript">
import storage from '@/storage';
import storage from "@/storage";

export default {
data() {
return {
dialogVisible: false,
labelPosition: 'left',
oldKey: '',
labelPosition: "left",
oldKey: "",
connection: {
host: '',
port: '',
auth: '',
name: '',
host: "",
port: "",
auth: "",
name: "",
tls: false,
cluster: false,
sshOptions: {
host: '',
host: "",
port: 22,
username: '',
password: '',
privatekey: '',
username: "",
password: "",
privatekey: ""
}
},
sshOptionsShow: false,
}
sshOptionsShow: false
};
},
props: ['config', 'editMode'],
props: ["config", "editMode"],
computed: {
dialogTitle() {
return this.editMode ? this.$t('message.edit_connection') :
this.$t('message.new_connection')
},
return this.editMode
? this.$t("message.edit_connection")
: this.$t("message.new_connection");
}
},
methods: {
editConnection() {
const config = JSON.parse(JSON.stringify(this.connection));

!config.host && (config.host = '127.0.0.1');
!config.host && (config.host = "127.0.0.1");
!config.port && (config.port = 6379);

if (!this.sshOptionsShow || !config.sshOptions.host) {
Expand All @@ -109,11 +150,11 @@ export default {
storage.editConnectionByKey(config, this.oldKey);

this.dialogVisible = false;
this.$emit('editConnectionFinished');
this.$emit("editConnectionFinished");
},
changePrivateKey() {
const path = document.getElementById('private-key-path').files[0].path;
this.$set(this.connection.sshOptions, 'privatekey', path);
const path = document.getElementById("private-key-path").files[0].path;
this.$set(this.connection.sshOptions, "privatekey", path);
}
},
mounted() {
Expand All @@ -123,10 +164,11 @@ export default {
this.oldKey = storage.getConnectionKey(this.config);

this.sshOptionsShow = !!this.connection.sshOptions;
!this.connection.sshOptions && (this.connection.sshOptions = sshOptionsNew);
!this.connection.sshOptions &&
(this.connection.sshOptions = sshOptionsNew);
}

delete this.connection.connectionName;
},
}
}
};
</script>
60 changes: 36 additions & 24 deletions src/redisClient.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import Redis from 'ioredis';
import tunnelssh from 'tunnel-ssh';
import Redis from "ioredis";
import tunnelssh from "tunnel-ssh";

// fix ioredis hgetall key has been toString()
Redis.Command.setReplyTransformer("hgetall", (result) => {
Redis.Command.setReplyTransformer("hgetall", result => {
let arr = [];
for (let i = 0; i < result.length; i += 2) {
arr.push([result[i], result[i + 1]]);
Expand All @@ -15,38 +15,43 @@ export default {
createConnection(host, port, auth, config) {
const options = {
connectTimeout: 3000,
retryStrategy: (times) => {return this.retryStragety(times, {host, port})},
retryStrategy: times => {
return this.retryStragety(times, { host, port });
},
enableReadyCheck: false,
connectionName: config.connectionName ? config.connectionName : null,
password: auth,
tls: config.tls
};

const clusterOptions = {
connectionName: options.connectionName,
enableReadyCheck: false,
redisOptions: options,
redisOptions: options
};

const client = config.cluster ?
new Redis.Cluster([{port, host}], clusterOptions) :
new Redis(port, host, options);
const client = config.cluster
? new Redis.Cluster([{ port, host }], clusterOptions)
: new Redis(port, host, options);

return client;
},

createSSHConnection(sshOptions, host, port, auth, config) {
const options = {
connectTimeout: 3000,
retryStrategy: (times) => {return this.retryStragety(times, {host, port})},
retryStrategy: times => {
return this.retryStragety(times, { host, port });
},
enableReadyCheck: false,
connectionName: config.connectionName ? config.connectionName : null,
password: auth,
password: auth
};

const clusterOptions = {
connectionName: options.connectionName,
enableReadyCheck: false,
redisOptions: options,
redisOptions: options
};

const sshConfig = {
Expand All @@ -57,28 +62,31 @@ export default {
readyTimeout: 2000,
dstHost: host,
dstPort: port,
localHost: '127.0.0.1',
localHost: "127.0.0.1",
localPort: null,
privateKey: sshOptions.privatekey ?
require('fs').readFileSync(sshOptions.privatekey) : '',
privateKey: sshOptions.privatekey
? require("fs").readFileSync(sshOptions.privatekey)
: ""
};

const sshPromise = new Promise((resolve, reject) => {
var server = tunnelssh(sshConfig, function (error, server) {
var server = tunnelssh(sshConfig, function(error, server) {
if (error) {
reject(error);
}
else {
} else {
const listenAddress = server.address();
const client = config.cluster ?
new Redis.Cluster([{port: listenAddress.port, host: listenAddress.address}], clusterOptions) :
new Redis(listenAddress.port, listenAddress.address, options);
const client = config.cluster
? new Redis.Cluster(
[{ port: listenAddress.port, host: listenAddress.address }],
clusterOptions
)
: new Redis(listenAddress.port, listenAddress.address, options);
resolve(client);
}
});

server.on('error', (error) => {
alert('SSH Connection Error: ' + error.message);
server.on("error", error => {
alert("SSH Connection Error: " + error.message);
reject(error);
});
});
Expand All @@ -90,11 +98,15 @@ export default {
const maxRetryTimes = 3;

if (times >= maxRetryTimes) {
alert(`${connection.host}:${connection.port}\nToo Many Attempts To Reconnect. Please Check The Server Status!`);
alert(
`${connection.host}:${
connection.port
}\nToo Many Attempts To Reconnect. Please Check The Server Status!`
);
return false;
}

// reconnect after
return Math.min(times * 200, 1000);
},
}
};