Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

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

List<UtmModuleGroupConfDTO> configDTOs = dbConfigs.stream()
List<UtmModuleGroupConfDTO> configDTOs = new ArrayList<>(dbConfigs.stream()
.map(dbConf -> {
UtmModuleGroupConfiguration override = findInKeys(keys, dbConf.getConfKey());
String value;
Expand All @@ -45,7 +49,17 @@ public boolean validate(UtmModule module, List<UtmModuleGroupConfiguration> keys
}
return new UtmModuleGroupConfDTO(dbConf.getConfDataType(),dbConf.getConfKey(), value);
})
.toList();
.collect(Collectors.toList()));

Set<String> dbKeys = dbConfigs.stream()
.map(UtmModuleGroupConfiguration::getConfKey)
.collect(Collectors.toCollection(HashSet::new));

keys.stream()
.filter(k -> !dbKeys.contains(k.getConfKey()))
.filter(k -> !Constants.MASKED_VALUE.equals(k.getConfValue()))
.map(k -> new UtmModuleGroupConfDTO(k.getConfDataType(), k.getConfKey(), k.getConfValue()))
.forEach(configDTOs::add);

UtmModuleGroupConfWrapperDTO body = new UtmModuleGroupConfWrapperDTO(configDTOs);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {UtmModuleGroupConfService} from '../../shared/services/utm-module-group-
import {UtmModuleGroupConfType} from '../../shared/type/utm-module-group-conf.type';
import {UtmToastService} from '../../../shared/alert/utm-toast.service';
import {ModuleChangeStatusBehavior} from '../../shared/behavior/module-change-status.behavior';
import { finalize } from 'rxjs/operators';

interface ProviderConfig {
id: string;
Expand Down Expand Up @@ -52,6 +53,10 @@ export class GuideSocAiComponent implements OnInit {
private rawConfigs: UtmModuleGroupConfType[] = [];
private groupId: number;

// Original masked customHeaders value (if backend returned an opaque mask like "*****")
private originalMaskedCustomHeaders: string | null = null;
private readonly maskedDisplay = '***';

providers: ProviderConfig[] = [
{
id: 'openai',
Expand Down Expand Up @@ -297,7 +302,12 @@ export class GuideSocAiComponent implements OnInit {

private loadConfig() {
this.loading = true;
this.moduleGroupService.query({moduleId: this.integrationId}).subscribe(response => {
this.moduleGroupService.query({moduleId: this.integrationId}).pipe(
finalize(()=>{
this.loading = false;
this.cdr.detectChanges();
})
).subscribe(response => {
const groups = response.body || [];
if (groups.length > 0) {
this.groupId = groups[0].id;
Expand Down Expand Up @@ -344,6 +354,7 @@ export class GuideSocAiComponent implements OnInit {
'changeAlertStatus': changeStatus ? changeStatus.confValue : 'false',
};
this.customModelValue = '';
this.originalMaskedCustomHeaders = null;

// Only load provider-specific values if viewing the saved provider
if (isCurrentSavedProvider) {
Expand Down Expand Up @@ -373,16 +384,25 @@ export class GuideSocAiComponent implements OnInit {
// Check if API key exists in custom headers — show masked if so
const customHeaders = this.getConf('utmstack.socai.customHeaders');
if (customHeaders && customHeaders.confValue && customHeaders.confValue !== '{}') {
try {
const headers = JSON.parse(customHeaders.confValue);
const authConfig = this.providerAuthHeaders[this.activeProvider];
if (authConfig && headers[authConfig.headerName]) {
// API key exists — show masked, don't expose the real value
this.formValues['apiKey'] = '*****';
}
} catch (e) {}
this.formValues['customHeaders'] = customHeaders.confValue;
this.parseHeadersFromJson(customHeaders.confValue);
const raw = customHeaders.confValue;
if (this.isMaskedValue(raw)) {
// Backend returned the whole confValue masked — preserve original for save
this.originalMaskedCustomHeaders = raw;
this.formValues['customHeaders'] = raw;
this.formValues['apiKey'] = this.maskedDisplay;
this.headerRows = [{key: this.maskedDisplay, value: this.maskedDisplay}];
} else {
try {
const headers = JSON.parse(raw);
const authConfig = this.providerAuthHeaders[this.activeProvider];
if (authConfig && headers[authConfig.headerName]) {
// API key exists — show masked, don't expose the real value
this.formValues['apiKey'] = this.maskedDisplay;
}
} catch (e) {}
this.formValues['customHeaders'] = raw;
this.parseHeadersFromJson(raw);
}
}

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

// Set provider
this.pushChange(changes, 'utmstack.socai.provider', this.activeProvider);
this.pushChange(changes, 'utmstack.socai.provider', this.activeProvider, 'text');

// Set model
this.pushChange(changes, 'utmstack.socai.model', this.getModelValue());
this.pushChange(changes, 'utmstack.socai.model', this.getModelValue(), 'text');

// Set URL for providers that need it (azure, ollama, custom)
if (this.formValues['url']) {
this.pushChange(changes, 'utmstack.socai.url', this.formValues['url']);
this.pushChange(changes, 'utmstack.socai.url', this.formValues['url'], 'text');
}

// Set maxTokens
if (this.formValues['maxTokens']) {
this.pushChange(changes, 'utmstack.socai.maxTokens', this.formValues['maxTokens']);
this.pushChange(changes, 'utmstack.socai.maxTokens', this.formValues['maxTokens'], 'text');
}

// Set behavior toggles
this.pushChange(changes, 'utmstack.socai.autoAnalyze', this.formValues['autoAnalyze'] || 'false');
this.pushChange(changes, 'utmstack.socai.incidentCreation', this.formValues['incidentCreation'] || 'false');
this.pushChange(changes, 'utmstack.socai.changeAlertStatus', this.formValues['changeAlertStatus'] || 'false');
this.pushChange(changes, 'utmstack.socai.autoAnalyze', this.formValues['autoAnalyze'] || 'false', 'text');
this.pushChange(changes, 'utmstack.socai.incidentCreation', this.formValues['incidentCreation'] || 'false', 'text');
this.pushChange(changes, 'utmstack.socai.changeAlertStatus', this.formValues['changeAlertStatus'] || 'false', 'text');

// Build auth headers
if (this.activeProvider === 'custom') {
// Custom provider: user manages auth type and headers directly
this.pushChange(changes, 'utmstack.socai.authType', this.formValues['authType'] || 'custom-headers');
this.pushChange(changes, 'utmstack.socai.customHeaders', this.formValues['customHeaders'] || '{}');
this.pushChange(changes, 'utmstack.socai.authType', this.formValues['authType'] || 'custom-headers', 'text');
if (this.originalMaskedCustomHeaders && this.hasMaskedHeaderRows()) {
// User didn't replace the masked rows — preserve original masked value
this.pushChange(changes, 'utmstack.socai.customHeaders', this.originalMaskedCustomHeaders, 'password');
} else {
this.pushChange(changes, 'utmstack.socai.customHeaders', this.formValues['customHeaders'] || '{}', 'password');
}
} else if (this.activeProvider === 'ollama') {
// Ollama: no auth needed
this.pushChange(changes, 'utmstack.socai.authType', 'none');
this.pushChange(changes, 'utmstack.socai.customHeaders', '{}');
this.pushChange(changes, 'utmstack.socai.authType', 'none', 'text');
this.pushChange(changes, 'utmstack.socai.customHeaders', '{}', 'password');
} else {
// Known providers: build auth header from API key
const authConfig = this.providerAuthHeaders[this.activeProvider];
if (authConfig && this.formValues['apiKey'] && this.formValues['apiKey'] !== '*****') {
const apiKey = this.formValues['apiKey'];
if (authConfig && apiKey && !this.isMaskedValue(apiKey)) {
// User entered a new API key — build auth headers
const headers: {[k: string]: string} = {};
headers[authConfig.headerName] = authConfig.headerValuePrefix + this.formValues['apiKey'];
this.pushChange(changes, 'utmstack.socai.authType', 'custom-headers');
this.pushChange(changes, 'utmstack.socai.customHeaders', JSON.stringify(headers));
headers[authConfig.headerName] = authConfig.headerValuePrefix + apiKey;
this.pushChange(changes, 'utmstack.socai.authType', 'custom-headers', 'text');
this.pushChange(changes, 'utmstack.socai.customHeaders', JSON.stringify(headers), 'password');
} else if (this.originalMaskedCustomHeaders && apiKey && this.isMaskedValue(apiKey)) {
// User didn't change the masked API key — preserve original masked value
this.pushChange(changes, 'utmstack.socai.authType', 'custom-headers', 'text');
this.pushChange(changes, 'utmstack.socai.customHeaders', this.originalMaskedCustomHeaders, 'password');
}
// If apiKey is '*****', don't touch customHeaders — keep existing value in DB
// Otherwise: don't touch customHeaders — keep existing value in DB
}

this.moduleGroupConfService.update({
Expand All @@ -486,7 +516,12 @@ export class GuideSocAiComponent implements OnInit {
);
}

private pushChange(changes: UtmModuleGroupConfType[], confKey: string, value: string) {
private pushChange(
changes: UtmModuleGroupConfType[],
confKey: string,
value: string,
confDataType: 'list' | 'password' | 'file' | 'bool' | 'select' | 'text' = 'text'
) {
const existing = this.getConf(confKey);
if (existing) {
changes.push({
Expand All @@ -495,6 +530,19 @@ export class GuideSocAiComponent implements OnInit {
confOptions: existing.confOptions ? JSON.stringify(existing.confOptions) : existing.confOptions,
confVisibility: existing.confVisibility ? JSON.stringify(existing.confVisibility) : existing.confVisibility,
});
} else {
changes.push({
id: undefined,
groupId: this.groupId,
confKey,
confValue: value,
confName: confKey.split('.')[2] || confKey,
confDataType,
confDescription: confKey,
confRequired: true,
confOptions: undefined,
confVisibility: undefined,
});
}
}

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

private isMaskedValue(value: string): boolean {
return !!value && /^\*+$/.test(value);
}

private hasMaskedHeaderRows(): boolean {
return this.headerRows.some(r => this.isMaskedValue(r.key) || this.isMaskedValue(r.value));
}

private parseHeadersFromJson(json: string) {
this.headerRows = [];
try {
Expand Down