RSS Git Download  Clone
Raw Blame History 10kB 284 lines
import { Component, Inject, OnInit, OnChanges, OnDestroy, SimpleChanges, ChangeDetectorRef, CUSTOM_ELEMENTS_SCHEMA, ViewEncapsulation } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatListModule } from '@angular/material/list';
import { MatDividerModule } from '@angular/material/divider';
import { BreakpointObserver } from '@angular/cdk/layout';
import { I18nService } from '../../../services/i18n.service';
import { SocketService } from '../../../services/socket.service';
import { CommonService } from '../../../services/common.service';
import { JsonViewDialogService } from '../../../dialogs/json-view-dialog.service';
import { KeyNewOrSetDialogService } from '../../../dialogs/key-new-or-set-dialog.service';
import { MainCommandService } from '../../../services/main-command.service';
import { RedisStateService } from '../../../services/redis-state.service';
import { SettingsService } from '../../../services/settings.service';
import { KeyTypeBase } from './key-type-base';
import { P3xrAccordionComponent } from '../../../components/p3xr-accordion.component';
import { P3xrButtonComponent } from '../../../components/p3xr-button.component';

@Component({
    selector: 'p3xr-key-probabilistic',
    standalone: true,
    imports: [
        CommonModule, FormsModule,
        MatButtonModule, MatIconModule, MatTooltipModule,
        MatFormFieldModule, MatInputModule,
        MatListModule, MatDividerModule,
        P3xrAccordionComponent, P3xrButtonComponent,
    ],
    schemas: [CUSTOM_ELEMENTS_SCHEMA],
    templateUrl: './key-probabilistic.component.html',
    encapsulation: ViewEncapsulation.None,
})
export class KeyProbabilisticComponent extends KeyTypeBase implements OnInit, OnChanges, OnDestroy {

    infoItems: Array<{ key: string; value: any }> = [];
    topkItems: Array<{ item: string; count: number }> = [];

    itemInput = '';
    incrementInput = 1;
    quantileInput = 0.5;

    autoRefresh = false;
    private autoRefreshInterval: any = null;
    readonly = false;

    constructor(
        @Inject(I18nService) i18n: I18nService,
        @Inject(SocketService) socket: SocketService,
        @Inject(CommonService) common: CommonService,
        @Inject(JsonViewDialogService) jsonViewDialog: JsonViewDialogService,
        @Inject(KeyNewOrSetDialogService) keyNewOrSetDialog: KeyNewOrSetDialogService,
        @Inject(BreakpointObserver) breakpointObserver: BreakpointObserver,
        @Inject(MainCommandService) cmd: MainCommandService,
        @Inject(ChangeDetectorRef) cdr: ChangeDetectorRef,
        @Inject(RedisStateService) redisState: RedisStateService,
        @Inject(SettingsService) settingsService: SettingsService,
    ) {
        super(i18n, socket, common, jsonViewDialog, keyNewOrSetDialog, breakpointObserver, cmd, cdr, redisState, settingsService);
    }

    get strings() {
        return this.i18n.strings;
    }

    get type(): string {
        return this.p3xrResponse?.type || '';
    }

    ngOnInit() {
        this.readonly = this.redisState.connection()?.readonly === true;
        this.parseInfo();
        if (this.type === 'topk') {
            this.loadTopkList();
        }
    }

    ngOnChanges(changes: SimpleChanges) {
        if (changes['p3xrValue']) {
            this.parseInfo();
        }
    }

    ngOnDestroy() {
        this.stopAutoRefresh();
        this.destroyBase();
    }

    toggleAutoRefresh(): void {
        this.autoRefresh = !this.autoRefresh;
        if (this.autoRefresh) {
            this.startAutoRefresh();
        } else {
            this.stopAutoRefresh();
        }
        this.cdr.markForCheck();
    }

    private startAutoRefresh(): void {
        this.stopAutoRefresh();
        this.autoRefreshInterval = setInterval(() => {
            this.refresh();
        }, 10000);
    }

    private stopAutoRefresh(): void {
        if (this.autoRefreshInterval) {
            clearInterval(this.autoRefreshInterval);
            this.autoRefreshInterval = null;
        }
    }

    private parseInfo() {
        this.infoItems = [];
        try {
            const val = this.p3xrValue;
            let info: any;
            if (typeof val === 'object' && val !== null && !ArrayBuffer.isView(val)) {
                info = val;
            } else if (typeof val === 'string') {
                info = JSON.parse(val);
            } else if (ArrayBuffer.isView(val)) {
                info = JSON.parse(new TextDecoder().decode(val as any));
            }
            if (info && typeof info === 'object') {
                this.infoItems = Object.entries(info).map(([key, value]) => ({ key, value }));
            }
        } catch {
            // ignore parse errors
        }
    }

    async addItem() {
        if (!this.itemInput.trim()) return;
        try {
            await this.socket.request({
                action: 'probabilistic/add',
                payload: {
                    key: this.p3xrKey,
                    type: this.type,
                    item: this.itemInput.trim(),
                    increment: this.incrementInput,
                },
            });
            this.common.toast(this.strings()?.page?.key?.probabilistic?.addedSuccessfully);
            this.itemInput = '';
            this.refresh();
        } catch (e: any) {
            this.common.toast(e.message || 'Error');
        }
        this.cdr.markForCheck();
    }

    async checkItem() {
        if (!this.itemInput.trim()) return;
        try {
            const response = await this.socket.request({
                action: 'probabilistic/check',
                payload: {
                    key: this.p3xrKey,
                    type: this.type,
                    item: this.itemInput.trim(),
                },
            });
            const strings = this.strings();
            const exists = (response as any).result === 1;
            this.common.toast(`"${this.itemInput}" — ${exists
                ? (strings?.page?.key?.probabilistic?.exists)
                : (strings?.page?.key?.probabilistic?.doesNotExist)}`);
        } catch (e: any) {
            this.common.toast(e.message || 'Error');
        }
        this.cdr.markForCheck();
    }

    async deleteItem() {
        if (!this.itemInput.trim()) return;
        try {
            await this.common.confirm({
                message: this.strings()?.confirm?.delete,
            });
            await this.socket.request({
                action: 'probabilistic/delete',
                payload: {
                    key: this.p3xrKey,
                    type: this.type,
                    item: this.itemInput.trim(),
                },
            });
            this.common.toast(this.strings()?.page?.key?.probabilistic?.deletedSuccessfully);
            this.itemInput = '';
            this.refresh();
        } catch (e: any) {
            if (e?.message) this.common.toast(e.message || 'Error');
        }
        this.cdr.markForCheck();
    }

    async queryItem() {
        if (!this.itemInput.trim()) return;
        try {
            const response = await this.socket.request({
                action: 'probabilistic/check',
                payload: {
                    key: this.p3xrKey,
                    type: this.type,
                    item: this.itemInput.trim(),
                },
            });
            const count = Array.isArray((response as any).result) ? (response as any).result[0] : (response as any).result;
            this.common.toast(`"${this.itemInput}" — ${this.strings()?.page?.key?.probabilistic?.topkCount}: ${count}`);
        } catch (e: any) {
            this.common.toast(e.message || 'Error');
        }
        this.cdr.markForCheck();
    }

    async queryQuantile() {
        try {
            const response = await this.socket.request({
                action: 'probabilistic/check',
                payload: {
                    key: this.p3xrKey,
                    type: this.type,
                    quantile: this.quantileInput,
                },
            });
            const strings = this.strings();
            const result = Array.isArray((response as any).result) ? (response as any).result[0] : (response as any).result;
            this.common.toast(`${strings?.page?.key?.probabilistic?.quantile} ${this.quantileInput} = ${result}`);
        } catch (e: any) {
            this.common.toast(e.message || 'Error');
        }
        this.cdr.markForCheck();
    }

    async resetTdigest() {
        try {
            await this.common.confirm({
                message: this.strings()?.page?.key?.probabilistic?.resetConfirm,
            });
            await this.socket.request({
                action: 'probabilistic/delete',
                payload: {
                    key: this.p3xrKey,
                    type: 'tdigest',
                },
            });
            this.common.toast('Reset');
            this.refresh();
        } catch (e: any) {
            if (e?.message) this.common.toast(e.message || 'Error');
        }
        this.cdr.markForCheck();
    }

    async loadTopkList() {
        try {
            const response = await this.socket.request({
                action: 'probabilistic/check',
                payload: {
                    key: this.p3xrKey,
                    type: 'topk',
                },
            });
            this.topkItems = (response as any).result || [];
        } catch {
            this.topkItems = [];
        }
        this.cdr.markForCheck();
    }

    refresh() {
        this.cmd.refreshKey$.next();
        if (this.type === 'topk') {
            this.loadTopkList();
        }
    }
}