import { Component, Inject, OnInit, ChangeDetectorRef } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatTooltipModule } from '@angular/material/tooltip'; import { DialogCancelButtonComponent } from '../components/dialog-cancel-button.component'; import { BreakpointObserver } from '@angular/cdk/layout'; import { I18nService } from '../services/i18n.service'; import { CommonService } from '../services/common.service'; import { SocketService } from '../services/socket.service'; import { JsonViewDialogService } from './json-view-dialog.service'; import { JsonEditorDialogService } from './json-editor-dialog.service'; declare const p3xr: any; export interface KeyNewOrSetDialogData { type: 'add' | 'edit' | 'append'; $event?: any; node?: any; model?: any; } /** * Key New/Edit dialog — Angular replacement for p3xrDialogKeyNewOrSet. * Multi-type form for creating or editing Redis keys (string, list, hash, set, zset, stream). */ @Component({ selector: 'p3xr-key-new-or-set-dialog', standalone: true, imports: [ CommonModule, FormsModule, MatDialogModule, MatFormFieldModule, MatInputModule, MatSelectModule, MatSlideToggleModule, MatButtonModule, MatIconModule, MatToolbarModule, MatTooltipModule, DialogCancelButtonComponent, ], template: `
{{ getTitle() }} {{ strings().form?.key?.field?.key || 'Key' }} @if (keyForm.controls['key']?.invalid && keyForm.controls['key']?.touched) { {{ strings().form?.key?.error?.key }} } {{ strings().form?.key?.field?.type || 'Type' }} @for (t of types; track t) { {{ strings().redisTypes?.[t] || t }} } @switch (model.type) { @case ('list') { {{ strings().form?.key?.field?.index || 'Index' }}
{{ strings().label?.redisListIndexInfo }}
} @case ('hash') { {{ strings().form?.key?.field?.hashKey || 'Hash Key' }} @if (keyForm.controls['hashKey']?.invalid && keyForm.controls['hashKey']?.touched) { {{ strings().form?.key?.error?.hashKey }} } } @case ('zset') { {{ strings().form?.key?.field?.score || 'Score' }} @if (keyForm.controls['score']?.invalid && keyForm.controls['score']?.touched) { {{ strings().form?.key?.error?.score }} } } @case ('stream') { {{ strings().form?.key?.field?.streamTimestamp || 'Timestamp' }} @if (keyForm.controls['streamTimestamp']?.invalid && keyForm.controls['streamTimestamp']?.touched) { {{ strings().form?.key?.error?.streamTimestamp }} }
{{ strings().label?.streamTimestampId }}
} } @if (model.type !== 'stream' && hasProOrEnterprise()) { } @if (hasProOrEnterprise()) { } {{ strings().label?.validateJson || 'Validate JSON' }} @if (model.type === 'stream') {
{{ strings().label?.streamValue }}
} @if (isBuffer) {
{{ strings().label?.isBuffer?.({ maxValueAsBuffer: p3xr?.settings?.prettyBytes?.(p3xr?.settings?.maxValueAsBuffer) ?? '' }) }} {{ bufferDisplay(model.value) }}
} {{ strings().form?.key?.field?.value || 'Value' }} @if (keyForm.controls['value']?.invalid && keyForm.controls['value']?.touched) { {{ strings().form?.key?.error?.value }} }
@if (!isReadonly) { }
`, styles: [` .full-width { width: 100%; } .info-text { opacity: 0.5; font-size: 12px; margin-bottom: 8px; } .hide-sm { display: inline; } @media (max-width: 959px) { .hide-sm { display: none; } } `], }) export class KeyNewOrSetDialogComponent implements OnInit { model: any = {}; options: KeyNewOrSetDialogData; get types(): string[] { const base = ['string', 'list', 'hash', 'set', 'zset', 'stream']; if (p3xr?.state?.hasReJSON && p3xr?.state?.hasProOrEnterpriseJsonBinary) { base.push('json'); } return base; } validateJson = false; isReadonly = false; isBuffer = false; isWide = window.innerWidth >= 720; strings; constructor( @Inject(MatDialogRef) private dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) private data: KeyNewOrSetDialogData, @Inject(I18nService) private i18n: I18nService, @Inject(CommonService) private common: CommonService, @Inject(SocketService) private socket: SocketService, @Inject(JsonViewDialogService) private jsonViewDialog: JsonViewDialogService, @Inject(JsonEditorDialogService) private jsonEditorDialog: JsonEditorDialogService, @Inject(BreakpointObserver) private breakpointObserver: BreakpointObserver, @Inject(ChangeDetectorRef) private cdr: ChangeDetectorRef, ) { this.strings = this.i18n.strings; this.options = data; } ngOnInit(): void { this.isReadonly = p3xr?.state?.connection?.readonly === true; this.breakpointObserver.observe('(min-width: 720px)').subscribe(r => { this.isWide = r.matches; this.cdr.markForCheck(); }); this.model = { type: 'string', key: this.data.node?.key ? this.data.node.key + (p3xr?.settings?.redisTreeDivider ?? ':') : '', value: undefined, score: undefined, streamTimestamp: '*', hashKey: undefined, index: undefined, }; if (this.data.model) { Object.assign(this.model, this.data.model); } this.isBuffer = typeof this.model.value === 'object' && this.model.value !== null; } getTitle(): string { const s = this.strings(); if (this.options.type === 'edit') return s.form?.key?.label?.formName?.edit || 'Edit Key'; if (this.options.type === 'append') return s.form?.key?.label?.formName?.append || 'Append'; return s.form?.key?.label?.formName?.add || 'Add Key'; } hasProOrEnterprise(): boolean { return p3xr?.state?.hasProOrEnterpriseJsonBinary === true; } bufferDisplay(value: any): string { if (value?.byteLength !== undefined) { return '(' + p3xr?.settings?.prettyBytes(value.byteLength) + ')'; } return ''; } async copy(): Promise { await p3xr.clipboard({ value: this.model.value }); this.common.toast(this.strings().status?.dataCopied || 'Copied'); } async openJsonViewer(): Promise { await this.jsonViewDialog.show({ value: this.model.value }); } async openJsonEditor(): Promise { try { const result = await this.jsonEditorDialog.show({ value: this.model.value }); this.model.value = result.obj; } catch (e) { /* cancelled */ } } formatJson(): void { try { this.model.value = JSON.stringify(JSON.parse(this.model.value), null, p3xr?.settings?.jsonFormat ?? 2); } catch (e) { this.common.toast(this.strings().label?.jsonViewNotParsable || 'Not valid JSON'); } } async onFileSelected(event: Event): Promise { const input = event.target as HTMLInputElement; const file = input.files?.[0]; if (!file) return; try { await this.common.confirm({ message: this.strings().confirm?.uploadBuffer || 'Upload buffer?' }); const arrayBuffer = await file.arrayBuffer(); this.model.value = arrayBuffer; this.isBuffer = true; this.common.toast(this.strings().confirm?.uploadBufferDone || 'Buffer uploaded'); } catch (e) { /* cancelled */ } input.value = ''; } async submit(): Promise { if (!this.model.key || this.model.key.trim().length === 0) { this.common.toast(this.strings().form?.key?.error?.key || 'Key cannot be empty'); return; } if (this.validateJson) { try { JSON.parse(this.model.value); } catch (e) { this.common.toast(this.strings().label?.jsonViewNotParsable || 'Not valid JSON'); return; } } try { p3xr.ui.overlay.show(); const response = await this.socket.request({ action: 'key-new-or-set', payload: { type: this.options.type, originalValue: this.data.model?.value, originalHashKey: this.data.model?.hashKey, model: p3xr.clone(this.model), }, }); if (typeof window['gtag'] === 'function') { window['gtag']('config', p3xr?.settings?.googleAnalytics, { page_path: '/key-new-or-set' }); } this.common.toast(this.strings().status?.set || 'Saved'); this.dialogRef.close(response); } catch (e) { this.common.generalHandleError(e); } finally { p3xr.ui.overlay.hide(); } } cancel(): void { this.dialogRef.close(undefined); } }