Skip to content

Commit 6e169dd

Browse files
fix[frontend](socai): added default template for empty previous socai… (#2095)
1 parent 81165da commit 6e169dd

2 files changed

Lines changed: 100 additions & 30 deletions

File tree

backend/src/main/java/com/park/utmstack/domain/application_modules/validators/UtmModuleConfigValidator.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@
1111
import lombok.RequiredArgsConstructor;
1212
import org.springframework.stereotype.Service;
1313

14+
import java.util.ArrayList;
15+
import java.util.HashSet;
1416
import java.util.List;
17+
import java.util.Set;
18+
import java.util.stream.Collectors;
1519

1620
@Service
1721
@RequiredArgsConstructor
@@ -32,7 +36,7 @@ public boolean validate(UtmModule module, List<UtmModuleGroupConfiguration> keys
3236
public boolean validate(UtmModule module, List<UtmModuleGroupConfiguration> keys, List<UtmModuleGroupConfiguration> dbConfigs) {
3337
if (keys.isEmpty()) return false;
3438

35-
List<UtmModuleGroupConfDTO> configDTOs = dbConfigs.stream()
39+
List<UtmModuleGroupConfDTO> configDTOs = new ArrayList<>(dbConfigs.stream()
3640
.map(dbConf -> {
3741
UtmModuleGroupConfiguration override = findInKeys(keys, dbConf.getConfKey());
3842
String value;
@@ -45,7 +49,17 @@ public boolean validate(UtmModule module, List<UtmModuleGroupConfiguration> keys
4549
}
4650
return new UtmModuleGroupConfDTO(dbConf.getConfDataType(),dbConf.getConfKey(), value);
4751
})
48-
.toList();
52+
.collect(Collectors.toList()));
53+
54+
Set<String> dbKeys = dbConfigs.stream()
55+
.map(UtmModuleGroupConfiguration::getConfKey)
56+
.collect(Collectors.toCollection(HashSet::new));
57+
58+
keys.stream()
59+
.filter(k -> !dbKeys.contains(k.getConfKey()))
60+
.filter(k -> !Constants.MASKED_VALUE.equals(k.getConfValue()))
61+
.map(k -> new UtmModuleGroupConfDTO(k.getConfDataType(), k.getConfKey(), k.getConfValue()))
62+
.forEach(configDTOs::add);
4963

5064
UtmModuleGroupConfWrapperDTO body = new UtmModuleGroupConfWrapperDTO(configDTOs);
5165

frontend/src/app/app-module/guides/guide-soc-ai/guide-soc-ai.component.ts

Lines changed: 84 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {UtmModuleGroupConfService} from '../../shared/services/utm-module-group-
55
import {UtmModuleGroupConfType} from '../../shared/type/utm-module-group-conf.type';
66
import {UtmToastService} from '../../../shared/alert/utm-toast.service';
77
import {ModuleChangeStatusBehavior} from '../../shared/behavior/module-change-status.behavior';
8+
import { finalize } from 'rxjs/operators';
89

910
interface ProviderConfig {
1011
id: string;
@@ -52,6 +53,10 @@ export class GuideSocAiComponent implements OnInit {
5253
private rawConfigs: UtmModuleGroupConfType[] = [];
5354
private groupId: number;
5455

56+
// Original masked customHeaders value (if backend returned an opaque mask like "*****")
57+
private originalMaskedCustomHeaders: string | null = null;
58+
private readonly maskedDisplay = '***';
59+
5560
providers: ProviderConfig[] = [
5661
{
5762
id: 'openai',
@@ -297,7 +302,12 @@ export class GuideSocAiComponent implements OnInit {
297302

298303
private loadConfig() {
299304
this.loading = true;
300-
this.moduleGroupService.query({moduleId: this.integrationId}).subscribe(response => {
305+
this.moduleGroupService.query({moduleId: this.integrationId}).pipe(
306+
finalize(()=>{
307+
this.loading = false;
308+
this.cdr.detectChanges();
309+
})
310+
).subscribe(response => {
301311
const groups = response.body || [];
302312
if (groups.length > 0) {
303313
this.groupId = groups[0].id;
@@ -344,6 +354,7 @@ export class GuideSocAiComponent implements OnInit {
344354
'changeAlertStatus': changeStatus ? changeStatus.confValue : 'false',
345355
};
346356
this.customModelValue = '';
357+
this.originalMaskedCustomHeaders = null;
347358

348359
// Only load provider-specific values if viewing the saved provider
349360
if (isCurrentSavedProvider) {
@@ -373,16 +384,25 @@ export class GuideSocAiComponent implements OnInit {
373384
// Check if API key exists in custom headers — show masked if so
374385
const customHeaders = this.getConf('utmstack.socai.customHeaders');
375386
if (customHeaders && customHeaders.confValue && customHeaders.confValue !== '{}') {
376-
try {
377-
const headers = JSON.parse(customHeaders.confValue);
378-
const authConfig = this.providerAuthHeaders[this.activeProvider];
379-
if (authConfig && headers[authConfig.headerName]) {
380-
// API key exists — show masked, don't expose the real value
381-
this.formValues['apiKey'] = '*****';
382-
}
383-
} catch (e) {}
384-
this.formValues['customHeaders'] = customHeaders.confValue;
385-
this.parseHeadersFromJson(customHeaders.confValue);
387+
const raw = customHeaders.confValue;
388+
if (this.isMaskedValue(raw)) {
389+
// Backend returned the whole confValue masked — preserve original for save
390+
this.originalMaskedCustomHeaders = raw;
391+
this.formValues['customHeaders'] = raw;
392+
this.formValues['apiKey'] = this.maskedDisplay;
393+
this.headerRows = [{key: this.maskedDisplay, value: this.maskedDisplay}];
394+
} else {
395+
try {
396+
const headers = JSON.parse(raw);
397+
const authConfig = this.providerAuthHeaders[this.activeProvider];
398+
if (authConfig && headers[authConfig.headerName]) {
399+
// API key exists — show masked, don't expose the real value
400+
this.formValues['apiKey'] = this.maskedDisplay;
401+
}
402+
} catch (e) {}
403+
this.formValues['customHeaders'] = raw;
404+
this.parseHeadersFromJson(raw);
405+
}
386406
}
387407

388408
const maxTokensConf = this.getConf('utmstack.socai.maxTokens');
@@ -422,46 +442,56 @@ export class GuideSocAiComponent implements OnInit {
422442
const changes: UtmModuleGroupConfType[] = [];
423443

424444
// Set provider
425-
this.pushChange(changes, 'utmstack.socai.provider', this.activeProvider);
445+
this.pushChange(changes, 'utmstack.socai.provider', this.activeProvider, 'text');
426446

427447
// Set model
428-
this.pushChange(changes, 'utmstack.socai.model', this.getModelValue());
448+
this.pushChange(changes, 'utmstack.socai.model', this.getModelValue(), 'text');
429449

430450
// Set URL for providers that need it (azure, ollama, custom)
431451
if (this.formValues['url']) {
432-
this.pushChange(changes, 'utmstack.socai.url', this.formValues['url']);
452+
this.pushChange(changes, 'utmstack.socai.url', this.formValues['url'], 'text');
433453
}
434454

435455
// Set maxTokens
436456
if (this.formValues['maxTokens']) {
437-
this.pushChange(changes, 'utmstack.socai.maxTokens', this.formValues['maxTokens']);
457+
this.pushChange(changes, 'utmstack.socai.maxTokens', this.formValues['maxTokens'], 'text');
438458
}
439459

440460
// Set behavior toggles
441-
this.pushChange(changes, 'utmstack.socai.autoAnalyze', this.formValues['autoAnalyze'] || 'false');
442-
this.pushChange(changes, 'utmstack.socai.incidentCreation', this.formValues['incidentCreation'] || 'false');
443-
this.pushChange(changes, 'utmstack.socai.changeAlertStatus', this.formValues['changeAlertStatus'] || 'false');
461+
this.pushChange(changes, 'utmstack.socai.autoAnalyze', this.formValues['autoAnalyze'] || 'false', 'text');
462+
this.pushChange(changes, 'utmstack.socai.incidentCreation', this.formValues['incidentCreation'] || 'false', 'text');
463+
this.pushChange(changes, 'utmstack.socai.changeAlertStatus', this.formValues['changeAlertStatus'] || 'false', 'text');
444464

445465
// Build auth headers
446466
if (this.activeProvider === 'custom') {
447467
// Custom provider: user manages auth type and headers directly
448-
this.pushChange(changes, 'utmstack.socai.authType', this.formValues['authType'] || 'custom-headers');
449-
this.pushChange(changes, 'utmstack.socai.customHeaders', this.formValues['customHeaders'] || '{}');
468+
this.pushChange(changes, 'utmstack.socai.authType', this.formValues['authType'] || 'custom-headers', 'text');
469+
if (this.originalMaskedCustomHeaders && this.hasMaskedHeaderRows()) {
470+
// User didn't replace the masked rows — preserve original masked value
471+
this.pushChange(changes, 'utmstack.socai.customHeaders', this.originalMaskedCustomHeaders, 'password');
472+
} else {
473+
this.pushChange(changes, 'utmstack.socai.customHeaders', this.formValues['customHeaders'] || '{}', 'password');
474+
}
450475
} else if (this.activeProvider === 'ollama') {
451476
// Ollama: no auth needed
452-
this.pushChange(changes, 'utmstack.socai.authType', 'none');
453-
this.pushChange(changes, 'utmstack.socai.customHeaders', '{}');
477+
this.pushChange(changes, 'utmstack.socai.authType', 'none', 'text');
478+
this.pushChange(changes, 'utmstack.socai.customHeaders', '{}', 'password');
454479
} else {
455480
// Known providers: build auth header from API key
456481
const authConfig = this.providerAuthHeaders[this.activeProvider];
457-
if (authConfig && this.formValues['apiKey'] && this.formValues['apiKey'] !== '*****') {
482+
const apiKey = this.formValues['apiKey'];
483+
if (authConfig && apiKey && !this.isMaskedValue(apiKey)) {
458484
// User entered a new API key — build auth headers
459485
const headers: {[k: string]: string} = {};
460-
headers[authConfig.headerName] = authConfig.headerValuePrefix + this.formValues['apiKey'];
461-
this.pushChange(changes, 'utmstack.socai.authType', 'custom-headers');
462-
this.pushChange(changes, 'utmstack.socai.customHeaders', JSON.stringify(headers));
486+
headers[authConfig.headerName] = authConfig.headerValuePrefix + apiKey;
487+
this.pushChange(changes, 'utmstack.socai.authType', 'custom-headers', 'text');
488+
this.pushChange(changes, 'utmstack.socai.customHeaders', JSON.stringify(headers), 'password');
489+
} else if (this.originalMaskedCustomHeaders && apiKey && this.isMaskedValue(apiKey)) {
490+
// User didn't change the masked API key — preserve original masked value
491+
this.pushChange(changes, 'utmstack.socai.authType', 'custom-headers', 'text');
492+
this.pushChange(changes, 'utmstack.socai.customHeaders', this.originalMaskedCustomHeaders, 'password');
463493
}
464-
// If apiKey is '*****', don't touch customHeaders — keep existing value in DB
494+
// Otherwise: don't touch customHeaders — keep existing value in DB
465495
}
466496

467497
this.moduleGroupConfService.update({
@@ -486,7 +516,12 @@ export class GuideSocAiComponent implements OnInit {
486516
);
487517
}
488518

489-
private pushChange(changes: UtmModuleGroupConfType[], confKey: string, value: string) {
519+
private pushChange(
520+
changes: UtmModuleGroupConfType[],
521+
confKey: string,
522+
value: string,
523+
confDataType: 'list' | 'password' | 'file' | 'bool' | 'select' | 'text' = 'text'
524+
) {
490525
const existing = this.getConf(confKey);
491526
if (existing) {
492527
changes.push({
@@ -495,6 +530,19 @@ export class GuideSocAiComponent implements OnInit {
495530
confOptions: existing.confOptions ? JSON.stringify(existing.confOptions) : existing.confOptions,
496531
confVisibility: existing.confVisibility ? JSON.stringify(existing.confVisibility) : existing.confVisibility,
497532
});
533+
} else {
534+
changes.push({
535+
id: undefined,
536+
groupId: this.groupId,
537+
confKey,
538+
confValue: value,
539+
confName: confKey.split('.')[2] || confKey,
540+
confDataType,
541+
confDescription: confKey,
542+
confRequired: true,
543+
confOptions: undefined,
544+
confVisibility: undefined,
545+
});
498546
}
499547
}
500548

@@ -543,6 +591,14 @@ export class GuideSocAiComponent implements OnInit {
543591
this.formValues['customHeaders'] = JSON.stringify(obj);
544592
}
545593

594+
private isMaskedValue(value: string): boolean {
595+
return !!value && /^\*+$/.test(value);
596+
}
597+
598+
private hasMaskedHeaderRows(): boolean {
599+
return this.headerRows.some(r => this.isMaskedValue(r.key) || this.isMaskedValue(r.value));
600+
}
601+
546602
private parseHeadersFromJson(json: string) {
547603
this.headerRows = [];
548604
try {

0 commit comments

Comments
 (0)