Skip to content

Commit 5c8e80c

Browse files
committed
Merge remote-tracking branch 'origin/release/v11.2.6' into backlog/compliance-evaluation
2 parents 147e2ef + 827d946 commit 5c8e80c

File tree

15 files changed

+767
-170
lines changed

15 files changed

+767
-170
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ export class GuideSocAiComponent implements OnInit {
259259
this.providerFormCache[this.activeProvider] = {
260260
values: {...this.formValues},
261261
customModel: this.customModelValue,
262-
headers: [...this.headerRows.map(r => ({...r}))]
262+
headers: (this.headerRows||[]).map(r => ({...r}))
263263
};
264264

265265
this.activeProvider = event.nextId;
@@ -470,10 +470,12 @@ export class GuideSocAiComponent implements OnInit {
470470
}).subscribe(
471471
() => {
472472
this.saving = false;
473+
this.cdr.markForCheck()
473474
this.toast.showSuccessBottom('SOC AI configuration saved successfully');
474475
},
475476
(err) => {
476477
this.saving = false;
478+
this.cdr.markForCheck()
477479
if (err.status === 400) {
478480
const message = this.extractValidationError(err);
479481
this.toast.showError('Invalid Configuration', message);

frontend/src/app/data-management/alert-management/alert-view/alert-view.component.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,8 +378,7 @@ export class AlertViewComponent implements OnInit, OnDestroy {
378378
}
379379

380380
addToSelected(alert: any) {
381-
console.log(alert);
382-
const index = this.alertSelected.indexOf(alert);
381+
const index = this.alertSelected.findIndex((a)=>a.id ===alert.id);
383382
if (index === -1) {
384383
this.alertSelected.push(alert);
385384
} else {
Lines changed: 6 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -1,115 +1,26 @@
11
package validations
2-
32
import (
43
"encoding/json"
54
"fmt"
6-
"maps"
7-
"net/http"
8-
"strings"
95

10-
"github.com/threatwinds/go-sdk/utils"
116
"github.com/utmstack/UTMStack/plugins/modules-config/config"
7+
"github.com/utmstack/UTMStack/plugins/modules-config/validations/socai"
128
)
139

14-
// isAnthropicProvider detects if the URL is for Anthropic API
15-
func isAnthropicProvider(url string) bool {
16-
return strings.Contains(url, "anthropic.com")
17-
}
18-
19-
// SOCAIConfig holds the parsed SOC-AI configuration
20-
type SOCAIConfig struct {
21-
AutoAnalyze bool
22-
IncidentCreation bool
23-
ChangeAlertStatus bool
24-
Provider string
25-
URL string
26-
Model string
27-
AuthType string // "custom-headers", "none"
28-
MaxTokens string
29-
CustomHeaders map[string]string // All headers including auth (from frontend)
30-
}
31-
32-
var providerDefaultURLs = map[string]string{
33-
"openai": "https://api.openai.com/v1/chat/completions",
34-
"anthropic": "https://api.anthropic.com/v1/messages",
35-
"azure": "",
36-
"gemini": "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions",
37-
"ollama": "http://localhost:11434/v1/chat/completions",
38-
"mistral": "https://api.mistral.ai/v1/chat/completions",
39-
"deepseek": "https://api.deepseek.com/chat/completions",
40-
"groq": "https://api.groq.com/openai/v1/chat/completions",
41-
}
4210

43-
var providerDisplayNames = map[string]string{
44-
"openai": "OpenAI",
45-
"anthropic": "Anthropic",
46-
"azure": "Azure OpenAI",
47-
"gemini": "Google Gemini",
48-
"ollama": "Ollama",
49-
"mistral": "Mistral AI",
50-
"deepseek": "DeepSeek",
51-
"groq": "Groq",
52-
"custom": "Custom",
53-
}
5411

55-
func getProviderName(provider string) string {
56-
if name, ok := providerDisplayNames[provider]; ok {
57-
return name
58-
}
59-
return provider
60-
}
6112

6213
func ValidateSOCAIConfig(cfg *config.ModuleGroup) error {
6314
if cfg == nil {
6415
return fmt.Errorf("SOC AI configuration is not provided")
6516
}
66-
67-
socai := parseSOCAIConfig(cfg)
68-
providerName := getProviderName(socai.Provider)
69-
70-
// Validate required fields
71-
if socai.URL == "" {
72-
if socai.Provider == "custom" || socai.Provider == "azure" || socai.Provider == "ollama" {
73-
return fmt.Errorf("API URL is required for %s provider", providerName)
74-
}
75-
return fmt.Errorf("API URL could not be determined. Please verify the provider configuration.")
76-
}
77-
if socai.Model == "" {
78-
return fmt.Errorf("Model is required for %s provider", providerName)
79-
}
80-
81-
// Validate authType
82-
if socai.AuthType == "" {
83-
socai.AuthType = "none"
84-
}
85-
if socai.AuthType != "custom-headers" && socai.AuthType != "none" {
86-
return fmt.Errorf("Invalid authentication type '%s'. Must be 'custom-headers' or 'none'.", socai.AuthType)
87-
}
88-
89-
// Validate auth headers exist when needed
90-
if socai.AuthType == "custom-headers" && len(socai.CustomHeaders) == 0 {
91-
if socai.Provider == "ollama" {
92-
// Ollama doesn't need auth
93-
} else {
94-
return fmt.Errorf("API Key is required for %s. Please provide your %s API Key.", providerName, providerName)
95-
}
96-
}
97-
98-
// Anthropic requires maxTokens
99-
if isAnthropicProvider(socai.URL) && socai.MaxTokens == "" {
100-
return fmt.Errorf("Max Tokens is required for Anthropic. Please set a value (e.g., 4096).")
101-
}
102-
103-
// Test connection
104-
if err := testSOCAIConnection(socai); err != nil {
105-
return err
106-
}
107-
108-
return nil
17+
socai_config := parseSOCAIConfig(cfg)
18+
verificator := socai.NewSocaiVerification(socai_config)
19+
return verificator.Validate()
10920
}
11021

111-
func parseSOCAIConfig(cfg *config.ModuleGroup) SOCAIConfig {
112-
socai := SOCAIConfig{
22+
func parseSOCAIConfig(cfg *config.ModuleGroup) socai.SOCAIConfig {
23+
socai := socai.SOCAIConfig{
11324
AuthType: "none",
11425
CustomHeaders: make(map[string]string),
11526
}
@@ -143,80 +54,8 @@ func parseSOCAIConfig(cfg *config.ModuleGroup) SOCAIConfig {
14354
}
14455
}
14556

146-
// Resolve URL from provider if not custom
147-
if socai.Provider != "" && socai.Provider != "custom" {
148-
if defaultURL, ok := providerDefaultURLs[socai.Provider]; ok && defaultURL != "" {
149-
socai.URL = defaultURL
150-
}
151-
}
152-
15357
return socai
15458
}
15559

156-
func testSOCAIConnection(socai SOCAIConfig) error {
157-
providerName := getProviderName(socai.Provider)
15860

159-
headers := map[string]string{
160-
"Content-Type": "application/json",
161-
}
16261

163-
// Add custom headers (includes auth headers configured by frontend)
164-
if socai.AuthType == "custom-headers" {
165-
maps.Copy(headers, socai.CustomHeaders)
166-
}
167-
168-
// Test connection with GET request (most APIs return error but validate auth)
169-
_, status, err := utils.DoReq[map[string]any](socai.URL, nil, "GET", headers, false)
170-
171-
switch status {
172-
case http.StatusOK, http.StatusMethodNotAllowed, http.StatusBadRequest:
173-
// These are acceptable - means we reached the API and auth worked
174-
return nil
175-
case http.StatusUnauthorized:
176-
if socai.Provider == "anthropic" {
177-
return fmt.Errorf("Invalid Anthropic API Key. Please verify your x-api-key is correct.")
178-
}
179-
if socai.Provider == "azure" {
180-
return fmt.Errorf("Invalid Azure OpenAI API Key. Please verify your api-key is correct.")
181-
}
182-
return fmt.Errorf("Invalid API Key for %s. Please verify your API Key is correct.", providerName)
183-
case http.StatusForbidden:
184-
return fmt.Errorf("%s API Key does not have the required permissions (HTTP 403). Please verify the API Key has access to the chat completions endpoint.", providerName)
185-
case http.StatusNotFound:
186-
if socai.Provider == "azure" {
187-
return fmt.Errorf("Azure OpenAI endpoint not found (HTTP 404). Please verify your Endpoint URL includes the correct resource name and deployment.")
188-
}
189-
if socai.Provider == "ollama" {
190-
return fmt.Errorf("Ollama API not found at '%s' (HTTP 404). Please verify Ollama is running and the URL is correct.", socai.URL)
191-
}
192-
return fmt.Errorf("%s API endpoint not found (HTTP 404). Please verify the API URL is correct.", providerName)
193-
case http.StatusRequestTimeout:
194-
if socai.Provider == "ollama" {
195-
return fmt.Errorf("Connection to Ollama timed out. Please verify Ollama is running at '%s' and is accessible from this server.", socai.URL)
196-
}
197-
return fmt.Errorf("Connection to %s timed out. Please verify the API URL is accessible from this server.", providerName)
198-
case http.StatusTooManyRequests:
199-
return fmt.Errorf("%s API rate limit exceeded (HTTP 429). Your API Key may have exceeded its quota. Please check your %s account billing/usage.", providerName, providerName)
200-
default:
201-
if err != nil {
202-
errMsg := strings.ToLower(err.Error())
203-
if strings.Contains(errMsg, "no such host") || strings.Contains(errMsg, "lookup") {
204-
if socai.Provider == "ollama" {
205-
return fmt.Errorf("Cannot resolve Ollama server at '%s'. Please verify the hostname is correct and accessible.", socai.URL)
206-
}
207-
return fmt.Errorf("Cannot resolve %s API host. Please verify the API URL is correct.", providerName)
208-
}
209-
if strings.Contains(errMsg, "connection refused") {
210-
if socai.Provider == "ollama" {
211-
return fmt.Errorf("Connection refused by Ollama at '%s'. Please verify Ollama is running.", socai.URL)
212-
}
213-
return fmt.Errorf("Connection refused by %s. Please verify the API URL is correct.", providerName)
214-
}
215-
if strings.Contains(errMsg, "timeout") || strings.Contains(errMsg, "deadline") {
216-
return fmt.Errorf("Connection to %s timed out. Please verify the API URL is accessible from this server.", providerName)
217-
}
218-
return fmt.Errorf("Cannot connect to %s. Please verify the API URL and API Key are correct.", providerName)
219-
}
220-
return nil // Accept other status codes as potentially valid
221-
}
222-
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package socai
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/utmstack/UTMStack/plugins/modules-config/validations/socai/providers"
7+
)
8+
9+
type Provider string
10+
11+
const (
12+
Openai Provider = "openai"
13+
Anthropic Provider = "anthropic"
14+
Azure Provider = "azure"
15+
Gemini Provider = "gemini"
16+
Ollama Provider = "ollama"
17+
Mistral Provider = "mistral"
18+
Deepseek Provider = "deepseek"
19+
Groq Provider = "groq"
20+
Custom Provider = "custom"
21+
)
22+
23+
type SOCAIConfig struct {
24+
AutoAnalyze bool
25+
IncidentCreation bool
26+
ChangeAlertStatus bool
27+
Provider string
28+
URL string
29+
Model string
30+
AuthType string // "custom-headers", "none"
31+
MaxTokens string
32+
CustomHeaders map[string]string // All headers including auth (from frontend)
33+
}
34+
35+
type ProviderVerificationBuilder struct {
36+
config SOCAIConfig
37+
}
38+
39+
func NewSocaiVerification(config SOCAIConfig) providers.IProvider {
40+
return &ProviderVerificationBuilder{
41+
config: config,
42+
}
43+
}
44+
45+
func (p *ProviderVerificationBuilder) Validate() error {
46+
var provider providers.IProvider
47+
48+
switch Provider(p.config.Provider) {
49+
case Openai:
50+
provider = providers.NewOpenAIProvider(p.config.Model, p.config.AuthType, p.config.CustomHeaders)
51+
case Anthropic:
52+
provider = providers.NewAnthropicProvider(p.config.Model, p.config.AuthType, p.config.CustomHeaders, p.config.MaxTokens)
53+
case Azure:
54+
provider = providers.NewAzureProvider(p.config.URL, p.config.Model, p.config.AuthType, p.config.CustomHeaders)
55+
case Gemini:
56+
provider = providers.NewGeminiProvider(p.config.Model, p.config.AuthType, p.config.CustomHeaders)
57+
case Ollama:
58+
provider = providers.NewOllamaProvider(p.config.URL, p.config.Model, p.config.AuthType, p.config.CustomHeaders)
59+
case Mistral:
60+
provider = providers.NewMistralProvider(p.config.Model, p.config.AuthType, p.config.CustomHeaders)
61+
case Deepseek:
62+
provider = providers.NewDeepSeekProvider(p.config.Model, p.config.AuthType, p.config.CustomHeaders)
63+
case Groq:
64+
provider = providers.NewGroqProvider(p.config.Model, p.config.AuthType, p.config.CustomHeaders)
65+
case Custom:
66+
provider = providers.NewCustomProvider(p.config.URL, p.config.Model, p.config.AuthType, p.config.CustomHeaders)
67+
default:
68+
return fmt.Errorf("unsupported provider: %s", p.config.Provider)
69+
}
70+
71+
return provider.Validate()
72+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package providers
2+
3+
import (
4+
"fmt"
5+
"maps"
6+
7+
"github.com/threatwinds/go-sdk/utils"
8+
)
9+
10+
type AbstractProvider struct {
11+
URL string
12+
Model string
13+
AuthType string
14+
CustomHeaders map[string]string
15+
}
16+
17+
func NewAbstractProvider(
18+
URL string,
19+
Model string,
20+
AuthType string,
21+
CustomHeaders map[string]string,
22+
) *AbstractProvider {
23+
return &AbstractProvider{
24+
URL: URL,
25+
Model: Model,
26+
AuthType: AuthType,
27+
CustomHeaders: CustomHeaders,
28+
}
29+
}
30+
31+
func (p *AbstractProvider) Validate() error {
32+
if p.AuthType == "" {
33+
p.AuthType = "none"
34+
}
35+
if p.AuthType != "custom-headers" && p.AuthType != "none" {
36+
return fmt.Errorf("Invalid authentication type '%s'. Must be 'custom-headers' or 'none'.", p.AuthType)
37+
}
38+
39+
return nil
40+
}
41+
42+
func (p *AbstractProvider) PerformTestRequest() (int, error) {
43+
headers := map[string]string{
44+
"Content-Type": "application/json",
45+
}
46+
47+
if p.AuthType == "custom-headers" {
48+
maps.Copy(headers, p.CustomHeaders)
49+
}
50+
51+
_, status, err := utils.DoReq[map[string]any](p.URL, nil, "POST", headers, false)
52+
return status, err
53+
}

0 commit comments

Comments
 (0)