.gitignore000066400000000000000000000003521520166267200130550ustar00rootroot00000000000000/build /node_modules /*.log /*.iws .idea/workspace.xml .idea/tasks.xml .idea/profiles_settings.xml .idea/inspectionProfiles/Project_Default.xml .idea/inspectionProfiles/profiles_settings.xml node_modules/.yarn-integrity /p3xrs.json.npmignore000066400000000000000000000002151520166267200130620ustar00rootroot00000000000000/.idea /build /test /node_modules /*.iml /*.ipr /*.iws /.travis.yml /.scrutinizer.yml /Gruntfile.js /*.lock *.log /corifeus-boot.json /secure.travis.yml000066400000000000000000000014301520166267200131740ustar00rootroot00000000000000language: node_js node_js: - node before_script: - npm install -g grunt-cli npm env: global: secure: r4bMeHdS9vdDWuL3ax+va5YoaZKmn5MxsXNxMgnT7lrk4AFkb2wr+17+4zDalQFyt7zyqM0HJzGXRYGTiG4kow6Jk2P55wv3LeiNJxpTBOGYfKTldjLZKIxxaqJLYJ40JhlfuA+TaTj0JWVsohz5CQJxO1O7Z+/xq/0ZwHVIhDNwW5XdFDdCdBZRQMV7XKSynGAB7L0pdhASSdl4laQTxjL/6A3B0Sr9rwO5Nd8YbzZpUPQ5opTLXfln2JSMTiAEtxgLS2ARu5Aa+9qfIA5VTDXzcyYAE+uErjIXeUfNRbTCJuizNQQZOoeMR36tam0cOu+B0qt0FWqCECOlmN12tL0TpY54vqsJfS4AIUT4dm2LpjPKCKZn4jIZxVJe3uxTRKZrWiDSaKUmnuoRIoTCPh4JMYc7t6pxP/4kKcAEpw4OxYS0eERG4RXKSJx9I2szqvJ+9nS/eAO3AC/5KWn+o+x1ebnjNgmGk/7BQbgdEOm1wsUazpEAg/nkL4b5EW6IEzUJm8V31NO1OxoAs+U0NLgOu3OKys0KxjwpzTFtQ0bvQoNcy715Y6JfLwZQFOTOUM6OjMXhAE4AF6mt/SYHE7iTY1I+TZxt71RLN5+t0DQSyNHQ/VStx1nTg9HOjs35ZxxJYG2y5fbsBBmeV8wGY1iX9GAsTO2oGgfAzUIuMQs= Gruntfile.js000066400000000000000000000004471520166267200133670ustar00rootroot00000000000000const utils = require('corifeus-utils'); module.exports = (grunt) => { const _ = require('lodash'); const builder = require(`corifeus-builder`); const loader = new builder.loader(grunt); loader.js({ }); grunt.registerTask('default', builder.config.task.build.js); } LICENSE000066400000000000000000000024371520166267200121000ustar00rootroot00000000000000 @license p3x-redis-ui-server v2019.4.115 🏍️ The p3x-redis-ui-server package motor that is connected to the p3x-redis-ui-material web user interface https://pages.corifeus.com/redis-ui-server Copyright (c) 2019 Patrik Laszlo / P3X / Corifeus and contributors. MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. README.md000066400000000000000000000077161520166267200123570ustar00rootroot00000000000000# This is a development package For the full-blown package, please follow: https://github.com/patrikx3/redis-ui https://www.npmjs.com/package/p3x-redis-ui https://pages.corifeus.com/redis-ui [//]: #@corifeus-header [![Donate for Corifeus / P3X](https://img.shields.io/badge/Donate-Corifeus-003087.svg)](https://paypal.me/patrikx3) [![Contact Corifeus / P3X](https://img.shields.io/badge/Contact-P3X-ff9900.svg)](https://www.patrikx3.com/en/front/contact) [![Corifeus @ Facebook](https://img.shields.io/badge/Facebook-Corifeus-3b5998.svg)](https://www.facebook.com/corifeus.software) [![Build Status](https://api.travis-ci.com/patrikx3/redis-ui-server.svg?branch=master)](https://travis-ci.com/patrikx3/redis-ui-server) [![Uptime Robot ratio (30 days)](https://img.shields.io/uptimerobot/ratio/m780749701-41bcade28c1ea8154eda7cca.svg)](https://uptimerobot.patrikx3.com/) --- # 🏍️ The p3x-redis-ui-server package motor that is connected to the p3x-redis-ui-material web user interface v2019.4.121 🙏 This is an open-source project. Star this repository, if you like it, or even donate to maintain the servers and the development. Thank you so much! Possible, this server, rarely, is down, please, hang on for 15-30 minutes and the server will be back up. All my domains ([patrikx3.com](https://patrikx3.com) and [corifeus.com](https://corifeus.com)) could have minor errors, since I am developing in my free time. However, it is usually stable. **Note about versioning:** Versions are cut in Major.Minor.Patch schema. Major is always the current year. Minor is either 4 (January - June) or 10 (July - December). Patch is incremental by every build. If there is a breaking change, it should be noted in the readme. **Bugs are evident™ - MATRIX️** ### Node Version Requirement ``` >=10.13.0 ``` ### Built on Node ``` v12.5.0 ``` The ```async``` and ```await``` keywords are required. Install NodeJs: https://nodejs.org/en/download/package-manager/ # Description [//]: #@corifeus-header:end This is part of a composable `p3x-redis-ui` package. This is the server based on Socket.IO (no rest at all). The server will be using the `p3x-redis-ui-material` web client package based on built with Webpack, Socket.IO and AngularJs Material. This package is named as `p3x-redis-ui-server`. ## Configuration For now, there are 2 configuration files: ```bash p3xrs --config ./p3xrs.json ``` The 2nd configuration is the list of the connections if found in `p3xrs.json` it either in the config: ```text p3xrs.json/p3xrs.connections['home-dir'] = undefined|home|absolute|relative ``` The best is to keep it undefined and it will be in your home dir, but you can choose any place as well. # For development standalone Copy from `./artifacts/boot/p3xrs.json` to the root folder (`./p3xrs.json`). ```bash npm install npm run dev ``` It uses `nodemon` and when any file is changed, it will re-load it. The server app is available @ http://localhost:7843 [//]: #@corifeus-footer --- [**P3X-REDIS-UI-SERVER**](https://pages.corifeus.com/redis-ui-server) Build v2019.4.121 [![Donate for Corifeus / P3X](https://img.shields.io/badge/Donate-Corifeus-003087.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QZVM4V6HVZJW6) [![Contact Corifeus / P3X](https://img.shields.io/badge/Contact-P3X-ff9900.svg)](https://www.patrikx3.com/en/front/contact) [![Like Corifeus @ Facebook](https://img.shields.io/badge/LIKE-Corifeus-3b5998.svg)](https://www.facebook.com/corifeus.software) ## P3X Sponsors [IntelliJ - The most intelligent Java IDE](https://www.jetbrains.com/?from=patrikx3) [![JetBrains](https://cdn.corifeus.com/assets/svg/jetbrains-logo.svg)](https://www.jetbrains.com/?from=patrikx3) [![NoSQLBooster](https://cdn.corifeus.com/assets/png/nosqlbooster-70x70.png)](https://www.nosqlbooster.com/) [The Smartest IDE for MongoDB](https://www.nosqlbooster.com) [//]: #@corifeus-footer:endartifacts/000077500000000000000000000000001520166267200130455ustar00rootroot00000000000000artifacts/boot/000077500000000000000000000000001520166267200140105ustar00rootroot00000000000000artifacts/boot/p3xrs.json000066400000000000000000000012731520166267200157650ustar00rootroot00000000000000{ "p3xrs": { "http": { "port-info": "this is ommitted, it will be default 7843", "port": 7843 }, "connections": { "home-dir-info": "if the dir config is empty or home, the connections are saved in the home folder, otherwise it will resolve the directory set as it is, either relative ./ or absolute starting with /. NodeJs will resolve this directory in p3xrs.connections.dir", "home-dir": "home" }, "static-info": "This is the best configuration, if it starts with ~, then it is in resolve the path in the node_modules, otherwise it resolves to the current process current working directory.", "static-disabled": "~p3x-redis-ui-material/dist" } }artifacts/cluster.md000066400000000000000000000024721520166267200150550ustar00rootroot00000000000000[//]: #@corifeus-header # 🏍️ The p3x-redis-ui-server package motor that is connected to the p3x-redis-ui-material web user interface [//]: #@corifeus-header:end # Run a Docker Cluster ```bash docker run -e "IP=0.0.0.0" -p 7000:7000 -p 7001:7001 -p 7002:7002 -p 7003:7003 -p 7004:7004 -p 7005:7005 grokzen/redis-cluster:latest ``` [//]: #@corifeus-footer --- [**P3X-REDIS-UI-SERVER**](https://pages.corifeus.com/redis-ui-server) Build v2019.4.121 [![Donate for Corifeus / P3X](https://img.shields.io/badge/Donate-Corifeus-003087.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QZVM4V6HVZJW6) [![Contact Corifeus / P3X](https://img.shields.io/badge/Contact-P3X-ff9900.svg)](https://www.patrikx3.com/en/front/contact) [![Like Corifeus @ Facebook](https://img.shields.io/badge/LIKE-Corifeus-3b5998.svg)](https://www.facebook.com/corifeus.software) ## P3X Sponsors [IntelliJ - The most intelligent Java IDE](https://www.jetbrains.com/?from=patrikx3) [![JetBrains](https://cdn.corifeus.com/assets/svg/jetbrains-logo.svg)](https://www.jetbrains.com/?from=patrikx3) [![NoSQLBooster](https://cdn.corifeus.com/assets/png/nosqlbooster-70x70.png)](https://www.nosqlbooster.com/) [The Smartest IDE for MongoDB](https://www.nosqlbooster.com) [//]: #@corifeus-footer:endbin/000077500000000000000000000000001520166267200116355ustar00rootroot00000000000000bin/p3xrs.js000077500000000000000000000001031520166267200132470ustar00rootroot00000000000000#!/usr/bin/env node const boot = require('../src/lib/boot') boot() package.json000066400000000000000000000040501520166267200133520ustar00rootroot00000000000000{ "name": "p3x-redis-ui-server", "version": "2019.4.121", "description": "🏍️ The p3x-redis-ui-server package motor that is connected to the p3x-redis-ui-material web user interface", "corifeus": { "icon": "fas fa-flag-checkered", "code": "Reverse", "opencollective": false, "build": true, "nodejs": "v12.5.0", "reponame": "redis-ui-server", "publish": true, "prefix": "p3x-", "type": "p3x" }, "main": "src/indexjs", "bin": { "p3xrs": "./bin/p3xrs.js" }, "scripts": { "test": "grunt", "start": "node ./bin/p3xrs --config ./p3xrs.json", "dev": "nodemon --watch src --watch package.json --watch bin ./bin/p3xrs", "dev-readonly-connections": "nodemon --watch src --watch package.json --watch bin ./bin/p3xrs --readonly-connections" }, "watch": { "run": "src/**/*.js" }, "repository": { "type": "git", "url": "https://github.com/patrikx3/redis-ui-server.git" }, "keywords": [ "redis", "ui", "gui", "web", "electron", "desktop", "server", "angularjs", "javascript", "material", "dark", "light" ], "author": "Patrik Laszlo ", "license": "MIT", "devDependencies": { "corifeus-builder": "^2019.4.111", "nodemon": "^1.19.1" }, "dependencies-saved": { "koa-body": "^4.0.4", "koa-router": "^7.4.0" }, "dependencies": { "chalk": "^2.4.2", "commander": "^2.20.0", "console-stamp": "^0.2.9", "corifeus-utils": "^2019.4.106", "ioredis": "^4.10.0", "koa": "^2.7.0", "koa-send": "^5.0.0", "koa-static": "^5.0.0", "lodash": "^4.17.11", "object-hash": "^1.3.1", "redis-info": "^3.0.7", "socket.io": "^2.2.0" }, "engines": { "node": ">=10.13.0" }, "homepage": "https://pages.corifeus.com/redis-ui-server" } redis-ui-server.iml000066400000000000000000000007441520166267200146220ustar00rootroot00000000000000 scripts/000077500000000000000000000000001520166267200125545ustar00rootroot00000000000000scripts/redis-cluster.sh000077500000000000000000000002311520166267200156740ustar00rootroot00000000000000#!/usr/bin/env bash docker run -e "IP=0.0.0.0" -p 7000:7000 -p 7001:7001 -p 7002:7002 -p 7003:7003 -p 7004:7004 -p 7005:7005 grokzen/redis-cluster:latestsrc/000077500000000000000000000000001520166267200116545ustar00rootroot00000000000000src/index.js000066400000000000000000000001251520166267200133170ustar00rootroot00000000000000module.exports = { lib: require('./lib'), services: require('./service'), } src/ioredis-cluster/000077500000000000000000000000001520166267200147715ustar00rootroot00000000000000src/ioredis-cluster/Cluster.js000066400000000000000000000117361520166267200167600ustar00rootroot00000000000000const Redis = require('ioredis') const {EventEmitter} = require('events') const redisInfo = require('redis-info') const setDefaultPasswordOptionFromServer = require('./setDefaultPasswordOptionFromServer') module.exports = class Cluster extends Redis.Cluster{ constructor(server, options={}){ server = Array.isArray(server) ? server : [server] options = setDefaultPasswordOptionFromServer(options, server) super(server, options) } async infoObject(...args){ const [key] = args if(key==='keyspace'){ return { databases: [await this._getClusterInfoKeyspace()] } } const info = await this.info(...args) const infoObject = redisInfo.parse(info) if(!key){ infoObject.databases[0] = await this._getClusterInfoKeyspace() } return infoObject } async _getClusterInfoKeyspace(info){ const keyspaceList = await Promise.all( this.nodes('master').map(node => { return node.info('keyspace') }) ) let keys = 0 let expires = 0 for(const nodeKeyspace of keyspaceList){ const parsed = redisInfo.parse(nodeKeyspace) const db0 = parsed.databases[0] const { keys:nodeKeys = 0, expires:nodeExpires = 0, } = db0 keys += nodeKeys expires += nodeExpires } const avg_ttl = keyspaceList.reduce((tt, {avg_ttl:nodeAvgTtl = 0})=>{ return tt + nodeAvgTtl },0)/keyspaceList.length const clusterKeyspace = { keys, expires, avg_ttl, } // console.log({clusterKeyspace}) return clusterKeyspace } async originalDbsize(...args){ return super.dbsize(...args) } async dbsize(){ const nodeCounts = await Promise.all( this.nodes('master').map(node => { return node.dbsize() }) ) const count = nodeCounts.reduce((tt, c) => tt+c ,0) // console.log({count}) return count } originalRename(...args){ return super.rename(...args) } async rename(key, keyNew, callback){ let res = null let err = null try{ let [ value, ttl ] = await Promise.all([ this.dumpBuffer(key), this.ttl(key), ]) ttl = parseInt(ttl) if(ttl<0){ ttl = 0 } await this.del(keyNew) await this.restore(keyNew, ttl, value) await this.del(key) res = 'OK' } catch(e){ err = e } if(typeof callback === 'function'){ callback(err, res) } else if (err){ throw err } return res } originalPipeline(...args){ return super.pipeline(...args) } pipeline(...pipelineArgs){ const calls = [] async function exec(calls){ const results = [] for(let c of calls){ const result = await c() results.push(result) } // console.log({results}) return results } const proxy = new Proxy(calls, { get: (calls, method)=>{ return (...callArgs)=>{ switch(method){ case 'exec': return exec(calls) break } const callback = async () => { let err = null let result = null try{ result = await this[method](...callArgs) } catch(e){ err = e } return [err, result] } calls.push(callback) return proxy } } }) return proxy } scanStream(...args){ const stream = new EventEmitter() this._streamNodes({ stream, method: 'scanStream', params: args, }) return stream } async _streamNodes(options={}){ let { nodes = this.nodes('master'), stream = new EventEmitter(), method, params = [], } = options for(let node of nodes){ await new Promise((resolve, reject)=>{ const nodeStream = node[method](...params) nodeStream.on('data', (resultKeys) => { // console.log({resultKeys}) stream.emit('data', resultKeys) }) nodeStream.on('end', async () => { try { resolve() } catch (e) { stream.emit('error',e) reject(e) } }) }) } stream.emit('end') } } src/ioredis-cluster/Redis.js000066400000000000000000000016331520166267200164000ustar00rootroot00000000000000const IORedis = require('ioredis') const redisInfo = require('redis-info') const Cluster = require('./Cluster') const createWithClusterAutoDetect = require('./createWithClusterAutoDetect') const getInfo = require('./getInfo') const getClusterNodes = require('./getClusterNodes') const isClusterEnabled = require('./isClusterEnabled') class Redis extends IORedis{ constructor(server, {autoDetectCluster, ...options} = {}){ if(autoDetectCluster && !Array.isArray(server)){ return createWithClusterAutoDetect(server, options) } if(Array.isArray(server)){ return new Cluster(server, options) } super(server) } infoObject(...args){ const info = this.info(...args) return redisInfo.parse(info) } } Redis.Cluster = Cluster Redis.isClusterEnabled = isClusterEnabled Redis.getClusterNodes = getClusterNodes Redis.getInfo = getInfo module.exports = Redissrc/ioredis-cluster/createWithClusterAutoDetect.js000066400000000000000000000013221520166267200227500ustar00rootroot00000000000000const Redis = require('ioredis') const isClusterEnabled = require('./isClusterEnabled') const getClusterNodes = require('./getClusterNodes') const Cluster = require('./Cluster') const setDefaultPasswordOptionFromServer = require('./setDefaultPasswordOptionFromServer') module.exports = async function createWithClusterAutoDetect(server, options = {}){ let isCluster if(Array.isArray(server)){ isCluster = true } else{ isCluster = await isClusterEnabled(server, true) } if(!isCluster){ return new Redis(server) } // server = await getClusterNodes(server) options = setDefaultPasswordOptionFromServer(options, server) return new Cluster(server, options) }src/ioredis-cluster/getClusterNodes.js000066400000000000000000000035571520166267200204530ustar00rootroot00000000000000const Redis = require('ioredis') const hash = require('object-hash') const redisNodesCache = {} module.exports = async function getClusterNodes(servers, options={}){ const { cache = false, force = false, } = options if(!Array.isArray(servers)){ servers = [servers] } const errors = [] let nodes for(const server of servers){ try{ const id = cache ? hash(server) : null if(cache && !force && redisNodesCache[id]){ return redisNodesCache[id] } const redis = new Redis({...server, retryStrategy: ()=>false}) const rawNodes = await new Promise((resolve, reject)=>{ redis.sendCommand( new Redis.Command( 'CLUSTER', ['NODES'], 'utf-8', function(err,value) { if (err) reject(err) else resolve(value.toString()) } ) ) }) const lines = rawNodes.trim().split("\n") nodes = lines.reduce((arr, line)=>{ if(!line){ return arr } const row = line.split(' ') const [ node_id, server, flags, ] = row const [ target, slots ] = server.split('@') const [ host, port ] = target.split(':') const node = { host, port, } arr.push(node) return arr }, []) if(cache){ redisNodesCache[id] = nodes } return nodes } catch(error){ errors.push(error) } finally{ redis.disconnect() } if(nodes){ break } } if(nodes){ return nodes } const errorsMsg = errors.map(e => e.toString()).join('\n') throw new Error('Unable to connect: '+errorsMsg) }src/ioredis-cluster/getDefaultPasswordFromServer.js000066400000000000000000000003251520166267200231510ustar00rootroot00000000000000module.exports = function getDefaultPasswordFromServer(server){ const server1 = Array.isArray(server) ? server[0] : server if(typeof server1 === 'object' && server1 !== null){ return server1.password } }src/ioredis-cluster/getInfo.js000066400000000000000000000011371520166267200167240ustar00rootroot00000000000000const Redis = require('ioredis') const redisInfo = require('redis-info') const hash = require('object-hash') const redisInfoCache = {} module.exports = async function getInfo(server, options={}){ const { cache = false, force = false, } = options const id = cache ? hash(server) : null if(cache && !force && redisInfoCache[id]){ return redisInfoCache[id] } const redis = new Redis(server) const rawInfo = await redis.info() redis.disconnect() const info = redisInfo.parse(rawInfo) if(cache){ redisInfoCache[id] = info } return info }src/ioredis-cluster/index.js000066400000000000000000000000701520166267200164330ustar00rootroot00000000000000const Redis = require('./Redis') module.exports = Redis src/ioredis-cluster/isClusterEnabled.js000066400000000000000000000003321520166267200205550ustar00rootroot00000000000000const getInfo = require('./getInfo') module.exports = async function isClusterEnabled(server, cache = false){ const {cluster_enabled} = await getInfo(server, {cache}) return Boolean(parseInt(cluster_enabled)) }src/ioredis-cluster/setDefaultPasswordOptionFromServer.js000066400000000000000000000007151520166267200243610ustar00rootroot00000000000000const getDefaultPasswordFromServer = require('./getDefaultPasswordFromServer') module.exports = function setDefaultPasswordOptionFromServer(options, server){ const defaultPassword = getDefaultPasswordFromServer(server) let {redisOptions} = options if(redisOptions === undefined){ redisOptions = {} options.redisOptions = redisOptions } if(redisOptions.password === undefined){ redisOptions.password = defaultPassword } return options }src/lib/000077500000000000000000000000001520166267200124225ustar00rootroot00000000000000src/lib/boot.js000066400000000000000000000012021520166267200137160ustar00rootroot00000000000000require('corifeus-utils'); const boot = async () => { global.p3xrs = {} p3xrs.cfg = undefined const cli = require('./cli'); if (!cli()) { return; } const consoleStamp = require('./console-stamp') consoleStamp() const koaService = require('../service/koa') p3xrs.koa = new koaService() await p3xrs.koa.boot() const socketIoService = require('../service/socket.io') p3xrs.socketIo = new socketIoService(); await p3xrs.socketIo.boot({ koaService: p3xrs.koa }) p3xrs.redisConnections = {} p3xrs.redisConnectionsSubscriber = {} } module.exports = boot src/lib/cli.js000066400000000000000000000062441520166267200135350ustar00rootroot00000000000000const path = require('path') const fs = require('fs') const cli = () => { const pkg = require('../../package') if (!process.versions.hasOwnProperty('electron')) { const program = require('commander') program .version(pkg.version) .option('-c, --config [config]', 'Set the p3xr.json p3x-redis-ui-server configuration, see more help in https://github.com/patrikx3/redis-ui-server') .option('-r, --readonly-connections', 'Set the connections to be readonly, no adding, saving or delete a connection') .parse(process.argv); if (!program.config) { program.config = path.resolve(path.dirname(require.main.filename) + path.sep + '..', `.${path.sep}p3xrs.json`) // program.outputHelp() // return false } const configPath = path.resolve(process.cwd(), program.config) //console.log(configPath) p3xrs.cfg = require(configPath).p3xrs if (program.readonlyConnections) { // console.warn(program.readonlyConnections) p3xrs.cfg.readonlyConnections = true //console.warn(p3xrs.cfg.readonlyConnections === true) } } else { p3xrs.cfg = { "http": { "port-info": "this is ommitted, it will be default 7843", "port": 7844 }, "connections": { "home-dir-info": "if the dir config is empty or home, the connections are saved in the home folder, otherwise it will resolve the directory set as it is, either relative ./ or absolute starting with /. NodeJs will resolve this directory in p3xrs.connections.dir", "home-dir": "home" }, "static-info": "This is the best configuration, if it starts with ~, then it is in resolve the path in the node_modules, otherwise it resolves to the current process current working directory.", "static": "~p3x-redis-ui-material/dist" } p3xrs.cfg.readonlyConnections = false } if (!p3xrs.cfg.hasOwnProperty('static')) { } if (!p3xrs.cfg.hasOwnProperty('connections')) { p3xrs.cfg.connections = {} } if (!p3xrs.cfg.connections.hasOwnProperty('home-dir')) { p3xrs.cfg.connections = 'home' } if (p3xrs.cfg.connections['home-dir'] === 'home') { p3xrs.cfg.connections['home-dir'] = require('os').homedir(); } p3xrs.cfg.connections['home'] = path.resolve(p3xrs.cfg.connections['home-dir'], '.p3xrs-conns.json') if (!fs.existsSync(p3xrs.cfg.connections.home)) { fs.writeFileSync(p3xrs.cfg.connections.home, JSON.stringify({ update: new Date(), list: [], }, null, 4)) } p3xrs.connections = require(p3xrs.cfg.connections.home) //console.log(p3xrs.cfg.connections.home, p3xrs.connections) //console.log(p3xrs.connections) /* p3xrs.redis = {} let keyStreamPaging = 10000 Object.defineProperty(p3xrs.redis, 'key-stream-paging', { get: () => { return keyStreamPaging }, set: (value) => { keyStreamPaging = value } }) */ return true; } module.exports = cli; src/lib/console-stamp.js000066400000000000000000000017341520166267200155510ustar00rootroot00000000000000const chalk = require('chalk'); const consoleStamp = () => { // overriding the console should be after this!!! require('console-stamp')(console, { pattern: 'yyyy/mm/dd HH:MM:ss.l', datePrefix: '[P3XRS] ', dateSuffix: '', metadata: function () { return `[PID: ${(String(process.pid).padStart(6, 0))}]`; }, colors: { stamp: "yellow", label: function() { let color; switch(arguments[0]) { case '[ERROR]': color = chalk.bold.red break; case '[WARN]': color = chalk.bold.blue break; default: color = chalk.green; } return color(arguments[0]) }, metadata: chalk.black.bgGreenBright, }, }); } module.exports = consoleStamp src/lib/index.js000066400000000000000000000001721520166267200140670ustar00rootroot00000000000000module.exports = { boot: require('./boot'), cli: require('./cli'), consoleStamp: require('./console-stamp'), }src/service/000077500000000000000000000000001520166267200133145ustar00rootroot00000000000000src/service/index.js000066400000000000000000000000571520166267200147630ustar00rootroot00000000000000module.exports = { koa: require('./koa'), }src/service/koa/000077500000000000000000000000001520166267200140665ustar00rootroot00000000000000src/service/koa/index.js000066400000000000000000000062201520166267200155330ustar00rootroot00000000000000const Koa = require('koa'); //const Router = require('koa-router') const fs = require('fs') //const koaBody = require('koa-body') const path = require('path') const koaService = function () { const self = this; self.boot = async () => { const app = new Koa(); this.app = app; // const router = new Router(); // this.router = router; // app.use(koaBody()); const resolvePath = (inputPath) => { let resolvedPath if (inputPath.startsWith('~')) { const inputPathFromNodeModules = inputPath.substring(1) resolvedPath = path.resolve(path.dirname(require.main.filename) + path.sep + '..', `node_modules${path.sep}${inputPathFromNodeModules}`) } else { resolvedPath = path.resolve(process.cwd(), inputPath) } return resolvedPath } let hasStatic = false let staticPath if (typeof p3xrs.cfg.static === 'string') { hasStatic = true staticPath = resolvePath(p3xrs.cfg.static) const serve = require('koa-static'); app.use(serve(staticPath)); } app.on('error', err => { console.error('koa server error', err) }); /* app.context.p3x = { status: { 404: () => { const error = new Error('not-found'); error.status = 404; throw error; } } } */ /* app.use(async (ctx) => { ctx.body = { status: 'operational' }; }); */ if (hasStatic) { const send = require('koa-send') app.use(async (ctx) => { await send(ctx, 'index.html', {root: staticPath}); }); } else { app.use(async (ctx) => { ctx.response.body = { status: 'operational' } }); } // app.use(router.routes()) // app.use(router.allowedMethods()); /* const keyFilename = resolvePath(p3xrs.cfg.https2.key) const certFilename = resolvePath(p3xrs.cfg.https2.cert) const certs = [ // key fs.readFileSync(keyFilename), // cert fs.readFileSync(certFilename), ] const options = { key: certs[0].toString(), cert: certs[1].toString(), }; */ //console.warn('keyFilename', keyFilename, options.key) //console.warn('certFilename', certFilename, options.cert) //const spdy = require('spdy'); //const server = spdy.createServer(options, app.callback()) // not working with websocket-s native node http2 //const http2 = require('http2'); //const server = http2.createSecureServer(options, app.callback()); const http = require('http') const server = http.createServer(app.callback()) this.server = server; server.listen(p3xrs.cfg.http.port || 7843); } } module.exports = koaServicesrc/service/socket.io/000077500000000000000000000000001520166267200152125ustar00rootroot00000000000000src/service/socket.io/index.js000066400000000000000000000006151520166267200166610ustar00rootroot00000000000000const socketIo = require('socket.io') const socketIoService = function() { const self = this; self.boot = async (options) => { const { koaService } = options const socketio = require('socket.io')(koaService.server, { secure: true, path: '/socket.io', }); require('./socket')(socketio); } } module.exports = socketIoService src/service/socket.io/request/000077500000000000000000000000001520166267200167025ustar00rootroot00000000000000src/service/socket.io/request/connection-connect.js000066400000000000000000000153541520166267200230360ustar00rootroot00000000000000const consolePrefix = 'socket.io connection-connect'; const Redis = require('../../../ioredis-cluster') const sharedIoRedis = require('../shared') const generateConnectInfo = async (options) => { const { socket, redis } = options // console.warn('generateConnectInfo', options.payload) let databases let results let commands if (options.payload.connection.awsElastiCache === true) { databases = 0 commands = await redis.command() } else { results = await Promise.all([ redis.config('get', 'databases'), redis.command(), ]) databases = parseInt(results[0][1]) commands = results[1] } //socket.p3xrs.commands = commands.map(e => e[0].toLowerCase()) //console.log('databases', databases) await sharedIoRedis.getFullInfoAndSendSocket({ redis: redis, responseEvent: options.responseEvent, socket: socket, extend: { databases: databases, commands: commands } }) } module.exports = async(options) => { const { socket, payload } = options; const { connection, db } = payload try { if (socket.p3xrs.connectionId !== connection.id) { sharedIoRedis.disconnectRedis({ socket: socket, }) } if (!p3xrs.redisConnections.hasOwnProperty(connection.id)) { p3xrs.redisConnections[connection.id] = { connection: connection, clients: [] } } if (!p3xrs.redisConnections[connection.id].clients.includes(socket.id)) { console.info(consolePrefix, 'added new socket.id', socket.id, 'to', connection.id, 'name with', connection.name) p3xrs.redisConnections[connection.id].clients.push(socket.id) } if (socket.p3xrs.ioredis !== undefined) { console.info(consolePrefix, 'redis was already connected') socket.p3xrs.connectionId = connection.id await generateConnectInfo({ redis: socket.p3xrs.ioredis, socket: socket, responseEvent: options.responseEvent, payload: payload }) sharedIoRedis.sendStatus({ socket: socket, }) } else { const actualConnection = p3xrs.connections.list.find(con => options.payload.connection.id === con.id) let redisConfig = Object.assign({}, actualConnection); delete redisConfig.name delete redisConfig.id redisConfig.retryStrategy = () => { return false } if (db !== undefined) { redisConfig.db = db } if (redisConfig.cluster === true) { redisConfig = [ redisConfig ].concat(actualConnection.nodes) } let redis = new Redis(redisConfig) let redisSubscriber = new Redis(redisConfig) // let redis = await new Redis(redisConfig, {autoDetectCluster: true}) // let redisSubscriber = await new Redis(redisConfig, {autoDetectCluster: true}) socket.p3xrs.connectionId = connection.id socket.p3xrs.ioredis = redis socket.p3xrs.ioredisSubscriber = redisSubscriber let didConnected = false const redisErrorFun = async function(error) { const consolePrefix = 'socket.io connection-connect redis error fun' console.warn(consolePrefix, connection.id, connection.name, 'error' ) console.error(error) console.warn(consolePrefix, 'didConnected', didConnected ) if (!didConnected) { socket.emit(options.responseEvent, { status: 'error', error: error }) } const disconnectedData = { connectionId: socket.p3xrs.connectionId, error: error, status: 'error', } console.warn(consolePrefix, 'disconnectedData', disconnectedData) socket.p3xrs.io.emit('redis-disconnected', disconnectedData) try { await sharedIoRedis.disconnectRedis({ socket: socket, }) } catch(e) { console.warn(consolePrefix, 'disconnectRedis') console.error(e) } delete p3xrs.redisConnections[socket.connectionId] socket.p3xrs.connectionId = undefined socket.p3xrs.ioredis = undefined socket.p3xrs.ioredisSubscriber = undefined sharedIoRedis.sendStatus({ socket: socket, }) } redis.on('error', redisErrorFun) redisSubscriber.on('error', redisErrorFun) //console.warn('create psubscribe', actualConnection.id) redisSubscriber.psubscribe('*', function(error, count){ if (error) { console.error(error) } }) //console.warn('create pmessage', actualConnection.id) redisSubscriber.on('pmessage', function (channel, pattern, message) { console.log(`receive pmessage channel: ${channel} - pattern: ${pattern}, message: ${message}`); //console.log('list clients', actualConnection.id, JSON.stringify(p3xrs.redisConnections[actualConnection.id].clients, null, 4)) socket.emit('pubsub-message', { channel: pattern, message: message, }) }); redis.on('connect', async function() { try { console.info(consolePrefix, options.payload.connection.id, options.payload.connection.name, 'connected' ) didConnected = true await generateConnectInfo({ redis: redis, socket: socket, responseEvent: options.responseEvent, payload: options.payload, }) } catch(e) { socket.emit(options.responseEvent, { status: 'error', error: e, }) } finally { sharedIoRedis.sendStatus({ socket: socket, }) } }) } } catch (error) { console.error(error) socket.emit(options.responseEvent, { status: 'error', error: error }) } } src/service/socket.io/request/connection-delete.js000066400000000000000000000030171520166267200226400ustar00rootroot00000000000000const sharedIoRedis = require('../shared') module.exports = async (options) => { const {socket} = options; const connectionSaveId = options.payload.id; let connectionIndexExisting; let disableReadonlyConnections = true try { sharedIoRedis.ensureReadonlyConnections() disableReadonlyConnections = false for (let connectionIndex in p3xrs.connections.list) { const connection = p3xrs.connections.list[connectionIndex] if (connection.id === connectionSaveId) { connectionIndexExisting = connectionIndex break; } } if (connectionIndexExisting !== undefined) { p3xrs.connections.list.splice(connectionIndexExisting, 1) p3xrs.connections.update = new Date() const fs = require('fs') fs.writeFileSync(p3xrs.cfg.connections.home, JSON.stringify(p3xrs.connections, null, 4)) } socket.emit(options.responseEvent, { status: 'ok', }) } catch (error) { console.log(error) socket.emit(options.responseEvent, { status: 'error', error: error }) } finally { if (!disableReadonlyConnections) { sharedIoRedis.sendConnections({ socket: socket, }) sharedIoRedis.triggerDisconnect({ connectionId: connectionSaveId, code: 'delete-connection', socket: socket, }) } } }src/service/socket.io/request/connection-disconnect.js000066400000000000000000000015621520166267200235320ustar00rootroot00000000000000const sharedIoRedis = require('../shared') const consolePrefix = 'socket.io connection disconnect' module.exports = async(options) => { const {socket, payload} = options; const { connectionId } = payload; console.warn(consolePrefix, 'connectionId', connectionId, 'socket.p3xrs.connectionId', socket.p3xrs.connectionId) try { if (socket.p3xrs.connectionId === connectionId) { console.warn(consolePrefix, 'will disconnect from redis') sharedIoRedis.disconnectRedis({ socket: socket, }) } socket.emit(options.responseEvent, { status: 'ok', }) } catch(e) { socket.emit(options.responseEvent, { status: 'error', error: error }) } finally { sharedIoRedis.sendStatus({ socket: socket, }) } }src/service/socket.io/request/connection-save.js000066400000000000000000000045121520166267200223350ustar00rootroot00000000000000const sharedIoRedis = require('../shared') module.exports = async(options) => { const { socket } = options; const connectionSave = options.payload.model; let disableReadonlyConnections = true try { sharedIoRedis.ensureReadonlyConnections() disableReadonlyConnections = false let connectionIndexExisting; for(let connectionIndex in p3xrs.connections.list) { const connection = p3xrs.connections.list[connectionIndex] if (connection.id === connectionSave.id) { connectionIndexExisting = connectionIndex break; } } p3xrs.connections.update = new Date() if (connectionIndexExisting !== undefined) { if (p3xrs.connections.list[connectionIndexExisting].id === connectionSave.password) { connectionSave.password = p3xrs.connections.list[connectionIndexExisting].password; } //TODO fix secured nodes password if (Array.isArray(connectionSave.nodes)) { for(let node of connectionSave.nodes) { const findNode = p3xrs.connections.list[connectionIndexExisting].nodes.find((findNode) => { return findNode.id === node.id && node.password === findNode.id }) if (findNode !== undefined) { node.password = findNode.password } } } p3xrs.connections.list[connectionIndexExisting] = connectionSave } else { p3xrs.connections.list.push(connectionSave) } const fs = require('fs') fs.writeFileSync(p3xrs.cfg.connections.home, JSON.stringify(p3xrs.connections, null, 4)) socket.emit(options.responseEvent, { status: 'ok', }) } catch (e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e }) } finally { if (!disableReadonlyConnections) { sharedIoRedis.sendConnections({ socket: socket, }) sharedIoRedis.triggerDisconnect({ connectionId: connectionSave.id, code: 'save-connection', socket: socket, }) } } } src/service/socket.io/request/console.js000066400000000000000000000034131520166267200207030ustar00rootroot00000000000000const consolePrefix = 'socket.io console call' module.exports = async(options) => { const { socket, payload } = options; const { command } = payload try { let redis = socket.p3xrs.ioredis const commands = command.trim().split(' ').filter(val => val.trim() !== '') let mainCommand = commands.shift() mainCommand = mainCommand.toLowerCase(); console.info(consolePrefix, mainCommand, commands) /* if (!socket.p3xrs.commands.includes(mainCommand)) { throw new Error(`ERR Unknown command '${mainCommand}'.`) } */ let result = await redis.call(mainCommand, commands) const defaultEmit = { } let generatedCommand = mainCommand if (commands.length > 0) { generatedCommand += ' ' + commands.join(' ') } switch(mainCommand) { case 'select': defaultEmit.database = parseInt(commands[0]) break; } /* switch (generatedCommand) { case 'client list': //result = result.split(' ') break; } */ //console.warn(consolePrefix, typeof result, result) /* try { const clone = JSON.parse(JSON.stringify(result)) console.warn(consolePrefix, typeof clone, clone) } catch(e) { console.warn(e) } */ socket.emit(options.responseEvent, Object.assign(defaultEmit, { status: 'ok', result: result, generatedCommand: generatedCommand, })) } catch(e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e, }) } }src/service/socket.io/request/delete.js000066400000000000000000000011731520166267200205040ustar00rootroot00000000000000const consolePrefix = 'socket.io del key' const sharedIoRedis = require('../shared') module.exports = async(options) => { const { socket, payload } = options; try { let redis = socket.p3xrs.ioredis console.info(consolePrefix, payload.key) await redis.del(payload.key) await sharedIoRedis.getFullInfoAndSendSocket({ redis: redis, responseEvent: options.responseEvent, socket: socket, }) } catch(e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e, }) } }src/service/socket.io/request/expire.js000066400000000000000000000010461520166267200205350ustar00rootroot00000000000000const consolePrefix = 'socket.io expire' module.exports = async(options) => { const { socket, payload } = options; try { let redis = socket.p3xrs.ioredis console.info(consolePrefix, payload.key, payload.ttl) await redis.expire(payload.key, parseInt(payload.ttl)) socket.emit(options.responseEvent, { status: 'ok', }) } catch(e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e, }) } }src/service/socket.io/request/key-del-tree.js000066400000000000000000000020111520166267200215210ustar00rootroot00000000000000const consolePrefix = 'socket.io key del tree' const sharedIoRedis = require('../shared') module.exports = async(options) => { const { socket, payload } = options; try { let redis = socket.p3xrs.ioredis const deleteTree = `${payload.key}${payload.redisTreeDivider}*`; console.info(consolePrefix, deleteTree) const keys = await sharedIoRedis.getStreamKeys({ redis: redis, match: deleteTree }) const pipelineDeleteTree = redis.pipeline() for(let key of keys) { console.info(consolePrefix, 'delete key ', key) pipelineDeleteTree.del(key) } await pipelineDeleteTree.exec(); await sharedIoRedis.getFullInfoAndSendSocket({ redis: redis, responseEvent: options.responseEvent, socket: socket, }) } catch(e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e, }) } }src/service/socket.io/request/key-get.js000066400000000000000000000042561520166267200206140ustar00rootroot00000000000000const consolePrefix = 'socket.io key get full' module.exports = async(options) => { const { socket, payload } = options; try { let redis = socket.p3xrs.ioredis const key = payload.key; //const type = payload.type; const type = await redis.type(key) //console.info(consolePrefix, payload, type, key) const viewPipeline = redis.pipeline() switch(type) { case 'string': viewPipeline.get(key) break; case 'list': viewPipeline.lrange(key, 0, -1) break; case 'hash': viewPipeline.hgetall(key) break; case 'set': viewPipeline.smembers(key) break; case 'zset': viewPipeline.zrange(key, 0, -1, 'WITHSCORES') break; } viewPipeline.ttl(key) viewPipeline.object('encoding', key) switch(type) { case 'hash': viewPipeline.hlen(key) break; case 'list': viewPipeline.llen(key) break; case 'set': viewPipeline.scard(key) break; case 'zset': viewPipeline.zcard(key) break; } const viewPipelineResult = await viewPipeline.exec() // console.log(viewPipelineResult) const value = viewPipelineResult[0][1] const ttl = viewPipelineResult[1][1] const encoding = viewPipelineResult[2][1] let length if (type !== 'string') { length = viewPipelineResult[3][1] } const socketResult = { length: length, key: key, status: 'ok', type: type, value: value, ttl: ttl, encoding: encoding, }; // console.warn('socketResult', socketResult) socket.emit(options.responseEvent, socketResult) } catch(e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e, }) } }src/service/socket.io/request/key-hash-delete-field.js000066400000000000000000000010441520166267200232710ustar00rootroot00000000000000const consolePrefix = 'socket.io key hash delete key' const utils = require('corifeus-utils') module.exports = async(options) => { const {socket, payload } = options; const redis = socket.p3xrs.ioredis try { const { hashKey, key } = payload; await redis.hdel(key, hashKey) socket.emit(options.responseEvent, { status: 'ok', }) } catch(e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e }) } }src/service/socket.io/request/key-list-delete-index.js000066400000000000000000000013211520166267200233430ustar00rootroot00000000000000const consolePrefix = 'socket.io key list delete index' const utils = require('corifeus-utils') module.exports = async(options) => { const {socket, payload } = options; const redis = socket.p3xrs.ioredis try { const { index, key } = payload; const uniqueValue = utils.random.complexUuid() console.log(consolePrefix, key, index, uniqueValue) await redis.lset(key, index, uniqueValue) await redis.lrem(key, 1, uniqueValue) socket.emit(options.responseEvent, { status: 'ok', }) } catch(e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e }) } }src/service/socket.io/request/key-new-or-set.js000066400000000000000000000052271520166267200220340ustar00rootroot00000000000000const consolePrefix = 'socket.io key new' const sharedIoRedis = require('../shared') module.exports = async(options) => { const {socket,payload } = options; const redis = socket.p3xrs.ioredis try { const { model } = payload; model.score = model.score === null ? undefined : model.score model.index = model.index === null ? undefined : model.index model.hashKey = model.hashKey === null ? undefined : model.hashKey //console.warn(consolePrefix, payload) switch(model.type) { case 'string': await redis.set(model.key, model.value) break; case 'list': if (model.index === undefined) { await redis.rpush(model.key, model.value) } else { if (model.index === -1) { await redis.lpush(model.key, model.value) } else { const size = await redis.llen(model.key); if (model.index > -1 && model.index < size) { await redis.lset(model.key, model.index, model.value) } else { const listOutOBoundsError = new Error('list-out-of-bounds') listOutOBoundsError.code = 'list-out-of-bounds' throw listOutOBoundsError } } } break; case 'hash': if (payload.hasOwnProperty('originalHashKey')) { await redis.hdel(model.key, payload.originalHashKey) } await redis.hset(model.key, model.hashKey, model.value) break; case 'set': if (payload.hasOwnProperty('originalValue')) { await redis.srem(model.key, payload.originalValue) } await redis.sadd(model.key, model.value) break; case 'zset': if (payload.hasOwnProperty('originalValue')) { await redis.zrem(model.key, payload.originalValue) } await redis.zadd(model.key, model.score, model.value) break; } await sharedIoRedis.getFullInfoAndSendSocket({ redis: redis, responseEvent: options.responseEvent, socket: socket, extend: { key: model.key } }) } catch(e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e }) } }src/service/socket.io/request/key-set-delete-member.js000066400000000000000000000010421520166267200233230ustar00rootroot00000000000000const consolePrefix = 'socket.io key list delete index' const utils = require('corifeus-utils') module.exports = async(options) => { const {socket, payload } = options; const redis = socket.p3xrs.ioredis try { const { key, value } = payload; await redis.srem(key, value) socket.emit(options.responseEvent, { status: 'ok', }) } catch(e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e }) } }src/service/socket.io/request/key-set.js000066400000000000000000000010521520166267200206170ustar00rootroot00000000000000module.exports = async(options) => { const {socket,payload } = options; const redis = socket.p3xrs.ioredis try { const ttl = await redis.ttl(payload.key) await redis.set(payload.key, payload.value) if (ttl !== -1) { await redis.expire(payload.key, ttl) } socket.emit(options.responseEvent, { status: 'ok', }) } catch(e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e }) } }src/service/socket.io/request/key-zset-delete-member.js000066400000000000000000000010461520166267200235210ustar00rootroot00000000000000const consolePrefix = 'socket.io key zsit delete member' module.exports = async(options) => { const {socket, payload } = options; const redis = socket.p3xrs.ioredis try { const { key, value } = payload; console.log(consolePrefix, payload) await redis.zrem(key, value) socket.emit(options.responseEvent, { status: 'ok', }) } catch(e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e }) } }src/service/socket.io/request/persist.js000066400000000000000000000007071520166267200207350ustar00rootroot00000000000000const consolePrefix = 'socket.io persists' module.exports = async(options) => { const { socket, payload } = options; try { let redis = socket.p3xrs.ioredis await redis.persist(payload.key) socket.emit(options.responseEvent, { status: 'ok', }) } catch(e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e, }) } }src/service/socket.io/request/redis-test-connection.js000066400000000000000000000036641520166267200234710ustar00rootroot00000000000000const Redis = require('../../../ioredis-cluster') module.exports = async(options) => { const { socket } = options; try { let redisConfig = options.payload.model; const actualConnection = p3xrs.connections.list.find(con => redisConfig.id === con.id) if (actualConnection !== undefined) { if (redisConfig.password === actualConnection.id) { redisConfig.password = actualConnection.password; } } //TODO fix secured nodes password delete redisConfig.name delete redisConfig.id if (redisConfig.cluster === true) { redisConfig.nodes = redisConfig.nodes.map((node) => { if (node.password === node.id) { const foundNode = actualConnection.nodes.find((findNode) => findNode.id === node.password) node.password = foundNode.password } return node }) redisConfig = [ redisConfig ].concat(redisConfig.nodes) } let redis = new Redis(redisConfig) redis.on('error', function(error) { console.error(error) socket.emit(options.responseEvent, { status: 'error', error: error }) redis.disconnect() }) redis.on('connect', async function() { try { await redis.call('client', 'list') socket.emit(options.responseEvent, { status: 'ok', }) } catch(error) { socket.emit(options.responseEvent, { status: 'error', error: error }) } finally { redis.disconnect() } }) } catch(e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e }) } } src/service/socket.io/request/refresh.js000066400000000000000000000011061520166267200206740ustar00rootroot00000000000000const sharedIoRedis = require('../shared') //const consolePrefix = 'socket.io refresh redis' module.exports = async(options) => { const {socket, payload } = options; const redis = socket.p3xrs.ioredis try { await sharedIoRedis.getFullInfoAndSendSocket({ redis: redis, responseEvent: options.responseEvent, socket: socket, payload: payload, }) } catch(e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e }) } }src/service/socket.io/request/rename.js000066400000000000000000000012211520166267200205030ustar00rootroot00000000000000const consolePrefix = 'socket.io rename key' const sharedIoRedis = require('../shared') module.exports = async(options) => { const { socket, payload } = options; try { let redis = socket.p3xrs.ioredis console.info(consolePrefix, payload.key) await redis.rename(payload.key, payload.keyNew) await sharedIoRedis.getFullInfoAndSendSocket({ redis: redis, responseEvent: options.responseEvent, socket: socket, }) } catch(e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e, }) } }src/service/socket.io/request/save.js000066400000000000000000000006501520166267200201770ustar00rootroot00000000000000module.exports = async(options) => { const {socket } = options; const redis = socket.p3xrs.ioredis try { await redis.save() socket.emit(options.responseEvent, { status: 'ok', info: await redis.info(), }) } catch(e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: e }) } }src/service/socket.io/request/trigger-redis-disconnect.js000066400000000000000000000011271520166267200241370ustar00rootroot00000000000000const sharedIoRedis = require('../shared') const consolePrefix = 'socket.io trigger redis disconnect' module.exports = async(options) => { const {socket } = options; try { console.warn(consolePrefix, 'socket.p3xrs.connectionId', socket.p3xrs.connectionId) sharedIoRedis.disconnectRedis({ socket: socket, }) socket.emit(options.responseEvent, { status: 'ok', }) } catch(e) { console.error(e) socket.emit(options.responseEvent, { status: 'error', error: error }) } }src/service/socket.io/shared.js000066400000000000000000000225751520166267200170310ustar00rootroot00000000000000const triggerDisconnect = (options) => { const { connectionId, code, socket } = options if (p3xrs.redisConnections.hasOwnProperty(connectionId)) { delete p3xrs.redisConnections[connectionId] socket.p3xrs.io.emit('redis-disconnected', { connectionId: connectionId, status: 'code', code: code }) sendStatus({ socket: socket }) } } const sendStatus = (options) => { const { socket } = options const redisConnections = {} Object.keys(p3xrs.redisConnections).forEach((redisConnectionKey) => { redisConnections[redisConnectionKey] = {} Object.keys(p3xrs.redisConnections[redisConnectionKey]).forEach(redisConnectionKey2 => { redisConnections[redisConnectionKey][redisConnectionKey2] = p3xrs.redisConnections[redisConnectionKey][redisConnectionKey2] }) }) socket.p3xrs.io.emit('redis-status', { redisConnections: redisConnections, }) } const consolePrefixDisconnectRedis = 'socket.io shared disconnect redis' const disconnectRedis = (options) => { const { socket } = options //console.warn(consolePrefixDisconnectRedis, `${socket.p3xrs.connectionId} !== ${connection.id}`) if (p3xrs.redisConnections.hasOwnProperty(socket.p3xrs.connectionId)) { console.warn(consolePrefixDisconnectRedis, `includes ${p3xrs.redisConnections[socket.p3xrs.connectionId].clients.includes(socket.id)} length === 1 ${p3xrs.redisConnections[socket.p3xrs.connectionId].clients.length}`) if (p3xrs.redisConnections[socket.p3xrs.connectionId].clients.includes(socket.id) && p3xrs.redisConnections[socket.p3xrs.connectionId].clients.length === 1) { //console.warn(consolePrefixDisconnectRedis, p3xrs.redisConnections[socket.p3xrs.connectionId]) //p3xrs.redisConnections[socket.p3xrs.connectionId].ioredis.disconnect() delete p3xrs.redisConnections[socket.p3xrs.connectionId] } else { let connectionIndexExisting = p3xrs.redisConnections[socket.p3xrs.connectionId].clients.indexOf(socket.id); console.warn(consolePrefixDisconnectRedis, socket.p3xrs.connectionId, p3xrs.redisConnections[socket.p3xrs.connectionId].clients, socket.id, connectionIndexExisting) if (connectionIndexExisting > -1) { p3xrs.redisConnections[socket.p3xrs.connectionId].clients.splice(connectionIndexExisting, 1) } } } if (p3xrs.redisConnections.hasOwnProperty(socket.p3xrs.connectionId) && p3xrs.redisConnections[socket.p3xrs.connectionId].hasOwnProperty('clients') && p3xrs.redisConnections[socket.p3xrs.connectionId].clients.length === 0) { delete p3xrs.redisConnections[socket.p3xrs.connectionId] } module.exports.disconnectRedisIo(options) socket.p3xrs.connectionId = undefined } const cloneDeep = require('lodash/cloneDeep') const sendConnections = (options) => { const { socket } = options const connections = cloneDeep(p3xrs.connections); let connectionsList = connections.list.map(connection => { delete connection.password //TODO fix secured nodes password if (Array.isArray(connection.nodes)) { connection.nodes = connection.nodes.map(node => { delete node.password return node }) } return connection }) connections.list = connectionsList socket.p3xrs.io.emit('connections', { status: 'ok', connections: connections }) } const disconnectRedisIo = (options) => { const { socket } = options console.warn('shared disconnectRedisIo', 'try') if (socket.p3xrs.ioredis !== undefined) { console.warn('shared disconnectRedisIo', 'executed') socket.p3xrs.ioredis.disconnect() socket.p3xrs.ioredisSubscriber.disconnect() socket.p3xrs.ioredis = undefined socket.p3xrs.ioredisSubscriber = undefined } } const getStreamKeys = (options) => { const { redis } = options let { dbsize } = options return new Promise(async (resolve, reject) => { try { if (dbsize === undefined) { dbsize = await redis.dbsize() } let count = 100 if (dbsize > 110000) { count = 10000 } else if (dbsize > 11000) { count = 1000 } console.warn('socket.io getStreamKeys dbsize', dbsize , 'count', count) const stream = redis.scanStream({ match: options.match, count: count }); let keys = []; stream.on('data', (resultKeys) => { keys = keys.concat(resultKeys); // console.log('loading keys', keys.length) }); stream.on('end', async () => { try { resolve(keys); } catch (e) { reject(e) } }); } catch (e) { reject(e) } }) } /* const getStreamTypedKeys = (options) => { const { redis, key, match } = options let { scan } = options if (scan === undefined) { scan = 'scanStream' } return new Promise((resolve, reject) => { let stream; if (scan === 'scanStream') { stream = redis[scan]({ match: match }); } else { stream = redis[scan](key, { match: match }); } let keys = []; stream.on('data', (resultKeys) => { keys = keys.concat(resultKeys); }); stream.on('end', async () => { try { resolve(keys); } catch (e) { console.error(e); reject(e) } }); }) } */ const getKeysInfo = async (options) => { const { redis, keys } = options; const keyTypePipeline = redis.pipeline() // const promises = []; for(let key of keys) { keyTypePipeline.type(key) // promises.push(redis.type(key)) } // const keysType = await Promise.all(promises); const keysType = await keyTypePipeline.exec(); const result = {} const complexLengthPipeline = redis.pipeline() for (let keysIndex in keys) { const keyType = keysType[keysIndex] const key = keys[keysIndex] const obj = { type: keyType[1 ] } switch(obj.type) { case 'hash': complexLengthPipeline.hlen(key) break; case 'list': complexLengthPipeline.llen(key) break; case 'set': complexLengthPipeline.scard(key) break; case 'zset': complexLengthPipeline.zcard(key) break; } result[key] = obj } const lengthsPipeline = await complexLengthPipeline.exec() for (let keysIndex in keys) { const key = keys[keysIndex] const obj = result[key] if (obj.type === 'string') { continue } const lengthPipelineElement = lengthsPipeline.shift() obj.length = lengthPipelineElement[1] } return result; } const ensureReadonlyConnections = () => { if (p3xrs.cfg.readonlyConnections === true) { const errorCode = new Error('Connections add/save/delete are readonly only') errorCode.code = 'readonly-connections' throw errorCode; } } const getFullInfo = async (options) => { const { redis } = options; let { payload } = options if (payload === undefined) { payload = {} } const dbsize = await redis.dbsize() const results = await Promise.all([ redis.info(), getStreamKeys({ dbsize: dbsize, redis: redis, match: payload.match, }), redis.pubsub('channels', '*'), redis.infoObject(), ]) const keys = results[1] let keysInfo = {} if (keys.length < 110000) { keysInfo = await getKeysInfo({ redis: redis, keys: keys, }) } // const keysInfo = [] const result = { info: results[0], infoObject: results[3], keys: keys, keysInfo: keysInfo, dbsize: dbsize, channels: results[2] } //console.log('get full info', result) return result } const getFullInfoAndSendSocket = async (options) => { const { redis, socket, payload} = options const result = await getFullInfo({ redis: redis, payload: payload, }) let { extend } = options if (extend === undefined) { extend = {} } socket.emit(options.responseEvent, Object.assign(extend, { status: 'ok', info: result.info, infoObject: result.infoObject, keys: result.keys, keysInfo: result.keysInfo, dbsize: result.dbsize, })) } module.exports.ensureReadonlyConnections = ensureReadonlyConnections module.exports.triggerDisconnect = triggerDisconnect module.exports.getStreamKeys = getStreamKeys module.exports.disconnectRedisIo = disconnectRedisIo module.exports.sendConnections = sendConnections module.exports.sendStatus = sendStatus module.exports.disconnectRedis = disconnectRedis module.exports.getKeysInfo = getKeysInfo module.exports.getFullInfo = getFullInfo module.exports.getFullInfoAndSendSocket = getFullInfoAndSendSocket src/service/socket.io/socket.js000066400000000000000000000043011520166267200170360ustar00rootroot00000000000000const socketIoShared = require('./shared') module.exports = (io) => { io.on('connect', function (socket) { //const token = socket.handshake.query.token; socket.p3xrs = { address: socket.handshake.headers.origin, connectedAt: new Date(), connectionId: undefined, io: io, ioredis: undefined, ioredisSubscriber: undefined, // commands: undefined, } console.info('socket.io connected %s', socket.id); socket.on('disconnect', function () { console.warn('socket.p3xrs.connectionId', socket.p3xrs.connectionId) if (socket.p3xrs.connectionId !== undefined) { const connectionId = socket.p3xrs.connectionId; if (p3xrs.redisConnections.hasOwnProperty(connectionId)) { const redisConnectionIndex = p3xrs.redisConnections[connectionId].clients.indexOf(socket.id); if (redisConnectionIndex !== -1) { p3xrs.redisConnections[connectionId].clients.splice(redisConnectionIndex, 1); } if (p3xrs.redisConnections[connectionId].clients.length === 0) { delete p3xrs.redisConnections[connectionId] } socketIoShared.disconnectRedisIo({ socket: socket, }) } } // Call on disconnect. console.info('socket.io disconnected %s', socket.id); socketIoShared.sendStatus({ socket: socket, }) }); socket.on('p3xr-request', (options) => { options.socket = socket; options.responseEvent = `p3xr-response-${options.requestId}` require(`./request/${options.action}`)(options) }) socket.emit('configuration', { readonlyConnections: p3xrs.cfg.readonlyConnections === true, snapshot: p3xrs.cfg.snapshot === true }) socketIoShared.sendStatus({ socket: socket, }) socketIoShared.sendConnections({ socket: socket, }) }); }