p3x-redis-ui-server 2026.4.310 → 2026.4.311
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/README.md +2 -2
- package/artifacts/cluster.md +1 -1
- package/dist/service/http/index.mjs +1 -1
- package/dist/service/socket.io/request/ai-redis-query.mjs +1 -1
- package/dist/service/socket.io/request/memory-analysis.mjs +1 -0
- package/dist/service/socket.io/request/set-monitor.mjs +1 -1
- package/dist/service/socket.io/request/set-subscription.mjs +1 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -96,7 +96,7 @@ All my domains, including [patrikx3.com](https://patrikx3.com), [corifeus.eu](ht
|
|
|
96
96
|
---
|
|
97
97
|
|
|
98
98
|
|
|
99
|
-
[**P3X-REDIS-UI-SERVER**](https://corifeus.com/redis-ui-server) Build v2026.4.
|
|
99
|
+
[**P3X-REDIS-UI-SERVER**](https://corifeus.com/redis-ui-server) Build v2026.4.311
|
|
100
100
|
|
|
101
101
|
[](https://www.npmjs.com/package/p3x-redis-ui-server) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QZVM4V6HVZJW6) [](https://www.patrikx3.com/en/front/contact) [](https://www.facebook.com/corifeus.software)
|
|
102
102
|
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ https://corifeus.com/redis-ui
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
---
|
|
15
|
-
# 🏍️ The p3x-redis-ui-server package motor that is connected to the p3x-redis-ui-material web user interface v2026.4.
|
|
15
|
+
# 🏍️ The p3x-redis-ui-server package motor that is connected to the p3x-redis-ui-material web user interface v2026.4.311
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
|
|
@@ -196,7 +196,7 @@ All my domains, including [patrikx3.com](https://patrikx3.com), [corifeus.eu](ht
|
|
|
196
196
|
---
|
|
197
197
|
|
|
198
198
|
|
|
199
|
-
[**P3X-REDIS-UI-SERVER**](https://corifeus.com/redis-ui-server) Build v2026.4.
|
|
199
|
+
[**P3X-REDIS-UI-SERVER**](https://corifeus.com/redis-ui-server) Build v2026.4.311
|
|
200
200
|
|
|
201
201
|
[](https://www.npmjs.com/package/p3x-redis-ui-server) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QZVM4V6HVZJW6) [](https://www.patrikx3.com/en/front/contact) [](https://www.facebook.com/corifeus.software)
|
|
202
202
|
|
package/artifacts/cluster.md
CHANGED
|
@@ -55,7 +55,7 @@ All my domains, including [patrikx3.com](https://patrikx3.com), [corifeus.eu](ht
|
|
|
55
55
|
---
|
|
56
56
|
|
|
57
57
|
|
|
58
|
-
[**P3X-REDIS-UI-SERVER**](https://corifeus.com/redis-ui-server) Build v2026.4.
|
|
58
|
+
[**P3X-REDIS-UI-SERVER**](https://corifeus.com/redis-ui-server) Build v2026.4.311
|
|
59
59
|
|
|
60
60
|
[](https://www.npmjs.com/package/p3x-redis-ui-server) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QZVM4V6HVZJW6) [](https://www.patrikx3.com/en/front/contact) [](https://www.facebook.com/corifeus.software)
|
|
61
61
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import t from"express";import e from"fs";import r from"path";import s from"http";import{fileURLToPath as o}from"url";import{resolveConfiguredHttpAuth as i,verifyAuthorizationHeader as n}from"../../lib/http-auth.mjs";const p=r.dirname(o(import.meta.url));export default function(){this.boot=async()=>{const o=t();this.app=o,o.disable("x-powered-by"),o.get("/health",(t,e)=>{
|
|
1
|
+
import t from"express";import e from"fs";import r from"path";import s from"http";import{fileURLToPath as o}from"url";import{resolveConfiguredHttpAuth as i,verifyAuthorizationHeader as n}from"../../lib/http-auth.mjs";const p=r.dirname(o(import.meta.url));export default function(){this.boot=async()=>{const o=t();this.app=o,o.disable("x-powered-by"),o.get("/health",(t,e)=>{e.json({status:"ok",version:p3xrs.cfg?.version||"unknown",uptime:process.uptime()})}),o.use((t,e,r)=>{if(!i().enabled)return void r();const s=t.get("authorization");n(s)?r():(e.set("WWW-Authenticate",'Basic realm="P3X Redis UI"'),e.status(401).json({error:"http_auth_required"}))});let a,u=!1;"string"==typeof p3xrs.cfg.static&&(u=!0,a=(t=>{if(t.startsWith("~")){return((t,s)=>{let o=p;for(;o!==r.resolve(o,"..");){const t=r.join(o,s);if(e.existsSync(t))return t;o=r.resolve(o,"..")}throw new Error("The specified module could not be found in any node_modules directory")})(0,t.substring(1))}return r.resolve(process.cwd(),t)})(p3xrs.cfg.static),o.use(t.static(a))),u?o.use((t,e,s)=>{t.path.startsWith("/socket.io")?s():e.sendFile(r.resolve(a,"index.html"),t=>{t&&s(t)})}):o.use((t,e)=>{e.json({status:"operational"})}),o.use((t,e,r,s)=>{console.error("express server error",t),r.headersSent?s(t):r.status(500).json({error:"internal_server_error"})});const c=s.createServer(o);this.server=c,c.listen(p3xrs.cfg.http.port||7843,p3xrs.cfg.http.bind?p3xrs.cfg.http.bind:"0.0.0.0")}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"groq-sdk";export default async n=>{const{socket:s,payload:t}=n;try{const{prompt:
|
|
1
|
+
import e from"groq-sdk";export default async n=>{const{socket:s,payload:t}=n;try{const{prompt:i,context:r}=t;if(!i||"string"!=typeof i||0===i.trim().length)throw new Error("AI_PROMPT_REQUIRED");if(!1===p3xrs.cfg.aiEnabled)throw new Error("AI_DISABLED");const a=p3xrs.cfg.groqApiKey||"";let o;!0===p3xrs.cfg.aiUseOwnKey&&a?(console.info("ai-redis-query: using direct Groq API (own key)"),o=await async function(n,s,t){const i=new e({apiKey:t}),r=function(e){let n='You are an expert Redis command generator embedded in a Redis GUI console. Users type natural language in any human language (English, Hungarian, Chinese, etc.) and you translate it into valid Redis CLI commands.\n\n# Output Format\nOne or more Redis commands (one per line), then a separator, then an explanation:\n\n```\nCOMMAND1\nCOMMAND2\n---\nBrief explanation in the user\'s language\n```\n\n- For simple requests: output a single command line\n- For complex requests needing multiple steps: output multiple command lines (one per line)\n- For bulk operations: prefer a single EVAL script, but use multiple commands if clearer\n- The --- separator is REQUIRED between commands and explanation\n- The explanation should be in the SAME LANGUAGE as the user\'s input\n\n# Core Principles\n1. Generate ONLY real, valid Redis commands that a Redis server will accept\n2. Never invent key names, index names, or field names \u2014 use only what is provided in context or use wildcard patterns\n3. The user\'s Redis GUI will execute your command directly \u2014 it must be syntactically correct\n4. Support all human languages as input \u2014 always output a Redis command regardless of input language\n\n# Command Selection Guide\n\n## Key Discovery & Listing\n- "show all keys" / "list keys" \u2192 KEYS *\n- "find keys matching user" \u2192 KEYS user:*\n- "keys starting with session" \u2192 KEYS session:*\n- "how many keys" \u2192 DBSIZE\n\n## Key Type Filtering\nWhen user asks for keys of a specific data type, use SCAN with TYPE filter:\n- "show all hash keys" \u2192 SCAN 0 MATCH * TYPE hash COUNT 10000\n- "show all json keys" / "rejson keys" \u2192 SCAN 0 MATCH * TYPE ReJSON-RL COUNT 10000\n- "show all set keys" \u2192 SCAN 0 MATCH * TYPE set COUNT 10000\n- "show all list keys" \u2192 SCAN 0 MATCH * TYPE list COUNT 10000\n- "show all string keys" \u2192 SCAN 0 MATCH * TYPE string COUNT 10000\n- "show all stream keys" \u2192 SCAN 0 MATCH * TYPE stream COUNT 10000\n- "show all sorted set keys" \u2192 SCAN 0 MATCH * TYPE zset COUNT 10000\n- For checking a single key\'s type \u2192 TYPE keyname\nNote: SCAN returns [cursor, [keys...]]. cursor=0 means scan complete.\n\n## Reading Values\n- String: GET key\n- Hash: HGETALL key | HGET key field\n- List: LRANGE key 0 -1\n- Set: SMEMBERS key\n- Sorted Set: ZRANGE key 0 -1 WITHSCORES\n- Stream: XRANGE key - +\n- JSON/ReJSON: JSON.GET key $ | JSON.GET key $.fieldname\n- Multiple strings: MGET key1 key2\n- Multiple JSON: JSON.MGET key1 key2 $\n\n## Writing Values\n- String: SET key value [EX seconds]\n- Hash: HSET key field value [field value ...]\n- List: LPUSH/RPUSH key value [value ...]\n- Set: SADD key member [member ...]\n- Sorted Set: ZADD key score member [score member ...]\n- Stream: XADD key * field value [field value ...]\n- JSON: JSON.SET key $ \'jsonvalue\'\n\n## Key Management\n- Delete: DEL key [key ...]\n- Rename: RENAME key newkey\n- TTL check: TTL key | PTTL key\n- Set expiry: EXPIRE key seconds | PEXPIRE key ms\n- Persist (remove TTL): PERSIST key\n- Check existence: EXISTS key [key ...]\n\n## Server & Info\n- Server info: INFO [section] (sections: server, clients, memory, stats, replication, cpu, modules, keyspace, all)\n- Memory usage: MEMORY USAGE key | INFO memory\n- Connected clients: CLIENT LIST\n- Config: CONFIG GET parameter\n- Slow log: SLOWLOG GET [count]\n- Database size: DBSIZE\n- Flush database: FLUSHDB\n- Flush all: FLUSHALL\n- Last save: LASTSAVE\n- Server time: TIME\n\n## RediSearch (only when explicitly requested)\n- Search: FT.SEARCH indexname query\n- List indexes: FT._LIST\n- Index info: FT.INFO indexname\n- Aggregate: FT.AGGREGATE indexname query\n- Create index: FT.CREATE indexname ON HASH PREFIX 1 prefix: SCHEMA field TYPE ...\n- Drop index: FT.DROPINDEX indexname\n\n## Pub/Sub\n- Publish: PUBLISH channel message\n- Subscribe: SUBSCRIBE channel\n\n## Cluster\n- Cluster info: CLUSTER INFO\n- Cluster nodes: CLUSTER NODES\n\n## Multi-step operations \u2014 PREFER multiple commands over EVAL\nWhen the user needs multiple Redis operations, output them as separate commands (one per line):\n- SET test:str hello\n- HSET test:hash f1 v1 f2 v2\n- RPUSH test:list a b c\nThis is ALWAYS preferred over EVAL unless a loop is needed.\n\n## Scripting (EVAL) \u2014 ONLY for loops or atomic operations\nUse EVAL ONLY when a loop or atomicity is required (e.g. "generate 100 random keys"):\n- EVAL "lua_script" numkeys [key ...] [arg ...]\n- Write Lua code with REAL line breaks inside the quotes \u2014 the console supports multi-line input\n- NEVER use literal \\n escape sequences \u2014 they cause Redis script compilation errors\n- CORRECT example:\nEVAL "\nfor i=1,3 do\n redis.call(\'SET\',\'k\'..i,i)\nend\nreturn \'done\'\n" 0\n- WRONG: EVAL "for i=1,3 do\\nredis.call(\'SET\',\'k\'..i,i)\\nend" 0\n\n# Redis Type Names (for TYPE command responses)\n- string, list, set, zset, hash, stream, ReJSON-RL\n\n# Critical Rules\n- NEVER use FT.SEARCH or FT.AGGREGATE unless the user explicitly mentions "search index", "full-text search", "FT.", or "RediSearch"\n- NEVER fabricate key names \u2014 if unsure, use patterns like KEYS * or KEYS prefix:*\n- NEVER fabricate index names \u2014 if indexes are provided in context, use those exact names\n- When the user mentions "rejson", "json keys", or "JSON type", they mean keys stored with the RedisJSON module\n- Prefer simple commands \u2014 KEYS over SCAN for readability in a GUI console\n- If the user asks something that needs multiple steps, output multiple commands (one per line)';if(e){const s=[];e.redisVersion&&s.push(`Redis version: ${e.redisVersion}`),e.redisMode&&s.push(`Mode: ${e.redisMode}`),e.usedMemory&&s.push(`Memory: ${e.usedMemory}`),e.connectedClients&&s.push(`Clients: ${e.connectedClients}`),e.os&&s.push(`OS: ${e.os}`),e.modules&&s.push(`Loaded modules: ${JSON.stringify(e.modules)}`),e.databases&&e.databases.length>0&&s.push(`Databases: ${e.databases.join(", ")}`),s.length>0&&(n+=`\n\n# Connected Redis Server\n${s.join("\n")}`),e.indexes&&e.indexes.length>0&&(n+=`\n\nAvailable RediSearch indexes: ${e.indexes.join(", ")}`),e.schema&&(n+=`\n\nSchema information: ${JSON.stringify(e.schema)}`),e.uiLanguage&&(n+="\n\n# Response Language\nYou MUST write the explanation (line 2) in the SAME language as the user's prompt. If they write in Hungarian, respond in Hungarian. If in English, respond in English. Always match the user's language.")}return n}(s),a=await i.chat.completions.create({messages:[{role:"system",content:r},{role:"user",content:n}],model:"openai/gpt-oss-120b",max_tokens:4096,temperature:.1});return function(e){const n=e.indexOf("\n---");if(-1!==n)return{command:e.substring(0,n).trim(),explanation:e.substring(n).replace(/^[\n\r]*---[\n\r]*/,"").trim()};const s=e.split("\n").filter(e=>e.trim().length>0);return{command:s[0]||"",explanation:s.slice(1).join(" ")||""}}(a.choices?.[0]?.message?.content?.trim()||"")}(i.trim(),r,a)):(console.info("ai-redis-query: using network proxy"),o=await async function(e,n,s){const t="string"==typeof p3xrs.cfg.aiNetworkUrl&&p3xrs.cfg.aiNetworkUrl.length>0?p3xrs.cfg.aiNetworkUrl:"development"===process.env.NODE_ENV?"http://localhost:8003":"https://network.corifeus.com";let i;try{i=await fetch(`${t}/public/ai/redis-query`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({prompt:e,context:n||{},apiKey:s||void 0})})}catch(e){throw new Error("AI service is not reachable")}if(!(i.headers.get("content-type")||"").includes("application/json"))throw new Error(`AI service returned invalid response (${i.status})`);const r=await i.json();if("ok"!==r.status)throw new Error(r.message||"AI query failed");return{command:r.data.command,explanation:r.data.explanation}}(i.trim(),r,a||void 0)),s.emit(n.responseEvent,{status:"ok",command:o.command,explanation:o.explanation})}catch(e){console.error("ai-redis-query error",e),s.emit(n.responseEvent,{status:"error",error:e.message})}};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default async e=>{const{socket:t,payload:o}=e;try{const s=t.p3xrs.ioredis;if(!s)return void t.emit(e.responseEvent,{status:"error",error:"Not connected to Redis"});const n=o.maxScanKeys||5e3,r=o.topN||20,a=500,i=await s.dbsize();let m="0";const u=[];do{const[e,t]=await s.scan(m,"COUNT",500);if(m=e,u.push(...t),u.length>=n)break}while("0"!==m);const c={},d={},p={},l=[];let y=0,_=0,f=0;for(let e=0;e<u.length;e+=a){const t=u.slice(e,e+a),o=s.pipeline();for(const e of t)o.type(e),o.call("MEMORY","USAGE",e),o.ttl(e);const n=await o.exec();for(let e=0;e<t.length;e++){const o=t[e],s=n[3*e][0],r=n[3*e][1]||"unknown",a=n[3*e+1][0],i=n[3*e+1][1],m=n[3*e+2][0],u=n[3*e+2][1],h=s?"unknown":r,k=a||"number"!=typeof i?0:i;c[h]=(c[h]||0)+1,d[h]=(d[h]||0)+k;const v=o.indexOf(":"),b=v>0?o.substring(0,v+1):"(no prefix)";p[b]||(p[b]={keyCount:0,totalBytes:0}),p[b].keyCount++,p[b].totalBytes+=k,m||"number"!=typeof u?_++:u>=0?(y++,f+=u):_++,k>0&&l.push({key:o,bytes:k,type:h})}}l.sort((e,t)=>t.bytes-e.bytes);const h=l.slice(0,r),k=Object.entries(p).map(([e,t])=>({prefix:e,...t})).sort((e,t)=>t.totalBytes-e.totalBytes).slice(0,50),v=await s.info("server"),b={},w={redis_version:"version",redis_mode:"mode",uptime_in_seconds:"uptime"};for(const e of v.split("\r\n")){const[t,o]=e.split(":");t&&w[t]&&(b[w[t]]="uptime_in_seconds"===t?parseInt(o)||0:o||"unknown")}b.mode||(b.mode="standalone");const g=await s.info("memory"),x={},N={used_memory:"used",used_memory_human:"usedHuman",used_memory_rss:"rss",used_memory_rss_human:"rssHuman",used_memory_peak:"peak",used_memory_peak_human:"peakHuman",used_memory_lua:"lua",used_memory_overhead:"overhead",used_memory_dataset:"dataset",mem_fragmentation_ratio:"fragRatio",mem_allocator:"allocator"};for(const e of g.split("\r\n")){const[t,o]=e.split(":");t&&N[t]&&(x[N[t]]=isNaN(o)?o:Number(o))}t.emit(e.responseEvent,{status:"ok",data:{totalScanned:u.length,dbSize:i,typeDistribution:c,typeMemory:d,prefixMemory:k,topKeys:h,expirationOverview:{withTTL:y,persistent:_,avgTTL:y>0?Math.round(f/y):0},memoryInfo:x,serverInfo:b}})}catch(o){console.error(o),t.emit(e.responseEvent,{status:"error",error:o.message})}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export default async o=>{const{socket:r,payload:e}=o;try{if(e.enabled){if(!r.p3xrs.ioredisMonitor){const o=r.p3xrs.ioredis;let e;for(let r=0;r<3;r++)try{e=await o.monitor();break}catch(o){if(2===r)throw o;await new Promise(o=>setTimeout(o,200))}r.p3xrs.ioredisMonitor=e,r.p3xrs.ioredisMonitor.on("monitor",(o,e,t,i)=>{r.emit("monitor-data",{time:o,args:e,source:t,database:i})}),console.info("MONITOR started for",r.id)}}else r.p3xrs.ioredisMonitor&&(r.p3xrs.ioredisMonitor.disconnect(),r.p3xrs.ioredisMonitor=void 0,console.info("MONITOR stopped for",r.id));r.emit(o.responseEvent,{status:"ok"})}catch(e){console.error("Monitor error:",e),r.emit(o.responseEvent,{status:"error",error:e.message})}};
|
|
1
|
+
export default async o=>{const{socket:r,payload:e}=o;try{if(!r.p3xrs.ioredis)return void r.emit(o.responseEvent,{status:"error",error:"Not connected to Redis"});if(e.enabled){if(!r.p3xrs.ioredisMonitor){const o=r.p3xrs.ioredis;let e;for(let r=0;r<3;r++)try{e=await o.monitor();break}catch(o){if(2===r)throw o;await new Promise(o=>setTimeout(o,200))}r.p3xrs.ioredisMonitor=e,r.p3xrs.ioredisMonitor.on("monitor",(o,e,t,i)=>{r.emit("monitor-data",{time:o,args:e,source:t,database:i})}),console.info("MONITOR started for",r.id)}}else r.p3xrs.ioredisMonitor&&(r.p3xrs.ioredisMonitor.disconnect(),r.p3xrs.ioredisMonitor=void 0,console.info("MONITOR stopped for",r.id));r.emit(o.responseEvent,{status:"ok"})}catch(e){console.error("Monitor error:",e),r.emit(o.responseEvent,{status:"error",error:e.message})}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export default async s=>{const{socket:r,payload:e}=s;try{await r.p3xrs.ioredisSubscriber.punsubscribe(),r.p3xrs.ioredisSubscriber.removeAllListeners("pmessage"),r.p3xrs.subscription=e.subscription,"string"==typeof e.subscriberPattern&&0!==e.subscriberPattern.trim().length||(e.subscriberPattern="*"),!0===r.p3xrs.subscription&&(await r.p3xrs.ioredisSubscriber.psubscribe(e.subscriberPattern),r.p3xrs.ioredisSubscriber.on("pmessage",(s,e,i)=>{console.log("socket.p3xrs.ioredisSubscriber.on(pmessage)",s,e,i),r.emit("pubsub-message",{channel:e,message:i})})),r.emit(s.responseEvent,{status:"ok"})}catch(e){console.error("Subscription error:",e),r.emit(s.responseEvent,{status:"error",error:e.message})}};
|
|
1
|
+
export default async s=>{const{socket:r,payload:e}=s;try{if(!r.p3xrs.ioredisSubscriber)return void r.emit(s.responseEvent,{status:"error",error:"Not connected to Redis"});await r.p3xrs.ioredisSubscriber.punsubscribe(),r.p3xrs.ioredisSubscriber.removeAllListeners("pmessage"),r.p3xrs.subscription=e.subscription,"string"==typeof e.subscriberPattern&&0!==e.subscriberPattern.trim().length||(e.subscriberPattern="*"),!0===r.p3xrs.subscription&&(await r.p3xrs.ioredisSubscriber.psubscribe(e.subscriberPattern),r.p3xrs.ioredisSubscriber.on("pmessage",(s,e,i)=>{console.log("socket.p3xrs.ioredisSubscriber.on(pmessage)",s,e,i),r.emit("pubsub-message",{channel:e,message:i})})),r.emit(s.responseEvent,{status:"ok"})}catch(e){console.error("Subscription error:",e),r.emit(s.responseEvent,{status:"error",error:e.message})}};
|
package/package.json
CHANGED