Skip to content

Commit c12133f

Browse files
Merge pull request #1292 from rocket-admin/uuid-widget
uuid widget, country flags, fix dynamodb filter components
2 parents 861cb6c + 28cfd60 commit c12133f

16 files changed

Lines changed: 401 additions & 9 deletions

File tree

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"puppeteer": "^23.10.4",
5656
"rxjs": "^7.4.0",
5757
"tslib": "^2.8.1",
58+
"uuid": "^11.1.0",
5859
"validator": "^13.12.0",
5960
"zone.js": "~0.15.0"
6061
},

frontend/src/app/components/dashboard/db-table-widgets/db-table-widgets.component.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,13 @@ export class DbTableWidgetsComponent implements OnInit {
176176
"phone_validation": true
177177
}
178178
`,
179-
Country: `// No settings required`,
179+
Country: `// Configure country display options
180+
// Example:
181+
{
182+
"show_flag": true,
183+
"allow_null": false
184+
}
185+
`,
180186
Foreign_key: `// Provide settings for foreign key widget
181187
{
182188
"column_name": "", // copy the name of the column you selected
@@ -206,6 +212,15 @@ export class DbTableWidgetsComponent implements OnInit {
206212
// - "hex_hash": Display as "#FF5733" (default)
207213
// - "rgb": Display as "rgb(255, 87, 51)"
208214
// - "hsl": Display as "hsl(9, 100%, 60%)"`,
215+
UUID: `// Configure UUID generation version and parameters
216+
// Available versions: "v1", "v3", "v4" (default), "v5", "v7"
217+
// For v3/v5: provide namespace and optionally name
218+
{
219+
"version": "v4",
220+
"namespace": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
221+
"name": ""
222+
}
223+
`
209224
}
210225

211226
constructor(

frontend/src/app/components/ui-components/record-edit-fields/country/country.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<mat-option *ngFor="let country of filteredCountries | async"
1010
[value]="country"
1111
(onSelectionChange)="country && onCountrySelected(country)">
12-
<span *ngIf="country.flag" class="country-flag">{{country.flag}}</span>
12+
<span *ngIf="country.flag && showFlag" class="country-flag">{{country.flag}}</span>
1313
<span class="country-name">{{country.label}}</span>
1414
<span *ngIf="country.value" class="country-code">({{country.value}})</span>
1515
</mat-option>

frontend/src/app/components/ui-components/record-edit-fields/country/country.component.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,36 @@ export class CountryEditComponent extends BaseEditFieldComponent {
2525
public countries: {value: string | null, label: string, flag: string}[] = [];
2626
public countryControl = new FormControl<{value: string | null, label: string, flag: string} | string>('');
2727
public filteredCountries: Observable<{value: string | null, label: string, flag: string}[]>;
28+
public showFlag: boolean = true;
2829

2930
originalOrder = () => { return 0; }
3031

3132
getCountryFlag = getCountryFlag;
3233

3334
ngOnInit(): void {
3435
super.ngOnInit();
36+
this.parseWidgetParams();
3537
this.loadCountries();
3638
this.setupAutocomplete();
3739
this.setInitialValue();
3840
}
3941

42+
private parseWidgetParams(): void {
43+
if (this.widgetStructure?.widget_params) {
44+
try {
45+
const params = typeof this.widgetStructure.widget_params === 'string'
46+
? JSON.parse(this.widgetStructure.widget_params)
47+
: this.widgetStructure.widget_params;
48+
49+
if (params.show_flag !== undefined) {
50+
this.showFlag = params.show_flag;
51+
}
52+
} catch (e) {
53+
console.error('Error parsing country widget params:', e);
54+
}
55+
}
56+
}
57+
4058
private setupAutocomplete(): void {
4159
this.filteredCountries = this.countryControl.valueChanges.pipe(
4260
startWith(''),
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
.uuid-field {
2+
width: 100%;
3+
}
4+
5+
.uuid-input {
6+
font-family: 'Roboto Mono', monospace;
7+
font-size: 14px;
8+
}
9+
10+
.regenerate-button {
11+
cursor: pointer;
12+
}
13+
14+
.regenerate-button:hover {
15+
color: var(--mat-primary);
16+
}
17+
18+
.uuid-config-info {
19+
display: flex;
20+
align-items: center;
21+
gap: 8px;
22+
margin-top: -16px;
23+
margin-bottom: 16px;
24+
padding: 8px;
25+
background-color: var(--mat-grey-100);
26+
border-radius: 4px;
27+
font-size: 12px;
28+
}
29+
30+
.info-icon {
31+
font-size: 16px;
32+
width: 16px;
33+
height: 16px;
34+
color: var(--mat-grey-600);
35+
}
36+
37+
.info-text {
38+
color: var(--mat-grey-700);
39+
}
40+
41+
/* Dark mode support */
42+
@media (prefers-color-scheme: dark) {
43+
.uuid-config-info {
44+
background-color: var(--mat-grey-800);
45+
}
46+
47+
.info-icon {
48+
color: var(--mat-grey-400);
49+
}
50+
51+
.info-text {
52+
color: var(--mat-grey-300);
53+
}
54+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<mat-form-field appearance="outline" class="uuid-field">
2+
<mat-label>{{ normalizedLabel }}</mat-label>
3+
<input
4+
matInput
5+
[(ngModel)]="value"
6+
[readonly]="readonly || disabled"
7+
[required]="required"
8+
(ngModelChange)="onFieldChange.emit($event)"
9+
placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
10+
class="uuid-input"
11+
/>
12+
13+
@if (!readonly && !disabled) {
14+
<button
15+
mat-icon-button
16+
matSuffix
17+
(click)="generateUuid()"
18+
matTooltip="Generate new UUID"
19+
type="button"
20+
class="regenerate-button"
21+
>
22+
<mat-icon>refresh</mat-icon>
23+
</button>
24+
}
25+
26+
@if (value && validateUuid(value)) {
27+
<mat-hint align="end">
28+
UUID {{ getUuidVersion(value) ? 'v' + getUuidVersion(value) : 'version unknown' }}
29+
</mat-hint>
30+
}
31+
32+
@if (value && !validateUuid(value)) {
33+
<mat-error>Invalid UUID format</mat-error>
34+
}
35+
</mat-form-field>
36+
37+
<!-- Widget configuration info for v3/v5 versions -->
38+
@if (!readonly && !disabled && (uuidVersion === 'v3' || uuidVersion === 'v5')) {
39+
<div class="uuid-config-info">
40+
<mat-icon class="info-icon">info</mat-icon>
41+
<span class="info-text">
42+
UUID {{ uuidVersion }} uses namespace/name hashing. Configure these in widget parameters.
43+
</span>
44+
</div>
45+
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import { Component, Injectable, Input, OnInit } from '@angular/core';
2+
import { validate as uuidValidate, version as uuidVersion, v1 as uuidv1, v3 as uuidv3, v4 as uuidv4, v5 as uuidv5, v7 as uuidv7 } from 'uuid';
3+
4+
import { BaseEditFieldComponent } from '../base-row-field/base-row-field.component';
5+
import { CommonModule } from '@angular/common';
6+
import { FormsModule } from '@angular/forms';
7+
import { MatButtonModule } from '@angular/material/button';
8+
import { MatFormFieldModule } from '@angular/material/form-field';
9+
import { MatIconModule } from '@angular/material/icon';
10+
import { MatInputModule } from '@angular/material/input';
11+
import { MatSelectModule } from '@angular/material/select';
12+
import { MatTooltipModule } from '@angular/material/tooltip';
13+
14+
@Injectable()
15+
16+
@Component({
17+
selector: 'app-edit-uuid',
18+
templateUrl: './uuid.component.html',
19+
styleUrls: ['./uuid.component.css'],
20+
imports: [
21+
CommonModule,
22+
MatFormFieldModule,
23+
MatInputModule,
24+
MatButtonModule,
25+
MatIconModule,
26+
MatSelectModule,
27+
MatTooltipModule,
28+
FormsModule
29+
]
30+
})
31+
export class UuidEditComponent extends BaseEditFieldComponent implements OnInit {
32+
@Input() value: string;
33+
34+
static type = 'uuid';
35+
36+
public uuidVersion: string = 'v4';
37+
public isCreateMode: boolean = true;
38+
public namespace: string = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; // Default DNS namespace
39+
public name: string = '';
40+
41+
// Available UUID versions
42+
public availableVersions = [
43+
{ value: 'v1', label: 'v1 (timestamp + MAC)', description: 'Timestamp and MAC address based' },
44+
{ value: 'v3', label: 'v3 (MD5 hash)', description: 'MD5 hash of namespace and name' },
45+
{ value: 'v4', label: 'v4 (random)', description: 'Random UUID (most common)' },
46+
{ value: 'v5', label: 'v5 (SHA-1 hash)', description: 'SHA-1 hash of namespace and name' },
47+
{ value: 'v7', label: 'v7 (timestamp)', description: 'Timestamp-based sortable UUID' }
48+
];
49+
50+
// Standard namespaces for v3/v5
51+
public namespaces = [
52+
{ value: '6ba7b810-9dad-11d1-80b4-00c04fd430c8', label: 'DNS' },
53+
{ value: '6ba7b811-9dad-11d1-80b4-00c04fd430c8', label: 'URL' },
54+
{ value: '6ba7b812-9dad-11d1-80b4-00c04fd430c8', label: 'OID' },
55+
{ value: '6ba7b814-9dad-11d1-80b4-00c04fd430c8', label: 'X500' }
56+
];
57+
58+
ngOnInit(): void {
59+
super.ngOnInit();
60+
61+
// Determine if we're in create or update mode
62+
this.isCreateMode = !this.value || this.value === '';
63+
64+
// Parse widget parameters
65+
if (this.widgetStructure?.widget_params) {
66+
try {
67+
const params = typeof this.widgetStructure.widget_params === 'string'
68+
? JSON.parse(this.widgetStructure.widget_params)
69+
: this.widgetStructure.widget_params;
70+
71+
if (params.version) {
72+
this.uuidVersion = params.version;
73+
}
74+
if (params.namespace) {
75+
this.namespace = params.namespace;
76+
}
77+
if (params.name) {
78+
this.name = params.name;
79+
}
80+
} catch (e) {
81+
console.error('Error parsing UUID widget params:', e);
82+
}
83+
}
84+
85+
// Generate UUID on create
86+
if (this.isCreateMode) {
87+
this.generateUuid();
88+
}
89+
}
90+
91+
generateUuid(): void {
92+
let uuid = '';
93+
94+
try {
95+
switch (this.uuidVersion) {
96+
case 'v1':
97+
uuid = uuidv1();
98+
break;
99+
case 'v3':
100+
if (this.name) {
101+
uuid = uuidv3(this.name, this.namespace);
102+
} else {
103+
uuid = uuidv3(Date.now().toString(), this.namespace);
104+
}
105+
break;
106+
case 'v4':
107+
uuid = uuidv4();
108+
break;
109+
case 'v5':
110+
if (this.name) {
111+
uuid = uuidv5(this.name, this.namespace);
112+
} else {
113+
uuid = uuidv5(Date.now().toString(), this.namespace);
114+
}
115+
break;
116+
case 'v7':
117+
uuid = uuidv7();
118+
break;
119+
default:
120+
uuid = uuidv4();
121+
}
122+
123+
this.value = uuid;
124+
this.onFieldChange.emit(this.value);
125+
} catch (error) {
126+
console.error('Error generating UUID:', error);
127+
// Fallback to v4
128+
this.value = uuidv4();
129+
this.onFieldChange.emit(this.value);
130+
}
131+
}
132+
133+
validateUuid(value: string): boolean {
134+
return uuidValidate(value);
135+
}
136+
137+
getUuidVersion(value: string): number | false {
138+
try {
139+
return uuidVersion(value);
140+
} catch {
141+
return false;
142+
}
143+
}
144+
}

frontend/src/app/components/ui-components/table-display-fields/country/country.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<div class="field-display">
22
<span class="field-value">
3-
<!--<span class="country-flag">{{ countryFlag }}</span>-->
3+
<span *ngIf="showFlag && countryFlag" class="country-flag">{{ countryFlag }}</span>
44
<span>{{ countryName }}</span>
55
</span>
66
<button type="button" mat-icon-button

frontend/src/app/components/ui-components/table-display-fields/country/country.component.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@ export class CountryDisplayComponent extends BaseTableDisplayFieldComponent impl
1919

2020
public countryName: string = '';
2121
public countryFlag: string = '';
22+
public showFlag: boolean = true;
2223

2324
ngOnInit(): void {
25+
this.parseWidgetParams();
26+
2427
if (this.value) {
2528
const country = COUNTRIES.find(c => c.code === this.value);
2629
this.countryName = country ? country.name : this.value;
@@ -30,4 +33,20 @@ export class CountryDisplayComponent extends BaseTableDisplayFieldComponent impl
3033
this.countryFlag = '';
3134
}
3235
}
36+
37+
private parseWidgetParams(): void {
38+
if (this.widgetStructure?.widget_params) {
39+
try {
40+
const params = typeof this.widgetStructure.widget_params === 'string'
41+
? JSON.parse(this.widgetStructure.widget_params)
42+
: this.widgetStructure.widget_params;
43+
44+
if (params.show_flag !== undefined) {
45+
this.showFlag = params.show_flag;
46+
}
47+
} catch (e) {
48+
console.error('Error parsing country widget params:', e);
49+
}
50+
}
51+
}
3352
}

0 commit comments

Comments
 (0)