RSS Git Download  Clone
Raw Blame History 7kB 201 lines
import { Injectable } from '@angular/core';

/**
 * Angular service that mirrors the AngularJS p3xrRedisParser factory.
 * Pure logic — no AngularJS dependencies. During hybrid mode, both this
 * service and the AngularJS factory coexist. Future Angular components
 * inject this service; existing AngularJS components keep using the factory.
 */
@Injectable({ providedIn: 'root' })
export class RedisParserService {

    /**
     * Parses a key=value line into an object.
     * e.g. "keys=10,expires=5" → { keys: "10", expires: "5" }
     */
    array(options: { line: string; divider?: string; fieldDivider?: string }): Record<string, string> {
        const { line } = options;
        const divider = options.divider ?? ',';
        const fieldDivider = options.fieldDivider ?? '=';
        const rows = line.split(divider);
        const obj: Record<string, string> = {};
        for (const row of rows) {
            const rowLine = row.split(fieldDivider);
            const rowLineData = rowLine[1] ?? '';
            obj[rowLine[0]] = rowLineData.trim();
        }
        return obj;
    }

    /**
     * Parses Redis INFO command output into a nested object grouped by section.
     */
    info(str: string): any {
        const lines = str.split('\n');
        const obj: any = {};
        let section: string | undefined;
        let currentSectionObj: any = {};
        let hadSection = false;
        let pikaIndex = 0;

        for (const line of lines) {
            if (line.startsWith('#')) {
                if (hadSection) {
                    continue;
                }
                hadSection = true;
                if (section !== undefined) {
                    obj[section] = currentSectionObj;
                }
                section = line.substring(1).toLowerCase().trim();
                currentSectionObj = {};
            } else if (line.length > 2) {
                hadSection = false;
                if (line.includes(':')) {
                    const lineArray = line.split(':');
                    const value = lineArray[1] ?? '';
                    currentSectionObj[lineArray[0]] = value.includes(',')
                        ? this.array({ line: value.trim() })
                        : value.trim();
                } else {
                    // pika format
                    const [key, ...rest] = line.split(/ (.+)/);
                    const values = rest[0] ?? '';
                    const value = values
                        .split(',')
                        .map((item: string) => `${pikaIndex}-${item.trim()}`)
                        .join(',');

                    if (currentSectionObj.hasOwnProperty('db0')) {
                        Object.assign(
                            currentSectionObj['db0'],
                            value.includes(',') ? this.array({ line: value.trim() }) : value.trim()
                        );
                    } else {
                        currentSectionObj['db0'] = value.includes(',')
                            ? this.array({ line: value.trim() })
                            : value.trim();
                    }
                    pikaIndex++;
                }
            }
        }
        if (section !== undefined && Object.keys(currentSectionObj).length > 0) {
            obj[section] = currentSectionObj;
        }

        obj.keyspaceDatabases = {};
        if (obj.hasOwnProperty('keyspace')) {
            Object.keys(obj.keyspace).forEach((key) => {
                const dbIndex = parseInt(key.substring(2));
                obj.keyspaceDatabases[dbIndex] = true;
            });
        }

        return obj;
    }

    /**
     * Converts a flat list of Redis keys into a hierarchical tree structure.
     * Used by the tree control to display keys grouped by divider (default ':').
     */
    keysToTreeControl(options: {
        keys: string[];
        divider?: string;
        keysInfo?: any;
        savedExpandedNodes?: any[];
    }): { nodes: any[]; expandedNodes: any[] } {
        const { keys } = options;
        const divider = options.divider ?? ':';
        const keysInfo = options.keysInfo ?? {};
        const savedExpandedNodes = options.savedExpandedNodes ?? [];

        const mainNodes: any[] = [];
        const newExpandedNodes: any[] = [];

        const recursiveNodes = (splitKey: string[], level: number = 0, nodes: any[] = mainNodes) => {
            let foundNode: any = false;
            if (level + 1 < splitKey.length) {
                for (const node of nodes) {
                    if (node.label === splitKey[level] && node.type === 'folder') {
                        foundNode = node;
                    }
                }
            }
            if (!foundNode) {
                const defaultFoundNode: any = {
                    label: splitKey[level],
                    key: splitKey.slice(0, level + 1).join(divider),
                    children: [],
                    childCount: 0,
                    type: level + 1 === splitKey.length ? 'element' : 'folder',
                };
                if (defaultFoundNode.type === 'element') {
                    defaultFoundNode.keysInfo = keysInfo[defaultFoundNode.key];
                }
                nodes.push(defaultFoundNode);
                foundNode = defaultFoundNode;

                for (const saveExpandedNode of savedExpandedNodes) {
                    if (saveExpandedNode.key === foundNode.key) {
                        newExpandedNodes.push(foundNode);
                    }
                }
            }

            if (level + 1 < splitKey.length) {
                recursiveNodes(splitKey, level + 1, foundNode.children);
            }
        };

        for (const key of keys) {
            const splitkey = divider === '' ? [key] : key.split(divider);
            recursiveNodes(splitkey);
        }

        const recursiveKeyCount = (node: any) => {
            node.childCount = 0;
            for (const child of node.children) {
                if (child.type === 'element') {
                    const info = child.keysInfo;
                    if (info && info.type !== 'string' && info.type !== 'json' && info.length != null) {
                        node.childCount += info.length;
                    } else {
                        node.childCount += 1;
                    }
                }
            }
            for (const child of node.children) {
                recursiveKeyCount(child);
                if (child.type === 'folder') {
                    node.childCount += child.childCount;
                }
            }
        };

        for (const node of mainNodes) {
            recursiveKeyCount(node);
        }

        return { nodes: mainNodes, expandedNodes: newExpandedNodes };
    }

    /**
     * Parses console command response into a display string.
     */
    consoleParse(responseResult: any): string {
        if (responseResult !== null && typeof responseResult === 'object') {
            let result = '';
            Object.keys(responseResult).forEach((key) => {
                if (result !== '') {
                    result += '\n';
                }
                result += responseResult[key];
            });
            return result;
        } else {
            return responseResult;
        }
    }
}