This is page 4 of 5. Use http://codebase.md/blankcut/kubernetes-mcp-server?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .gitignore ├── docs │ ├── .astro │ │ ├── collections │ │ │ └── docs.schema.json │ │ ├── content-assets.mjs │ │ ├── content-modules.mjs │ │ ├── content.d.ts │ │ ├── data-store.json │ │ ├── settings.json │ │ └── types.d.ts │ ├── .gitignore │ ├── astro.config.mjs │ ├── package-lock.json │ ├── package.json │ ├── public │ │ └── images │ │ └── logo.svg │ ├── README.md │ ├── src │ │ ├── components │ │ │ ├── CodeBlock.astro │ │ │ ├── DocSidebar.astro │ │ │ ├── Footer.astro │ │ │ ├── Header.astro │ │ │ ├── HeadSEO.astro │ │ │ ├── Search.astro │ │ │ ├── Sidebar.astro │ │ │ └── TableOfContents.astro │ │ ├── content │ │ │ ├── config.ts │ │ │ └── docs │ │ │ ├── api-overview.md │ │ │ ├── configuration.md │ │ │ ├── installation.md │ │ │ ├── introduction.md │ │ │ ├── model-context-protocol.md │ │ │ ├── quick-start.md │ │ │ └── troubleshooting-resources.md │ │ ├── env.d.ts │ │ ├── layouts │ │ │ ├── BaseLayout.astro │ │ │ └── DocLayout.astro │ │ ├── pages │ │ │ ├── [...slug].astro │ │ │ ├── 404.astro │ │ │ ├── docs │ │ │ │ └── index.astro │ │ │ ├── docs-test.astro │ │ │ ├── examples │ │ │ │ └── index.astro │ │ │ └── index.astro │ │ └── styles │ │ └── global.css │ ├── tailwind.config.cjs │ └── tsconfig.json ├── go.mod ├── kubernetes-claude-mcp │ ├── .gitignore │ ├── cmd │ │ └── server │ │ └── main.go │ ├── docker-compose.yml │ ├── Dockerfile │ ├── go.mod │ ├── go.sum │ ├── internal │ │ ├── api │ │ │ ├── namespace_routes.go │ │ │ ├── routes.go │ │ │ └── server.go │ │ ├── argocd │ │ │ ├── applications.go │ │ │ ├── client.go │ │ │ └── history.go │ │ ├── auth │ │ │ ├── credentials.go │ │ │ ├── secrets.go │ │ │ └── vault.go │ │ ├── claude │ │ │ ├── client.go │ │ │ └── protocol.go │ │ ├── correlator │ │ │ ├── gitops.go │ │ │ ├── helm_correlator.go │ │ │ └── troubleshoot.go │ │ ├── gitlab │ │ │ ├── client.go │ │ │ ├── mergerequests.go │ │ │ ├── pipelines.go │ │ │ └── repositories.go │ │ ├── helm │ │ │ └── parser.go │ │ ├── k8s │ │ │ ├── client.go │ │ │ ├── enhanced_client.go │ │ │ ├── events.go │ │ │ ├── resource_mapper.go │ │ │ └── resources.go │ │ ├── mcp │ │ │ ├── context.go │ │ │ ├── namespace_analyzer.go │ │ │ ├── prompt.go │ │ │ └── protocol.go │ │ └── models │ │ ├── argocd.go │ │ ├── context.go │ │ ├── gitlab.go │ │ └── kubernetes.go │ └── pkg │ ├── config │ │ └── config.go │ ├── logging │ │ └── logging.go │ └── utils │ ├── serialization.go │ └── truncation.go ├── LICENSE └── README.md ``` # Files -------------------------------------------------------------------------------- /kubernetes-claude-mcp/internal/correlator/gitops.go: -------------------------------------------------------------------------------- ```go 1 | package correlator 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | "time" 8 | 9 | "github.com/Blankcut/kubernetes-mcp-server/kubernetes-claude-mcp/internal/argocd" 10 | "github.com/Blankcut/kubernetes-mcp-server/kubernetes-claude-mcp/internal/gitlab" 11 | "github.com/Blankcut/kubernetes-mcp-server/kubernetes-claude-mcp/internal/k8s" 12 | "github.com/Blankcut/kubernetes-mcp-server/kubernetes-claude-mcp/internal/models" 13 | "github.com/Blankcut/kubernetes-mcp-server/kubernetes-claude-mcp/pkg/logging" 14 | ) 15 | 16 | // GitOpsCorrelator correlates data between Kubernetes, ArgoCD, and GitLab 17 | type GitOpsCorrelator struct { 18 | k8sClient *k8s.Client 19 | argoClient *argocd.Client 20 | gitlabClient *gitlab.Client 21 | helmCorrelator *HelmCorrelator 22 | logger *logging.Logger 23 | } 24 | 25 | // NewGitOpsCorrelator creates a new GitOps correlator 26 | func NewGitOpsCorrelator(k8sClient *k8s.Client, argoClient *argocd.Client, gitlabClient *gitlab.Client, logger *logging.Logger) *GitOpsCorrelator { 27 | if logger == nil { 28 | logger = logging.NewLogger().Named("correlator") 29 | } 30 | 31 | correlator := &GitOpsCorrelator{ 32 | k8sClient: k8sClient, 33 | argoClient: argoClient, 34 | gitlabClient: gitlabClient, 35 | logger: logger, 36 | } 37 | 38 | // Initialize the Helm correlator 39 | correlator.helmCorrelator = NewHelmCorrelator(gitlabClient, logger.Named("helm")) 40 | 41 | return correlator 42 | } 43 | 44 | // Add a new method to analyze merge requests 45 | func (c *GitOpsCorrelator) AnalyzeMergeRequest( 46 | ctx context.Context, 47 | projectID string, 48 | mergeRequestIID int, 49 | ) ([]models.ResourceContext, error) { 50 | c.logger.Info("Analyzing merge request", "projectID", projectID, "mergeRequestIID", mergeRequestIID) 51 | 52 | // Get merge request details 53 | mergeRequest, err := c.gitlabClient.AnalyzeMergeRequest(ctx, projectID, mergeRequestIID) 54 | if err != nil { 55 | return nil, fmt.Errorf("failed to analyze merge request: %w", err) 56 | } 57 | 58 | // Check if the MR affects Helm charts or Kubernetes manifests 59 | if !mergeRequest.MergeRequestContext.HelmChartAffected && !mergeRequest.MergeRequestContext.KubernetesManifest { 60 | c.logger.Info("Merge request does not affect Kubernetes resources") 61 | return []models.ResourceContext{}, nil 62 | } 63 | 64 | // Get all ArgoCD applications 65 | argoApps, err := c.argoClient.ListApplications(ctx) 66 | if err != nil { 67 | return nil, fmt.Errorf("failed to list ArgoCD applications: %w", err) 68 | } 69 | 70 | // Find the project path 71 | projectPath := fmt.Sprintf("%s", projectID) 72 | project, err := c.gitlabClient.GetProject(ctx, projectID) 73 | if err == nil && project != nil { 74 | projectPath = project.PathWithNamespace 75 | } 76 | 77 | // For Helm-affected MRs, analyze Helm changes 78 | var helmAffectedResources []string 79 | if mergeRequest.MergeRequestContext.HelmChartAffected { 80 | helmResources, err := c.helmCorrelator.AnalyzeMergeRequestHelmChanges(ctx, projectID, mergeRequestIID) 81 | if err != nil { 82 | c.logger.Warn("Failed to analyze Helm changes in MR", "error", err) 83 | } else if len(helmResources) > 0 { 84 | helmAffectedResources = helmResources 85 | c.logger.Info("Found resources affected by Helm changes in MR", "count", len(helmResources)) 86 | } 87 | } 88 | 89 | // Identify potentially affected applications 90 | var affectedApps []models.ArgoApplication 91 | for _, app := range argoApps { 92 | if isAppSourcedFromProject(app, projectPath) { 93 | // For each file changed in the MR, check if it affects the app 94 | isAffected := false 95 | 96 | // Check if any changed file affects the app 97 | for _, file := range mergeRequest.MergeRequestContext.AffectedFiles { 98 | if isFileInAppSourcePath(app, file) { 99 | isAffected = true 100 | break 101 | } 102 | } 103 | 104 | // Check Helm-derived resources 105 | if !isAffected && len(helmAffectedResources) > 0 { 106 | if appContainsAnyResource(ctx, c.argoClient, app, helmAffectedResources) { 107 | isAffected = true 108 | } 109 | } 110 | 111 | if isAffected { 112 | affectedApps = append(affectedApps, app) 113 | } 114 | } 115 | } 116 | 117 | // For each affected app, identify the resources that would be affected 118 | var result []models.ResourceContext 119 | for _, app := range affectedApps { 120 | c.logger.Info("Found potentially affected ArgoCD application", "app", app.Name) 121 | 122 | // Get resources managed by this application 123 | tree, err := c.argoClient.GetResourceTree(ctx, app.Name) 124 | if err != nil { 125 | c.logger.Warn("Failed to get resource tree", "app", app.Name, "error", err) 126 | continue 127 | } 128 | 129 | // For each resource in the app, create a deployment info object 130 | for _, node := range tree.Nodes { 131 | // Skip non-Kubernetes resources or resources with no name/namespace 132 | if node.Kind == "" || node.Name == "" { 133 | continue 134 | } 135 | 136 | // Avoid unnecessary duplicates in the result 137 | if isResourceAlreadyInResults(result, node.Kind, node.Name, node.Namespace) { 138 | continue 139 | } 140 | 141 | // Trace the deployment for this resource 142 | resourceContext, err := c.TraceResourceDeployment( 143 | ctx, 144 | node.Namespace, 145 | node.Kind, 146 | node.Name, 147 | ) 148 | if err != nil { 149 | c.logger.Warn("Failed to trace resource deployment", 150 | "kind", node.Kind, 151 | "name", node.Name, 152 | "namespace", node.Namespace, 153 | "error", err) 154 | continue 155 | } 156 | 157 | // Add source info 158 | resourceContext.RelatedResources = append(resourceContext.RelatedResources, 159 | fmt.Sprintf("MergeRequest/%d", mergeRequestIID)) 160 | 161 | // Add to results 162 | result = append(result, resourceContext) 163 | } 164 | } 165 | 166 | // Add cleanup on exit 167 | defer func() { 168 | if c.helmCorrelator != nil { 169 | c.helmCorrelator.Cleanup() 170 | } 171 | }() 172 | 173 | c.logger.Info("Analysis of merge request completed", 174 | "projectID", projectID, 175 | "mergeRequestIID", mergeRequestIID, 176 | "resourceCount", len(result)) 177 | 178 | return result, nil 179 | } 180 | 181 | func (c *GitOpsCorrelator) TraceResourceDeployment( 182 | ctx context.Context, 183 | namespace, kind, name string, 184 | ) (models.ResourceContext, error) { 185 | c.logger.Info("Tracing resource deployment", "kind", kind, "name", name, "namespace", namespace) 186 | 187 | resourceContext := models.ResourceContext{ 188 | Kind: kind, 189 | Name: name, 190 | Namespace: namespace, 191 | } 192 | 193 | var errors []string 194 | 195 | // Get Kubernetes resource information 196 | resource, err := c.k8sClient.GetResource(ctx, kind, namespace, name) 197 | if err != nil { 198 | errMsg := fmt.Sprintf("Failed to get Kubernetes resource: %v", err) 199 | errors = append(errors, errMsg) 200 | c.logger.Warn(errMsg) 201 | } else { 202 | resourceContext.APIVersion = resource.GetAPIVersion() 203 | 204 | // Get events related to this resource 205 | events, err := c.k8sClient.GetResourceEvents(ctx, namespace, kind, name) 206 | if err != nil { 207 | errMsg := fmt.Sprintf("Failed to get resource events: %v", err) 208 | errors = append(errors, errMsg) 209 | c.logger.Warn(errMsg) 210 | } else { 211 | resourceContext.Events = events 212 | } 213 | } 214 | 215 | // Find the ArgoCD application managing this resource 216 | argoApps, err := c.argoClient.FindApplicationsByResource(ctx, kind, name, namespace) 217 | if err != nil { 218 | errMsg := fmt.Sprintf("Failed to find ArgoCD applications: %v", err) 219 | errors = append(errors, errMsg) 220 | c.logger.Warn(errMsg) 221 | } else if len(argoApps) > 0 { 222 | // Use the first application that manages this resource 223 | app := argoApps[0] 224 | resourceContext.ArgoApplication = &app 225 | resourceContext.ArgoSyncStatus = app.Status.Sync.Status 226 | resourceContext.ArgoHealthStatus = app.Status.Health.Status 227 | 228 | // Get recent syncs 229 | history, err := c.argoClient.GetApplicationHistory(ctx, app.Name) 230 | if err != nil { 231 | errMsg := fmt.Sprintf("Failed to get application history: %v", err) 232 | errors = append(errors, errMsg) 233 | c.logger.Warn(errMsg) 234 | } else { 235 | // Limit to recent syncs (last 5) 236 | if len(history) > 5 { 237 | history = history[:5] 238 | } 239 | resourceContext.ArgoSyncHistory = history 240 | } 241 | 242 | // Connect to GitLab if we have source information 243 | if app.Spec.Source.RepoURL != "" { 244 | // Extract GitLab project path from repo URL 245 | projectPath := extractGitLabProjectPath(app.Spec.Source.RepoURL) 246 | if projectPath != "" { 247 | project, err := c.gitlabClient.GetProjectByPath(ctx, projectPath) 248 | if err != nil { 249 | errMsg := fmt.Sprintf("Failed to get GitLab project: %v", err) 250 | errors = append(errors, errMsg) 251 | c.logger.Warn(errMsg) 252 | } else { 253 | resourceContext.GitLabProject = project 254 | 255 | // Get recent pipelines 256 | pipelines, err := c.gitlabClient.ListPipelines(ctx, fmt.Sprintf("%d", project.ID)) 257 | if err != nil { 258 | errMsg := fmt.Sprintf("Failed to list pipelines: %v", err) 259 | errors = append(errors, errMsg) 260 | c.logger.Warn(errMsg) 261 | } else { 262 | // Get the latest pipeline 263 | if len(pipelines) > 0 { 264 | resourceContext.LastPipeline = &pipelines[0] 265 | } 266 | } 267 | 268 | // Find environment from ArgoCD application 269 | environment := extractEnvironmentFromArgoApp(app) 270 | if environment != "" { 271 | // Get recent deployments to this environment 272 | deployments, err := c.gitlabClient.FindRecentDeployments( 273 | ctx, 274 | fmt.Sprintf("%d", project.ID), 275 | environment, 276 | ) 277 | if err != nil { 278 | errMsg := fmt.Sprintf("Failed to find deployments: %v", err) 279 | errors = append(errors, errMsg) 280 | c.logger.Warn(errMsg) 281 | } else if len(deployments) > 0 { 282 | resourceContext.LastDeployment = &deployments[0] 283 | } 284 | } 285 | 286 | // Get recent commits 287 | sinceTime := time.Now().Add(-24 * time.Hour) // Last 24 hours 288 | commits, err := c.gitlabClient.FindRecentChanges( 289 | ctx, 290 | fmt.Sprintf("%d", project.ID), 291 | sinceTime, 292 | ) 293 | if err != nil { 294 | errMsg := fmt.Sprintf("Failed to find recent changes: %v", err) 295 | errors = append(errors, errMsg) 296 | c.logger.Warn(errMsg) 297 | } else { 298 | // Here we'll limit to recent commits (last 5)... 299 | if len(commits) > 5 { 300 | commits = commits[:5] 301 | } 302 | resourceContext.RecentCommits = commits 303 | } 304 | } 305 | } 306 | } 307 | } 308 | 309 | // Collect any errors that occurred during correlation 310 | resourceContext.Errors = errors 311 | 312 | c.logger.Info("Resource deployment traced", 313 | "kind", kind, 314 | "name", name, 315 | "namespace", namespace, 316 | "argoApp", resourceContext.ArgoApplication != nil, 317 | "gitlabProject", resourceContext.GitLabProject != nil, 318 | "errors", len(errors)) 319 | 320 | return resourceContext, nil 321 | } 322 | 323 | // isFileInAppSourcePath checks if a file is in the application's source path 324 | func isFileInAppSourcePath(app models.ArgoApplication, file string) bool { 325 | sourcePath := app.Spec.Source.Path 326 | if sourcePath == "" { 327 | // If no specific path is provided, any file could affect the app 328 | return true 329 | } 330 | 331 | return strings.HasPrefix(file, sourcePath) 332 | } 333 | 334 | // hasHelmChanges checks if any of the changed files are related to Helm charts 335 | func hasHelmChanges(diffs []models.GitLabDiff) bool { 336 | for _, diff := range diffs { 337 | path := diff.NewPath 338 | 339 | if strings.Contains(path, "Chart.yaml") || 340 | strings.Contains(path, "values.yaml") || 341 | (strings.Contains(path, "templates/") && strings.HasSuffix(path, ".yaml")) { 342 | return true 343 | } 344 | } 345 | 346 | return false 347 | } 348 | 349 | // appContainsAnyResource checks if an ArgoCD application contains any of the specified resources 350 | func appContainsAnyResource(ctx context.Context, argoClient *argocd.Client, app models.ArgoApplication, resources []string) bool { 351 | tree, err := argoClient.GetResourceTree(ctx, app.Name) 352 | if err != nil { 353 | return false 354 | } 355 | 356 | for _, resource := range resources { 357 | parts := strings.Split(resource, "/") 358 | 359 | if len(parts) == 2 { 360 | // Format: Kind/Name 361 | kind := parts[0] 362 | name := parts[1] 363 | 364 | for _, node := range tree.Nodes { 365 | if strings.EqualFold(node.Kind, kind) && node.Name == name { 366 | return true 367 | } 368 | } 369 | } else if len(parts) == 3 { 370 | // Format: Namespace/Kind/Name 371 | namespace := parts[0] 372 | kind := parts[1] 373 | name := parts[2] 374 | 375 | for _, node := range tree.Nodes { 376 | if strings.EqualFold(node.Kind, kind) && node.Name == name && node.Namespace == namespace { 377 | return true 378 | } 379 | } 380 | } 381 | } 382 | 383 | return false 384 | } 385 | 386 | // FindResourcesAffectedByCommit finds resources affected by a specific Git commit 387 | func (c *GitOpsCorrelator) FindResourcesAffectedByCommit( 388 | ctx context.Context, 389 | projectID string, 390 | commitSHA string, 391 | ) ([]models.ResourceContext, error) { 392 | c.logger.Info("Finding resources affected by commit", "projectID", projectID, "commitSHA", commitSHA) 393 | 394 | var result []models.ResourceContext 395 | 396 | // Get commit information from GitLab 397 | commit, err := c.gitlabClient.GetCommit(ctx, projectID, commitSHA) 398 | if err != nil { 399 | return nil, fmt.Errorf("failed to get commit: %w", err) 400 | } 401 | c.logger.Info("Processing commit", "author", commit.AuthorName, "message", commit.Title) 402 | 403 | // Get commit diff to see what files were changed 404 | diffs, err := c.gitlabClient.GetCommitDiff(ctx, projectID, commitSHA) 405 | if err != nil { 406 | return nil, fmt.Errorf("failed to get commit diff: %w", err) 407 | } 408 | 409 | // Get all ArgoCD applications 410 | argoApps, err := c.argoClient.ListApplications(ctx) 411 | if err != nil { 412 | return nil, fmt.Errorf("failed to list ArgoCD applications: %w", err) 413 | } 414 | 415 | // Find applications that use this GitLab project as source 416 | projectPath := fmt.Sprintf("%s", projectID) // This might need more parsing depending on projectID format 417 | project, err := c.gitlabClient.GetProject(ctx, projectID) 418 | if err == nil && project != nil { 419 | projectPath = project.PathWithNamespace 420 | } 421 | 422 | // For each application, check if it's affected by the changed files 423 | for _, app := range argoApps { 424 | if !isAppSourcedFromProject(app, projectPath) { 425 | continue 426 | } 427 | 428 | // Check if the commit affects files used by this application 429 | if isAppAffectedByDiffs(app, diffs) { 430 | c.logger.Info("Found affected ArgoCD application", "app", app.Name) 431 | 432 | // Get resources managed by this application 433 | tree, err := c.argoClient.GetResourceTree(ctx, app.Name) 434 | if err != nil { 435 | c.logger.Warn("Failed to get resource tree", "app", app.Name, "error", err) 436 | continue 437 | } 438 | 439 | // For each resource in the app, create a deployment info object 440 | for _, node := range tree.Nodes { 441 | // Skip non-Kubernetes resources or resources with no name/namespace 442 | if node.Kind == "" || node.Name == "" { 443 | continue 444 | } 445 | 446 | // Avoid unnecessary duplicates in the result 447 | if isResourceAlreadyInResults(result, node.Kind, node.Name, node.Namespace) { 448 | continue 449 | } 450 | 451 | // Trace the deployment for this resource 452 | resourceContext, err := c.TraceResourceDeployment( 453 | ctx, 454 | node.Namespace, 455 | node.Kind, 456 | node.Name, 457 | ) 458 | if err != nil { 459 | c.logger.Warn("Failed to trace resource deployment", 460 | "kind", node.Kind, 461 | "name", node.Name, 462 | "namespace", node.Namespace, 463 | "error", err) 464 | continue 465 | } 466 | 467 | result = append(result, resourceContext) 468 | } 469 | } 470 | } 471 | 472 | c.logger.Info("Found resources affected by commit", 473 | "projectID", projectID, 474 | "commitSHA", commitSHA, 475 | "resourceCount", len(result)) 476 | 477 | return result, nil 478 | } 479 | 480 | // Helper functions 481 | 482 | // extractGitLabProjectPath extracts the GitLab project path from a repo URL 483 | func extractGitLabProjectPath(repoURL string) string { 484 | // Handle different URL formats 485 | 486 | // Format: https://gitlab.com/namespace/project.git 487 | if strings.HasPrefix(repoURL, "https://") || strings.HasPrefix(repoURL, "http://") { 488 | parts := strings.Split(repoURL, "/") 489 | if len(parts) < 3 { 490 | return "" 491 | } 492 | 493 | // Remove ".git" suffix if present 494 | lastPart := parts[len(parts)-1] 495 | if strings.HasSuffix(lastPart, ".git") { 496 | parts[len(parts)-1] = lastPart[:len(lastPart)-4] 497 | } 498 | 499 | // Reconstruct path without protocol and domain 500 | domainIndex := 2 // After http:// or https:// 501 | if len(parts) <= domainIndex+1 { 502 | return "" 503 | } 504 | 505 | return strings.Join(parts[domainIndex+1:], "/") 506 | } 507 | 508 | // Format: [email protected]:namespace/project.git 509 | if strings.HasPrefix(repoURL, "git@") { 510 | // Split at ":" to get the path part 511 | parts := strings.Split(repoURL, ":") 512 | if len(parts) != 2 { 513 | return "" 514 | } 515 | 516 | // Remove ".git" suffix if present 517 | pathPart := parts[1] 518 | if strings.HasSuffix(pathPart, ".git") { 519 | pathPart = pathPart[:len(pathPart)-4] 520 | } 521 | 522 | return pathPart 523 | } 524 | 525 | return "" 526 | } 527 | 528 | // extractEnvironmentFromArgoApp tries to determine the environment from an ArgoCD application 529 | func extractEnvironmentFromArgoApp(app models.ArgoApplication) string { 530 | // Check for environment in labels 531 | if env, ok := app.Metadata.Labels["environment"]; ok { 532 | return env 533 | } 534 | if env, ok := app.Metadata.Labels["env"]; ok { 535 | return env 536 | } 537 | 538 | // Check if environment is in the destination namespace 539 | if strings.Contains(app.Spec.Destination.Namespace, "prod") { 540 | return "production" 541 | } 542 | if strings.Contains(app.Spec.Destination.Namespace, "staging") { 543 | return "staging" 544 | } 545 | if strings.Contains(app.Spec.Destination.Namespace, "dev") { 546 | return "development" 547 | } 548 | 549 | // Check path in source for environment indicators 550 | if app.Spec.Source.Path != "" { 551 | if strings.Contains(app.Spec.Source.Path, "prod") { 552 | return "production" 553 | } 554 | if strings.Contains(app.Spec.Source.Path, "staging") { 555 | return "staging" 556 | } 557 | if strings.Contains(app.Spec.Source.Path, "dev") { 558 | return "development" 559 | } 560 | } 561 | 562 | // Default to destination namespace as a fallback 563 | return app.Spec.Destination.Namespace 564 | } 565 | 566 | // isAppSourcedFromProject checks if an ArgoCD application uses a specific GitLab project 567 | func isAppSourcedFromProject(app models.ArgoApplication, projectPath string) bool { 568 | // Extract project path from app's repo URL 569 | appProjectPath := extractGitLabProjectPath(app.Spec.Source.RepoURL) 570 | 571 | // Compare paths 572 | return strings.EqualFold(appProjectPath, projectPath) 573 | } 574 | 575 | // isAppAffectedByDiffs checks if application manifests are affected by file changes 576 | func isAppAffectedByDiffs(app models.ArgoApplication, diffs []models.GitLabDiff) bool { 577 | sourcePath := app.Spec.Source.Path 578 | if sourcePath == "" { 579 | // If no specific path is provided, any change could affect the app 580 | return true 581 | } 582 | 583 | // Check if any changed file is in the application's source path 584 | for _, diff := range diffs { 585 | if strings.HasPrefix(diff.NewPath, sourcePath) || strings.HasPrefix(diff.OldPath, sourcePath) { 586 | return true 587 | } 588 | } 589 | 590 | return false 591 | } 592 | 593 | // isResourceAlreadyInResults checks if a resource is already in the results list 594 | func isResourceAlreadyInResults(results []models.ResourceContext, kind, name, namespace string) bool { 595 | for _, rc := range results { 596 | if rc.Kind == kind && rc.Name == name && rc.Namespace == namespace { 597 | return true 598 | } 599 | } 600 | return false 601 | } 602 | ``` -------------------------------------------------------------------------------- /kubernetes-claude-mcp/internal/k8s/resource_mapper.go: -------------------------------------------------------------------------------- ```go 1 | package k8s 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 10 | "k8s.io/apimachinery/pkg/runtime/schema" 11 | 12 | "github.com/Blankcut/kubernetes-mcp-server/kubernetes-claude-mcp/pkg/logging" 13 | ) 14 | 15 | // ResourceMapper maps relationships between Kubernetes resources 16 | type ResourceMapper struct { 17 | client *Client 18 | logger *logging.Logger 19 | } 20 | 21 | // ResourceRelationship represents a relationship between two resources 22 | type ResourceRelationship struct { 23 | SourceKind string `json:"sourceKind"` 24 | SourceName string `json:"sourceName"` 25 | SourceNamespace string `json:"sourceNamespace"` 26 | TargetKind string `json:"targetKind"` 27 | TargetName string `json:"targetName"` 28 | TargetNamespace string `json:"targetNamespace"` 29 | RelationType string `json:"relationType"` 30 | } 31 | 32 | // NamespaceTopology represents the topology of resources in a namespace 33 | type NamespaceTopology struct { 34 | Namespace string `json:"namespace"` 35 | Resources map[string][]string `json:"resources"` 36 | Relationships []ResourceRelationship `json:"relationships"` 37 | Metrics map[string]map[string]int `json:"metrics"` 38 | Health map[string]map[string]string `json:"health"` 39 | } 40 | 41 | // NewResourceMapper creates a new resource mapper 42 | func NewResourceMapper(client *Client) *ResourceMapper { 43 | return &ResourceMapper{ 44 | client: client, 45 | logger: client.logger.Named("resource-mapper"), 46 | } 47 | } 48 | 49 | // GetNamespaceTopology maps all resources and their relationships in a namespace 50 | func (m *ResourceMapper) GetNamespaceTopology(ctx context.Context, namespace string) (*NamespaceTopology, error) { 51 | m.logger.Info("Mapping namespace topology", "namespace", namespace) 52 | 53 | // Initialize topology 54 | topology := &NamespaceTopology{ 55 | Namespace: namespace, 56 | Resources: make(map[string][]string), 57 | Relationships: []ResourceRelationship{}, 58 | Metrics: make(map[string]map[string]int), 59 | Health: make(map[string]map[string]string), 60 | } 61 | 62 | // Discover all available resource types 63 | resources, err := m.client.discoveryClient.ServerPreferredResources() 64 | if err != nil { 65 | return nil, fmt.Errorf("failed to get server resources: %w", err) 66 | } 67 | 68 | // Collect all namespaced resources 69 | for _, resourceList := range resources { 70 | gv, err := schema.ParseGroupVersion(resourceList.GroupVersion) 71 | if err != nil { 72 | m.logger.Warn("Failed to parse group version", "groupVersion", resourceList.GroupVersion) 73 | continue 74 | } 75 | 76 | for _, r := range resourceList.APIResources { 77 | // Skip resources that can't be listed or aren't namespaced 78 | if !strings.Contains(r.Verbs.String(), "list") || !r.Namespaced { 79 | continue 80 | } 81 | 82 | // Build GVR for this resource type 83 | gvr := schema.GroupVersionResource{ 84 | Group: gv.Group, 85 | Version: gv.Version, 86 | Resource: r.Name, 87 | } 88 | 89 | // List resources of this type 90 | m.logger.Debug("Listing resources", "namespace", namespace, "resource", r.Name) 91 | list, err := m.client.dynamicClient.Resource(gvr).Namespace(namespace).List(ctx, metav1.ListOptions{}) 92 | if err != nil { 93 | m.logger.Warn("Failed to list resources", 94 | "namespace", namespace, 95 | "resource", r.Name, 96 | "error", err) 97 | continue 98 | } 99 | 100 | // Add to topology 101 | if len(list.Items) > 0 { 102 | topology.Resources[r.Kind] = make([]string, len(list.Items)) 103 | topology.Metrics[r.Kind] = map[string]int{"count": len(list.Items)} 104 | topology.Health[r.Kind] = make(map[string]string) 105 | 106 | for i, item := range list.Items { 107 | topology.Resources[r.Kind][i] = item.GetName() 108 | 109 | // Determine health status 110 | health := m.determineResourceHealth(&item) 111 | topology.Health[r.Kind][item.GetName()] = health 112 | } 113 | 114 | // Find relationships for this resource type 115 | relationships := m.findRelationships(ctx, list.Items, namespace) 116 | topology.Relationships = append(topology.Relationships, relationships...) 117 | } 118 | } 119 | } 120 | 121 | m.logger.Info("Namespace topology mapped", 122 | "namespace", namespace, 123 | "resourceTypes", len(topology.Resources), 124 | "relationships", len(topology.Relationships)) 125 | 126 | return topology, nil 127 | } 128 | 129 | // GetResourceGraph returns a resource graph for visualization 130 | func (m *ResourceMapper) GetResourceGraph(ctx context.Context, namespace string) (map[string]interface{}, error) { 131 | topology, err := m.GetNamespaceTopology(ctx, namespace) 132 | if err != nil { 133 | return nil, err 134 | } 135 | 136 | // Convert topology to graph format 137 | graph := map[string]interface{}{ 138 | "nodes": []map[string]interface{}{}, 139 | "edges": []map[string]interface{}{}, 140 | } 141 | 142 | // Add nodes 143 | nodeIndex := make(map[string]int) 144 | nodeCount := 0 145 | 146 | for kind, resources := range topology.Resources { 147 | for _, name := range resources { 148 | health := "unknown" 149 | if h, ok := topology.Health[kind][name]; ok { 150 | health = h 151 | } 152 | 153 | node := map[string]interface{}{ 154 | "id": nodeCount, 155 | "kind": kind, 156 | "name": name, 157 | "health": health, 158 | "group": kind, 159 | } 160 | 161 | // Add to nodes array 162 | graph["nodes"] = append(graph["nodes"].([]map[string]interface{}), node) 163 | 164 | // Save index for edge creation 165 | nodeIndex[fmt.Sprintf("%s/%s", kind, name)] = nodeCount 166 | nodeCount++ 167 | } 168 | } 169 | 170 | // Add edges 171 | for _, rel := range topology.Relationships { 172 | sourceKey := fmt.Sprintf("%s/%s", rel.SourceKind, rel.SourceName) 173 | targetKey := fmt.Sprintf("%s/%s", rel.TargetKind, rel.TargetName) 174 | 175 | sourceIdx, sourceOk := nodeIndex[sourceKey] 176 | targetIdx, targetOk := nodeIndex[targetKey] 177 | 178 | if sourceOk && targetOk { 179 | edge := map[string]interface{}{ 180 | "source": sourceIdx, 181 | "target": targetIdx, 182 | "relationship": rel.RelationType, 183 | } 184 | 185 | graph["edges"] = append(graph["edges"].([]map[string]interface{}), edge) 186 | } 187 | } 188 | 189 | return graph, nil 190 | } 191 | 192 | // findRelationships discovers relationships between resources 193 | func (m *ResourceMapper) findRelationships(ctx context.Context, resources []unstructured.Unstructured, namespace string) []ResourceRelationship { 194 | var relationships []ResourceRelationship 195 | 196 | for _, resource := range resources { 197 | // Check owner references 198 | for _, ownerRef := range resource.GetOwnerReferences() { 199 | rel := ResourceRelationship{ 200 | SourceKind: ownerRef.Kind, 201 | SourceName: ownerRef.Name, 202 | SourceNamespace: namespace, 203 | TargetKind: resource.GetKind(), 204 | TargetName: resource.GetName(), 205 | TargetNamespace: namespace, 206 | RelationType: "owns", 207 | } 208 | relationships = append(relationships, rel) 209 | } 210 | 211 | // Check for Pod -> Service relationships (via labels/selectors) 212 | if resource.GetKind() == "Service" { 213 | selector, found, _ := unstructured.NestedMap(resource.Object, "spec", "selector") 214 | if found && len(selector) > 0 { 215 | // Find pods matching this selector 216 | pods, err := m.client.clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{ 217 | LabelSelector: m.labelsToSelector(selector), 218 | }) 219 | 220 | if err == nil { 221 | for _, pod := range pods.Items { 222 | rel := ResourceRelationship{ 223 | SourceKind: "Service", 224 | SourceName: resource.GetName(), 225 | SourceNamespace: namespace, 226 | TargetKind: "Pod", 227 | TargetName: pod.Name, 228 | TargetNamespace: namespace, 229 | RelationType: "selects", 230 | } 231 | relationships = append(relationships, rel) 232 | } 233 | } 234 | } 235 | } 236 | 237 | // Check for ConfigMap/Secret references in Pods 238 | if resource.GetKind() == "Pod" { 239 | // Check volumes for ConfigMap references 240 | volumes, found, _ := unstructured.NestedSlice(resource.Object, "spec", "volumes") 241 | if found { 242 | for _, v := range volumes { 243 | volume, ok := v.(map[string]interface{}) 244 | if !ok { 245 | continue 246 | } 247 | 248 | // Check for ConfigMap references 249 | if configMap, hasConfigMap, _ := unstructured.NestedMap(volume, "configMap"); hasConfigMap { 250 | if cmName, hasName, _ := unstructured.NestedString(configMap, "name"); hasName { 251 | rel := ResourceRelationship{ 252 | SourceKind: "Pod", 253 | SourceName: resource.GetName(), 254 | SourceNamespace: namespace, 255 | TargetKind: "ConfigMap", 256 | TargetName: cmName, 257 | TargetNamespace: namespace, 258 | RelationType: "mounts", 259 | } 260 | relationships = append(relationships, rel) 261 | } 262 | } 263 | 264 | // Check for Secret references 265 | if secret, hasSecret, _ := unstructured.NestedMap(volume, "secret"); hasSecret { 266 | if secretName, hasName, _ := unstructured.NestedString(secret, "secretName"); hasName { 267 | rel := ResourceRelationship{ 268 | SourceKind: "Pod", 269 | SourceName: resource.GetName(), 270 | SourceNamespace: namespace, 271 | TargetKind: "Secret", 272 | TargetName: secretName, 273 | TargetNamespace: namespace, 274 | RelationType: "mounts", 275 | } 276 | relationships = append(relationships, rel) 277 | } 278 | } 279 | } 280 | } 281 | 282 | // Check environment variables for ConfigMap/Secret references 283 | containers, found, _ := unstructured.NestedSlice(resource.Object, "spec", "containers") 284 | if found { 285 | for _, c := range containers { 286 | container, ok := c.(map[string]interface{}) 287 | if !ok { 288 | continue 289 | } 290 | 291 | // Check for EnvFrom references 292 | envFrom, hasEnvFrom, _ := unstructured.NestedSlice(container, "envFrom") 293 | if hasEnvFrom { 294 | for _, ef := range envFrom { 295 | envFromObj, ok := ef.(map[string]interface{}) 296 | if !ok { 297 | continue 298 | } 299 | 300 | // Check for ConfigMap references 301 | if configMap, hasConfigMap, _ := unstructured.NestedMap(envFromObj, "configMapRef"); hasConfigMap { 302 | if cmName, hasName, _ := unstructured.NestedString(configMap, "name"); hasName { 303 | rel := ResourceRelationship{ 304 | SourceKind: "Pod", 305 | SourceName: resource.GetName(), 306 | SourceNamespace: namespace, 307 | TargetKind: "ConfigMap", 308 | TargetName: cmName, 309 | TargetNamespace: namespace, 310 | RelationType: "configures", 311 | } 312 | relationships = append(relationships, rel) 313 | } 314 | } 315 | 316 | // Check for Secret references 317 | if secret, hasSecret, _ := unstructured.NestedMap(envFromObj, "secretRef"); hasSecret { 318 | if secretName, hasName, _ := unstructured.NestedString(secret, "name"); hasName { 319 | rel := ResourceRelationship{ 320 | SourceKind: "Pod", 321 | SourceName: resource.GetName(), 322 | SourceNamespace: namespace, 323 | TargetKind: "Secret", 324 | TargetName: secretName, 325 | TargetNamespace: namespace, 326 | RelationType: "configures", 327 | } 328 | relationships = append(relationships, rel) 329 | } 330 | } 331 | } 332 | } 333 | 334 | // Check individual env vars for ConfigMap/Secret references 335 | env, hasEnv, _ := unstructured.NestedSlice(container, "env") 336 | if hasEnv { 337 | for _, e := range env { 338 | envVar, ok := e.(map[string]interface{}) 339 | if !ok { 340 | continue 341 | } 342 | 343 | // Check for ConfigMap references 344 | if valueFrom, hasValueFrom, _ := unstructured.NestedMap(envVar, "valueFrom"); hasValueFrom { 345 | if configMap, hasConfigMap, _ := unstructured.NestedMap(valueFrom, "configMapKeyRef"); hasConfigMap { 346 | if cmName, hasName, _ := unstructured.NestedString(configMap, "name"); hasName { 347 | rel := ResourceRelationship{ 348 | SourceKind: "Pod", 349 | SourceName: resource.GetName(), 350 | SourceNamespace: namespace, 351 | TargetKind: "ConfigMap", 352 | TargetName: cmName, 353 | TargetNamespace: namespace, 354 | RelationType: "configures", 355 | } 356 | relationships = append(relationships, rel) 357 | } 358 | } 359 | 360 | // Check for Secret references 361 | if secret, hasSecret, _ := unstructured.NestedMap(valueFrom, "secretKeyRef"); hasSecret { 362 | if secretName, hasName, _ := unstructured.NestedString(secret, "name"); hasName { 363 | rel := ResourceRelationship{ 364 | SourceKind: "Pod", 365 | SourceName: resource.GetName(), 366 | SourceNamespace: namespace, 367 | TargetKind: "Secret", 368 | TargetName: secretName, 369 | TargetNamespace: namespace, 370 | RelationType: "configures", 371 | } 372 | relationships = append(relationships, rel) 373 | } 374 | } 375 | } 376 | } 377 | } 378 | } 379 | } 380 | } 381 | 382 | // Check for PVC -> PV relationships 383 | if resource.GetKind() == "PersistentVolumeClaim" { 384 | volumeName, found, _ := unstructured.NestedString(resource.Object, "spec", "volumeName") 385 | if found && volumeName != "" { 386 | rel := ResourceRelationship{ 387 | SourceKind: "PersistentVolumeClaim", 388 | SourceName: resource.GetName(), 389 | SourceNamespace: namespace, 390 | TargetKind: "PersistentVolume", 391 | TargetName: volumeName, 392 | TargetNamespace: "", 393 | RelationType: "binds", 394 | } 395 | relationships = append(relationships, rel) 396 | } 397 | } 398 | 399 | // Check for Ingress -> Service relationships 400 | if resource.GetKind() == "Ingress" { 401 | rules, found, _ := unstructured.NestedSlice(resource.Object, "spec", "rules") 402 | if found { 403 | for _, r := range rules { 404 | rule, ok := r.(map[string]interface{}) 405 | if !ok { 406 | continue 407 | } 408 | 409 | http, found, _ := unstructured.NestedMap(rule, "http") 410 | if !found { 411 | continue 412 | } 413 | 414 | paths, found, _ := unstructured.NestedSlice(http, "paths") 415 | if !found { 416 | continue 417 | } 418 | 419 | for _, p := range paths { 420 | path, ok := p.(map[string]interface{}) 421 | if !ok { 422 | continue 423 | } 424 | 425 | backend, found, _ := unstructured.NestedMap(path, "backend") 426 | if !found { 427 | // Check for newer API version format 428 | backend, found, _ = unstructured.NestedMap(path, "backend", "service") 429 | if !found { 430 | continue 431 | } 432 | } 433 | 434 | serviceName, found, _ := unstructured.NestedString(backend, "name") 435 | if found { 436 | rel := ResourceRelationship{ 437 | SourceKind: "Ingress", 438 | SourceName: resource.GetName(), 439 | SourceNamespace: namespace, 440 | TargetKind: "Service", 441 | TargetName: serviceName, 442 | TargetNamespace: namespace, 443 | RelationType: "routes", 444 | } 445 | relationships = append(relationships, rel) 446 | } 447 | } 448 | } 449 | } 450 | } 451 | } 452 | 453 | // Deduplicate relationships 454 | deduplicatedRelationships := make([]ResourceRelationship, 0) 455 | relMap := make(map[string]bool) 456 | 457 | for _, rel := range relationships { 458 | key := fmt.Sprintf("%s/%s/%s/%s/%s/%s/%s", 459 | rel.SourceKind, rel.SourceName, rel.SourceNamespace, 460 | rel.TargetKind, rel.TargetName, rel.TargetNamespace, 461 | rel.RelationType) 462 | 463 | if _, exists := relMap[key]; !exists { 464 | relMap[key] = true 465 | deduplicatedRelationships = append(deduplicatedRelationships, rel) 466 | } 467 | } 468 | 469 | return deduplicatedRelationships 470 | } 471 | 472 | // labelsToSelector converts a map of labels to a selector string 473 | func (m *ResourceMapper) labelsToSelector(labels map[string]interface{}) string { 474 | var selectors []string 475 | 476 | for key, value := range labels { 477 | if strValue, ok := value.(string); ok { 478 | selectors = append(selectors, fmt.Sprintf("%s=%s", key, strValue)) 479 | } 480 | } 481 | 482 | return strings.Join(selectors, ",") 483 | } 484 | 485 | // determineResourceHealth determines the health status of a resource 486 | func (m *ResourceMapper) determineResourceHealth(obj *unstructured.Unstructured) string { 487 | kind := obj.GetKind() 488 | 489 | // Check common status fields 490 | status, found, _ := unstructured.NestedMap(obj.Object, "status") 491 | if !found { 492 | return "unknown" 493 | } 494 | 495 | // Check different resource types 496 | switch kind { 497 | case "Pod": 498 | phase, found, _ := unstructured.NestedString(status, "phase") 499 | if found { 500 | switch phase { 501 | case "Running", "Succeeded": 502 | return "healthy" 503 | case "Pending": 504 | return "progressing" 505 | case "Failed": 506 | return "unhealthy" 507 | default: 508 | return "unknown" 509 | } 510 | } 511 | 512 | case "Deployment", "StatefulSet", "DaemonSet", "ReplicaSet": 513 | // Check if all replicas are available 514 | replicas, foundReplicas, _ := unstructured.NestedInt64(obj.Object, "spec", "replicas") 515 | if !foundReplicas { 516 | replicas = 1 // Default to 1 if not specified 517 | } 518 | 519 | availableReplicas, foundAvailable, _ := unstructured.NestedInt64(status, "availableReplicas") 520 | if foundAvailable && availableReplicas == replicas { 521 | return "healthy" 522 | } else if foundAvailable && availableReplicas > 0 { 523 | return "progressing" 524 | } else { 525 | return "unhealthy" 526 | } 527 | 528 | case "Service": 529 | // Services are typically healthy unless they have no endpoints 530 | // We'd need to check endpoints separately 531 | return "healthy" 532 | 533 | case "Ingress": 534 | // Check if LoadBalancer has assigned addresses 535 | ingress, found, _ := unstructured.NestedSlice(status, "loadBalancer", "ingress") 536 | if found && len(ingress) > 0 { 537 | return "healthy" 538 | } 539 | return "progressing" 540 | 541 | case "PersistentVolumeClaim": 542 | phase, found, _ := unstructured.NestedString(status, "phase") 543 | if found && phase == "Bound" { 544 | return "healthy" 545 | } else if found && phase == "Pending" { 546 | return "progressing" 547 | } else { 548 | return "unhealthy" 549 | } 550 | 551 | case "Job": 552 | conditions, found, _ := unstructured.NestedSlice(status, "conditions") 553 | if found { 554 | for _, c := range conditions { 555 | condition, ok := c.(map[string]interface{}) 556 | if !ok { 557 | continue 558 | } 559 | 560 | condType, typeFound, _ := unstructured.NestedString(condition, "type") 561 | condStatus, statusFound, _ := unstructured.NestedString(condition, "status") 562 | 563 | if typeFound && statusFound && condType == "Complete" && condStatus == "True" { 564 | return "healthy" 565 | } else if typeFound && statusFound && condType == "Failed" && condStatus == "True" { 566 | return "unhealthy" 567 | } 568 | } 569 | return "progressing" 570 | } 571 | 572 | default: 573 | // For other resources, try to check common status conditions 574 | conditions, found, _ := unstructured.NestedSlice(status, "conditions") 575 | if found { 576 | for _, c := range conditions { 577 | condition, ok := c.(map[string]interface{}) 578 | if !ok { 579 | continue 580 | } 581 | 582 | condType, typeFound, _ := unstructured.NestedString(condition, "type") 583 | condStatus, statusFound, _ := unstructured.NestedString(condition, "status") 584 | 585 | if typeFound && statusFound { 586 | // Check for common condition types indicating health 587 | if (condType == "Ready" || condType == "Available") && condStatus == "True" { 588 | return "healthy" 589 | } else if condType == "Progressing" && condStatus == "True" { 590 | return "progressing" 591 | } else if (condType == "Failed" || condType == "Error") && condStatus == "True" { 592 | return "unhealthy" 593 | } 594 | } 595 | } 596 | } 597 | } 598 | 599 | return "unknown" 600 | } ``` -------------------------------------------------------------------------------- /kubernetes-claude-mcp/internal/correlator/troubleshoot.go: -------------------------------------------------------------------------------- ```go 1 | package correlator 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/Blankcut/kubernetes-mcp-server/kubernetes-claude-mcp/internal/k8s" 9 | "github.com/Blankcut/kubernetes-mcp-server/kubernetes-claude-mcp/internal/models" 10 | "github.com/Blankcut/kubernetes-mcp-server/kubernetes-claude-mcp/pkg/logging" 11 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 12 | ) 13 | 14 | // TroubleshootCorrelator provides specialized logic for troubleshooting 15 | type TroubleshootCorrelator struct { 16 | gitOpsCorrelator *GitOpsCorrelator 17 | k8sClient *k8s.Client 18 | logger *logging.Logger 19 | } 20 | 21 | // NewTroubleshootCorrelator creates a new troubleshooting correlator 22 | func NewTroubleshootCorrelator(gitOpsCorrelator *GitOpsCorrelator, k8sClient *k8s.Client, logger *logging.Logger) *TroubleshootCorrelator { 23 | if logger == nil { 24 | logger = logging.NewLogger().Named("troubleshoot") 25 | } 26 | 27 | return &TroubleshootCorrelator{ 28 | gitOpsCorrelator: gitOpsCorrelator, 29 | k8sClient: k8sClient, 30 | logger: logger, 31 | } 32 | } 33 | 34 | // TroubleshootResource analyzes a resource for common issues 35 | func (tc *TroubleshootCorrelator) TroubleshootResource(ctx context.Context, namespace, kind, name string) (*models.TroubleshootResult, error) { 36 | tc.logger.Info("Troubleshooting resource", "kind", kind, "name", name, "namespace", namespace) 37 | 38 | // First, trace the resource deployment 39 | resourceContext, err := tc.gitOpsCorrelator.TraceResourceDeployment(ctx, namespace, kind, name) 40 | if err != nil { 41 | return nil, fmt.Errorf("failed to trace resource deployment: %w", err) 42 | } 43 | 44 | // Get the raw resource for detailed analysis 45 | resource, err := tc.k8sClient.GetResource(ctx, kind, namespace, name) 46 | if err != nil { 47 | tc.logger.Warn("Failed to get resource for detailed analysis", "error", err) 48 | } 49 | 50 | // Initialize troubleshooting result 51 | result := &models.TroubleshootResult{ 52 | ResourceContext: resourceContext, 53 | Issues: []models.Issue{}, 54 | Recommendations: []string{}, 55 | } 56 | 57 | // Analyze Kubernetes events for issues 58 | tc.analyzeKubernetesEvents(resourceContext, result) 59 | 60 | // Analyze resource status and conditions if resource was retrieved 61 | if resource != nil { 62 | // Pod-specific analysis 63 | if strings.EqualFold(kind, "pod") { 64 | tc.analyzePodStatus(ctx, resource, result) 65 | } 66 | 67 | // Deployment-specific analysis 68 | if strings.EqualFold(kind, "deployment") { 69 | tc.analyzeDeploymentStatus(resource, result) 70 | } 71 | } 72 | 73 | // Analyze ArgoCD sync status 74 | tc.analyzeArgoStatus(resourceContext, result) 75 | 76 | // Analyze GitLab pipeline status 77 | tc.analyzeGitLabStatus(resourceContext, result) 78 | 79 | // Check if resource is healthy 80 | if len(result.Issues) == 0 && resource != nil && !tc.isResourceHealthy(resource) { 81 | issue := models.Issue{ 82 | Source: "Kubernetes", 83 | Category: "UnknownIssue", 84 | Severity: "Warning", 85 | Title: "Resource Not Healthy", 86 | Description: fmt.Sprintf("%s %s/%s is not in a healthy state", kind, namespace, name), 87 | } 88 | result.Issues = append(result.Issues, issue) 89 | } 90 | 91 | // Generate recommendations based on issues 92 | tc.generateRecommendations(result) 93 | 94 | tc.logger.Info("Troubleshooting completed", 95 | "kind", kind, 96 | "name", name, 97 | "namespace", namespace, 98 | "issueCount", len(result.Issues), 99 | "recommendationCount", len(result.Recommendations)) 100 | 101 | return result, nil 102 | } 103 | 104 | // isResourceHealthy checks if a resource is in a healthy state 105 | func (tc *TroubleshootCorrelator) isResourceHealthy(resource *unstructured.Unstructured) bool { 106 | kind := resource.GetKind() 107 | 108 | // Pod health check 109 | if strings.EqualFold(kind, "pod") { 110 | phase, found, _ := unstructured.NestedString(resource.Object, "status", "phase") 111 | return found && phase == "Running" 112 | } 113 | 114 | // Deployment health check 115 | if strings.EqualFold(kind, "deployment") { 116 | // Check if available replicas match desired replicas 117 | desiredReplicas, found1, _ := unstructured.NestedInt64(resource.Object, "spec", "replicas") 118 | availableReplicas, found2, _ := unstructured.NestedInt64(resource.Object, "status", "availableReplicas") 119 | return found1 && found2 && desiredReplicas == availableReplicas && availableReplicas > 0 120 | } 121 | 122 | // Default: assume healthy 123 | return true 124 | } 125 | 126 | // analyzeDeploymentStatus analyzes deployment-specific status 127 | func (tc *TroubleshootCorrelator) analyzeDeploymentStatus(deployment *unstructured.Unstructured, result *models.TroubleshootResult) { 128 | // Check if deployment is ready 129 | desiredReplicas, found1, _ := unstructured.NestedInt64(deployment.Object, "spec", "replicas") 130 | availableReplicas, found2, _ := unstructured.NestedInt64(deployment.Object, "status", "availableReplicas") 131 | readyReplicas, found3, _ := unstructured.NestedInt64(deployment.Object, "status", "readyReplicas") 132 | 133 | if !found1 || !found2 || availableReplicas < desiredReplicas { 134 | issue := models.Issue{ 135 | Source: "Kubernetes", 136 | Category: "DeploymentNotAvailable", 137 | Severity: "Warning", 138 | Title: "Deployment Not Fully Available", 139 | Description: fmt.Sprintf("Deployment has %d/%d available replicas", availableReplicas, desiredReplicas), 140 | } 141 | result.Issues = append(result.Issues, issue) 142 | } 143 | 144 | if !found1 || !found3 || readyReplicas < desiredReplicas { 145 | issue := models.Issue{ 146 | Source: "Kubernetes", 147 | Category: "DeploymentNotReady", 148 | Severity: "Warning", 149 | Title: "Deployment Not Fully Ready", 150 | Description: fmt.Sprintf("Deployment has %d/%d ready replicas", readyReplicas, desiredReplicas), 151 | } 152 | result.Issues = append(result.Issues, issue) 153 | } 154 | 155 | // Check deployment conditions 156 | conditions, found, _ := unstructured.NestedSlice(deployment.Object, "status", "conditions") 157 | if found { 158 | for _, c := range conditions { 159 | condition, ok := c.(map[string]interface{}) 160 | if !ok { 161 | continue 162 | } 163 | 164 | conditionType, _, _ := unstructured.NestedString(condition, "type") 165 | status, _, _ := unstructured.NestedString(condition, "status") 166 | reason, _, _ := unstructured.NestedString(condition, "reason") 167 | message, _, _ := unstructured.NestedString(condition, "message") 168 | 169 | if conditionType == "Available" && status != "True" { 170 | issue := models.Issue{ 171 | Source: "Kubernetes", 172 | Category: "DeploymentNotAvailable", 173 | Severity: "Warning", 174 | Title: "Deployment Not Available", 175 | Description: fmt.Sprintf("Deployment availability issue: %s - %s", reason, message), 176 | } 177 | result.Issues = append(result.Issues, issue) 178 | } 179 | 180 | if conditionType == "Progressing" && status != "True" { 181 | issue := models.Issue{ 182 | Source: "Kubernetes", 183 | Category: "DeploymentNotProgressing", 184 | Severity: "Warning", 185 | Title: "Deployment Not Progressing", 186 | Description: fmt.Sprintf("Deployment progress issue: %s - %s", reason, message), 187 | } 188 | result.Issues = append(result.Issues, issue) 189 | } 190 | } 191 | } 192 | } 193 | 194 | // analyzePodStatus analyzes pod-specific status information 195 | func (tc *TroubleshootCorrelator) analyzePodStatus(ctx context.Context, pod *unstructured.Unstructured, result *models.TroubleshootResult) { 196 | // Check pod phase 197 | phase, found, _ := unstructured.NestedString(pod.Object, "status", "phase") 198 | if found && phase != "Running" && phase != "Succeeded" { 199 | issue := models.Issue{ 200 | Source: "Kubernetes", 201 | Category: "PodNotRunning", 202 | Severity: "Warning", 203 | Title: "Pod Not Running", 204 | Description: fmt.Sprintf("Pod is in %s state", phase), 205 | } 206 | 207 | if phase == "Pending" { 208 | issue.Title = "Pod Pending" 209 | issue.Description = "Pod is still in Pending state and hasn't started running" 210 | } else if phase == "Failed" { 211 | issue.Severity = "Error" 212 | issue.Title = "Pod Failed" 213 | } 214 | 215 | result.Issues = append(result.Issues, issue) 216 | } 217 | 218 | // Check pod conditions 219 | conditions, found, _ := unstructured.NestedSlice(pod.Object, "status", "conditions") 220 | if found { 221 | for _, c := range conditions { 222 | condition, ok := c.(map[string]interface{}) 223 | if !ok { 224 | continue 225 | } 226 | 227 | conditionType, _, _ := unstructured.NestedString(condition, "type") 228 | status, _, _ := unstructured.NestedString(condition, "status") 229 | 230 | if conditionType == "PodScheduled" && status != "True" { 231 | issue := models.Issue{ 232 | Source: "Kubernetes", 233 | Category: "SchedulingIssue", 234 | Severity: "Warning", 235 | Title: "Pod Scheduling Issue", 236 | Description: "Pod cannot be scheduled onto a node", 237 | } 238 | result.Issues = append(result.Issues, issue) 239 | } 240 | 241 | if conditionType == "Initialized" && status != "True" { 242 | issue := models.Issue{ 243 | Source: "Kubernetes", 244 | Category: "InitializationIssue", 245 | Severity: "Warning", 246 | Title: "Pod Initialization Issue", 247 | Description: "Pod initialization containers have not completed successfully", 248 | } 249 | result.Issues = append(result.Issues, issue) 250 | } 251 | 252 | if conditionType == "ContainersReady" && status != "True" { 253 | issue := models.Issue{ 254 | Source: "Kubernetes", 255 | Category: "ContainerReadinessIssue", 256 | Severity: "Warning", 257 | Title: "Container Readiness Issue", 258 | Description: "One or more containers are not ready", 259 | } 260 | result.Issues = append(result.Issues, issue) 261 | } 262 | 263 | if conditionType == "Ready" && status != "True" { 264 | issue := models.Issue{ 265 | Source: "Kubernetes", 266 | Category: "PodNotReady", 267 | Severity: "Warning", 268 | Title: "Pod Not Ready", 269 | Description: "Pod is not ready to serve traffic", 270 | } 271 | result.Issues = append(result.Issues, issue) 272 | } 273 | } 274 | } 275 | 276 | // Check container statuses 277 | containerStatuses, found, _ := unstructured.NestedSlice(pod.Object, "status", "containerStatuses") 278 | if found { 279 | tc.analyzeContainerStatuses(containerStatuses, false, result) 280 | } 281 | 282 | // Check init container statuses if they exist 283 | initContainerStatuses, found, _ := unstructured.NestedSlice(pod.Object, "status", "initContainerStatuses") 284 | if found { 285 | tc.analyzeContainerStatuses(initContainerStatuses, true, result) 286 | } 287 | 288 | // Check for volume issues 289 | volumes, found, _ := unstructured.NestedSlice(pod.Object, "spec", "volumes") 290 | if found { 291 | // Track PVC usage 292 | pvcVolumes := []string{} 293 | 294 | for _, v := range volumes { 295 | volume, ok := v.(map[string]interface{}) 296 | if !ok { 297 | continue 298 | } 299 | 300 | // Check for PVC volumes 301 | pvc, pvcFound, _ := unstructured.NestedMap(volume, "persistentVolumeClaim") 302 | if pvcFound && pvc != nil { 303 | claimName, nameFound, _ := unstructured.NestedString(pvc, "claimName") 304 | if nameFound && claimName != "" { 305 | pvcVolumes = append(pvcVolumes, claimName) 306 | } 307 | } 308 | } 309 | 310 | // If PVC volumes found, check their status 311 | if len(pvcVolumes) > 0 { 312 | for _, pvcName := range pvcVolumes { 313 | pvc, err := tc.k8sClient.GetResource(ctx, "persistentvolumeclaim", pod.GetNamespace(), pvcName) 314 | if err != nil { 315 | issue := models.Issue{ 316 | Source: "Kubernetes", 317 | Category: "VolumeIssue", 318 | Severity: "Warning", 319 | Title: "PVC Not Found", 320 | Description: fmt.Sprintf("PersistentVolumeClaim %s not found", pvcName), 321 | } 322 | result.Issues = append(result.Issues, issue) 323 | continue 324 | } 325 | 326 | phase, phaseFound, _ := unstructured.NestedString(pvc.Object, "status", "phase") 327 | if !phaseFound || phase != "Bound" { 328 | issue := models.Issue{ 329 | Source: "Kubernetes", 330 | Category: "VolumeIssue", 331 | Severity: "Warning", 332 | Title: "PVC Not Bound", 333 | Description: fmt.Sprintf("PersistentVolumeClaim %s is in %s state", pvcName, phase), 334 | } 335 | result.Issues = append(result.Issues, issue) 336 | } 337 | } 338 | } 339 | } 340 | } 341 | 342 | // analyzeContainerStatuses analyzes container status information 343 | func (tc *TroubleshootCorrelator) analyzeContainerStatuses(statuses []interface{}, isInit bool, result *models.TroubleshootResult) { 344 | containerType := "Container" 345 | if isInit { 346 | containerType = "Init Container" 347 | } 348 | 349 | for _, cs := range statuses { 350 | containerStatus, ok := cs.(map[string]interface{}) 351 | if !ok { 352 | continue 353 | } 354 | 355 | containerName, _, _ := unstructured.NestedString(containerStatus, "name") 356 | ready, _, _ := unstructured.NestedBool(containerStatus, "ready") 357 | restartCount, _, _ := unstructured.NestedInt64(containerStatus, "restartCount") 358 | 359 | if !ready { 360 | // Check for specific container state 361 | state, stateExists, _ := unstructured.NestedMap(containerStatus, "state") 362 | if stateExists && state != nil { 363 | waitingState, waitingExists, _ := unstructured.NestedMap(state, "waiting") 364 | if waitingExists && waitingState != nil { 365 | reason, reasonFound, _ := unstructured.NestedString(waitingState, "reason") 366 | message, messageFound, _ := unstructured.NestedString(waitingState, "message") 367 | 368 | reasonStr := "" 369 | if reasonFound { 370 | reasonStr = reason 371 | } 372 | 373 | messageStr := "" 374 | if messageFound { 375 | messageStr = message 376 | } 377 | 378 | issue := models.Issue{ 379 | Source: "Kubernetes", 380 | Category: "ContainerWaiting", 381 | Severity: "Warning", 382 | Title: fmt.Sprintf("%s %s Waiting", containerType, containerName), 383 | Description: fmt.Sprintf("%s is waiting: %s - %s", containerType, reasonStr, messageStr), 384 | } 385 | 386 | if reason == "CrashLoopBackOff" { 387 | issue.Category = "CrashLoopBackOff" 388 | issue.Severity = "Error" 389 | issue.Title = fmt.Sprintf("%s %s CrashLoopBackOff", containerType, containerName) 390 | } else if reason == "ImagePullBackOff" || reason == "ErrImagePull" { 391 | issue.Category = "ImagePullError" 392 | issue.Title = fmt.Sprintf("%s %s Image Pull Error", containerType, containerName) 393 | } else if reason == "PodInitializing" || reason == "ContainerCreating" { 394 | issue.Category = "PodInitializing" 395 | issue.Title = fmt.Sprintf("%s Still Initializing", containerType) 396 | issue.Description = fmt.Sprintf("%s is still being created or initialized", containerType) 397 | } 398 | 399 | result.Issues = append(result.Issues, issue) 400 | } 401 | 402 | terminatedState, terminatedExists, _ := unstructured.NestedMap(state, "terminated") 403 | if terminatedExists && terminatedState != nil { 404 | reason, reasonFound, _ := unstructured.NestedString(terminatedState, "reason") 405 | exitCode, exitCodeFound, _ := unstructured.NestedInt64(terminatedState, "exitCode") 406 | message, messageFound, _ := unstructured.NestedString(terminatedState, "message") 407 | 408 | reasonStr := "" 409 | if reasonFound { 410 | reasonStr = reason 411 | } 412 | 413 | messageStr := "" 414 | if messageFound { 415 | messageStr = message 416 | } 417 | 418 | var exitCodeVal int64 = 0 419 | if exitCodeFound { 420 | exitCodeVal = exitCode 421 | } 422 | 423 | if exitCodeVal != 0 { 424 | issue := models.Issue{ 425 | Source: "Kubernetes", 426 | Category: "ContainerTerminated", 427 | Severity: "Error", 428 | Title: fmt.Sprintf("%s %s Terminated", containerType, containerName), 429 | Description: fmt.Sprintf("%s terminated with exit code %d: %s - %s", containerType, exitCodeVal, reasonStr, messageStr), 430 | } 431 | result.Issues = append(result.Issues, issue) 432 | } 433 | } 434 | } 435 | } 436 | 437 | if restartCount > 3 { 438 | issue := models.Issue{ 439 | Source: "Kubernetes", 440 | Category: "FrequentRestarts", 441 | Severity: "Warning", 442 | Title: fmt.Sprintf("%s %s Frequent Restarts", containerType, containerName), 443 | Description: fmt.Sprintf("%s has restarted %d times", containerType, restartCount), 444 | } 445 | result.Issues = append(result.Issues, issue) 446 | } 447 | } 448 | } 449 | 450 | // analyzeKubernetesEvents looks for common issues in Kubernetes events 451 | func (tc *TroubleshootCorrelator) analyzeKubernetesEvents(rc models.ResourceContext, result *models.TroubleshootResult) { 452 | for _, event := range rc.Events { 453 | // Look for error events 454 | if event.Type == "Warning" { 455 | issue := models.Issue{ 456 | Source: "Kubernetes", 457 | Severity: "Warning", 458 | Description: fmt.Sprintf("%s: %s", event.Reason, event.Message), 459 | } 460 | 461 | // Categorize common issues 462 | switch { 463 | case strings.Contains(event.Reason, "Failed") && strings.Contains(event.Message, "ImagePull"): 464 | issue.Category = "ImagePullError" 465 | issue.Title = "Image Pull Failure" 466 | 467 | case strings.Contains(event.Reason, "Unhealthy"): 468 | issue.Category = "HealthCheckFailure" 469 | issue.Title = "Health Check Failure" 470 | 471 | case strings.Contains(event.Message, "memory"): 472 | issue.Category = "ResourceIssue" 473 | issue.Title = "Memory Resource Issue" 474 | 475 | case strings.Contains(event.Message, "cpu"): 476 | issue.Category = "ResourceIssue" 477 | issue.Title = "CPU Resource Issue" 478 | 479 | case strings.Contains(event.Reason, "BackOff"): 480 | issue.Category = "CrashLoopBackOff" 481 | issue.Title = "Container Crash Loop" 482 | 483 | default: 484 | issue.Category = "OtherWarning" 485 | issue.Title = "Kubernetes Warning" 486 | } 487 | 488 | result.Issues = append(result.Issues, issue) 489 | } 490 | } 491 | } 492 | 493 | // analyzeArgoStatus looks for issues in ArgoCD status 494 | func (tc *TroubleshootCorrelator) analyzeArgoStatus(rc models.ResourceContext, result *models.TroubleshootResult) { 495 | if rc.ArgoApplication == nil { 496 | // No ArgoCD application managing this resource 497 | return 498 | } 499 | 500 | // Check sync status 501 | if rc.ArgoSyncStatus != "Synced" { 502 | issue := models.Issue{ 503 | Source: "ArgoCD", 504 | Category: "SyncIssue", 505 | Severity: "Warning", 506 | Title: "ArgoCD Sync Issue", 507 | Description: fmt.Sprintf("Application %s is not synced (status: %s)", rc.ArgoApplication.Name, rc.ArgoSyncStatus), 508 | } 509 | result.Issues = append(result.Issues, issue) 510 | } 511 | 512 | // Check health status 513 | if rc.ArgoHealthStatus != "Healthy" { 514 | issue := models.Issue{ 515 | Source: "ArgoCD", 516 | Category: "HealthIssue", 517 | Severity: "Warning", 518 | Title: "ArgoCD Health Issue", 519 | Description: fmt.Sprintf("Application %s is not healthy (status: %s)", rc.ArgoApplication.Name, rc.ArgoHealthStatus), 520 | } 521 | result.Issues = append(result.Issues, issue) 522 | } 523 | 524 | // Check for recent sync failures 525 | for _, history := range rc.ArgoSyncHistory { 526 | if history.Status == "Failed" { 527 | issue := models.Issue{ 528 | Source: "ArgoCD", 529 | Category: "SyncFailure", 530 | Severity: "Error", 531 | Title: "Recent Sync Failure", 532 | Description: fmt.Sprintf("Sync at %s failed with revision %s", history.DeployedAt.Format("2006-01-02 15:04:05"), history.Revision), 533 | } 534 | result.Issues = append(result.Issues, issue) 535 | break // Only report the most recent failure 536 | } 537 | } 538 | } 539 | 540 | // analyzeGitLabStatus looks for issues in GitLab pipelines and deployments 541 | func (tc *TroubleshootCorrelator) analyzeGitLabStatus(rc models.ResourceContext, result *models.TroubleshootResult) { 542 | if rc.GitLabProject == nil { 543 | // No GitLab project information 544 | return 545 | } 546 | 547 | // Check last pipeline status 548 | if rc.LastPipeline != nil && rc.LastPipeline.Status != "success" { 549 | severity := "Warning" 550 | if rc.LastPipeline.Status == "failed" { 551 | severity = "Error" 552 | } 553 | 554 | issue := models.Issue{ 555 | Source: "GitLab", 556 | Category: "PipelineIssue", 557 | Severity: severity, 558 | Title: "GitLab Pipeline Issue", 559 | Description: fmt.Sprintf("Pipeline #%d status: %s", rc.LastPipeline.ID, rc.LastPipeline.Status), 560 | } 561 | result.Issues = append(result.Issues, issue) 562 | } 563 | 564 | // Check last deployment status 565 | if rc.LastDeployment != nil && rc.LastDeployment.Status != "success" { 566 | severity := "Warning" 567 | if rc.LastDeployment.Status == "failed" { 568 | severity = "Error" 569 | } 570 | 571 | issue := models.Issue{ 572 | Source: "GitLab", 573 | Category: "DeploymentIssue", 574 | Severity: severity, 575 | Title: "GitLab Deployment Issue", 576 | Description: fmt.Sprintf("Deployment to %s status: %s", rc.LastDeployment.Environment.Name, rc.LastDeployment.Status), 577 | } 578 | result.Issues = append(result.Issues, issue) 579 | } 580 | } 581 | 582 | // generateRecommendations creates recommendations based on identified issues 583 | func (tc *TroubleshootCorrelator) generateRecommendations(result *models.TroubleshootResult) { 584 | // Update the original implementation to include more recommendations 585 | recommendationMap := make(map[string]bool) 586 | 587 | for _, issue := range result.Issues { 588 | switch issue.Category { 589 | case "ImagePullError": 590 | recommendationMap["Check image name and credentials for accessing private registries."] = true 591 | recommendationMap["Verify that the image tag exists in the registry."] = true 592 | 593 | case "HealthCheckFailure": 594 | recommendationMap["Review liveness and readiness probe configuration."] = true 595 | recommendationMap["Check application logs for errors during startup."] = true 596 | 597 | case "ResourceIssue": 598 | recommendationMap["Review resource requests and limits in the deployment."] = true 599 | recommendationMap["Monitor resource usage to determine appropriate values."] = true 600 | 601 | case "CrashLoopBackOff": 602 | recommendationMap["Check container logs for errors."] = true 603 | recommendationMap["Verify environment variables and configuration."] = true 604 | 605 | case "SyncIssue", "SyncFailure": 606 | recommendationMap["Check ArgoCD application manifest for errors."] = true 607 | recommendationMap["Verify that the target revision exists in the Git repository."] = true 608 | 609 | case "PipelineIssue": 610 | recommendationMap["Review GitLab pipeline logs for errors."] = true 611 | recommendationMap["Check if the pipeline configuration is valid."] = true 612 | 613 | case "DeploymentIssue": 614 | recommendationMap["Check GitLab deployment job logs for errors."] = true 615 | recommendationMap["Verify deployment environment configuration."] = true 616 | 617 | case "PodNotRunning", "PodNotReady", "PodInitializing": 618 | recommendationMap["Check pod events for scheduling or initialization issues."] = true 619 | recommendationMap["Examine init container logs for errors."] = true 620 | 621 | case "InitializationIssue": 622 | recommendationMap["Check init container logs for errors."] = true 623 | recommendationMap["Verify that volumes can be mounted properly."] = true 624 | 625 | case "ContainerReadinessIssue": 626 | recommendationMap["Review readiness probe configuration."] = true 627 | recommendationMap["Check container logs for application startup issues."] = true 628 | 629 | case "VolumeIssue": 630 | recommendationMap["Verify that PersistentVolumeClaims are bound."] = true 631 | recommendationMap["Check if storage classes are properly configured."] = true 632 | recommendationMap["Ensure sufficient storage space is available on the nodes."] = true 633 | 634 | case "SchedulingIssue": 635 | recommendationMap["Check if nodes have sufficient resources for the pod."] = true 636 | recommendationMap["Verify that node selectors or taints are not preventing scheduling."] = true 637 | } 638 | } 639 | 640 | // Add generic recommendations if no specific issues found 641 | if len(result.Issues) == 0 { 642 | recommendationMap["Check pod logs for errors."] = true 643 | recommendationMap["Examine Kubernetes events for the resource."] = true 644 | recommendationMap["Verify network connectivity between components."] = true 645 | } 646 | 647 | // Convert map to slice 648 | for rec := range recommendationMap { 649 | result.Recommendations = append(result.Recommendations, rec) 650 | } 651 | } 652 | 653 | ``` -------------------------------------------------------------------------------- /docs/public/images/logo.svg: -------------------------------------------------------------------------------- ``` 1 | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="500" zoomAndPan="magnify" viewBox="0 0 375 374.999991" height="500" preserveAspectRatio="xMidYMid meet" version="1.0"><path fill="#ffffff" d="M 176.261719 185.164062 L 170.148438 223.289062 L 203.796875 223.289062 L 196.632812 185.164062 L 176.261719 185.164062 " fill-opacity="1" fill-rule="nonzero"/><path fill="#4c44cb" d="M 176.261719 185.164062 L 175.214844 184.996094 L 169.101562 223.121094 C 169.054688 223.425781 169.140625 223.738281 169.34375 223.972656 C 169.542969 224.210938 169.835938 224.347656 170.148438 224.347656 L 203.796875 224.347656 C 204.113281 224.347656 204.410156 224.207031 204.613281 223.960938 C 204.8125 223.722656 204.894531 223.402344 204.839844 223.089844 L 197.675781 184.96875 C 197.578125 184.46875 197.140625 184.105469 196.632812 184.105469 L 176.261719 184.105469 C 175.742188 184.105469 175.296875 184.484375 175.214844 184.996094 L 176.261719 185.164062 L 176.261719 186.222656 L 195.753906 186.222656 L 202.523438 222.230469 L 171.390625 222.230469 L 177.304688 185.332031 L 176.261719 185.164062 L 176.261719 186.222656 L 176.261719 185.164062 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 180.769531 185.164062 L 177.230469 223.289062 L 196.71875 223.289062 L 192.566406 185.164062 L 180.769531 185.164062 " fill-opacity="1" fill-rule="nonzero"/><path fill="#4c44cb" d="M 180.769531 185.164062 L 179.71875 185.066406 L 176.175781 223.191406 C 176.152344 223.488281 176.246094 223.78125 176.449219 224 C 176.648438 224.21875 176.929688 224.347656 177.230469 224.347656 L 196.71875 224.347656 C 197.019531 224.347656 197.304688 224.21875 197.503906 223.996094 C 197.707031 223.769531 197.800781 223.472656 197.769531 223.171875 L 193.621094 185.050781 C 193.5625 184.515625 193.105469 184.105469 192.566406 184.105469 L 180.769531 184.105469 C 180.226562 184.105469 179.765625 184.523438 179.71875 185.066406 L 180.769531 185.164062 L 180.769531 186.222656 L 191.617188 186.222656 L 195.535156 222.230469 L 178.390625 222.230469 L 181.824219 185.261719 L 180.769531 185.164062 L 180.769531 186.222656 L 180.769531 185.164062 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 186.582031 120.640625 C 186.582031 120.640625 210.292969 131.441406 196.632812 185.164062 C 196.632812 185.164062 194.464844 189.5 186.582031 189.5 L 186.3125 189.5 C 178.429688 189.5 176.261719 185.164062 176.261719 185.164062 C 162.601562 131.441406 186.3125 120.640625 186.3125 120.640625 L 186.582031 120.640625 " fill-opacity="1" fill-rule="nonzero"/><path fill="#4c44cb" d="M 186.582031 120.640625 L 186.140625 121.605469 L 186.390625 121.058594 L 186.136719 121.601562 L 186.140625 121.605469 L 186.390625 121.058594 L 186.136719 121.601562 L 186.171875 121.621094 C 186.558594 121.8125 189.953125 123.585938 193.261719 128.597656 C 196.566406 133.613281 199.8125 141.890625 199.816406 155.199219 C 199.816406 163.175781 198.644531 172.957031 195.605469 184.902344 L 196.632812 185.164062 L 195.683594 184.691406 L 196.09375 184.894531 L 195.691406 184.679688 L 195.683594 184.691406 L 196.09375 184.894531 L 195.691406 184.679688 C 195.679688 184.699219 195.1875 185.628906 193.820312 186.558594 C 192.453125 187.484375 190.210938 188.4375 186.582031 188.441406 L 186.3125 188.441406 C 182.566406 188.4375 180.296875 187.425781 178.941406 186.46875 C 178.269531 185.988281 177.820312 185.515625 177.546875 185.179688 C 177.410156 185.007812 177.320312 184.875 177.265625 184.789062 C 177.238281 184.746094 177.21875 184.714844 177.210938 184.695312 L 177.203125 184.679688 L 176.796875 184.898438 L 177.207031 184.691406 L 177.203125 184.679688 L 176.796875 184.898438 L 177.207031 184.691406 L 176.261719 185.164062 L 177.289062 184.902344 C 174.25 172.957031 173.074219 163.175781 173.078125 155.199219 C 173.082031 141.351562 176.597656 132.953125 180.035156 128.007812 C 181.757812 125.535156 183.464844 123.917969 184.730469 122.929688 C 185.363281 122.4375 185.882812 122.097656 186.238281 121.886719 C 186.417969 121.78125 186.550781 121.707031 186.640625 121.660156 C 186.683594 121.636719 186.71875 121.621094 186.734375 121.613281 L 186.753906 121.605469 L 186.753906 121.601562 L 186.5 121.050781 L 186.75 121.605469 L 186.753906 121.601562 L 186.5 121.050781 L 186.75 121.605469 L 186.3125 120.640625 L 186.3125 121.699219 L 186.582031 121.699219 L 186.582031 120.640625 L 186.140625 121.605469 L 186.582031 120.640625 L 186.582031 119.582031 L 186.3125 119.582031 C 186.164062 119.582031 186.007812 119.617188 185.875 119.679688 C 185.742188 119.734375 182 121.476562 178.296875 126.796875 C 174.589844 132.117188 170.957031 140.996094 170.957031 155.199219 C 170.960938 163.382812 172.160156 173.339844 175.234375 185.425781 C 175.253906 185.5 175.28125 185.570312 175.316406 185.636719 C 175.367188 185.75 176.011719 186.984375 177.71875 188.195312 C 179.417969 189.410156 182.175781 190.5625 186.3125 190.558594 L 186.582031 190.558594 C 190.714844 190.5625 193.476562 189.410156 195.175781 188.195312 C 196.878906 186.984375 197.523438 185.75 197.578125 185.636719 C 197.613281 185.570312 197.640625 185.5 197.65625 185.425781 C 200.734375 173.339844 201.933594 163.382812 201.933594 155.199219 C 201.9375 140.996094 198.300781 132.117188 194.59375 126.796875 C 190.890625 121.476562 187.148438 119.734375 187.019531 119.679688 C 186.886719 119.617188 186.726562 119.582031 186.582031 119.582031 L 186.582031 120.640625 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 186.3125 120.640625 C 181.777344 123.351562 178.625 127.820312 176.347656 132.347656 C 176.347656 132.347656 188.722656 136.957031 196.21875 131.667969 C 195.710938 130.625 195.152344 129.609375 194.539062 128.628906 C 193.066406 126.28125 191.277344 124.101562 189.125 122.347656 C 188.339844 121.707031 187.5 121.085938 186.582031 120.640625 " fill-opacity="1" fill-rule="nonzero"/><path fill="#4c44cb" d="M 185.769531 119.734375 C 183.371094 121.164062 181.363281 123.046875 179.660156 125.144531 C 177.957031 127.246094 176.566406 129.558594 175.402344 131.871094 C 175.269531 132.140625 175.25 132.453125 175.363281 132.734375 C 175.472656 133.015625 175.695312 133.234375 175.976562 133.339844 C 176.011719 133.351562 177.296875 133.828125 179.292969 134.308594 C 181.296875 134.785156 184.011719 135.265625 186.910156 135.265625 C 188.574219 135.265625 190.304688 135.109375 191.996094 134.6875 C 193.679688 134.269531 195.339844 133.585938 196.832031 132.53125 C 197.253906 132.230469 197.398438 131.671875 197.171875 131.203125 C 196.648438 130.128906 196.070312 129.082031 195.433594 128.066406 C 193.917969 125.640625 192.058594 123.371094 189.796875 121.527344 C 188.980469 120.863281 188.078125 120.191406 187.042969 119.6875 C 186.511719 119.433594 185.878906 119.65625 185.625 120.183594 C 185.371094 120.710938 185.59375 121.339844 186.121094 121.59375 C 186.917969 121.976562 187.699219 122.546875 188.457031 123.164062 C 190.5 124.828125 192.21875 126.921875 193.640625 129.1875 L 193.636719 129.1875 C 194.234375 130.136719 194.777344 131.121094 195.269531 132.132812 L 196.21875 131.667969 L 195.609375 130.804688 C 194.367188 131.679688 192.964844 132.265625 191.484375 132.632812 C 190 133.003906 188.441406 133.152344 186.910156 133.148438 C 184.246094 133.152344 181.679688 132.703125 179.789062 132.25 C 178.84375 132.023438 178.070312 131.796875 177.535156 131.628906 C 177.265625 131.542969 177.058594 131.472656 176.921875 131.425781 C 176.851562 131.402344 176.800781 131.382812 176.765625 131.371094 L 176.726562 131.359375 L 176.71875 131.355469 L 176.347656 132.347656 L 177.292969 132.820312 C 178.40625 130.609375 179.726562 128.425781 181.304688 126.480469 C 182.882812 124.535156 184.714844 122.828125 186.855469 121.550781 C 187.355469 121.25 187.519531 120.601562 187.21875 120.097656 C 186.921875 119.597656 186.269531 119.433594 185.769531 119.734375 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 173.035156 169.011719 C 173.035156 169.011719 156.296875 176.589844 165.527344 197.429688 C 165.527344 197.429688 165.996094 186.835938 176.046875 184.324219 C 176.046875 184.324219 173.890625 175.25 173.035156 169.011719 " fill-opacity="1" fill-rule="nonzero"/><path fill="#4c44cb" d="M 173.035156 169.011719 L 172.597656 168.042969 C 172.519531 168.082031 169.84375 169.296875 167.128906 172.113281 C 165.777344 173.515625 164.414062 175.328125 163.390625 177.585938 C 162.367188 179.847656 161.691406 182.558594 161.691406 185.71875 C 161.691406 189.210938 162.511719 193.242188 164.554688 197.859375 C 164.757812 198.308594 165.238281 198.5625 165.71875 198.472656 C 166.203125 198.378906 166.558594 197.96875 166.582031 197.476562 L 166.546875 197.476562 L 166.582031 197.476562 L 166.546875 197.476562 L 166.582031 197.476562 C 166.582031 197.398438 166.734375 194.863281 168.039062 192.136719 C 168.6875 190.769531 169.613281 189.359375 170.941406 188.152344 C 172.269531 186.945312 173.996094 185.929688 176.304688 185.351562 C 176.863281 185.210938 177.210938 184.644531 177.074219 184.082031 C 177.074219 184.082031 177.039062 183.945312 176.984375 183.683594 C 176.773438 182.78125 176.230469 180.421875 175.652344 177.617188 C 175.070312 174.8125 174.453125 171.554688 174.082031 168.867188 C 174.039062 168.535156 173.839844 168.246094 173.546875 168.082031 C 173.257812 167.925781 172.902344 167.90625 172.597656 168.042969 L 173.035156 169.011719 L 171.988281 169.152344 C 172.421875 172.324219 173.175781 176.164062 173.828125 179.226562 C 174.476562 182.289062 175.015625 184.566406 175.015625 184.570312 L 176.046875 184.324219 L 175.789062 183.296875 C 173.105469 183.96875 171.023438 185.195312 169.441406 186.65625 C 167.074219 188.847656 165.839844 191.523438 165.191406 193.632812 C 164.542969 195.75 164.472656 197.320312 164.46875 197.382812 L 165.527344 197.429688 L 166.492188 197.003906 C 164.546875 192.613281 163.808594 188.875 163.808594 185.71875 C 163.808594 182.859375 164.414062 180.464844 165.320312 178.460938 C 166.679688 175.457031 168.730469 173.320312 170.441406 171.941406 C 171.300781 171.253906 172.074219 170.753906 172.625 170.429688 C 172.898438 170.269531 173.117188 170.152344 173.265625 170.078125 C 173.339844 170.039062 173.390625 170.007812 173.429688 169.996094 L 173.464844 169.976562 L 173.472656 169.972656 L 173.378906 169.769531 L 173.472656 169.972656 L 173.378906 169.769531 L 173.472656 169.972656 L 173.035156 169.011719 L 171.988281 169.152344 L 173.035156 169.011719 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 199.9375 168.421875 C 199.9375 168.421875 217.230469 176.589844 208.003906 197.429688 C 208.003906 197.429688 206.9375 186.664062 196.886719 184.15625 C 196.886719 184.15625 199.054688 176.238281 199.9375 168.421875 " fill-opacity="1" fill-rule="nonzero"/><path fill="#4c44cb" d="M 199.9375 168.421875 L 199.484375 169.382812 L 199.699219 168.929688 L 199.484375 169.378906 L 199.484375 169.382812 L 199.699219 168.929688 L 199.484375 169.378906 C 199.492188 169.382812 202.082031 170.625 204.621094 173.3125 C 205.890625 174.652344 207.148438 176.351562 208.089844 178.433594 C 209.03125 180.519531 209.652344 182.984375 209.652344 185.910156 C 209.652344 189.046875 208.925781 192.722656 207.035156 197.003906 L 208.003906 197.429688 L 209.058594 197.324219 C 209.046875 197.246094 208.769531 194.445312 207.15625 191.257812 C 206.351562 189.664062 205.203125 187.96875 203.574219 186.5 C 201.941406 185.035156 199.824219 183.796875 197.140625 183.128906 L 196.886719 184.15625 L 197.90625 184.433594 C 197.914062 184.40625 200.089844 176.464844 200.988281 168.542969 L 199.9375 168.421875 L 199.484375 169.382812 L 199.9375 168.421875 L 198.882812 168.304688 C 198.449219 172.15625 197.695312 176.058594 197.046875 178.992188 C 196.726562 180.457031 196.429688 181.683594 196.214844 182.539062 C 196.105469 182.96875 196.019531 183.300781 195.957031 183.53125 C 195.929688 183.644531 195.90625 183.730469 195.890625 183.789062 C 195.875 183.847656 195.863281 183.875 195.863281 183.875 C 195.789062 184.152344 195.828125 184.441406 195.972656 184.6875 C 196.117188 184.933594 196.351562 185.113281 196.628906 185.183594 C 198.972656 185.769531 200.757812 186.820312 202.152344 188.074219 C 204.246094 189.957031 205.464844 192.328125 206.136719 194.246094 C 206.476562 195.199219 206.683594 196.039062 206.804688 196.628906 C 206.859375 196.925781 206.898438 197.160156 206.921875 197.3125 C 206.933594 197.390625 206.941406 197.453125 206.945312 197.488281 L 206.949219 197.53125 L 206.953125 197.535156 L 207.199219 197.511719 L 206.953125 197.535156 L 207.199219 197.511719 L 206.953125 197.535156 C 206.996094 198.011719 207.363281 198.402344 207.835938 198.476562 C 208.308594 198.550781 208.78125 198.296875 208.972656 197.859375 C 210.964844 193.355469 211.773438 189.378906 211.773438 185.910156 C 211.773438 182.65625 211.0625 179.855469 209.992188 177.507812 C 208.386719 173.984375 206 171.488281 204.015625 169.867188 C 202.03125 168.246094 200.441406 167.492188 200.386719 167.464844 C 200.078125 167.320312 199.71875 167.332031 199.421875 167.5 C 199.121094 167.667969 198.921875 167.964844 198.882812 168.304688 L 199.9375 168.421875 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 186.582031 120.640625 C 186.582031 120.640625 210.292969 131.441406 196.632812 185.164062 C 196.632812 185.164062 194.464844 189.5 186.582031 189.5 L 186.3125 189.5 C 178.429688 189.5 176.261719 185.164062 176.261719 185.164062 C 162.601562 131.441406 186.3125 120.640625 186.3125 120.640625 L 186.582031 120.640625 " fill-opacity="1" fill-rule="nonzero"/><path fill="#4c44cb" d="M 186.582031 120.640625 L 186.140625 121.605469 L 186.390625 121.058594 L 186.136719 121.601562 L 186.140625 121.605469 L 186.390625 121.058594 L 186.136719 121.601562 L 186.171875 121.621094 C 186.558594 121.8125 189.953125 123.585938 193.261719 128.597656 C 196.566406 133.613281 199.8125 141.890625 199.816406 155.199219 C 199.816406 163.175781 198.644531 172.957031 195.605469 184.902344 L 196.632812 185.164062 L 195.683594 184.691406 L 196.09375 184.894531 L 195.691406 184.679688 L 195.683594 184.691406 L 196.09375 184.894531 L 195.691406 184.679688 C 195.679688 184.699219 195.1875 185.628906 193.820312 186.558594 C 192.453125 187.484375 190.210938 188.4375 186.582031 188.441406 L 186.3125 188.441406 C 182.566406 188.4375 180.296875 187.425781 178.941406 186.46875 C 178.269531 185.988281 177.820312 185.515625 177.546875 185.179688 C 177.410156 185.007812 177.320312 184.875 177.265625 184.789062 C 177.238281 184.746094 177.21875 184.714844 177.210938 184.695312 L 177.203125 184.679688 L 176.796875 184.898438 L 177.207031 184.691406 L 177.203125 184.679688 L 176.796875 184.898438 L 177.207031 184.691406 L 176.261719 185.164062 L 177.289062 184.902344 C 174.25 172.957031 173.074219 163.175781 173.078125 155.199219 C 173.082031 141.351562 176.597656 132.953125 180.035156 128.007812 C 181.757812 125.535156 183.464844 123.917969 184.730469 122.929688 C 185.363281 122.4375 185.882812 122.097656 186.238281 121.886719 C 186.417969 121.78125 186.550781 121.707031 186.640625 121.660156 C 186.683594 121.636719 186.71875 121.621094 186.734375 121.613281 L 186.753906 121.605469 L 186.753906 121.601562 L 186.5 121.050781 L 186.75 121.605469 L 186.753906 121.601562 L 186.5 121.050781 L 186.75 121.605469 L 186.3125 120.640625 L 186.3125 121.699219 L 186.582031 121.699219 L 186.582031 120.640625 L 186.140625 121.605469 L 186.582031 120.640625 L 186.582031 119.582031 L 186.3125 119.582031 C 186.164062 119.582031 186.007812 119.617188 185.875 119.679688 C 185.742188 119.734375 182 121.476562 178.296875 126.796875 C 174.589844 132.117188 170.957031 140.996094 170.957031 155.199219 C 170.960938 163.382812 172.160156 173.339844 175.234375 185.425781 C 175.253906 185.5 175.28125 185.570312 175.316406 185.636719 C 175.367188 185.75 176.011719 186.984375 177.71875 188.195312 C 179.417969 189.410156 182.175781 190.5625 186.3125 190.558594 L 186.582031 190.558594 C 190.714844 190.5625 193.476562 189.410156 195.175781 188.195312 C 196.878906 186.984375 197.523438 185.75 197.578125 185.636719 C 197.613281 185.570312 197.640625 185.5 197.65625 185.425781 C 200.734375 173.339844 201.933594 163.382812 201.933594 155.199219 C 201.9375 140.996094 198.300781 132.117188 194.59375 126.796875 C 190.890625 121.476562 187.148438 119.734375 187.019531 119.679688 C 186.886719 119.617188 186.726562 119.582031 186.582031 119.582031 L 186.582031 120.640625 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 186.3125 120.640625 C 181.777344 123.351562 178.625 127.820312 176.347656 132.347656 C 176.347656 132.347656 188.722656 136.957031 196.21875 131.667969 C 195.710938 130.625 195.152344 129.609375 194.539062 128.628906 C 193.066406 126.28125 191.277344 124.101562 189.125 122.347656 C 188.339844 121.707031 187.5 121.085938 186.582031 120.640625 " fill-opacity="1" fill-rule="nonzero"/><path fill="#4c44cb" d="M 185.769531 119.734375 C 183.371094 121.164062 181.363281 123.046875 179.660156 125.144531 C 177.957031 127.246094 176.566406 129.558594 175.402344 131.871094 C 175.269531 132.140625 175.25 132.453125 175.363281 132.734375 C 175.472656 133.015625 175.695312 133.234375 175.976562 133.339844 C 176.011719 133.351562 177.296875 133.828125 179.292969 134.308594 C 181.296875 134.785156 184.011719 135.265625 186.910156 135.265625 C 188.574219 135.265625 190.304688 135.109375 191.996094 134.6875 C 193.679688 134.269531 195.339844 133.585938 196.832031 132.53125 C 197.253906 132.230469 197.398438 131.671875 197.171875 131.203125 C 196.648438 130.128906 196.070312 129.082031 195.433594 128.066406 C 193.917969 125.640625 192.058594 123.371094 189.796875 121.527344 C 188.980469 120.863281 188.078125 120.191406 187.042969 119.6875 C 186.511719 119.433594 185.878906 119.65625 185.625 120.183594 C 185.371094 120.710938 185.59375 121.339844 186.121094 121.59375 C 186.917969 121.976562 187.699219 122.546875 188.457031 123.164062 C 190.5 124.828125 192.21875 126.921875 193.640625 129.1875 L 193.636719 129.1875 C 194.234375 130.136719 194.777344 131.121094 195.269531 132.132812 L 196.21875 131.667969 L 195.609375 130.804688 C 194.367188 131.679688 192.964844 132.265625 191.484375 132.632812 C 190 133.003906 188.441406 133.152344 186.910156 133.148438 C 184.246094 133.152344 181.679688 132.703125 179.789062 132.25 C 178.84375 132.023438 178.070312 131.796875 177.535156 131.628906 C 177.265625 131.542969 177.058594 131.472656 176.921875 131.425781 C 176.851562 131.402344 176.800781 131.382812 176.765625 131.371094 L 176.726562 131.359375 L 176.71875 131.355469 L 176.347656 132.347656 L 177.292969 132.820312 C 178.40625 130.609375 179.726562 128.425781 181.304688 126.480469 C 182.882812 124.535156 184.714844 122.828125 186.855469 121.550781 C 187.355469 121.25 187.519531 120.601562 187.21875 120.097656 C 186.921875 119.597656 186.269531 119.433594 185.769531 119.734375 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 196.441406 150.234375 C 196.441406 155.753906 191.964844 160.230469 186.445312 160.230469 C 180.925781 160.230469 176.449219 155.753906 176.449219 150.234375 C 176.449219 144.710938 180.925781 140.238281 186.445312 140.238281 C 191.964844 140.238281 196.441406 144.710938 196.441406 150.234375 " fill-opacity="1" fill-rule="nonzero"/><path fill="#4c44cb" d="M 196.441406 150.234375 L 195.382812 150.234375 C 195.382812 152.707031 194.386719 154.933594 192.765625 156.554688 C 191.144531 158.171875 188.917969 159.171875 186.445312 159.171875 C 183.976562 159.171875 181.746094 158.171875 180.128906 156.554688 C 178.507812 154.933594 177.507812 152.707031 177.507812 150.234375 C 177.507812 147.761719 178.507812 145.535156 180.128906 143.914062 C 181.746094 142.296875 183.976562 141.296875 186.445312 141.296875 C 188.917969 141.296875 191.144531 142.296875 192.765625 143.914062 C 194.386719 145.535156 195.382812 147.761719 195.382812 150.234375 L 197.5 150.234375 C 197.5 147.183594 196.261719 144.414062 194.261719 142.417969 C 192.265625 140.417969 189.496094 139.179688 186.445312 139.179688 C 183.398438 139.179688 180.628906 140.417969 178.632812 142.417969 C 176.632812 144.414062 175.394531 147.183594 175.394531 150.234375 C 175.394531 153.285156 176.632812 156.054688 178.632812 158.050781 C 180.628906 160.050781 183.398438 161.289062 186.445312 161.289062 C 189.496094 161.289062 192.265625 160.050781 194.261719 158.050781 C 196.261719 156.054688 197.5 153.285156 197.5 150.234375 L 196.441406 150.234375 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 173.035156 169.011719 C 173.035156 169.011719 156.296875 176.589844 165.527344 197.429688 C 165.527344 197.429688 165.996094 186.835938 176.046875 184.324219 C 176.046875 184.324219 173.890625 175.25 173.035156 169.011719 " fill-opacity="1" fill-rule="nonzero"/><path fill="#4c44cb" d="M 173.035156 169.011719 L 172.597656 168.042969 C 172.519531 168.082031 169.84375 169.296875 167.128906 172.113281 C 165.777344 173.515625 164.414062 175.328125 163.390625 177.585938 C 162.367188 179.847656 161.691406 182.558594 161.691406 185.71875 C 161.691406 189.210938 162.511719 193.242188 164.554688 197.859375 C 164.757812 198.308594 165.238281 198.5625 165.71875 198.472656 C 166.203125 198.378906 166.558594 197.96875 166.582031 197.476562 L 166.546875 197.476562 L 166.582031 197.476562 L 166.546875 197.476562 L 166.582031 197.476562 C 166.582031 197.398438 166.734375 194.863281 168.039062 192.136719 C 168.6875 190.769531 169.613281 189.359375 170.941406 188.152344 C 172.269531 186.945312 173.996094 185.929688 176.304688 185.351562 C 176.863281 185.210938 177.210938 184.644531 177.074219 184.082031 C 177.074219 184.082031 177.039062 183.945312 176.984375 183.683594 C 176.773438 182.78125 176.230469 180.421875 175.652344 177.617188 C 175.070312 174.8125 174.453125 171.554688 174.082031 168.867188 C 174.039062 168.535156 173.839844 168.246094 173.546875 168.082031 C 173.257812 167.925781 172.902344 167.90625 172.597656 168.042969 L 173.035156 169.011719 L 171.988281 169.152344 C 172.421875 172.324219 173.175781 176.164062 173.828125 179.226562 C 174.476562 182.289062 175.015625 184.566406 175.015625 184.570312 L 176.046875 184.324219 L 175.789062 183.296875 C 173.105469 183.96875 171.023438 185.195312 169.441406 186.65625 C 167.074219 188.847656 165.839844 191.523438 165.191406 193.632812 C 164.542969 195.75 164.472656 197.320312 164.46875 197.382812 L 165.527344 197.429688 L 166.492188 197.003906 C 164.546875 192.613281 163.808594 188.875 163.808594 185.71875 C 163.808594 182.859375 164.414062 180.464844 165.320312 178.460938 C 166.679688 175.457031 168.730469 173.320312 170.441406 171.941406 C 171.300781 171.253906 172.074219 170.753906 172.625 170.429688 C 172.898438 170.269531 173.117188 170.152344 173.265625 170.078125 C 173.339844 170.039062 173.390625 170.007812 173.429688 169.996094 L 173.464844 169.976562 L 173.472656 169.972656 L 173.378906 169.769531 L 173.472656 169.972656 L 173.378906 169.769531 L 173.472656 169.972656 L 173.035156 169.011719 L 171.988281 169.152344 L 173.035156 169.011719 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 199.9375 168.421875 C 199.9375 168.421875 217.230469 176.589844 208.003906 197.429688 C 208.003906 197.429688 206.9375 186.664062 196.886719 184.15625 C 196.886719 184.15625 199.054688 176.238281 199.9375 168.421875 " fill-opacity="1" fill-rule="nonzero"/><path fill="#4c44cb" d="M 199.9375 168.421875 L 199.484375 169.382812 L 199.699219 168.929688 L 199.484375 169.378906 L 199.484375 169.382812 L 199.699219 168.929688 L 199.484375 169.378906 C 199.492188 169.382812 202.082031 170.625 204.621094 173.3125 C 205.890625 174.652344 207.148438 176.351562 208.089844 178.433594 C 209.03125 180.519531 209.652344 182.984375 209.652344 185.910156 C 209.652344 189.046875 208.925781 192.722656 207.035156 197.003906 L 208.003906 197.429688 L 209.058594 197.324219 C 209.046875 197.246094 208.769531 194.445312 207.15625 191.257812 C 206.351562 189.664062 205.203125 187.96875 203.574219 186.5 C 201.941406 185.035156 199.824219 183.796875 197.140625 183.128906 L 196.886719 184.15625 L 197.90625 184.433594 C 197.914062 184.40625 200.089844 176.464844 200.988281 168.542969 L 199.9375 168.421875 L 199.484375 169.382812 L 199.9375 168.421875 L 198.882812 168.304688 C 198.449219 172.15625 197.695312 176.058594 197.046875 178.992188 C 196.726562 180.457031 196.429688 181.683594 196.214844 182.539062 C 196.105469 182.96875 196.019531 183.300781 195.957031 183.53125 C 195.929688 183.644531 195.90625 183.730469 195.890625 183.789062 C 195.875 183.847656 195.863281 183.875 195.863281 183.875 C 195.789062 184.152344 195.828125 184.441406 195.972656 184.6875 C 196.117188 184.933594 196.351562 185.113281 196.628906 185.183594 C 198.972656 185.769531 200.757812 186.820312 202.152344 188.074219 C 204.246094 189.957031 205.464844 192.328125 206.136719 194.246094 C 206.476562 195.199219 206.683594 196.039062 206.804688 196.628906 C 206.859375 196.925781 206.898438 197.160156 206.921875 197.3125 C 206.933594 197.390625 206.941406 197.453125 206.945312 197.488281 L 206.949219 197.53125 L 206.953125 197.535156 L 207.199219 197.511719 L 206.953125 197.535156 L 207.199219 197.511719 L 206.953125 197.535156 C 206.996094 198.011719 207.363281 198.402344 207.835938 198.476562 C 208.308594 198.550781 208.78125 198.296875 208.972656 197.859375 C 210.964844 193.355469 211.773438 189.378906 211.773438 185.910156 C 211.773438 182.65625 211.0625 179.855469 209.992188 177.507812 C 208.386719 173.984375 206 171.488281 204.015625 169.867188 C 202.03125 168.246094 200.441406 167.492188 200.386719 167.464844 C 200.078125 167.320312 199.71875 167.332031 199.421875 167.5 C 199.121094 167.667969 198.921875 167.964844 198.882812 168.304688 L 199.9375 168.421875 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 193.109375 150.234375 C 193.109375 153.914062 190.128906 156.898438 186.445312 156.898438 C 182.765625 156.898438 179.785156 153.914062 179.785156 150.234375 C 179.785156 146.554688 182.765625 143.570312 186.445312 143.570312 C 190.128906 143.570312 193.109375 146.554688 193.109375 150.234375 " fill-opacity="1" fill-rule="nonzero"/><path fill="#4c44cb" d="M 193.109375 150.234375 L 192.050781 150.234375 C 192.050781 151.785156 191.425781 153.179688 190.410156 154.199219 C 189.390625 155.214844 187.996094 155.839844 186.445312 155.839844 C 184.898438 155.839844 183.503906 155.214844 182.484375 154.199219 C 181.46875 153.179688 180.84375 151.785156 180.84375 150.234375 C 180.84375 148.683594 181.46875 147.289062 182.484375 146.269531 C 183.503906 145.253906 184.898438 144.628906 186.445312 144.628906 C 187.996094 144.628906 189.390625 145.257812 190.410156 146.269531 C 191.425781 147.289062 192.050781 148.683594 192.050781 150.234375 L 194.167969 150.234375 C 194.167969 148.105469 193.304688 146.167969 191.90625 144.773438 C 190.511719 143.378906 188.574219 142.511719 186.445312 142.511719 C 184.316406 142.511719 182.382812 143.378906 180.988281 144.773438 C 179.589844 146.167969 178.722656 148.105469 178.726562 150.234375 C 178.722656 152.363281 179.589844 154.300781 180.988281 155.695312 C 182.382812 157.089844 184.316406 157.953125 186.445312 157.953125 C 188.574219 157.953125 190.511719 157.089844 191.90625 155.695312 C 193.304688 154.300781 194.167969 152.363281 194.167969 150.234375 L 193.109375 150.234375 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 193.109375 150.234375 C 193.109375 153.914062 190.128906 156.898438 186.445312 156.898438 C 182.765625 156.898438 179.785156 153.914062 179.785156 150.234375 C 179.785156 146.554688 182.765625 143.570312 186.445312 143.570312 C 190.128906 143.570312 193.109375 146.554688 193.109375 150.234375 " fill-opacity="1" fill-rule="nonzero"/><path fill="#4c44cb" d="M 193.109375 150.234375 L 192.050781 150.234375 C 192.050781 151.785156 191.425781 153.179688 190.410156 154.199219 C 189.390625 155.214844 187.996094 155.839844 186.445312 155.839844 C 184.898438 155.839844 183.503906 155.214844 182.484375 154.199219 C 181.46875 153.179688 180.84375 151.785156 180.84375 150.234375 C 180.84375 148.683594 181.46875 147.289062 182.484375 146.269531 C 183.503906 145.253906 184.898438 144.628906 186.445312 144.628906 C 187.996094 144.628906 189.390625 145.257812 190.410156 146.269531 C 191.425781 147.289062 192.050781 148.683594 192.050781 150.234375 L 194.167969 150.234375 C 194.167969 148.105469 193.304688 146.167969 191.90625 144.773438 C 190.511719 143.378906 188.574219 142.511719 186.445312 142.511719 C 184.316406 142.511719 182.382812 143.378906 180.988281 144.773438 C 179.589844 146.167969 178.722656 148.105469 178.726562 150.234375 C 178.722656 152.363281 179.589844 154.300781 180.988281 155.695312 C 182.382812 157.089844 184.316406 157.953125 186.445312 157.953125 C 188.574219 157.953125 190.511719 157.089844 191.90625 155.695312 C 193.304688 154.300781 194.167969 152.363281 194.167969 150.234375 L 193.109375 150.234375 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 197.230469 182.75 C 197.039062 183.542969 196.839844 184.347656 196.632812 185.164062 C 196.632812 185.164062 194.464844 189.5 186.582031 189.5 L 186.3125 189.5 C 178.429688 189.5 176.261719 185.164062 176.261719 185.164062 C 176.050781 184.347656 175.855469 183.542969 175.664062 182.75 C 175.664062 182.75 185.40625 186.84375 197.230469 182.75 " fill-opacity="1" fill-rule="nonzero"/><path fill="#4c44cb" d="M 197.230469 182.75 L 196.199219 182.5 C 196.007812 183.292969 195.8125 184.09375 195.605469 184.902344 L 196.632812 185.164062 L 195.683594 184.691406 L 196.09375 184.894531 L 195.691406 184.679688 L 195.683594 184.691406 L 196.09375 184.894531 L 195.691406 184.679688 C 195.679688 184.699219 195.1875 185.628906 193.820312 186.558594 C 192.453125 187.484375 190.210938 188.4375 186.582031 188.441406 L 186.3125 188.441406 C 182.566406 188.4375 180.296875 187.425781 178.941406 186.46875 C 178.269531 185.988281 177.820312 185.515625 177.546875 185.179688 C 177.410156 185.007812 177.320312 184.875 177.265625 184.789062 C 177.238281 184.746094 177.21875 184.714844 177.210938 184.695312 L 177.203125 184.679688 L 176.796875 184.898438 L 177.207031 184.691406 L 177.203125 184.679688 L 176.796875 184.898438 L 177.207031 184.691406 L 176.261719 185.164062 L 177.289062 184.902344 C 177.078125 184.09375 176.886719 183.292969 176.695312 182.5 L 175.664062 182.75 L 175.253906 183.722656 C 175.339844 183.757812 179.792969 185.625 186.382812 185.625 C 189.675781 185.625 193.507812 185.15625 197.574219 183.746094 L 197.230469 182.75 L 196.199219 182.5 L 197.230469 182.75 L 196.882812 181.746094 C 193.066406 183.070312 189.480469 183.511719 186.382812 183.511719 C 183.289062 183.511719 180.691406 183.070312 178.878906 182.628906 C 177.972656 182.410156 177.257812 182.191406 176.777344 182.027344 C 176.539062 181.949219 176.359375 181.878906 176.238281 181.839844 C 176.179688 181.8125 176.136719 181.796875 176.109375 181.785156 L 176.082031 181.773438 L 176.074219 181.769531 L 175.976562 182.011719 L 176.074219 181.769531 L 175.976562 182.011719 L 176.074219 181.769531 C 175.707031 181.621094 175.285156 181.683594 174.980469 181.941406 C 174.675781 182.199219 174.542969 182.605469 174.632812 182.992188 C 174.828125 183.792969 175.027344 184.605469 175.234375 185.425781 C 175.253906 185.5 175.28125 185.570312 175.316406 185.636719 C 175.367188 185.75 176.011719 186.984375 177.71875 188.195312 C 179.417969 189.410156 182.175781 190.5625 186.3125 190.558594 L 186.582031 190.558594 C 190.714844 190.5625 193.476562 189.410156 195.175781 188.195312 C 196.878906 186.984375 197.523438 185.75 197.578125 185.636719 C 197.613281 185.570312 197.640625 185.5 197.65625 185.425781 C 197.863281 184.605469 198.066406 183.792969 198.257812 182.992188 C 198.347656 182.617188 198.226562 182.222656 197.9375 181.960938 C 197.652344 181.699219 197.246094 181.621094 196.882812 181.746094 L 197.230469 182.75 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 187.519531 168.023438 L 185.371094 168.023438 C 185.371094 168.023438 183.441406 168.15625 183.722656 171.570312 L 185.277344 199.953125 C 185.277344 199.953125 185.601562 201.660156 186.445312 201.398438 C 187.292969 201.660156 187.617188 199.953125 187.617188 199.953125 L 189.167969 171.570312 C 189.449219 168.15625 187.519531 168.023438 187.519531 168.023438 " fill-opacity="1" fill-rule="nonzero"/><path fill="#4c44cb" d="M 187.519531 168.023438 L 187.519531 166.964844 L 185.371094 166.964844 L 185.300781 166.96875 C 185.226562 166.976562 185.035156 166.992188 184.765625 167.085938 C 184.570312 167.15625 184.324219 167.265625 184.074219 167.445312 C 183.703125 167.707031 183.320312 168.128906 183.054688 168.710938 C 182.789062 169.292969 182.636719 170.019531 182.640625 170.921875 C 182.640625 171.15625 182.644531 171.398438 182.671875 171.664062 L 183.722656 171.570312 L 182.667969 171.632812 L 184.21875 200.011719 C 184.222656 200.054688 184.230469 200.109375 184.238281 200.152344 C 184.246094 200.195312 184.320312 200.613281 184.535156 201.109375 C 184.644531 201.359375 184.789062 201.640625 185.039062 201.914062 C 185.164062 202.046875 185.324219 202.1875 185.53125 202.300781 C 185.738281 202.410156 186 202.484375 186.273438 202.484375 C 186.441406 202.484375 186.609375 202.457031 186.753906 202.410156 L 186.445312 201.398438 L 186.132812 202.410156 C 186.285156 202.457031 186.453125 202.484375 186.621094 202.484375 C 186.894531 202.484375 187.152344 202.410156 187.363281 202.300781 C 187.730469 202.101562 187.9375 201.839844 188.097656 201.605469 C 188.324219 201.246094 188.445312 200.910156 188.53125 200.636719 C 188.613281 200.371094 188.648438 200.179688 188.65625 200.152344 C 188.664062 200.109375 188.671875 200.054688 188.675781 200.011719 L 190.226562 171.632812 L 189.167969 171.570312 L 190.222656 171.660156 C 190.246094 171.398438 190.253906 171.15625 190.253906 170.921875 C 190.253906 170.121094 190.136719 169.457031 189.921875 168.910156 C 189.761719 168.5 189.546875 168.15625 189.308594 167.890625 C 188.949219 167.484375 188.546875 167.246094 188.230469 167.125 C 187.910156 167 187.671875 166.976562 187.589844 166.96875 L 187.519531 166.964844 L 187.519531 168.023438 L 187.445312 169.082031 L 187.480469 168.652344 L 187.398438 169.078125 C 187.398438 169.078125 187.410156 169.078125 187.445312 169.082031 L 187.480469 168.652344 L 187.398438 169.078125 C 187.398438 169.078125 187.445312 169.085938 187.503906 169.113281 C 187.589844 169.164062 187.714844 169.242188 187.851562 169.476562 C 187.988281 169.714844 188.136719 170.148438 188.136719 170.921875 C 188.136719 171.09375 188.132812 171.28125 188.113281 171.488281 L 188.113281 171.515625 L 186.558594 199.898438 L 187.617188 199.953125 L 186.574219 199.753906 C 186.570312 199.792969 186.5 200.101562 186.394531 200.3125 C 186.371094 200.367188 186.347656 200.40625 186.328125 200.441406 L 186.300781 200.480469 L 186.304688 200.480469 L 186.300781 200.480469 L 186.304688 200.480469 L 186.300781 200.480469 L 186.527344 200.703125 L 186.390625 200.421875 C 186.328125 200.445312 186.304688 200.476562 186.300781 200.480469 L 186.527344 200.703125 L 186.390625 200.421875 L 186.621094 200.898438 L 186.621094 200.371094 C 186.535156 200.367188 186.441406 200.390625 186.390625 200.421875 L 186.621094 200.898438 L 186.621094 200.371094 L 186.621094 200.84375 L 186.757812 200.390625 C 186.726562 200.378906 186.675781 200.371094 186.621094 200.371094 L 186.621094 200.84375 L 186.757812 200.390625 C 186.558594 200.328125 186.339844 200.324219 186.136719 200.386719 L 186.273438 200.832031 L 186.273438 200.371094 C 186.214844 200.371094 186.167969 200.378906 186.136719 200.386719 L 186.273438 200.832031 L 186.273438 200.371094 L 186.273438 200.890625 L 186.519531 200.429688 C 186.460938 200.398438 186.367188 200.367188 186.273438 200.371094 L 186.273438 200.890625 L 186.519531 200.429688 L 186.382812 200.6875 L 186.59375 200.480469 C 186.589844 200.476562 186.570312 200.453125 186.519531 200.429688 L 186.382812 200.6875 L 186.59375 200.480469 L 186.585938 200.484375 L 186.59375 200.480469 L 186.585938 200.484375 L 186.59375 200.480469 C 186.589844 200.476562 186.550781 200.421875 186.511719 200.34375 C 186.457031 200.226562 186.398438 200.066406 186.363281 199.945312 C 186.347656 199.882812 186.332031 199.828125 186.324219 199.792969 L 186.316406 199.757812 L 186.316406 199.753906 L 185.882812 199.839844 L 186.316406 199.753906 L 185.882812 199.839844 L 186.316406 199.753906 L 185.277344 199.953125 L 186.335938 199.898438 L 184.78125 171.515625 L 184.78125 171.484375 C 184.761719 171.28125 184.753906 171.097656 184.753906 170.921875 C 184.753906 170.332031 184.84375 169.9375 184.941406 169.679688 C 185.019531 169.488281 185.101562 169.371094 185.171875 169.289062 C 185.277344 169.167969 185.371094 169.121094 185.4375 169.097656 L 185.492188 169.078125 L 185.492188 169.074219 L 185.492188 169.078125 L 185.492188 169.074219 L 185.492188 169.078125 L 185.410156 168.570312 L 185.441406 169.082031 L 185.492188 169.078125 L 185.410156 168.570312 L 185.441406 169.082031 L 185.371094 168.023438 L 185.371094 169.082031 L 187.519531 169.082031 L 187.519531 168.023438 L 187.445312 169.082031 L 187.519531 168.023438 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 187.519531 168.023438 L 185.371094 168.023438 C 185.371094 168.023438 183.441406 168.15625 183.722656 171.570312 L 185.277344 199.953125 C 185.277344 199.953125 185.601562 201.660156 186.445312 201.398438 C 187.292969 201.660156 187.617188 199.953125 187.617188 199.953125 L 189.167969 171.570312 C 189.449219 168.15625 187.519531 168.023438 187.519531 168.023438 " fill-opacity="1" fill-rule="nonzero"/><path fill="#4c44cb" d="M 187.519531 168.023438 L 187.519531 166.964844 L 185.371094 166.964844 L 185.300781 166.96875 C 185.226562 166.976562 185.035156 166.992188 184.765625 167.085938 C 184.570312 167.15625 184.324219 167.265625 184.074219 167.445312 C 183.703125 167.707031 183.320312 168.128906 183.054688 168.710938 C 182.789062 169.292969 182.636719 170.019531 182.640625 170.921875 C 182.640625 171.15625 182.644531 171.398438 182.671875 171.664062 L 183.722656 171.570312 L 182.667969 171.632812 L 184.21875 200.011719 C 184.222656 200.054688 184.230469 200.109375 184.238281 200.152344 C 184.246094 200.195312 184.320312 200.613281 184.535156 201.109375 C 184.644531 201.359375 184.789062 201.640625 185.039062 201.914062 C 185.164062 202.046875 185.324219 202.1875 185.53125 202.300781 C 185.738281 202.410156 186 202.484375 186.273438 202.484375 C 186.441406 202.484375 186.609375 202.457031 186.753906 202.410156 L 186.445312 201.398438 L 186.132812 202.410156 C 186.285156 202.457031 186.453125 202.484375 186.621094 202.484375 C 186.894531 202.484375 187.152344 202.410156 187.363281 202.300781 C 187.730469 202.101562 187.9375 201.839844 188.097656 201.605469 C 188.324219 201.246094 188.445312 200.910156 188.53125 200.636719 C 188.613281 200.371094 188.648438 200.179688 188.65625 200.152344 C 188.664062 200.109375 188.671875 200.054688 188.675781 200.011719 L 190.226562 171.632812 L 189.167969 171.570312 L 190.222656 171.660156 C 190.246094 171.398438 190.253906 171.15625 190.253906 170.921875 C 190.253906 170.121094 190.136719 169.457031 189.921875 168.910156 C 189.761719 168.5 189.546875 168.15625 189.308594 167.890625 C 188.949219 167.484375 188.546875 167.246094 188.230469 167.125 C 187.910156 167 187.671875 166.976562 187.589844 166.96875 L 187.519531 166.964844 L 187.519531 168.023438 L 187.445312 169.082031 L 187.480469 168.652344 L 187.398438 169.078125 C 187.398438 169.078125 187.410156 169.078125 187.445312 169.082031 L 187.480469 168.652344 L 187.398438 169.078125 C 187.398438 169.078125 187.445312 169.085938 187.503906 169.113281 C 187.589844 169.164062 187.714844 169.242188 187.851562 169.476562 C 187.988281 169.714844 188.136719 170.148438 188.136719 170.921875 C 188.136719 171.09375 188.132812 171.28125 188.113281 171.488281 L 188.113281 171.515625 L 186.558594 199.898438 L 187.617188 199.953125 L 186.574219 199.753906 C 186.570312 199.792969 186.5 200.101562 186.394531 200.3125 C 186.371094 200.367188 186.347656 200.40625 186.328125 200.441406 L 186.300781 200.480469 L 186.304688 200.480469 L 186.300781 200.480469 L 186.304688 200.480469 L 186.300781 200.480469 L 186.527344 200.703125 L 186.390625 200.421875 C 186.328125 200.445312 186.304688 200.476562 186.300781 200.480469 L 186.527344 200.703125 L 186.390625 200.421875 L 186.621094 200.898438 L 186.621094 200.371094 C 186.535156 200.367188 186.441406 200.390625 186.390625 200.421875 L 186.621094 200.898438 L 186.621094 200.371094 L 186.621094 200.84375 L 186.757812 200.390625 C 186.726562 200.378906 186.675781 200.371094 186.621094 200.371094 L 186.621094 200.84375 L 186.757812 200.390625 C 186.558594 200.328125 186.339844 200.324219 186.136719 200.386719 L 186.273438 200.832031 L 186.273438 200.371094 C 186.214844 200.371094 186.167969 200.378906 186.136719 200.386719 L 186.273438 200.832031 L 186.273438 200.371094 L 186.273438 200.890625 L 186.519531 200.429688 C 186.460938 200.398438 186.367188 200.367188 186.273438 200.371094 L 186.273438 200.890625 L 186.519531 200.429688 L 186.382812 200.6875 L 186.59375 200.480469 C 186.589844 200.476562 186.570312 200.453125 186.519531 200.429688 L 186.382812 200.6875 L 186.59375 200.480469 L 186.585938 200.484375 L 186.59375 200.480469 L 186.585938 200.484375 L 186.59375 200.480469 C 186.589844 200.476562 186.550781 200.421875 186.511719 200.34375 C 186.457031 200.226562 186.398438 200.066406 186.363281 199.945312 C 186.347656 199.882812 186.332031 199.828125 186.324219 199.792969 L 186.316406 199.757812 L 186.316406 199.753906 L 185.882812 199.839844 L 186.316406 199.753906 L 185.882812 199.839844 L 186.316406 199.753906 L 185.277344 199.953125 L 186.335938 199.898438 L 184.78125 171.515625 L 184.78125 171.484375 C 184.761719 171.28125 184.753906 171.097656 184.753906 170.921875 C 184.753906 170.332031 184.84375 169.9375 184.941406 169.679688 C 185.019531 169.488281 185.101562 169.371094 185.171875 169.289062 C 185.277344 169.167969 185.371094 169.121094 185.4375 169.097656 L 185.492188 169.078125 L 185.492188 169.074219 L 185.492188 169.078125 L 185.492188 169.074219 L 185.492188 169.078125 L 185.410156 168.570312 L 185.441406 169.082031 L 185.492188 169.078125 L 185.410156 168.570312 L 185.441406 169.082031 L 185.371094 168.023438 L 185.371094 169.082031 L 187.519531 169.082031 L 187.519531 168.023438 L 187.445312 169.082031 L 187.519531 168.023438 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 216.609375 220.285156 C 215.164062 220.359375 213.808594 220.753906 212.609375 221.386719 C 212.890625 220.394531 213.015625 219.339844 212.960938 218.257812 C 212.675781 212.914062 208.117188 208.816406 202.773438 209.101562 C 201.019531 209.195312 199.394531 209.753906 198.023438 210.644531 C 197.527344 205.515625 193.066406 201.640625 187.871094 201.914062 C 183.457031 202.152344 179.890625 205.308594 178.949219 209.410156 C 177.132812 207.886719 174.757812 207.019531 172.207031 207.152344 C 166.867188 207.4375 162.765625 212 163.050781 217.339844 C 163.070312 217.742188 163.121094 218.136719 163.191406 218.53125 C 161.523438 217.4375 159.507812 216.84375 157.367188 216.957031 C 152.023438 217.238281 147.925781 221.800781 148.210938 227.140625 C 148.492188 232.480469 153.050781 236.582031 158.390625 236.300781 C 158.726562 236.28125 159.054688 236.246094 159.378906 236.195312 C 158.816406 237.511719 158.539062 238.976562 158.617188 240.507812 C 158.902344 245.847656 163.460938 249.949219 168.804688 249.664062 C 171.023438 249.546875 173.027344 248.6875 174.59375 247.34375 C 175.804688 251.589844 179.816406 254.585938 184.414062 254.34375 C 189.519531 254.070312 193.484375 249.894531 193.578125 244.863281 C 195.269531 248.15625 198.773438 250.328125 202.707031 250.117188 C 208.050781 249.835938 212.148438 245.273438 211.863281 239.933594 C 211.828125 239.21875 211.710938 238.523438 211.527344 237.859375 C 213.242188 239.074219 215.367188 239.746094 217.632812 239.625 C 222.976562 239.34375 227.074219 234.78125 226.792969 229.441406 C 226.507812 224.105469 221.949219 220 216.609375 220.285156 " fill-opacity="1" fill-rule="nonzero"/><path fill="#4c44cb" d="M 216.609375 220.285156 L 216.550781 219.230469 C 214.949219 219.3125 213.445312 219.75 212.117188 220.449219 L 212.609375 221.386719 L 213.628906 221.671875 C 213.890625 220.75 214.027344 219.773438 214.027344 218.773438 C 214.027344 218.582031 214.023438 218.390625 214.015625 218.203125 L 214.015625 218.199219 C 213.863281 215.339844 212.601562 212.789062 210.667969 210.964844 C 208.738281 209.144531 206.128906 208.027344 203.296875 208.027344 C 203.105469 208.027344 202.914062 208.03125 202.714844 208.046875 L 202.71875 208.046875 C 200.769531 208.148438 198.96875 208.769531 197.445312 209.761719 L 198.023438 210.644531 L 199.074219 210.542969 C 198.539062 205.039062 193.910156 200.84375 188.390625 200.84375 C 188.203125 200.84375 188.007812 200.847656 187.8125 200.859375 C 185.371094 200.988281 183.148438 201.933594 181.421875 203.40625 C 179.691406 204.882812 178.441406 206.894531 177.914062 209.175781 L 178.949219 209.410156 L 179.628906 208.597656 C 177.757812 207.027344 175.335938 206.082031 172.726562 206.082031 C 172.535156 206.082031 172.339844 206.085938 172.148438 206.097656 L 172.152344 206.097656 C 169.285156 206.25 166.738281 207.511719 164.914062 209.441406 C 163.089844 211.375 161.976562 213.984375 161.976562 216.820312 C 161.976562 217.007812 161.984375 217.203125 161.996094 217.402344 L 161.996094 217.394531 C 162.015625 217.847656 162.070312 218.289062 162.144531 218.71875 L 163.191406 218.53125 L 163.769531 217.644531 C 162.074219 216.53125 160.046875 215.882812 157.886719 215.882812 C 157.695312 215.882812 157.503906 215.886719 157.308594 215.902344 C 154.449219 216.050781 151.898438 217.316406 150.074219 219.246094 C 148.25 221.175781 147.136719 223.78125 147.136719 226.621094 C 147.136719 226.808594 147.140625 227.003906 147.152344 227.203125 L 147.152344 227.199219 C 147.304688 230.058594 148.566406 232.609375 150.496094 234.433594 C 152.425781 236.257812 155.035156 237.367188 157.871094 237.367188 C 158.0625 237.367188 158.253906 237.363281 158.449219 237.355469 C 158.820312 237.332031 159.183594 237.296875 159.539062 237.242188 L 159.378906 236.195312 L 158.40625 235.78125 C 157.851562 237.070312 157.546875 238.496094 157.546875 239.980469 C 157.546875 240.171875 157.550781 240.367188 157.558594 240.5625 C 157.710938 243.425781 158.976562 245.976562 160.90625 247.800781 C 162.835938 249.625 165.445312 250.738281 168.28125 250.734375 C 168.472656 250.738281 168.664062 250.730469 168.855469 250.722656 L 168.859375 250.722656 C 171.316406 250.589844 173.546875 249.632812 175.28125 248.148438 L 174.59375 247.34375 L 173.574219 247.632812 C 174.222656 249.898438 175.585938 251.847656 177.398438 253.222656 C 179.207031 254.601562 181.464844 255.417969 183.886719 255.417969 C 184.082031 255.417969 184.273438 255.414062 184.464844 255.398438 C 187.296875 255.25 189.816406 254.015625 191.632812 252.125 C 193.453125 250.230469 194.582031 247.675781 194.632812 244.886719 L 193.578125 244.863281 L 192.636719 245.347656 C 193.53125 247.09375 194.886719 248.550781 196.53125 249.578125 C 198.179688 250.601562 200.121094 251.191406 202.183594 251.191406 C 202.375 251.191406 202.570312 251.183594 202.765625 251.171875 C 205.625 251.023438 208.175781 249.761719 210 247.828125 C 211.824219 245.898438 212.933594 243.292969 212.933594 240.453125 C 212.933594 240.265625 212.929688 240.070312 212.921875 239.878906 C 212.878906 239.082031 212.75 238.308594 212.546875 237.578125 L 211.527344 237.859375 L 210.914062 238.71875 C 212.671875 239.964844 214.820312 240.699219 217.113281 240.699219 C 217.304688 240.699219 217.496094 240.695312 217.691406 240.683594 C 220.550781 240.53125 223.101562 239.269531 224.925781 237.335938 C 226.75 235.40625 227.863281 232.800781 227.863281 229.96875 C 227.863281 229.773438 227.859375 229.578125 227.847656 229.386719 C 227.695312 226.523438 226.433594 223.972656 224.5 222.148438 C 222.574219 220.324219 219.960938 219.214844 217.128906 219.214844 C 216.9375 219.214844 216.746094 219.21875 216.546875 219.230469 L 216.550781 219.230469 L 216.609375 220.285156 L 216.664062 221.339844 C 216.820312 221.335938 216.972656 221.332031 217.128906 221.332031 C 219.410156 221.332031 221.492188 222.21875 223.046875 223.6875 C 224.601562 225.160156 225.609375 227.195312 225.734375 229.496094 C 225.742188 229.652344 225.746094 229.8125 225.746094 229.96875 C 225.746094 232.242188 224.855469 234.328125 223.386719 235.882812 C 221.917969 237.433594 219.882812 238.449219 217.578125 238.566406 C 217.421875 238.578125 217.265625 238.582031 217.113281 238.582031 C 215.265625 238.582031 213.550781 237.996094 212.136719 236.996094 C 211.773438 236.738281 211.285156 236.734375 210.917969 236.992188 C 210.554688 237.25 210.386719 237.710938 210.503906 238.140625 C 210.671875 238.734375 210.773438 239.351562 210.808594 239.988281 C 210.816406 240.144531 210.820312 240.304688 210.820312 240.453125 C 210.820312 242.734375 209.929688 244.824219 208.460938 246.375 C 206.992188 247.925781 204.957031 248.9375 202.652344 249.0625 C 202.496094 249.070312 202.335938 249.074219 202.183594 249.074219 C 200.527344 249.074219 198.972656 248.601562 197.652344 247.777344 C 196.328125 246.957031 195.238281 245.78125 194.519531 244.382812 C 194.296875 243.949219 193.8125 243.726562 193.339844 243.835938 C 192.867188 243.945312 192.53125 244.359375 192.519531 244.84375 C 192.476562 247.082031 191.574219 249.132812 190.109375 250.652344 C 188.648438 252.175781 186.632812 253.164062 184.355469 253.289062 C 184.199219 253.296875 184.042969 253.300781 183.886719 253.300781 C 181.941406 253.300781 180.132812 252.648438 178.675781 251.539062 C 177.222656 250.429688 176.125 248.867188 175.609375 247.050781 C 175.507812 246.707031 175.242188 246.433594 174.894531 246.328125 C 174.550781 246.226562 174.175781 246.304688 173.902344 246.539062 C 172.503906 247.738281 170.726562 248.5 168.746094 248.609375 C 168.585938 248.613281 168.433594 248.617188 168.28125 248.621094 C 166 248.617188 163.917969 247.730469 162.359375 246.261719 C 160.808594 244.789062 159.796875 242.753906 159.675781 240.453125 L 159.675781 240.449219 C 159.667969 240.292969 159.664062 240.136719 159.664062 239.980469 C 159.664062 238.789062 159.910156 237.652344 160.351562 236.613281 C 160.503906 236.257812 160.453125 235.851562 160.214844 235.546875 C 159.980469 235.246094 159.597656 235.09375 159.21875 235.148438 C 158.929688 235.195312 158.636719 235.226562 158.335938 235.242188 L 158.339844 235.242188 C 158.179688 235.25 158.027344 235.25 157.871094 235.25 C 155.59375 235.25 153.507812 234.363281 151.953125 232.894531 C 150.398438 231.425781 149.390625 229.386719 149.265625 227.085938 L 149.265625 227.082031 C 149.257812 226.929688 149.253906 226.773438 149.253906 226.621094 C 149.253906 224.34375 150.144531 222.253906 151.613281 220.699219 C 153.082031 219.148438 155.117188 218.136719 157.421875 218.011719 C 157.578125 218.003906 157.734375 218 157.886719 218 C 159.625 218 161.242188 218.519531 162.609375 219.414062 C 162.960938 219.644531 163.417969 219.644531 163.773438 219.410156 C 164.125 219.179688 164.304688 218.753906 164.230469 218.34375 C 164.167969 217.992188 164.125 217.640625 164.109375 217.285156 L 164.109375 217.28125 C 164.097656 217.125 164.09375 216.972656 164.09375 216.820312 C 164.09375 214.539062 164.980469 212.457031 166.453125 210.898438 C 167.925781 209.34375 169.960938 208.335938 172.261719 208.210938 C 172.417969 208.203125 172.574219 208.195312 172.726562 208.195312 C 174.824219 208.195312 176.757812 208.957031 178.269531 210.222656 C 178.546875 210.457031 178.933594 210.53125 179.285156 210.417969 C 179.632812 210.300781 179.898438 210.007812 179.980469 209.652344 C 180.398438 207.824219 181.402344 206.203125 182.792969 205.019531 C 184.1875 203.828125 185.957031 203.078125 187.925781 202.972656 L 187.929688 202.972656 C 188.082031 202.960938 188.238281 202.957031 188.390625 202.957031 C 192.816406 202.957031 196.542969 206.335938 196.96875 210.746094 C 197.003906 211.117188 197.230469 211.4375 197.5625 211.597656 C 197.894531 211.761719 198.289062 211.734375 198.597656 211.535156 C 199.824219 210.738281 201.261719 210.238281 202.828125 210.160156 L 202.832031 210.160156 C 202.988281 210.148438 203.140625 210.144531 203.296875 210.144531 C 205.578125 210.144531 207.660156 211.035156 209.214844 212.503906 C 210.765625 213.972656 211.777344 216.007812 211.902344 218.316406 L 211.902344 218.3125 C 211.910156 218.464844 211.910156 218.621094 211.910156 218.773438 C 211.910156 219.578125 211.800781 220.355469 211.589844 221.101562 C 211.476562 221.507812 211.621094 221.945312 211.945312 222.210938 C 212.273438 222.472656 212.730469 222.519531 213.105469 222.324219 C 214.171875 221.757812 215.375 221.414062 216.664062 221.339844 L 216.609375 220.285156 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 175.125 248.769531 C 173.078125 245.347656 173.792969 238.472656 178.664062 236.183594 " fill-opacity="1" fill-rule="nonzero"/><path fill="#4c44cb" d="M 176.035156 248.226562 C 175.460938 247.273438 175.101562 245.875 175.105469 244.382812 C 175.105469 242.9375 175.429688 241.421875 176.09375 240.132812 C 176.761719 238.84375 177.746094 237.785156 179.109375 237.140625 C 179.640625 236.890625 179.871094 236.261719 179.617188 235.730469 C 179.371094 235.203125 178.742188 234.976562 178.210938 235.222656 C 176.371094 236.085938 175.046875 237.535156 174.210938 239.164062 C 173.371094 240.804688 172.992188 242.632812 172.992188 244.382812 C 172.992188 246.195312 173.390625 247.929688 174.21875 249.3125 C 174.519531 249.8125 175.167969 249.980469 175.667969 249.679688 C 176.167969 249.375 176.335938 248.726562 176.035156 248.226562 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 163.191406 218.53125 C 166.910156 220.535156 168.28125 227.589844 165.128906 230.453125 " fill-opacity="1" fill-rule="nonzero"/><path fill="#4c44cb" d="M 162.6875 219.460938 C 163.582031 219.9375 164.382812 220.855469 164.945312 222.011719 C 165.507812 223.164062 165.824219 224.539062 165.824219 225.820312 C 165.824219 226.621094 165.703125 227.386719 165.464844 228.042969 C 165.226562 228.703125 164.875 229.253906 164.417969 229.671875 C 163.984375 230.0625 163.953125 230.734375 164.347656 231.167969 C 164.738281 231.597656 165.410156 231.628906 165.84375 231.238281 C 166.589844 230.550781 167.121094 229.691406 167.457031 228.765625 C 167.789062 227.835938 167.9375 226.835938 167.941406 225.820312 C 167.9375 224.199219 167.558594 222.542969 166.84375 221.085938 C 166.136719 219.628906 165.089844 218.355469 163.691406 217.59375 C 163.175781 217.320312 162.535156 217.515625 162.257812 218.027344 C 161.980469 218.539062 162.171875 219.183594 162.6875 219.460938 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 180.515625 213.964844 C 171.566406 219.621094 182.125 232.152344 188.96875 224.671875 " fill-opacity="1" fill-rule="nonzero"/><path fill="#4c44cb" d="M 179.949219 213.070312 C 178.597656 213.921875 177.605469 214.953125 176.957031 216.082031 C 176.308594 217.210938 176.015625 218.429688 176.015625 219.613281 C 176.015625 220.710938 176.265625 221.777344 176.6875 222.765625 C 177.328125 224.25 178.375 225.546875 179.664062 226.5 C 180.957031 227.445312 182.519531 228.046875 184.175781 228.046875 C 185.128906 228.046875 186.109375 227.84375 187.054688 227.40625 C 188.003906 226.96875 188.914062 226.296875 189.746094 225.386719 C 190.140625 224.957031 190.109375 224.289062 189.679688 223.890625 C 189.25 223.496094 188.578125 223.527344 188.183594 223.960938 C 187.519531 224.6875 186.835938 225.171875 186.167969 225.488281 C 185.496094 225.796875 184.832031 225.929688 184.175781 225.929688 C 183.414062 225.929688 182.660156 225.746094 181.949219 225.410156 C 180.875 224.902344 179.90625 224.042969 179.21875 223.011719 C 178.53125 221.976562 178.128906 220.78125 178.132812 219.613281 C 178.132812 218.769531 178.335938 217.9375 178.792969 217.136719 C 179.253906 216.335938 179.980469 215.554688 181.082031 214.859375 C 181.578125 214.546875 181.726562 213.890625 181.410156 213.402344 C 181.101562 212.902344 180.449219 212.757812 179.949219 213.070312 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 198.5625 222.84375 C 204.863281 228.929688 195.984375 238.382812 189.96875 231.152344 " fill-opacity="1" fill-rule="nonzero"/><path fill="#4c44cb" d="M 197.824219 223.605469 C 198.5 224.253906 198.953125 224.925781 199.242188 225.59375 C 199.535156 226.261719 199.667969 226.925781 199.667969 227.578125 C 199.667969 228.921875 199.101562 230.222656 198.203125 231.171875 C 197.753906 231.644531 197.226562 232.03125 196.65625 232.296875 C 196.085938 232.554688 195.476562 232.699219 194.851562 232.699219 C 194.210938 232.699219 193.546875 232.554688 192.863281 232.207031 C 192.179688 231.859375 191.472656 231.304688 190.78125 230.472656 C 190.40625 230.023438 189.738281 229.964844 189.292969 230.335938 C 188.839844 230.710938 188.78125 231.378906 189.15625 231.828125 C 190.003906 232.84375 190.933594 233.601562 191.902344 234.09375 C 192.871094 234.589844 193.878906 234.816406 194.851562 234.816406 C 195.804688 234.816406 196.71875 234.597656 197.542969 234.214844 C 198.785156 233.640625 199.835938 232.699219 200.589844 231.550781 C 201.332031 230.402344 201.785156 229.027344 201.785156 227.578125 C 201.785156 226.644531 201.59375 225.679688 201.183594 224.746094 C 200.777344 223.8125 200.152344 222.90625 199.296875 222.082031 C 198.878906 221.675781 198.207031 221.6875 197.800781 222.105469 C 197.394531 222.527344 197.40625 223.199219 197.824219 223.605469 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 203.792969 230.453125 C 203.792969 230.453125 210.300781 229.828125 211.703125 238.628906 " fill-opacity="1" fill-rule="nonzero"/><path fill="#4c44cb" d="M 203.898438 231.503906 L 203.835938 230.878906 L 203.890625 231.503906 L 203.898438 231.503906 L 203.835938 230.878906 L 203.890625 231.503906 L 203.902344 231.503906 C 203.921875 231.503906 203.980469 231.5 204.074219 231.5 C 204.320312 231.5 204.792969 231.527344 205.371094 231.667969 C 206.238281 231.878906 207.324219 232.339844 208.308594 233.378906 C 209.296875 234.421875 210.21875 236.066406 210.65625 238.792969 C 210.746094 239.371094 211.292969 239.761719 211.871094 239.671875 C 212.445312 239.582031 212.839844 239.035156 212.75 238.457031 C 212.421875 236.421875 211.832031 234.8125 211.074219 233.554688 C 209.949219 231.660156 208.453125 230.585938 207.128906 230.027344 C 205.800781 229.460938 204.644531 229.386719 204.074219 229.386719 C 203.859375 229.386719 203.722656 229.394531 203.691406 229.398438 C 203.113281 229.457031 202.6875 229.972656 202.742188 230.550781 C 202.800781 231.132812 203.316406 231.5625 203.898438 231.503906 " fill-opacity="1" fill-rule="nonzero"/><path fill="#ffffff" d="M 198.566406 210.316406 C 198.566406 210.316406 193.628906 214.152344 195.347656 220.476562 " fill-opacity="1" fill-rule="nonzero"/><path fill="#4c44cb" d="M 197.914062 209.480469 C 197.859375 209.527344 196.886719 210.285156 195.90625 211.695312 C 195.417969 212.402344 194.921875 213.273438 194.546875 214.300781 C 194.175781 215.328125 193.925781 216.515625 193.925781 217.835938 C 193.925781 218.75 194.046875 219.726562 194.328125 220.757812 C 194.476562 221.316406 195.058594 221.652344 195.625 221.496094 C 196.1875 221.34375 196.519531 220.761719 196.367188 220.199219 C 196.136719 219.351562 196.039062 218.566406 196.039062 217.835938 C 196.039062 216.785156 196.238281 215.847656 196.539062 215.023438 C 196.984375 213.785156 197.667969 212.800781 198.238281 212.132812 C 198.519531 211.800781 198.777344 211.546875 198.953125 211.382812 C 199.042969 211.296875 199.113281 211.238281 199.160156 211.199219 L 199.207031 211.15625 L 199.214844 211.148438 L 199.03125 210.917969 L 199.214844 211.152344 L 199.214844 211.148438 L 199.03125 210.917969 L 199.214844 211.152344 C 199.675781 210.792969 199.757812 210.128906 199.402344 209.667969 C 199.042969 209.207031 198.375 209.121094 197.917969 209.480469 " fill-opacity="1" fill-rule="nonzero"/></svg> ```