import React, { Component } from "react"; import { Input, Button, Modal, Select, Form, Tooltip, Tree, Dropdown, Menu, Row, Col, AutoComplete, } from "antd"; import { DeleteTwoTone } from "@ant-design/icons"; import { message } from "antd"; import { SearchOutlined } from "@ant-design/icons"; import { REDIS_DATA_TYPE, CONNECT_TYPE } from "@/utils/constant"; import "@/pages/CommonCss/zebra.css"; import uuid from "node-uuid"; import Log from "@/services/LogService"; import KeysHistoryService from "@/services/KeysHistoryService"; import LocaleUtils from "@/utils/LocaleUtils"; import intl from "react-intl-universal"; import BufferUtils from "@/utils/BufferUtils"; var lodash = window.require("lodash"); const { Search } = Input; const { Option } = Select; const { DirectoryTree } = Tree; /** * tree 显示 keys * * @class HostKeyTree * @extends {Component} */ class HostKeyTree extends Component { /** * table layout * * @memberof HostKeySortSet */ layout = { labelCol: { span: 7 }, wrapperCol: { span: 17 }, }; /** *标记key分割的结束符号 * * @memberof HostKeyTree */ splitEndSign = "]@[+p"; /** *分隔符 * * @memberof HostKeyTree */ splitSign = ""; /** * 选中的key * * @memberof HostKeyTree */ resourceTreeSelectKey = { selectedKey: "", isLeaf: true }; /** * * * @memberof HostKeyTree */ initValues = { key: "", keyType: REDIS_DATA_TYPE.STRING }; /** * * * @memberof HostKeyTree */ state = { treeData: [], searchDisable: false, createKeyMadal: { visible: false, keyType: REDIS_DATA_TYPE.STRING }, autoCompleteOptions: [], }; searchInput = React.createRef(); componentDidMount() { // 获取配置的key分割符 this.splitSign = LocaleUtils.readSystemConfig().splitSign; this.searchKey("*"); this.props.triggerRef(this); } /** * 删除key重新搜索 */ deleteKeyAndSearch = () => { this.searchKey(this.searchInput.current.input.value); }; /** * 构建 treeMap * @param {*} parentMap * @param {*} parentKeyArr */ keyArrToTreeMap(parentMap, parentKeyArr) { let childMap = parentMap.get(parentKeyArr[0]); if (childMap === null || childMap === undefined) { childMap = new Map(); parentMap.set(parentKeyArr[0], childMap); } if (parentKeyArr.length > 1) { this.keyArrToTreeMap( childMap, parentKeyArr.slice(1, parentKeyArr.length) ); } else { parentMap.set(parentKeyArr[0] + this.splitEndSign, undefined); } } /** * treeMap to treeData * @param {*} treeMap * @param {*} treeData */ treeMapToTreeData(treeMap, treeData, parentKey) { var repeatKeyCheck = new Set(); for (let item of treeMap.entries()) { let key = item[0]; let value = item[1]; if (value !== undefined && value.size > 0) { let children = []; treeData.push({ title: key, key: uuid.v4(), currentKey: parentKey + this.splitSign + key, children, }); this.treeMapToTreeData( value, children, parentKey + this.splitSign + key ); } else { let orgiKey = key; if (key.endsWith(this.splitEndSign)) { orgiKey = key.substr( 0, key.length - this.splitEndSign.length ); } if (!repeatKeyCheck.has(orgiKey)) { repeatKeyCheck.add(orgiKey); treeData.push({ title: orgiKey, key: uuid.v4(), currentKey: parentKey + this.splitSign + orgiKey, isLeaf: true, }); } } } } /** * 加载 redis key * @param {*} pattern */ loadRedisKeysByPattern(key) { let pattern = "*"; if (key !== null && key !== undefined && key !== "") { pattern = "*" + key + "*"; } let redisArr = [this.props.node.redis]; if (this.props.node.data.connectType === CONNECT_TYPE.CLUSTER) { redisArr = this.props.node.redis.nodes("master"); } let keyBuffer = BufferUtils.hexToBuffer(pattern); redisArr[0].keysBuffer(keyBuffer).then( (res) => { this.setState({ treeData: [], searchDisable: true, }); let treeData = []; if (res !== null && res !== undefined && res.length !== 0) { // 先对key进行排序 res = lodash.orderBy( res ); let rootTreeMap = new Map(); for (let i = 0; i < res.length; i++) { let keyTemp = BufferUtils.bufferToString(res[i]); let childKeyArr = keyTemp.split(this.splitSign); let childMap = rootTreeMap.get(childKeyArr[0]); if (childMap === null || childMap === undefined) { childMap = new Map(); } rootTreeMap.set(childKeyArr[0], childMap); if (childKeyArr.length > 1) { this.keyArrToTreeMap( childMap, childKeyArr.slice(1, childKeyArr.length) ); } else { rootTreeMap.set( childKeyArr[0] + this.splitEndSign, undefined ); } } // 如果key存在,则添加到搜索历史记录 let host = this.props.node.data.host; let port = this.props.node.data.port; KeysHistoryService.addKeysHistory(host, port, key); this.treeMapToTreeData(rootTreeMap, treeData, ""); } this.setState({ treeData: treeData, searchDisable: false, }); }, (err) => { message.error("loadRedisKeysByPattern error" + err); Log.error("HostKeyTree loadRedisKeysByPattern error", err); } ); } /** *搜索key * * @param {*} key * @memberof HostKeyTree */ searchKey(key) { this.loadRedisKeysByPattern(key); } /** * 打开 创建key 窗口 */ openCreateKeyMadal() { this.setState({ createKeyMadalVisible: true }); } /** *关闭 创建key 窗口 * * @memberof HostKeyTree */ cancelCreateKeyMadal = (e) => { this.setState({ createKeyMadalVisible: false, }); }; /** *确定 创建key 窗口 * * @memberof HostKeyTree */ okCreateKeyMadal = (e) => { let form = this.refs.form; if (form === undefined) { return; } let redis = this.props.node.redis; let key = form.getFieldValue("key"); let keyType = form.getFieldValue("keyType"); let keyBuffer = BufferUtils.hexToBuffer(key); redis.typeBuffer(keyBuffer, (err, retKeyType) => { if (err) { message.error("" + err); Log.error("HostKeyTree create key error", key, err); return; } let strRetKeyType = BufferUtils.bufferToString(retKeyType); if (strRetKeyType !== "none") { message.error(intl.get("HostKey.key.exist") + ", key > " + key); return; } if (keyType === REDIS_DATA_TYPE.STRING) { redis.setBuffer(keyBuffer, "").then( (value) => { this.okCreateKeyMadalSuccess(key, keyType); }, (err) => { message.error("" + err); Log.error( "HostKeyTree okCreateKeyMadal string error", err ); } ); } else if (keyType === REDIS_DATA_TYPE.ZSET) { redis.zaddBuffer(keyBuffer, 1, "default-member").then( (value) => { this.okCreateKeyMadalSuccess(key, keyType); }, (err) => { message.error("" + err); Log.error( "HostKeyTree okCreateKeyMadal zset error", err ); } ); } else if (keyType === REDIS_DATA_TYPE.SET) { redis.saddBuffer(keyBuffer, "default-member").then( (value) => { this.okCreateKeyMadalSuccess(key, keyType); }, (err) => { message.error("" + err); Log.error( "HostKeyTree okCreateKeyMadal set error", err ); } ); } else if (keyType === REDIS_DATA_TYPE.HASH) { redis.hsetBuffer(keyBuffer, "default-member", "default-value").then( (value) => { this.okCreateKeyMadalSuccess(key, keyType); }, (err) => { message.error("" + err); Log.error( "HostKeyTree okCreateKeyMadal hash error", err ); } ); } else if (keyType === REDIS_DATA_TYPE.LIST) { redis.lpushBuffer(keyBuffer, "default-member").then( (value) => { this.okCreateKeyMadalSuccess(key, keyType); }, (err) => { message.error("" + err); Log.error( "HostKeyTree okCreateKeyMadal list error", err ); } ); } }); this.searchKey(this.searchInput.current.input.value); }; /** *创建KEY成功,关闭窗口,调用父组件创建key * * @param {*} key * @param {*} keyType * @memberof HostKeyTree */ okCreateKeyMadalSuccess(key, keyType) { this.setState({ createKeyMadalVisible: false, }); this.props.createKey(key, keyType); this.initValues = { key: "", keyType: keyType }; let form = this.refs.form; if (form === undefined) { return; } form.resetFields(); } /** *显示右键菜单 * * @memberof HostKeyTree */ showTreeRightClickMenu() { let showMenuItem = [1]; return ( <Menu onClick={this.clickTreeRightClickMenu.bind(this)} style={{ width: 200 }} > {showMenuItem[0] === 1 ? ( <Menu.Item key="0"> <DeleteTwoTone />{" "} {intl.get("HostKeyTree.delete.key.node")} </Menu.Item> ) : ( "" )} </Menu> ); } /** *点击菜单项目 * * @param {*} item * @memberof HostKeyTree */ clickTreeRightClickMenu(item) { let selectedKey = this.resourceTreeSelectKey.selectedKey; let isLeaf = this.resourceTreeSelectKey.isLeaf; let orgiKey = selectedKey.substr( this.splitSign.length, selectedKey.length ); let pattern = orgiKey; if (!isLeaf) { pattern = pattern + this.splitSign + "*"; } let redis = this.props.node.redis; redis.keysBuffer(pattern).then( (res) => { if (res !== null && res !== undefined && res.length !== 0) { for (let i = 0; i < res.length; i++) { let key = res[i]; redis.del(key).then( (value) => { }, (err) => { message.error( "del key error. key: " + key + ". " + err ); Log.error( "clickTreeRightClickMenu del key error. key: " + key + ". ", err ); } ); } this.searchKey(this.searchInput.current.input.value); } }, (err) => { message.error("clickTreeRightClickMenu keys error" + err); Log.error("clickTreeRightClickMenu keys error", err); } ); } /** *选择树节点 * * @memberof ResourceTree */ onDirectoryTreeSelect = (keys, event) => { this.resourceTreeSelectKey.selectedKey = event.node.currentKey; this.resourceTreeSelectKey.isLeaf = event.node.isLeaf; if (this.resourceTreeSelectKey.isLeaf) { let currentKey = this.resourceTreeSelectKey.selectedKey; let orgiKey = currentKey.substr( this.splitSign.length, currentKey.length ); this.props.updateHostKey(orgiKey + ""); } }; /** * 双击tree */ handleDoubleClick(event, node) { if (node.isLeaf) { let currentKey = node.currentKey; let orgiKey = currentKey.substr( this.splitSign.length, currentKey.length ); this.props.updateHostKey(orgiKey); } } onAutoCompleteSelect = (data) => { this.setState({ autoCompleteOptions: [] }); }; onAutoCompleteChange = (data) => { // 如果key存在,则添加到搜索历史记录 let host = this.props.node.data.host; let port = this.props.node.data.port; let keyHistoryArr = KeysHistoryService.searchKey(host, port, data); this.setState({ autoCompleteOptions: keyHistoryArr }); }; render() { return ( <div> <Row gutter={[16, 6]}> <Col span={24}> <Button block onClick={this.openCreateKeyMadal.bind(this)} > {intl.get("HostKey.create.key")} </Button> </Col> <Col span={24}> <Tooltip placement="right" title={intl.get("common.search.tooltip.limit")} > <AutoComplete options={this.state.autoCompleteOptions} onSelect={this.onAutoCompleteSelect.bind(this)} onChange={this.onAutoCompleteChange.bind(this)} style={{ width: "100%" }} > <Search ref={this.searchInput} onSearch={this.searchKey.bind(this)} enterButton={ <Button icon={<SearchOutlined />} ></Button> } disabled={this.state.searchDisable} /> </AutoComplete> </Tooltip> </Col> <Col span={24}> <Dropdown overlay={this.showTreeRightClickMenu.bind(this)} trigger={["contextMenu"]} > <div> <DirectoryTree treeData={this.state.treeData} onSelect={this.onDirectoryTreeSelect.bind( this )} onDoubleClick={this.handleDoubleClick.bind( this )} height={750} ></DirectoryTree> </div> </Dropdown> </Col> </Row> <div> <Modal title={intl.get("HostKey.create.key")} okText={intl.get("common.ok")} cancelText={intl.get("common.cancel")} visible={this.state.createKeyMadalVisible} onOk={() => { let form = this.refs.form; if (form === undefined) { return; } form.submit(); }} onCancel={this.cancelCreateKeyMadal} > <Form initialValues={{ ...this.initValues }} {...this.layout} ref="form" onFinish={this.okCreateKeyMadal.bind(this)} > <Form.Item name="key" label="key" rules={[ { required: true, message: intl.get( "common.error.input.value" ), }, ]} > <Input /> </Form.Item> <Form.Item name="keyType" label={intl.get("HostKey.modal.keyType")} rules={[ { required: true, message: intl.get( "common.error.input.value" ), }, ]} > <Select> <Option value={REDIS_DATA_TYPE.STRING}> {REDIS_DATA_TYPE.STRING} </Option> <Option value={REDIS_DATA_TYPE.ZSET}> {REDIS_DATA_TYPE.ZSET} </Option> <Option value={REDIS_DATA_TYPE.SET}> {REDIS_DATA_TYPE.SET} </Option> <Option value={REDIS_DATA_TYPE.HASH}> {REDIS_DATA_TYPE.HASH} </Option> <Option value={REDIS_DATA_TYPE.LIST}> {REDIS_DATA_TYPE.LIST} </Option> </Select> </Form.Item> </Form> </Modal> </div> </div> ); } } export default HostKeyTree;