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>
```