diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 00000000..49cc874d
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,119 @@
+name: HW5 Tests
+
+on:
+ push:
+ branches: [ main, master, develop ]
+ paths:
+ - 'hw5/**'
+ - '.github/workflows/hw5-tests.yml'
+ pull_request:
+ branches: [ main, master, develop ]
+ paths:
+ - 'hw5/**'
+ workflow_dispatch:
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ services:
+ postgres:
+ image: postgres:14-alpine
+ env:
+ POSTGRES_USER: shop_user
+ POSTGRES_PASSWORD: shop_password
+ POSTGRES_DB: shop_db
+ options: >-
+ --health-cmd pg_isready
+ --health-interval 10s
+ --health-timeout 5s
+ --health-retries 5
+ ports:
+ - 5432:5432
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.12'
+
+ - name: Cache pip packages
+ uses: actions/cache@v4
+ with:
+ path: ~/.cache/pip
+ key: ${{ runner.os }}-pip-${{ hashFiles('hw5/shop_api/requirements.txt') }}
+ restore-keys: |
+ ${{ runner.os }}-pip-
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r hw5/shop_api/requirements.txt
+ pip install pytest pytest-cov
+
+ - name: Run tests with coverage
+ env:
+ DATABASE_URL: postgresql://shop_user:shop_password@localhost:5432/shop_db
+ PYTHONPATH: ${{ github.workspace }}/hw5
+ run: |
+ cd hw5
+ pytest tests/ --cov=shop_api --cov-report=term-missing --cov-report=xml --cov-report=html -v
+
+ - name: Check coverage threshold
+ env:
+ PYTHONPATH: ${{ github.workspace }}/hw5
+ run: |
+ cd hw5
+ pytest tests/ --cov=shop_api --cov-fail-under=90 -q
+
+ - name: Generate coverage badge
+ if: github.ref == 'refs/heads/main'
+ run: |
+ cd hw5
+ COVERAGE=$(pytest tests/ --cov=shop_api --cov-report=term | grep TOTAL | awk '{print $4}' | sed 's/%//')
+ echo "Coverage: ${COVERAGE}%"
+ echo "COVERAGE=${COVERAGE}" >> $GITHUB_ENV
+
+ - name: Upload coverage HTML report
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: hw5-coverage-report
+ path: hw5/htmlcov/
+ retention-days: 30
+
+ - name: Upload coverage XML
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: hw5-coverage-xml
+ path: hw5/coverage.xml
+ retention-days: 30
+
+ - name: Comment PR with coverage
+ if: github.event_name == 'pull_request'
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const fs = require('fs');
+ const coverage = fs.readFileSync('hw5/coverage.xml', 'utf8');
+ const match = coverage.match(/line-rate="([0-9.]+)"/);
+ const percent = match ? (parseFloat(match[1]) * 100).toFixed(2) : 'N/A';
+
+ github.rest.issues.createComment({
+ issue_number: context.issue.number,
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ body: `## 📊 Test Coverage Report\n\nCoverage: **${percent}%**\n\n[View detailed report](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})`
+ });
+
+ - name: Test summary
+ if: always()
+ run: |
+ cd hw5
+ echo "## Test Results" >> $GITHUB_STEP_SUMMARY
+ echo "" >> $GITHUB_STEP_SUMMARY
+ pytest tests/ --cov=shop_api --tb=no -q 2>&1 | tail -n 20 >> $GITHUB_STEP_SUMMARY || true
diff --git a/hw1/app.py b/hw1/app.py
index 6107b870..a8a8e5c2 100644
--- a/hw1/app.py
+++ b/hw1/app.py
@@ -1,4 +1,68 @@
-from typing import Any, Awaitable, Callable
+import json
+from math import factorial
+from typing import Any, Awaitable, Callable, List
+from urllib.parse import parse_qs
+
+import uvicorn
+
+ALLOWED_PATHS = ["/factorial", "/mean", "/fibonacci"]
+
+
+def calculate_factorial(value: int) -> int:
+ """Функция для расчета факториала"""
+ return factorial(value)
+
+
+def calculate_fibonacсі(n: int) -> int:
+ """Функция для расчета n-ого числа фибоначи"""
+ a, b = 0, 1
+ for _ in range(n):
+ a, b = b, a + b
+ return b
+
+
+def calculate_mean(data: List[float]):
+ """Функция для расчета среднего из массива чисел"""
+ result = sum(data) / len(data)
+ return result
+
+
+async def _read_body(receive: Callable[[], Awaitable[dict[str, Any]]]) -> bytes:
+ body = b""
+ more = True
+ while more:
+ message = await receive()
+ body += message.get("body", b"")
+ more = message.get("more_body", False)
+ return body
+
+
+async def _send_json(send, status: int, payload: dict[str, Any]) -> None:
+ body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
+ headers = [
+ [b"content-type", b"application/json; charset=utf-8"],
+ [b"content-length", str(len(body)).encode("utf-8")],
+ ]
+ await send({"type": "http.response.start", "status": status, "headers": headers})
+ await send({"type": "http.response.body", "body": body, "more_body": False})
+
+
+def _parse_query(scope: dict[str, Any]) -> dict[str, list[str]]:
+ qs_bytes = scope.get("query_string", b"") or b""
+ return parse_qs(qs_bytes.decode("utf-8"), keep_blank_values=True)
+
+
+def _parse_int_strict(s: str) -> int:
+ s2 = s.strip()
+ if s2 == "":
+ raise ValueError("empty")
+ if s2[0] in "+-":
+ if len(s2) == 1 or not s2[1:].isdigit():
+ raise ValueError("non-integer")
+ else:
+ if not s2.isdigit():
+ raise ValueError("non-integer")
+ return int(s2, 10)
async def application(
@@ -12,8 +76,72 @@ async def application(
receive: Корутина для получения сообщений от клиента
send: Корутина для отправки сообщений клиенту
"""
- # TODO: Ваша реализация здесь
+ if scope.get("type") != "http":
+ await _send_json(send, 404, {"error": "Not Found"})
+ return
+
+ path = scope.get("path") or ""
+
+ if path not in ALLOWED_PATHS and not path.startswith("/fibonacci"):
+ await _send_json(send, 404, {"error": "Not Found"})
+ return
+
+ try:
+ if path == "/factorial":
+ params = _parse_query(scope)
+ values = params.get("n", [])
+ if not values:
+ return await _send_json(send, 422, {"error": "missing 'n'"})
+ try:
+ n = _parse_int_strict(values[0])
+ except ValueError:
+ return await _send_json(send, 422, {"error": "n must be integer"})
+ if n < 0:
+ return await _send_json(send, 400, {"error": "n must be >= 0"})
+ return await _send_json(send, 200, {"result": calculate_factorial(n)})
+
+ if path.startswith("/fibonacci"):
+ suffix = path[len("/fibonacci") :]
+ if not suffix or suffix == "/":
+ return await _send_json(send, 422, {"error": "missing path param 'n'"})
+ if suffix[0] == "/":
+ raw = suffix[1:]
+ else:
+ raw = suffix
+ try:
+ n = _parse_int_strict(raw)
+ except ValueError:
+ return await _send_json(send, 422, {"error": "n must be integer"})
+ if n < 0:
+ return await _send_json(send, 400, {"error": "n must be >= 0"})
+ return await _send_json(send, 200, {"result": calculate_fibonacсі(n)})
+
+ if path == "/mean":
+ body = await _read_body(receive)
+ if not body:
+ return await _send_json(send, 422, {"error": "missing JSON body"})
+ try:
+ data = json.loads(body.decode("utf-8"))
+ except Exception:
+ return await _send_json(send, 422, {"error": "invalid JSON"})
+ if data is None or not isinstance(data, list):
+ return await _send_json(send, 422, {"error": "body must be JSON array"})
+ if len(data) == 0:
+ return await _send_json(send, 400, {"error": "array must be non-empty"})
+ nums: list[float] = []
+ for item in data:
+ if isinstance(item, (int, float)):
+ nums.append(float(item))
+ else:
+ return await _send_json(
+ send, 422, {"error": "array must contain numbers"}
+ )
+ mean = calculate_mean(nums)
+ return await _send_json(send, 200, {"result": mean})
+
+ except Exception:
+ await _send_json(send, 500, {"error": "Internal Server Error"})
+
if __name__ == "__main__":
- import uvicorn
uvicorn.run("app:application", host="0.0.0.0", port=8000, reload=True)
diff --git a/hw2/hw/shop_api/main.py b/hw2/hw/shop_api/main.py
index f60a8c60..2402d5c8 100644
--- a/hw2/hw/shop_api/main.py
+++ b/hw2/hw/shop_api/main.py
@@ -1,3 +1,390 @@
-from fastapi import FastAPI
+import random
+import string
+from typing import Dict, List, Optional
+
+from fastapi import (
+ FastAPI,
+ HTTPException,
+ Response,
+ WebSocket,
+ WebSocketDisconnect,
+ status,
+)
+from pydantic import BaseModel, ConfigDict, Field
app = FastAPI(title="Shop API")
+
+
+# Модели данных
+class Item(BaseModel):
+ id: int
+ name: str
+ price: float = Field(gt=0)
+ deleted: bool = False
+
+
+class CartItem(BaseModel):
+ id: int
+ name: str
+ quantity: int = Field(ge=1)
+ available: bool
+
+
+class Cart(BaseModel):
+ id: int
+ items: List[CartItem] = []
+ price: float = Field(ge=0)
+
+
+# Хранилище данных в памяти
+items_db: Dict[int, Item] = {}
+carts_db: Dict[int, Cart] = {}
+next_item_id = 1
+next_cart_id = 1
+
+
+# WebSocket Chat Manager
+class ConnectionManager:
+ """Управляет WebSocket соединениями для разных комнат чата"""
+
+ def __init__(self):
+ # Словарь: chat_name -> список кортежей (websocket, username)
+ self.active_connections: Dict[str, List[tuple[WebSocket, str]]] = {}
+
+ def generate_username(self) -> str:
+ """Генерирует случайное имя пользователя"""
+ adjectives = [
+ "Fast",
+ "Brave",
+ "Smart",
+ "Cool",
+ "Swift",
+ "Bold",
+ "Bright",
+ "Quick",
+ ]
+ nouns = ["Tiger", "Eagle", "Wolf", "Lion", "Hawk", "Bear", "Fox", "Dragon"]
+ number = "".join(random.choices(string.digits, k=3))
+ return f"{random.choice(adjectives)}{random.choice(nouns)}{number}"
+
+ async def connect(self, websocket: WebSocket, chat_name: str) -> str:
+ """Подключает пользователя к комнате и возвращает его username"""
+ await websocket.accept()
+ username = self.generate_username()
+
+ if chat_name not in self.active_connections:
+ self.active_connections[chat_name] = []
+
+ self.active_connections[chat_name].append((websocket, username))
+ return username
+
+ def disconnect(self, websocket: WebSocket, chat_name: str):
+ """Отключает пользователя от комнаты"""
+ if chat_name in self.active_connections:
+ self.active_connections[chat_name] = [
+ (ws, user)
+ for ws, user in self.active_connections[chat_name]
+ if ws != websocket
+ ]
+ # Удаляем комнату, если она пустая
+ if not self.active_connections[chat_name]:
+ del self.active_connections[chat_name]
+
+ async def broadcast(self, message: str, chat_name: str, sender_username: str):
+ """Отправляет сообщение всем пользователям в комнате"""
+ if chat_name not in self.active_connections:
+ return
+
+ formatted_message = f"{sender_username} :: {message}"
+
+ # Список соединений для удаления (если возникнут ошибки)
+ to_remove = []
+
+ for websocket, username in self.active_connections[chat_name]:
+ try:
+ await websocket.send_text(formatted_message)
+ except Exception:
+ # Соединение разорвано, помечаем для удаления
+ to_remove.append(websocket)
+
+ # Удаляем проблемные соединения
+ for ws in to_remove:
+ self.disconnect(ws, chat_name)
+
+
+manager = ConnectionManager()
+
+
+# WebSocket endpoint для чата
+@app.websocket("/chat/{chat_name}")
+async def websocket_chat_endpoint(websocket: WebSocket, chat_name: str):
+ """WebSocket endpoint для чата с поддержкой комнат"""
+ username = await manager.connect(websocket, chat_name)
+
+ try:
+ while True:
+ # Получаем текстовое сообщение от клиента
+ message = await websocket.receive_text()
+
+ # Broadcast сообщение всем в комнате
+ await manager.broadcast(message, chat_name, username)
+
+ except WebSocketDisconnect:
+ # Пользователь отключился
+ manager.disconnect(websocket, chat_name)
+
+
+# Вспомогательные функции
+def get_next_item_id() -> int:
+ global next_item_id
+ next_item_id += 1
+ return next_item_id - 1
+
+
+def get_next_cart_id() -> int:
+ global next_cart_id
+ next_cart_id += 1
+ return next_cart_id - 1
+
+
+def calculate_cart_price(cart: Cart) -> float:
+ total = 0.0
+ for item in cart.items:
+ if item.available:
+ item_obj = items_db.get(item.id)
+ if item_obj and not item_obj.deleted:
+ total += item_obj.price * item.quantity
+ return total
+
+
+# Эндпоинты для корзин
+@app.post("/cart", status_code=status.HTTP_201_CREATED)
+async def create_cart(response: Response):
+ cart_id = get_next_cart_id()
+ cart = Cart(id=cart_id, items=[], price=0.0)
+ carts_db[cart_id] = cart
+ response.headers["location"] = f"/cart/{cart_id}"
+ return {"id": cart_id}
+
+
+@app.get("/cart/{cart_id}")
+async def get_cart(cart_id: int):
+ if cart_id not in carts_db:
+ raise HTTPException(status_code=404, detail="Cart not found")
+
+ cart = carts_db[cart_id]
+ # Пересчитываем цену на случай изменений в товарах
+ cart.price = calculate_cart_price(cart)
+ return cart
+
+
+@app.get("/cart")
+async def get_carts(
+ offset: int = 0,
+ limit: int = 10,
+ min_price: Optional[float] = None,
+ max_price: Optional[float] = None,
+ min_quantity: Optional[int] = None,
+ max_quantity: Optional[int] = None,
+):
+ # Валидация параметров
+ if offset < 0:
+ raise HTTPException(status_code=422, detail="Offset must be non-negative")
+ if limit <= 0:
+ raise HTTPException(status_code=422, detail="Limit must be positive")
+ if min_price is not None and min_price < 0:
+ raise HTTPException(status_code=422, detail="Min price must be non-negative")
+ if max_price is not None and max_price < 0:
+ raise HTTPException(status_code=422, detail="Max price must be non-negative")
+ if min_quantity is not None and min_quantity < 0:
+ raise HTTPException(status_code=422, detail="Min quantity must be non-negative")
+ if max_quantity is not None and max_quantity < 0:
+ raise HTTPException(status_code=422, detail="Max quantity must be non-negative")
+
+ carts = list(carts_db.values())
+
+ # Применяем фильтры
+ filtered_carts = []
+ for cart in carts:
+ # Пересчитываем цену для каждого запроса
+ cart.price = calculate_cart_price(cart)
+
+ # Фильтр по цене
+ if min_price is not None and cart.price < min_price:
+ continue
+ if max_price is not None and cart.price > max_price:
+ continue
+
+ filtered_carts.append(cart)
+
+ # Применяем пагинацию
+ paginated_carts = filtered_carts[offset : offset + limit]
+
+ # ВАЖНО: фильтр по quantity применяется к суммарному количеству
+ # товаров во ВСЕХ возвращаемых корзинах (после пагинации)
+ if min_quantity is not None or max_quantity is not None:
+ total_quantity = sum(
+ item.quantity for cart in paginated_carts for item in cart.items
+ )
+
+ if min_quantity is not None and total_quantity < min_quantity:
+ return []
+ if max_quantity is not None and total_quantity > max_quantity:
+ return []
+
+ return paginated_carts
+
+
+@app.post("/cart/{cart_id}/add/{item_id}")
+async def add_to_cart(cart_id: int, item_id: int):
+ if cart_id not in carts_db:
+ raise HTTPException(status_code=404, detail="Cart not found")
+ if item_id not in items_db:
+ raise HTTPException(status_code=404, detail="Item not found")
+
+ item = items_db[item_id]
+ if item.deleted:
+ raise HTTPException(status_code=400, detail="Item is deleted")
+
+ cart = carts_db[cart_id]
+
+ # Проверяем, есть ли товар уже в корзине
+ for cart_item in cart.items:
+ if cart_item.id == item_id:
+ cart_item.quantity += 1
+ cart.price = calculate_cart_price(cart)
+ return {"message": "Item quantity increased"}
+
+ # Добавляем новый товар
+ cart_item = CartItem(
+ id=item_id, name=item.name, quantity=1, available=not item.deleted
+ )
+ cart.items.append(cart_item)
+ cart.price = calculate_cart_price(cart)
+
+ return {"message": "Item added to cart"}
+
+
+# Эндпоинты для товаров
+class ItemCreate(BaseModel):
+ name: str
+ price: float = Field(gt=0)
+
+
+class ItemPut(BaseModel):
+ """Модель для PUT - все поля обязательны"""
+
+ name: str
+ price: float = Field(gt=0)
+
+
+class ItemPatch(BaseModel):
+ """Модель для PATCH - все поля опциональны, extra поля запрещены"""
+
+ model_config = ConfigDict(extra="forbid")
+
+ name: Optional[str] = None
+ price: Optional[float] = Field(gt=0, default=None)
+
+
+@app.post("/item", status_code=status.HTTP_201_CREATED)
+async def create_item(item: ItemCreate):
+ item_id = get_next_item_id()
+ new_item = Item(id=item_id, name=item.name, price=item.price)
+ items_db[item_id] = new_item
+ return new_item
+
+
+@app.get("/item/{item_id}")
+async def get_item(item_id: int):
+ if item_id not in items_db:
+ raise HTTPException(status_code=404, detail="Item not found")
+
+ item = items_db[item_id]
+ # Возвращаем 404 для удаленных товаров
+ if item.deleted:
+ raise HTTPException(status_code=404, detail="Item not found")
+
+ return item
+
+
+@app.get("/item")
+async def get_items(
+ offset: int = 0,
+ limit: int = 10,
+ min_price: Optional[float] = None,
+ max_price: Optional[float] = None,
+ show_deleted: bool = False,
+):
+ # Валидация параметров
+ if offset < 0:
+ raise HTTPException(status_code=422, detail="Offset must be non-negative")
+ if limit <= 0:
+ raise HTTPException(status_code=422, detail="Limit must be positive")
+ if min_price is not None and min_price < 0:
+ raise HTTPException(status_code=422, detail="Min price must be non-negative")
+ if max_price is not None and max_price < 0:
+ raise HTTPException(status_code=422, detail="Max price must be non-negative")
+
+ items = list(items_db.values())
+
+ # Фильтрация
+ filtered_items = []
+ for item in items:
+ if not show_deleted and item.deleted:
+ continue
+
+ if min_price is not None and item.price < min_price:
+ continue
+ if max_price is not None and item.price > max_price:
+ continue
+
+ filtered_items.append(item)
+
+ # Пагинация
+ result = filtered_items[offset : offset + limit]
+ return result
+
+
+@app.put("/item/{item_id}")
+async def update_item(item_id: int, item_update: ItemPut):
+ if item_id not in items_db:
+ raise HTTPException(status_code=404, detail="Item not found")
+
+ existing_item = items_db[item_id]
+ if existing_item.deleted:
+ raise HTTPException(status_code=400, detail="Cannot update deleted item")
+
+ # Обновляем поля
+ existing_item.name = item_update.name
+ existing_item.price = item_update.price
+
+ return existing_item
+
+
+@app.patch("/item/{item_id}")
+async def partial_update_item(item_id: int, item_update: ItemPatch):
+ if item_id not in items_db:
+ raise HTTPException(status_code=404, detail="Item not found")
+
+ existing_item = items_db[item_id]
+
+ # Для удаленных товаров возвращаем 304 NOT_MODIFIED
+ if existing_item.deleted:
+ return Response(status_code=status.HTTP_304_NOT_MODIFIED)
+
+ # Обновляем только переданные поля
+ update_data = item_update.model_dump(exclude_unset=True)
+ for field, value in update_data.items():
+ setattr(existing_item, field, value)
+
+ return existing_item
+
+
+@app.delete("/item/{item_id}")
+async def delete_item(item_id: int):
+ if item_id not in items_db:
+ raise HTTPException(status_code=404, detail="Item not found")
+
+ items_db[item_id].deleted = True
+ return {"message": "Item deleted successfully"}
diff --git a/hw3/dashboard_screenshots/Api_latency_monitoring.png b/hw3/dashboard_screenshots/Api_latency_monitoring.png
new file mode 100644
index 00000000..5cfb8acd
Binary files /dev/null and b/hw3/dashboard_screenshots/Api_latency_monitoring.png differ
diff --git a/hw3/dashboard_screenshots/Dashboard_list.png b/hw3/dashboard_screenshots/Dashboard_list.png
new file mode 100644
index 00000000..df68027d
Binary files /dev/null and b/hw3/dashboard_screenshots/Dashboard_list.png differ
diff --git a/hw3/dashboard_screenshots/Http_requests_monitoring.png b/hw3/dashboard_screenshots/Http_requests_monitoring.png
new file mode 100644
index 00000000..9fe2eb08
Binary files /dev/null and b/hw3/dashboard_screenshots/Http_requests_monitoring.png differ
diff --git a/hw3/docker-compose.yml b/hw3/docker-compose.yml
new file mode 100644
index 00000000..edcd5d91
--- /dev/null
+++ b/hw3/docker-compose.yml
@@ -0,0 +1,57 @@
+version: "3.8"
+
+services:
+ local:
+ build:
+ context: ./shop_api
+ dockerfile: Dockerfile
+ target: local
+ container_name: shop_api
+ restart: always
+ command: uvicorn main:app --host 0.0.0.0 --port 8080 --reload
+ ports:
+ - 8080:8080
+ environment:
+ - PROMETHEUS_MULTIPROC_DIR=/tmp
+ volumes:
+ - ./shop_api/src:/app
+ networks:
+ - monitoring_network
+
+ grafana:
+ image: grafana/grafana:latest
+ container_name: grafana
+ ports:
+ - 3000:3000
+ restart: always
+ volumes:
+ - ./settings/grafana:/var/lib/grafana
+ networks:
+ - monitoring_network
+ depends_on:
+ - prometheus
+
+ prometheus:
+ image: prom/prometheus:latest
+ container_name: prometheus
+ volumes:
+ - ./settings/prometheus/:/etc/prometheus/
+ - prometheus_data:/prometheus
+ command:
+ - "--config.file=/etc/prometheus/prometheus.yml"
+ - "--storage.tsdb.path=/prometheus"
+ - "--web.console.libraries=/usr/share/prometheus/console_libraries"
+ - "--web.console.templates=/usr/share/prometheus/consoles"
+ - "--web.enable-lifecycle"
+ ports:
+ - 9090:9090
+ restart: always
+ networks:
+ - monitoring_network
+
+volumes:
+ prometheus_data:
+
+networks:
+ monitoring_network:
+ driver: bridge
diff --git a/hw3/settings/grafana/grafana.db b/hw3/settings/grafana/grafana.db
new file mode 100644
index 00000000..9797d650
Binary files /dev/null and b/hw3/settings/grafana/grafana.db differ
diff --git a/hw3/settings/grafana/plugins/grafana-exploretraces-app/1382cadfeb81ccdaa67d.svg b/hw3/settings/grafana/plugins/grafana-exploretraces-app/1382cadfeb81ccdaa67d.svg
new file mode 100644
index 00000000..6dee4c70
--- /dev/null
+++ b/hw3/settings/grafana/plugins/grafana-exploretraces-app/1382cadfeb81ccdaa67d.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/hw3/settings/grafana/plugins/grafana-exploretraces-app/150.js b/hw3/settings/grafana/plugins/grafana-exploretraces-app/150.js
new file mode 100644
index 00000000..b4320c35
--- /dev/null
+++ b/hw3/settings/grafana/plugins/grafana-exploretraces-app/150.js
@@ -0,0 +1,2 @@
+"use strict";(self.webpackChunkgrafana_exploretraces_app=self.webpackChunkgrafana_exploretraces_app||[]).push([[150],{5150:(e,a,r)=>{r.r(a),r.d(a,{default:()=>o});var o={"grafana-scenes":{components:{"adhoc-filter-pill":{"edit-filter-with-key":"Editar filtro com chave {{keyLabel}}","managed-filter":"Filtro gerenciado de {{origin}}","remove-filter-with-key":"Remover filtro com chave {{keyLabel}}"},"adhoc-filters-combobox":{"remove-filter-value":"Remover valor do filtro: {{itemLabel}}","use-custom-value":"Usar valor personalizado: {{itemLabel}}"},"fallback-page":{content:"Se você chegou aqui usando um link, pode haver um bug neste aplicativo.",subTitle:"O URL não corresponde a nenhuma página",title:"Não encontrado"},"nested-scene-renderer":{"collapse-button-label":"Recolher cena","expand-button-label":"Expandir cena","remove-button-label":"Remover cena"},"scene-debugger":{"object-details":"Detalhes do objeto","scene-graph":"Gráfico de cena","title-scene-debugger":"Depurador de cena"},"scene-grid-row":{"collapse-row":"Recolher linha","expand-row":"Expandir linha"},"scene-time-range-compare-renderer":{"button-label":"Comparação","button-tooltip":"Ativar comparação de intervalo de tempo"},splitter:{"aria-label-pane-resize-widget":"Widget de redimensionamento do painel"},"viz-panel":{title:{title:"Título"}},"viz-panel-explore-button":{explore:"Explorar"},"viz-panel-renderer":{"loading-plugin-panel":"Carregando painel do plug-in…","panel-plugin-has-no-panel-component":"O plug-in do painel não possui componente de painel"},"viz-panel-series-limit":{"content-rendering-series-single-panel-impact-performance":"Renderizar muitas séries em um único painel pode afetar o desempenho e dificultar a leitura dos dados.","warning-message":"Mostrando apenas {{seriesLimit}} série(s)"}},utils:{"controls-label":{"tooltip-remove":"Remover"},"loading-indicator":{"content-cancel-query":"Cancelar consulta"}},variables:{"ad-hoc-combobox":{"aria-label-edit-filter-operator":"Editar operador de filtro"},"ad-hoc-filter-builder":{"aria-label-add-filter":"Adicionar filtro","title-add-filter":"Adicionar filtro"},"ad-hoc-filter-renderer":{"aria-label-remove-filter":"Remover filtro","key-select":{"placeholder-select-label":"Selecionar rótulo"},"label-select-label":"Selecionar rótulo","title-remove-filter":"Remover filtro","value-select":{"placeholder-select-value":"Selecionar valor"}},"data-source-variable":{label:{default:"padrão"}},"default-group-by-custom-indicator-container":{"aria-label-clear":"limpar",tooltip:"Aplicado por padrão neste painel. Se editado, ele é transferido para outros painéis.","tooltip-restore-groupby-set-by-this-dashboard":"Restaura a função groupby definida por este painel."},"format-registry":{formats:{description:{"commaseparated-values":"Valores separados por vírgula","double-quoted-values":"Valores entre aspas duplas","format-date-in-different-ways":"Formatar data de diferentes maneiras","format-multivalued-variables-using-syntax-example":"Formatar variáveis de múltiplos valores usando a sintaxe glob. Por exemplo: {value1,value2}","html-escaping-of-values":"Escape HTML de valores","json-stringify-value":"Valor convertido em string JSON","keep-value-as-is":"Manter o valor como está","multiple-values-are-formatted-like-variablevalue":"Vários valores são formatados como variável=valor","single-quoted-values":"Valores entre aspas simples","useful-escaping-values-taking-syntax-characters":"Útil para valores de escape de URL, levando em consideração caracteres de sintaxe URI","useful-for-url-escaping-values":"Útil para valores de escape de URL","values-are-separated-by-character":'Os valores são separados pelo caractere "|"'}}},"group-by-variable-renderer":{"aria-label-group-by-selector":"Agrupar por seletor","placeholder-group-by-label":"Agrupar por rótulo"},"interval-variable":{"placeholder-select-value":"Selecionar valor"},"loading-options-placeholder":{"loading-options":"Carregando opções…"},"multi-value-apply-button":{apply:"Aplicar"},"no-options-placeholder":{"no-options-found":"Nenhuma opção encontrada"},"options-error-placeholder":{"error-occurred-fetching-labels-click-retry":"Ocorreu um erro ao buscar rótulos. Clique para tentar novamente"},"test-object-with-variable-dependency":{title:{hello:"Olá"}},"test-variable":{text:{text:"Texto"}},"variable-value-input":{"placeholder-enter-value":"Digite um valor"},"variable-value-select":{"placeholder-select-value":"Selecionar valor"}}}}}}]);
+//# sourceMappingURL=150.js.map?_cache=a99ca9bc52a47e729056
\ No newline at end of file
diff --git a/hw3/settings/grafana/plugins/grafana-exploretraces-app/150.js.map b/hw3/settings/grafana/plugins/grafana-exploretraces-app/150.js.map
new file mode 100644
index 00000000..056883d3
--- /dev/null
+++ b/hw3/settings/grafana/plugins/grafana-exploretraces-app/150.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"150.js?_cache=a99ca9bc52a47e729056","mappings":"mKAAA,IAAIA,EAAgB,CACnB,iBAAkB,CAClBC,WAAY,CACX,oBAAqB,CACpB,uBAAwB,uCACxB,iBAAkB,kCAClB,yBAA0B,yCAE3B,yBAA0B,CACzB,sBAAuB,yCACvB,mBAAoB,2CAErB,gBAAiB,CAChBC,QAAS,0EACTC,SAAU,yCACVC,MAAO,kBAER,wBAAyB,CACxB,wBAAyB,gBACzB,sBAAuB,gBACvB,sBAAuB,gBAExB,iBAAkB,CACjB,iBAAkB,qBAClB,cAAe,kBACf,uBAAwB,qBAEzB,iBAAkB,CACjB,eAAgB,iBAChB,aAAc,kBAEf,oCAAqC,CACpC,eAAgB,aAChB,iBAAkB,2CAEnBC,SAAU,CACT,gCAAiC,yCAElC,YAAa,CACZD,MAAO,CACNA,MAAO,WAGT,2BAA4B,CAC3BE,QAAS,YAEV,qBAAsB,CACrB,uBAAwB,gCACxB,sCAAuC,uDAExC,yBAA0B,CACzB,2DAA4D,yGAC5D,kBAAmB,8CAGrBC,MAAO,CACN,iBAAkB,CACjB,iBAAkB,WAEnB,oBAAqB,CACpB,uBAAwB,sBAG1BC,UAAW,CACV,kBAAmB,CAClB,kCAAmC,6BAEpC,wBAAyB,CACxB,wBAAyB,mBACzB,mBAAoB,oBAErB,yBAA0B,CACzB,2BAA4B,iBAC5B,aAAc,CACb,2BAA4B,qBAE7B,qBAAsB,oBACtB,sBAAuB,iBACvB,eAAgB,CACf,2BAA4B,qBAG9B,uBAAwB,CACvBC,MAAO,CACN,QAAW,WAGb,8CAA+C,CAC9C,mBAAoB,SACpBC,QAAS,uFACT,gDAAiD,uDAElD,kBAAmB,CAClBC,QAAS,CACRC,YAAa,CACZ,wBAAyB,gCACzB,uBAAwB,6BACxB,gCAAiC,uCACjC,oDAAqD,8FACrD,0BAA2B,yBAC3B,uBAAwB,kCACxB,mBAAoB,2BACpB,mDAAoD,oDACpD,uBAAwB,8BACxB,kDAAmD,wFACnD,iCAAkC,qCAClC,oCAAqC,iDAIxC,6BAA8B,CAC7B,+BAAgC,sBAChC,6BAA8B,sBAE/B,oBAAqB,CACpB,2BAA4B,oBAE7B,8BAA+B,CAC9B,kBAAmB,sBAEpB,2BAA4B,CAC3BC,MAAO,WAER,yBAA0B,CACzB,mBAAoB,4BAErB,4BAA6B,CAC5B,6CAA8C,mEAE/C,uCAAwC,CACvCT,MAAO,CACNU,MAAO,QAGT,gBAAiB,CAChBC,KAAM,CACLA,KAAM,UAGR,uBAAwB,CACvB,0BAA2B,mBAE5B,wBAAyB,CACxB,2BAA4B,sB","sources":["webpack://grafana-exploretraces-app/../node_modules/@grafana/scenes/dist/esm/locales/pt-BR/grafana-scenes.json.js"],"sourcesContent":["var grafanaScenes = {\n\t\"grafana-scenes\": {\n\tcomponents: {\n\t\t\"adhoc-filter-pill\": {\n\t\t\t\"edit-filter-with-key\": \"Editar filtro com chave {{keyLabel}}\",\n\t\t\t\"managed-filter\": \"Filtro gerenciado de {{origin}}\",\n\t\t\t\"remove-filter-with-key\": \"Remover filtro com chave {{keyLabel}}\"\n\t\t},\n\t\t\"adhoc-filters-combobox\": {\n\t\t\t\"remove-filter-value\": \"Remover valor do filtro: {{itemLabel}}\",\n\t\t\t\"use-custom-value\": \"Usar valor personalizado: {{itemLabel}}\"\n\t\t},\n\t\t\"fallback-page\": {\n\t\t\tcontent: \"Se você chegou aqui usando um link, pode haver um bug neste aplicativo.\",\n\t\t\tsubTitle: \"O URL não corresponde a nenhuma página\",\n\t\t\ttitle: \"Não encontrado\"\n\t\t},\n\t\t\"nested-scene-renderer\": {\n\t\t\t\"collapse-button-label\": \"Recolher cena\",\n\t\t\t\"expand-button-label\": \"Expandir cena\",\n\t\t\t\"remove-button-label\": \"Remover cena\"\n\t\t},\n\t\t\"scene-debugger\": {\n\t\t\t\"object-details\": \"Detalhes do objeto\",\n\t\t\t\"scene-graph\": \"Gráfico de cena\",\n\t\t\t\"title-scene-debugger\": \"Depurador de cena\"\n\t\t},\n\t\t\"scene-grid-row\": {\n\t\t\t\"collapse-row\": \"Recolher linha\",\n\t\t\t\"expand-row\": \"Expandir linha\"\n\t\t},\n\t\t\"scene-time-range-compare-renderer\": {\n\t\t\t\"button-label\": \"Comparação\",\n\t\t\t\"button-tooltip\": \"Ativar comparação de intervalo de tempo\"\n\t\t},\n\t\tsplitter: {\n\t\t\t\"aria-label-pane-resize-widget\": \"Widget de redimensionamento do painel\"\n\t\t},\n\t\t\"viz-panel\": {\n\t\t\ttitle: {\n\t\t\t\ttitle: \"Título\"\n\t\t\t}\n\t\t},\n\t\t\"viz-panel-explore-button\": {\n\t\t\texplore: \"Explorar\"\n\t\t},\n\t\t\"viz-panel-renderer\": {\n\t\t\t\"loading-plugin-panel\": \"Carregando painel do plug-in…\",\n\t\t\t\"panel-plugin-has-no-panel-component\": \"O plug-in do painel não possui componente de painel\"\n\t\t},\n\t\t\"viz-panel-series-limit\": {\n\t\t\t\"content-rendering-series-single-panel-impact-performance\": \"Renderizar muitas séries em um único painel pode afetar o desempenho e dificultar a leitura dos dados.\",\n\t\t\t\"warning-message\": \"Mostrando apenas {{seriesLimit}} série(s)\"\n\t\t}\n\t},\n\tutils: {\n\t\t\"controls-label\": {\n\t\t\t\"tooltip-remove\": \"Remover\"\n\t\t},\n\t\t\"loading-indicator\": {\n\t\t\t\"content-cancel-query\": \"Cancelar consulta\"\n\t\t}\n\t},\n\tvariables: {\n\t\t\"ad-hoc-combobox\": {\n\t\t\t\"aria-label-edit-filter-operator\": \"Editar operador de filtro\"\n\t\t},\n\t\t\"ad-hoc-filter-builder\": {\n\t\t\t\"aria-label-add-filter\": \"Adicionar filtro\",\n\t\t\t\"title-add-filter\": \"Adicionar filtro\"\n\t\t},\n\t\t\"ad-hoc-filter-renderer\": {\n\t\t\t\"aria-label-remove-filter\": \"Remover filtro\",\n\t\t\t\"key-select\": {\n\t\t\t\t\"placeholder-select-label\": \"Selecionar rótulo\"\n\t\t\t},\n\t\t\t\"label-select-label\": \"Selecionar rótulo\",\n\t\t\t\"title-remove-filter\": \"Remover filtro\",\n\t\t\t\"value-select\": {\n\t\t\t\t\"placeholder-select-value\": \"Selecionar valor\"\n\t\t\t}\n\t\t},\n\t\t\"data-source-variable\": {\n\t\t\tlabel: {\n\t\t\t\t\"default\": \"padrão\"\n\t\t\t}\n\t\t},\n\t\t\"default-group-by-custom-indicator-container\": {\n\t\t\t\"aria-label-clear\": \"limpar\",\n\t\t\ttooltip: \"Aplicado por padrão neste painel. Se editado, ele é transferido para outros painéis.\",\n\t\t\t\"tooltip-restore-groupby-set-by-this-dashboard\": \"Restaura a função groupby definida por este painel.\"\n\t\t},\n\t\t\"format-registry\": {\n\t\t\tformats: {\n\t\t\t\tdescription: {\n\t\t\t\t\t\"commaseparated-values\": \"Valores separados por vírgula\",\n\t\t\t\t\t\"double-quoted-values\": \"Valores entre aspas duplas\",\n\t\t\t\t\t\"format-date-in-different-ways\": \"Formatar data de diferentes maneiras\",\n\t\t\t\t\t\"format-multivalued-variables-using-syntax-example\": \"Formatar variáveis de múltiplos valores usando a sintaxe glob. Por exemplo: {value1,value2}\",\n\t\t\t\t\t\"html-escaping-of-values\": \"Escape HTML de valores\",\n\t\t\t\t\t\"json-stringify-value\": \"Valor convertido em string JSON\",\n\t\t\t\t\t\"keep-value-as-is\": \"Manter o valor como está\",\n\t\t\t\t\t\"multiple-values-are-formatted-like-variablevalue\": \"Vários valores são formatados como variável=valor\",\n\t\t\t\t\t\"single-quoted-values\": \"Valores entre aspas simples\",\n\t\t\t\t\t\"useful-escaping-values-taking-syntax-characters\": \"Útil para valores de escape de URL, levando em consideração caracteres de sintaxe URI\",\n\t\t\t\t\t\"useful-for-url-escaping-values\": \"Útil para valores de escape de URL\",\n\t\t\t\t\t\"values-are-separated-by-character\": \"Os valores são separados pelo caractere \\\"|\\\"\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"group-by-variable-renderer\": {\n\t\t\t\"aria-label-group-by-selector\": \"Agrupar por seletor\",\n\t\t\t\"placeholder-group-by-label\": \"Agrupar por rótulo\"\n\t\t},\n\t\t\"interval-variable\": {\n\t\t\t\"placeholder-select-value\": \"Selecionar valor\"\n\t\t},\n\t\t\"loading-options-placeholder\": {\n\t\t\t\"loading-options\": \"Carregando opções…\"\n\t\t},\n\t\t\"multi-value-apply-button\": {\n\t\t\tapply: \"Aplicar\"\n\t\t},\n\t\t\"no-options-placeholder\": {\n\t\t\t\"no-options-found\": \"Nenhuma opção encontrada\"\n\t\t},\n\t\t\"options-error-placeholder\": {\n\t\t\t\"error-occurred-fetching-labels-click-retry\": \"Ocorreu um erro ao buscar rótulos. Clique para tentar novamente\"\n\t\t},\n\t\t\"test-object-with-variable-dependency\": {\n\t\t\ttitle: {\n\t\t\t\thello: \"Olá\"\n\t\t\t}\n\t\t},\n\t\t\"test-variable\": {\n\t\t\ttext: {\n\t\t\t\ttext: \"Texto\"\n\t\t\t}\n\t\t},\n\t\t\"variable-value-input\": {\n\t\t\t\"placeholder-enter-value\": \"Digite um valor\"\n\t\t},\n\t\t\"variable-value-select\": {\n\t\t\t\"placeholder-select-value\": \"Selecionar valor\"\n\t\t}\n\t}\n}\n};\n\nexport { grafanaScenes as default };\n//# sourceMappingURL=grafana-scenes.json.js.map\n"],"names":["grafanaScenes","components","content","subTitle","title","splitter","explore","utils","variables","label","tooltip","formats","description","apply","hello","text"],"sourceRoot":""}
\ No newline at end of file
diff --git a/hw3/settings/grafana/plugins/grafana-exploretraces-app/190.js b/hw3/settings/grafana/plugins/grafana-exploretraces-app/190.js
new file mode 100644
index 00000000..b4339da4
--- /dev/null
+++ b/hw3/settings/grafana/plugins/grafana-exploretraces-app/190.js
@@ -0,0 +1,2 @@
+"use strict";(self.webpackChunkgrafana_exploretraces_app=self.webpackChunkgrafana_exploretraces_app||[]).push([[190],{2190:(e,a,l)=>{l.r(a),l.d(a,{default:()=>r});var r={"grafana-scenes":{components:{"adhoc-filter-pill":{"edit-filter-with-key":"Editar filtro con la clave {{keyLabel}}","managed-filter":"Filtro gestionado de {{origin}}","remove-filter-with-key":"Eliminar filtro con la clave {{keyLabel}}"},"adhoc-filters-combobox":{"remove-filter-value":"Eliminar valor del filtro: {{itemLabel}}","use-custom-value":"Usar valor personalizado: {{itemLabel}}"},"fallback-page":{content:"Si ha llegado hasta aquí mediante un enlace, es posible que haya un error en esta aplicación.",subTitle:"La URL no coincide con ninguna página",title:"No se ha encontrado"},"nested-scene-renderer":{"collapse-button-label":"Contraer escena","expand-button-label":"Expandir escena","remove-button-label":"Eliminar escena"},"scene-debugger":{"object-details":"Detalles del objeto","scene-graph":"Gráfico de la escena","title-scene-debugger":"Depurador de escenas"},"scene-grid-row":{"collapse-row":"Contraer fila","expand-row":"Expandir fila"},"scene-time-range-compare-renderer":{"button-label":"Comparación","button-tooltip":"Habilitar comparación de intervalos de tiempo"},splitter:{"aria-label-pane-resize-widget":"Widget de cambio de tamaño del panel"},"viz-panel":{title:{title:"Título"}},"viz-panel-explore-button":{explore:"Explorar"},"viz-panel-renderer":{"loading-plugin-panel":"Cargando panel de plugins...","panel-plugin-has-no-panel-component":"El plugin del panel no tiene ningún componente de panel"},"viz-panel-series-limit":{"content-rendering-series-single-panel-impact-performance":"Representar demasiadas series en un solo panel puede afectar al rendimiento y dificultar la lectura de los datos.","warning-message":"Mostrando solo {{seriesLimit}} serie(s)"}},utils:{"controls-label":{"tooltip-remove":"Eliminar"},"loading-indicator":{"content-cancel-query":"Cancelar consulta"}},variables:{"ad-hoc-combobox":{"aria-label-edit-filter-operator":"Editar operador de filtro"},"ad-hoc-filter-builder":{"aria-label-add-filter":"Añadir filtro","title-add-filter":"Añadir filtro"},"ad-hoc-filter-renderer":{"aria-label-remove-filter":"Eliminar filtro","key-select":{"placeholder-select-label":"Seleccionar etiqueta"},"label-select-label":"Seleccionar etiqueta","title-remove-filter":"Eliminar filtro","value-select":{"placeholder-select-value":"Seleccionar valor"}},"data-source-variable":{label:{default:"predeterminada"}},"default-group-by-custom-indicator-container":{"aria-label-clear":"borrar",tooltip:"Aplicado de forma predeterminada en este dashboard. Si se edita, se transfiere a otros dashboards.","tooltip-restore-groupby-set-by-this-dashboard":"Restaura la función groupby definida por este dashboard."},"format-registry":{formats:{description:{"commaseparated-values":"Valores separados por comas","double-quoted-values":"Valores entre comillas dobles","format-date-in-different-ways":"Dar formato a la fecha de diferentes maneras","format-multivalued-variables-using-syntax-example":"Dar formato a las variables de múltiples valores con la sintaxis glob, por ejemplo, {value1,value2}","html-escaping-of-values":"Escape HTML de valores","json-stringify-value":"Valor de JSON stringify","keep-value-as-is":"Mantener el valor tal cual","multiple-values-are-formatted-like-variablevalue":"Los valores múltiples tienen el formato variable=valor","single-quoted-values":"Valores entre comillas simples","useful-escaping-values-taking-syntax-characters":"Útil para valores de escape URL, utilizando caracteres de sintaxis URI","useful-for-url-escaping-values":"Útil para valores de escape URL","values-are-separated-by-character":"Los valores están separados por el carácter |"}}},"group-by-variable-renderer":{"aria-label-group-by-selector":"Agrupar por selector","placeholder-group-by-label":"Agrupar por etiqueta"},"interval-variable":{"placeholder-select-value":"Seleccionar valor"},"loading-options-placeholder":{"loading-options":"Cargando opciones..."},"multi-value-apply-button":{apply:"Aplicar"},"no-options-placeholder":{"no-options-found":"No se han encontrado opciones"},"options-error-placeholder":{"error-occurred-fetching-labels-click-retry":"Se ha producido un error al recuperar las etiquetas. Haga clic para volver a intentarlo"},"test-object-with-variable-dependency":{title:{hello:"Hola"}},"test-variable":{text:{text:"Texto"}},"variable-value-input":{"placeholder-enter-value":"Introducir valor"},"variable-value-select":{"placeholder-select-value":"Seleccionar valor"}}}}}}]);
+//# sourceMappingURL=190.js.map?_cache=e94cf276a1cd31bbbc4d
\ No newline at end of file
diff --git a/hw3/settings/grafana/plugins/grafana-exploretraces-app/190.js.map b/hw3/settings/grafana/plugins/grafana-exploretraces-app/190.js.map
new file mode 100644
index 00000000..a0978c97
--- /dev/null
+++ b/hw3/settings/grafana/plugins/grafana-exploretraces-app/190.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"190.js?_cache=e94cf276a1cd31bbbc4d","mappings":"mKAAA,IAAIA,EAAgB,CACnB,iBAAkB,CAClBC,WAAY,CACX,oBAAqB,CACpB,uBAAwB,0CACxB,iBAAkB,kCAClB,yBAA0B,6CAE3B,yBAA0B,CACzB,sBAAuB,2CACvB,mBAAoB,2CAErB,gBAAiB,CAChBC,QAAS,gGACTC,SAAU,wCACVC,MAAO,uBAER,wBAAyB,CACxB,wBAAyB,kBACzB,sBAAuB,kBACvB,sBAAuB,mBAExB,iBAAkB,CACjB,iBAAkB,sBAClB,cAAe,uBACf,uBAAwB,wBAEzB,iBAAkB,CACjB,eAAgB,gBAChB,aAAc,iBAEf,oCAAqC,CACpC,eAAgB,cAChB,iBAAkB,iDAEnBC,SAAU,CACT,gCAAiC,wCAElC,YAAa,CACZD,MAAO,CACNA,MAAO,WAGT,2BAA4B,CAC3BE,QAAS,YAEV,qBAAsB,CACrB,uBAAwB,+BACxB,sCAAuC,2DAExC,yBAA0B,CACzB,2DAA4D,oHAC5D,kBAAmB,4CAGrBC,MAAO,CACN,iBAAkB,CACjB,iBAAkB,YAEnB,oBAAqB,CACpB,uBAAwB,sBAG1BC,UAAW,CACV,kBAAmB,CAClB,kCAAmC,6BAEpC,wBAAyB,CACxB,wBAAyB,gBACzB,mBAAoB,iBAErB,yBAA0B,CACzB,2BAA4B,kBAC5B,aAAc,CACb,2BAA4B,wBAE7B,qBAAsB,uBACtB,sBAAuB,kBACvB,eAAgB,CACf,2BAA4B,sBAG9B,uBAAwB,CACvBC,MAAO,CACN,QAAW,mBAGb,8CAA+C,CAC9C,mBAAoB,SACpBC,QAAS,qGACT,gDAAiD,4DAElD,kBAAmB,CAClBC,QAAS,CACRC,YAAa,CACZ,wBAAyB,8BACzB,uBAAwB,gCACxB,gCAAiC,+CACjC,oDAAqD,sGACrD,0BAA2B,yBAC3B,uBAAwB,0BACxB,mBAAoB,6BACpB,mDAAoD,yDACpD,uBAAwB,iCACxB,kDAAmD,yEACnD,iCAAkC,kCAClC,oCAAqC,mDAIxC,6BAA8B,CAC7B,+BAAgC,uBAChC,6BAA8B,wBAE/B,oBAAqB,CACpB,2BAA4B,qBAE7B,8BAA+B,CAC9B,kBAAmB,wBAEpB,2BAA4B,CAC3BC,MAAO,WAER,yBAA0B,CACzB,mBAAoB,iCAErB,4BAA6B,CAC5B,6CAA8C,2FAE/C,uCAAwC,CACvCT,MAAO,CACNU,MAAO,SAGT,gBAAiB,CAChBC,KAAM,CACLA,KAAM,UAGR,uBAAwB,CACvB,0BAA2B,oBAE5B,wBAAyB,CACxB,2BAA4B,uB","sources":["webpack://grafana-exploretraces-app/../node_modules/@grafana/scenes/dist/esm/locales/es-ES/grafana-scenes.json.js"],"sourcesContent":["var grafanaScenes = {\n\t\"grafana-scenes\": {\n\tcomponents: {\n\t\t\"adhoc-filter-pill\": {\n\t\t\t\"edit-filter-with-key\": \"Editar filtro con la clave {{keyLabel}}\",\n\t\t\t\"managed-filter\": \"Filtro gestionado de {{origin}}\",\n\t\t\t\"remove-filter-with-key\": \"Eliminar filtro con la clave {{keyLabel}}\"\n\t\t},\n\t\t\"adhoc-filters-combobox\": {\n\t\t\t\"remove-filter-value\": \"Eliminar valor del filtro: {{itemLabel}}\",\n\t\t\t\"use-custom-value\": \"Usar valor personalizado: {{itemLabel}}\"\n\t\t},\n\t\t\"fallback-page\": {\n\t\t\tcontent: \"Si ha llegado hasta aquí mediante un enlace, es posible que haya un error en esta aplicación.\",\n\t\t\tsubTitle: \"La URL no coincide con ninguna página\",\n\t\t\ttitle: \"No se ha encontrado\"\n\t\t},\n\t\t\"nested-scene-renderer\": {\n\t\t\t\"collapse-button-label\": \"Contraer escena\",\n\t\t\t\"expand-button-label\": \"Expandir escena\",\n\t\t\t\"remove-button-label\": \"Eliminar escena\"\n\t\t},\n\t\t\"scene-debugger\": {\n\t\t\t\"object-details\": \"Detalles del objeto\",\n\t\t\t\"scene-graph\": \"Gráfico de la escena\",\n\t\t\t\"title-scene-debugger\": \"Depurador de escenas\"\n\t\t},\n\t\t\"scene-grid-row\": {\n\t\t\t\"collapse-row\": \"Contraer fila\",\n\t\t\t\"expand-row\": \"Expandir fila\"\n\t\t},\n\t\t\"scene-time-range-compare-renderer\": {\n\t\t\t\"button-label\": \"Comparación\",\n\t\t\t\"button-tooltip\": \"Habilitar comparación de intervalos de tiempo\"\n\t\t},\n\t\tsplitter: {\n\t\t\t\"aria-label-pane-resize-widget\": \"Widget de cambio de tamaño del panel\"\n\t\t},\n\t\t\"viz-panel\": {\n\t\t\ttitle: {\n\t\t\t\ttitle: \"Título\"\n\t\t\t}\n\t\t},\n\t\t\"viz-panel-explore-button\": {\n\t\t\texplore: \"Explorar\"\n\t\t},\n\t\t\"viz-panel-renderer\": {\n\t\t\t\"loading-plugin-panel\": \"Cargando panel de plugins...\",\n\t\t\t\"panel-plugin-has-no-panel-component\": \"El plugin del panel no tiene ningún componente de panel\"\n\t\t},\n\t\t\"viz-panel-series-limit\": {\n\t\t\t\"content-rendering-series-single-panel-impact-performance\": \"Representar demasiadas series en un solo panel puede afectar al rendimiento y dificultar la lectura de los datos.\",\n\t\t\t\"warning-message\": \"Mostrando solo {{seriesLimit}} serie(s)\"\n\t\t}\n\t},\n\tutils: {\n\t\t\"controls-label\": {\n\t\t\t\"tooltip-remove\": \"Eliminar\"\n\t\t},\n\t\t\"loading-indicator\": {\n\t\t\t\"content-cancel-query\": \"Cancelar consulta\"\n\t\t}\n\t},\n\tvariables: {\n\t\t\"ad-hoc-combobox\": {\n\t\t\t\"aria-label-edit-filter-operator\": \"Editar operador de filtro\"\n\t\t},\n\t\t\"ad-hoc-filter-builder\": {\n\t\t\t\"aria-label-add-filter\": \"Añadir filtro\",\n\t\t\t\"title-add-filter\": \"Añadir filtro\"\n\t\t},\n\t\t\"ad-hoc-filter-renderer\": {\n\t\t\t\"aria-label-remove-filter\": \"Eliminar filtro\",\n\t\t\t\"key-select\": {\n\t\t\t\t\"placeholder-select-label\": \"Seleccionar etiqueta\"\n\t\t\t},\n\t\t\t\"label-select-label\": \"Seleccionar etiqueta\",\n\t\t\t\"title-remove-filter\": \"Eliminar filtro\",\n\t\t\t\"value-select\": {\n\t\t\t\t\"placeholder-select-value\": \"Seleccionar valor\"\n\t\t\t}\n\t\t},\n\t\t\"data-source-variable\": {\n\t\t\tlabel: {\n\t\t\t\t\"default\": \"predeterminada\"\n\t\t\t}\n\t\t},\n\t\t\"default-group-by-custom-indicator-container\": {\n\t\t\t\"aria-label-clear\": \"borrar\",\n\t\t\ttooltip: \"Aplicado de forma predeterminada en este dashboard. Si se edita, se transfiere a otros dashboards.\",\n\t\t\t\"tooltip-restore-groupby-set-by-this-dashboard\": \"Restaura la función groupby definida por este dashboard.\"\n\t\t},\n\t\t\"format-registry\": {\n\t\t\tformats: {\n\t\t\t\tdescription: {\n\t\t\t\t\t\"commaseparated-values\": \"Valores separados por comas\",\n\t\t\t\t\t\"double-quoted-values\": \"Valores entre comillas dobles\",\n\t\t\t\t\t\"format-date-in-different-ways\": \"Dar formato a la fecha de diferentes maneras\",\n\t\t\t\t\t\"format-multivalued-variables-using-syntax-example\": \"Dar formato a las variables de múltiples valores con la sintaxis glob, por ejemplo, {value1,value2}\",\n\t\t\t\t\t\"html-escaping-of-values\": \"Escape HTML de valores\",\n\t\t\t\t\t\"json-stringify-value\": \"Valor de JSON stringify\",\n\t\t\t\t\t\"keep-value-as-is\": \"Mantener el valor tal cual\",\n\t\t\t\t\t\"multiple-values-are-formatted-like-variablevalue\": \"Los valores múltiples tienen el formato variable=valor\",\n\t\t\t\t\t\"single-quoted-values\": \"Valores entre comillas simples\",\n\t\t\t\t\t\"useful-escaping-values-taking-syntax-characters\": \"Útil para valores de escape URL, utilizando caracteres de sintaxis URI\",\n\t\t\t\t\t\"useful-for-url-escaping-values\": \"Útil para valores de escape URL\",\n\t\t\t\t\t\"values-are-separated-by-character\": \"Los valores están separados por el carácter |\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"group-by-variable-renderer\": {\n\t\t\t\"aria-label-group-by-selector\": \"Agrupar por selector\",\n\t\t\t\"placeholder-group-by-label\": \"Agrupar por etiqueta\"\n\t\t},\n\t\t\"interval-variable\": {\n\t\t\t\"placeholder-select-value\": \"Seleccionar valor\"\n\t\t},\n\t\t\"loading-options-placeholder\": {\n\t\t\t\"loading-options\": \"Cargando opciones...\"\n\t\t},\n\t\t\"multi-value-apply-button\": {\n\t\t\tapply: \"Aplicar\"\n\t\t},\n\t\t\"no-options-placeholder\": {\n\t\t\t\"no-options-found\": \"No se han encontrado opciones\"\n\t\t},\n\t\t\"options-error-placeholder\": {\n\t\t\t\"error-occurred-fetching-labels-click-retry\": \"Se ha producido un error al recuperar las etiquetas. Haga clic para volver a intentarlo\"\n\t\t},\n\t\t\"test-object-with-variable-dependency\": {\n\t\t\ttitle: {\n\t\t\t\thello: \"Hola\"\n\t\t\t}\n\t\t},\n\t\t\"test-variable\": {\n\t\t\ttext: {\n\t\t\t\ttext: \"Texto\"\n\t\t\t}\n\t\t},\n\t\t\"variable-value-input\": {\n\t\t\t\"placeholder-enter-value\": \"Introducir valor\"\n\t\t},\n\t\t\"variable-value-select\": {\n\t\t\t\"placeholder-select-value\": \"Seleccionar valor\"\n\t\t}\n\t}\n}\n};\n\nexport { grafanaScenes as default };\n//# sourceMappingURL=grafana-scenes.json.js.map\n"],"names":["grafanaScenes","components","content","subTitle","title","splitter","explore","utils","variables","label","tooltip","formats","description","apply","hello","text"],"sourceRoot":""}
\ No newline at end of file
diff --git a/hw3/settings/grafana/plugins/grafana-exploretraces-app/2.js b/hw3/settings/grafana/plugins/grafana-exploretraces-app/2.js
new file mode 100644
index 00000000..081d9e5e
--- /dev/null
+++ b/hw3/settings/grafana/plugins/grafana-exploretraces-app/2.js
@@ -0,0 +1,2 @@
+"use strict";(self.webpackChunkgrafana_exploretraces_app=self.webpackChunkgrafana_exploretraces_app||[]).push([[2],{6002:(e,a,l)=>{l.r(a),l.d(a,{default:()=>r});var r={"grafana-scenes":{components:{"adhoc-filter-pill":{"edit-filter-with-key":"Modifier le filtre ayant pour clé {{keyLabel}}","managed-filter":"Filtre géré {{origin}}","remove-filter-with-key":"Supprimer le filtre ayant pour clé {{keyLabel}}"},"adhoc-filters-combobox":{"remove-filter-value":"Supprimer la valeur du filtre – {{itemLabel}}","use-custom-value":"Utiliser une valeur personnalisée : {{itemLabel}}"},"fallback-page":{content:"Si vous êtes arrivé ici via un lien, il se peut qu’il y ait un bug dans l’application.",subTitle:"L’URL ne correspond à aucune page",title:"Page introuvable"},"nested-scene-renderer":{"collapse-button-label":"Réduire la scène","expand-button-label":"Développer la scène","remove-button-label":"Supprimer la scène"},"scene-debugger":{"object-details":"Détails de l’objet","scene-graph":"Graphique de la scène","title-scene-debugger":"Débogueur de scène"},"scene-grid-row":{"collapse-row":"Réduire la ligne","expand-row":"Développer la ligne"},"scene-time-range-compare-renderer":{"button-label":"Comparaison","button-tooltip":"Activer la comparaison d’intervalles"},splitter:{"aria-label-pane-resize-widget":"Widget de redimensionnement du panneau"},"viz-panel":{title:{title:"Titre"}},"viz-panel-explore-button":{explore:"Explorer"},"viz-panel-renderer":{"loading-plugin-panel":"Chargement du panneau du plugin…","panel-plugin-has-no-panel-component":"Le plugin de panneau ne contient aucun composant de panneau"},"viz-panel-series-limit":{"content-rendering-series-single-panel-impact-performance":"Le rendu d’un trop grand nombre de séries dans un seul panneau peut nuire aux performances et rendre les données plus difficiles à lire.","warning-message":"Affichage limité à {{seriesLimit}} séries"}},utils:{"controls-label":{"tooltip-remove":"Supprimer"},"loading-indicator":{"content-cancel-query":"Annuler la requête"}},variables:{"ad-hoc-combobox":{"aria-label-edit-filter-operator":"Modifier l’opérateur du filtre"},"ad-hoc-filter-builder":{"aria-label-add-filter":"Ajouter un filtre","title-add-filter":"Ajouter un filtre"},"ad-hoc-filter-renderer":{"aria-label-remove-filter":"Supprimer le filtre","key-select":{"placeholder-select-label":"Sélectionner une étiquette"},"label-select-label":"Sélectionner une étiquette","title-remove-filter":"Supprimer le filtre","value-select":{"placeholder-select-value":"Sélectionner une valeur"}},"data-source-variable":{label:{default:"par défaut"}},"default-group-by-custom-indicator-container":{"aria-label-clear":"effacer",tooltip:"Appliqué par défaut dans ce tableau de bord. En cas de modification, il s’applique aussi aux autres tableaux de bord.","tooltip-restore-groupby-set-by-this-dashboard":"Restaurer le groupage défini par ce tableau de bord."},"format-registry":{formats:{description:{"commaseparated-values":"Valeurs séparées par des virgules","double-quoted-values":"Valeurs entre guillemets doubles","format-date-in-different-ways":"Formater la date de différentes façons","format-multivalued-variables-using-syntax-example":"Formater les variables à valeurs multiples avec la syntaxe glob : exemple {value1,value2}","html-escaping-of-values":"Échappement HTML des valeurs","json-stringify-value":"Valeur au format JSON (stringify)","keep-value-as-is":"Conserver la valeur telle quelle","multiple-values-are-formatted-like-variablevalue":"Plusieurs valeurs sont formatées ainsi : variable=valeur","single-quoted-values":"Valeurs entre guillemets simples","useful-escaping-values-taking-syntax-characters":"Utile pour l’échappement des valeurs dans les URL en tenant compte des caractères de syntaxe URI","useful-for-url-escaping-values":"Utile pour l’échappement des valeurs dans les URL","values-are-separated-by-character":"Les valeurs sont séparées par le caractère « | »"}}},"group-by-variable-renderer":{"aria-label-group-by-selector":"Sélecteur de regroupement","placeholder-group-by-label":"Regrouper par étiquette"},"interval-variable":{"placeholder-select-value":"Sélectionner une valeur"},"loading-options-placeholder":{"loading-options":"Chargement des options..."},"multi-value-apply-button":{apply:"Appliquer"},"no-options-placeholder":{"no-options-found":"Aucune option trouvée"},"options-error-placeholder":{"error-occurred-fetching-labels-click-retry":"Une erreur est survenue lors de la récupération des étiquettes. Cliquez pour réessayer"},"test-object-with-variable-dependency":{title:{hello:"Bonjour"}},"test-variable":{text:{text:"Texte"}},"variable-value-input":{"placeholder-enter-value":"Saisir une valeur"},"variable-value-select":{"placeholder-select-value":"Sélectionner une valeur"}}}}}}]);
+//# sourceMappingURL=2.js.map?_cache=e8dfc983374527051249
\ No newline at end of file
diff --git a/hw3/settings/grafana/plugins/grafana-exploretraces-app/2.js.map b/hw3/settings/grafana/plugins/grafana-exploretraces-app/2.js.map
new file mode 100644
index 00000000..15c37f21
--- /dev/null
+++ b/hw3/settings/grafana/plugins/grafana-exploretraces-app/2.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"2.js?_cache=e8dfc983374527051249","mappings":"iKAAA,IAAIA,EAAgB,CACnB,iBAAkB,CAClBC,WAAY,CACX,oBAAqB,CACpB,uBAAwB,iDACxB,iBAAkB,yBAClB,yBAA0B,mDAE3B,yBAA0B,CACzB,sBAAuB,gDACvB,mBAAoB,qDAErB,gBAAiB,CAChBC,QAAS,yFACTC,SAAU,oCACVC,MAAO,oBAER,wBAAyB,CACxB,wBAAyB,mBACzB,sBAAuB,sBACvB,sBAAuB,sBAExB,iBAAkB,CACjB,iBAAkB,qBAClB,cAAe,wBACf,uBAAwB,sBAEzB,iBAAkB,CACjB,eAAgB,mBAChB,aAAc,uBAEf,oCAAqC,CACpC,eAAgB,cAChB,iBAAkB,wCAEnBC,SAAU,CACT,gCAAiC,0CAElC,YAAa,CACZD,MAAO,CACNA,MAAO,UAGT,2BAA4B,CAC3BE,QAAS,YAEV,qBAAsB,CACrB,uBAAwB,mCACxB,sCAAuC,+DAExC,yBAA0B,CACzB,2DAA4D,2IAC5D,kBAAmB,8CAGrBC,MAAO,CACN,iBAAkB,CACjB,iBAAkB,aAEnB,oBAAqB,CACpB,uBAAwB,uBAG1BC,UAAW,CACV,kBAAmB,CAClB,kCAAmC,kCAEpC,wBAAyB,CACxB,wBAAyB,oBACzB,mBAAoB,qBAErB,yBAA0B,CACzB,2BAA4B,sBAC5B,aAAc,CACb,2BAA4B,8BAE7B,qBAAsB,6BACtB,sBAAuB,sBACvB,eAAgB,CACf,2BAA4B,4BAG9B,uBAAwB,CACvBC,MAAO,CACN,QAAW,eAGb,8CAA+C,CAC9C,mBAAoB,UACpBC,QAAS,wHACT,gDAAiD,wDAElD,kBAAmB,CAClBC,QAAS,CACRC,YAAa,CACZ,wBAAyB,oCACzB,uBAAwB,mCACxB,gCAAiC,yCACjC,oDAAqD,4FACrD,0BAA2B,+BAC3B,uBAAwB,oCACxB,mBAAoB,mCACpB,mDAAoD,2DACpD,uBAAwB,mCACxB,kDAAmD,mGACnD,iCAAkC,oDAClC,oCAAqC,sDAIxC,6BAA8B,CAC7B,+BAAgC,4BAChC,6BAA8B,2BAE/B,oBAAqB,CACpB,2BAA4B,2BAE7B,8BAA+B,CAC9B,kBAAmB,6BAEpB,2BAA4B,CAC3BC,MAAO,aAER,yBAA0B,CACzB,mBAAoB,yBAErB,4BAA6B,CAC5B,6CAA8C,0FAE/C,uCAAwC,CACvCT,MAAO,CACNU,MAAO,YAGT,gBAAiB,CAChBC,KAAM,CACLA,KAAM,UAGR,uBAAwB,CACvB,0BAA2B,qBAE5B,wBAAyB,CACxB,2BAA4B,6B","sources":["webpack://grafana-exploretraces-app/../node_modules/@grafana/scenes/dist/esm/locales/fr-FR/grafana-scenes.json.js"],"sourcesContent":["var grafanaScenes = {\n\t\"grafana-scenes\": {\n\tcomponents: {\n\t\t\"adhoc-filter-pill\": {\n\t\t\t\"edit-filter-with-key\": \"Modifier le filtre ayant pour clé {{keyLabel}}\",\n\t\t\t\"managed-filter\": \"Filtre géré {{origin}}\",\n\t\t\t\"remove-filter-with-key\": \"Supprimer le filtre ayant pour clé {{keyLabel}}\"\n\t\t},\n\t\t\"adhoc-filters-combobox\": {\n\t\t\t\"remove-filter-value\": \"Supprimer la valeur du filtre – {{itemLabel}}\",\n\t\t\t\"use-custom-value\": \"Utiliser une valeur personnalisée : {{itemLabel}}\"\n\t\t},\n\t\t\"fallback-page\": {\n\t\t\tcontent: \"Si vous êtes arrivé ici via un lien, il se peut qu’il y ait un bug dans l’application.\",\n\t\t\tsubTitle: \"L’URL ne correspond à aucune page\",\n\t\t\ttitle: \"Page introuvable\"\n\t\t},\n\t\t\"nested-scene-renderer\": {\n\t\t\t\"collapse-button-label\": \"Réduire la scène\",\n\t\t\t\"expand-button-label\": \"Développer la scène\",\n\t\t\t\"remove-button-label\": \"Supprimer la scène\"\n\t\t},\n\t\t\"scene-debugger\": {\n\t\t\t\"object-details\": \"Détails de l’objet\",\n\t\t\t\"scene-graph\": \"Graphique de la scène\",\n\t\t\t\"title-scene-debugger\": \"Débogueur de scène\"\n\t\t},\n\t\t\"scene-grid-row\": {\n\t\t\t\"collapse-row\": \"Réduire la ligne\",\n\t\t\t\"expand-row\": \"Développer la ligne\"\n\t\t},\n\t\t\"scene-time-range-compare-renderer\": {\n\t\t\t\"button-label\": \"Comparaison\",\n\t\t\t\"button-tooltip\": \"Activer la comparaison d’intervalles\"\n\t\t},\n\t\tsplitter: {\n\t\t\t\"aria-label-pane-resize-widget\": \"Widget de redimensionnement du panneau\"\n\t\t},\n\t\t\"viz-panel\": {\n\t\t\ttitle: {\n\t\t\t\ttitle: \"Titre\"\n\t\t\t}\n\t\t},\n\t\t\"viz-panel-explore-button\": {\n\t\t\texplore: \"Explorer\"\n\t\t},\n\t\t\"viz-panel-renderer\": {\n\t\t\t\"loading-plugin-panel\": \"Chargement du panneau du plugin…\",\n\t\t\t\"panel-plugin-has-no-panel-component\": \"Le plugin de panneau ne contient aucun composant de panneau\"\n\t\t},\n\t\t\"viz-panel-series-limit\": {\n\t\t\t\"content-rendering-series-single-panel-impact-performance\": \"Le rendu d’un trop grand nombre de séries dans un seul panneau peut nuire aux performances et rendre les données plus difficiles à lire.\",\n\t\t\t\"warning-message\": \"Affichage limité à {{seriesLimit}} séries\"\n\t\t}\n\t},\n\tutils: {\n\t\t\"controls-label\": {\n\t\t\t\"tooltip-remove\": \"Supprimer\"\n\t\t},\n\t\t\"loading-indicator\": {\n\t\t\t\"content-cancel-query\": \"Annuler la requête\"\n\t\t}\n\t},\n\tvariables: {\n\t\t\"ad-hoc-combobox\": {\n\t\t\t\"aria-label-edit-filter-operator\": \"Modifier l’opérateur du filtre\"\n\t\t},\n\t\t\"ad-hoc-filter-builder\": {\n\t\t\t\"aria-label-add-filter\": \"Ajouter un filtre\",\n\t\t\t\"title-add-filter\": \"Ajouter un filtre\"\n\t\t},\n\t\t\"ad-hoc-filter-renderer\": {\n\t\t\t\"aria-label-remove-filter\": \"Supprimer le filtre\",\n\t\t\t\"key-select\": {\n\t\t\t\t\"placeholder-select-label\": \"Sélectionner une étiquette\"\n\t\t\t},\n\t\t\t\"label-select-label\": \"Sélectionner une étiquette\",\n\t\t\t\"title-remove-filter\": \"Supprimer le filtre\",\n\t\t\t\"value-select\": {\n\t\t\t\t\"placeholder-select-value\": \"Sélectionner une valeur\"\n\t\t\t}\n\t\t},\n\t\t\"data-source-variable\": {\n\t\t\tlabel: {\n\t\t\t\t\"default\": \"par défaut\"\n\t\t\t}\n\t\t},\n\t\t\"default-group-by-custom-indicator-container\": {\n\t\t\t\"aria-label-clear\": \"effacer\",\n\t\t\ttooltip: \"Appliqué par défaut dans ce tableau de bord. En cas de modification, il s’applique aussi aux autres tableaux de bord.\",\n\t\t\t\"tooltip-restore-groupby-set-by-this-dashboard\": \"Restaurer le groupage défini par ce tableau de bord.\"\n\t\t},\n\t\t\"format-registry\": {\n\t\t\tformats: {\n\t\t\t\tdescription: {\n\t\t\t\t\t\"commaseparated-values\": \"Valeurs séparées par des virgules\",\n\t\t\t\t\t\"double-quoted-values\": \"Valeurs entre guillemets doubles\",\n\t\t\t\t\t\"format-date-in-different-ways\": \"Formater la date de différentes façons\",\n\t\t\t\t\t\"format-multivalued-variables-using-syntax-example\": \"Formater les variables à valeurs multiples avec la syntaxe glob : exemple {value1,value2}\",\n\t\t\t\t\t\"html-escaping-of-values\": \"Échappement HTML des valeurs\",\n\t\t\t\t\t\"json-stringify-value\": \"Valeur au format JSON (stringify)\",\n\t\t\t\t\t\"keep-value-as-is\": \"Conserver la valeur telle quelle\",\n\t\t\t\t\t\"multiple-values-are-formatted-like-variablevalue\": \"Plusieurs valeurs sont formatées ainsi : variable=valeur\",\n\t\t\t\t\t\"single-quoted-values\": \"Valeurs entre guillemets simples\",\n\t\t\t\t\t\"useful-escaping-values-taking-syntax-characters\": \"Utile pour l’échappement des valeurs dans les URL en tenant compte des caractères de syntaxe URI\",\n\t\t\t\t\t\"useful-for-url-escaping-values\": \"Utile pour l’échappement des valeurs dans les URL\",\n\t\t\t\t\t\"values-are-separated-by-character\": \"Les valeurs sont séparées par le caractère « | »\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"group-by-variable-renderer\": {\n\t\t\t\"aria-label-group-by-selector\": \"Sélecteur de regroupement\",\n\t\t\t\"placeholder-group-by-label\": \"Regrouper par étiquette\"\n\t\t},\n\t\t\"interval-variable\": {\n\t\t\t\"placeholder-select-value\": \"Sélectionner une valeur\"\n\t\t},\n\t\t\"loading-options-placeholder\": {\n\t\t\t\"loading-options\": \"Chargement des options...\"\n\t\t},\n\t\t\"multi-value-apply-button\": {\n\t\t\tapply: \"Appliquer\"\n\t\t},\n\t\t\"no-options-placeholder\": {\n\t\t\t\"no-options-found\": \"Aucune option trouvée\"\n\t\t},\n\t\t\"options-error-placeholder\": {\n\t\t\t\"error-occurred-fetching-labels-click-retry\": \"Une erreur est survenue lors de la récupération des étiquettes. Cliquez pour réessayer\"\n\t\t},\n\t\t\"test-object-with-variable-dependency\": {\n\t\t\ttitle: {\n\t\t\t\thello: \"Bonjour\"\n\t\t\t}\n\t\t},\n\t\t\"test-variable\": {\n\t\t\ttext: {\n\t\t\t\ttext: \"Texte\"\n\t\t\t}\n\t\t},\n\t\t\"variable-value-input\": {\n\t\t\t\"placeholder-enter-value\": \"Saisir une valeur\"\n\t\t},\n\t\t\"variable-value-select\": {\n\t\t\t\"placeholder-select-value\": \"Sélectionner une valeur\"\n\t\t}\n\t}\n}\n};\n\nexport { grafanaScenes as default };\n//# sourceMappingURL=grafana-scenes.json.js.map\n"],"names":["grafanaScenes","components","content","subTitle","title","splitter","explore","utils","variables","label","tooltip","formats","description","apply","hello","text"],"sourceRoot":""}
\ No newline at end of file
diff --git a/hw3/settings/grafana/plugins/grafana-exploretraces-app/202.js b/hw3/settings/grafana/plugins/grafana-exploretraces-app/202.js
new file mode 100644
index 00000000..26e02829
--- /dev/null
+++ b/hw3/settings/grafana/plugins/grafana-exploretraces-app/202.js
@@ -0,0 +1,8 @@
+"use strict";(self.webpackChunkgrafana_exploretraces_app=self.webpackChunkgrafana_exploretraces_app||[]).push([[202],{202:(e,t,a)=>{a.r(t),a.d(t,{default:()=>v,updatePlugin:()=>f});var n=a(5959),r=a.n(n),i=a(2007),l=a(8531),o=a(6089),c=a(1269);function p(e,t,a,n,r,i,l){try{var o=e[i](l),c=o.value}catch(e){return void a(e)}o.done?t(c):Promise.resolve(c).then(n,r)}function s(e){return function(){var t=this,a=arguments;return new Promise(function(n,r){var i=e.apply(t,a);function l(e){p(i,n,r,l,o,"next",e)}function o(e){p(i,n,r,l,o,"throw",e)}l(void 0)})}}function u(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function d(e){for(var t=1;t({colorWeak:o.css`
+ color: ${e.colors.text.secondary};
+ `,marginTop:o.css`
+ margin-top: ${e.spacing(3)};
+ `,marginTopXl:o.css`
+ margin-top: ${e.spacing(6)};
+ `}),g=(e,t)=>s(function*(){try{yield f(e,t),l.locationService.reload()}catch(e){console.error("Error while updating the plugin",e)}})(),b={appConfig:{container:"data-testid ac-container",apiKey:"data-testid ac-api-key",apiUrl:"data-testid ac-api-url",submit:"data-testid ac-submit-form"}},f=(e,t)=>s(function*(){const a=(0,l.getBackendSrv)().fetch({url:`/api/plugins/${e}/settings`,method:"POST",data:t});return(yield(0,c.lastValueFrom)(a)).data})(),v=({plugin:e})=>{const t=(0,i.useStyles2)(y),{enabled:a,pinned:l,jsonData:o}=e.meta,[c,p]=(0,n.useState)({apiUrl:(null==o?void 0:o.apiUrl)||"",apiKey:"",isApiKeySet:Boolean(null==o?void 0:o.isApiKeySet)});return r().createElement("div",{"data-testid":b.appConfig.container},r().createElement(i.FieldSet,{label:"Enable / Disable"},!a&&r().createElement(r().Fragment,null,r().createElement("div",{className:t.colorWeak},"The plugin is currently not enabled."),r().createElement(i.Button,{className:t.marginTop,variant:"primary",onClick:()=>g(e.meta.id,{enabled:!0,pinned:!0,jsonData:o})},"Enable plugin")),a&&r().createElement(r().Fragment,null,r().createElement("div",{className:t.colorWeak},"The plugin is currently enabled."),r().createElement(i.Button,{className:t.marginTop,variant:"destructive",onClick:()=>g(e.meta.id,{enabled:!1,pinned:!1,jsonData:o})},"Disable plugin"))),r().createElement(i.FieldSet,{label:"API Settings",className:t.marginTopXl},r().createElement(i.Field,{label:"API Key",description:"A secret key for authenticating to our custom API"},r().createElement(i.SecretInput,{width:60,"data-testid":b.appConfig.apiKey,id:"api-key",value:null==c?void 0:c.apiKey,isConfigured:c.isApiKeySet,placeholder:"Your secret API key",onChange:e=>{p(m(d({},c),{apiKey:e.target.value.trim()}))},onReset:()=>p(m(d({},c),{apiKey:"",isApiKeySet:!1}))})),r().createElement(i.Field,{label:"API Url",description:"",className:t.marginTop},r().createElement(i.Input,{width:60,id:"api-url","data-testid":b.appConfig.apiUrl,label:"API Url",value:null==c?void 0:c.apiUrl,placeholder:"E.g.: http://mywebsite.com/api/v1",onChange:e=>{p(m(d({},c),{apiUrl:e.target.value.trim()}))}})),r().createElement("div",{className:t.marginTop},r().createElement(i.Button,{type:"submit","data-testid":b.appConfig.submit,onClick:()=>g(e.meta.id,{enabled:a,pinned:l,jsonData:{apiUrl:c.apiUrl,isApiKeySet:!0},secureJsonData:c.isApiKeySet?void 0:{apiKey:c.apiKey}}),disabled:Boolean(!c.apiUrl||!c.isApiKeySet&&!c.apiKey)},"Save API settings"))))}}}]);
+//# sourceMappingURL=202.js.map?_cache=71520d621a6b7c0f04bb
\ No newline at end of file
diff --git a/hw3/settings/grafana/plugins/grafana-exploretraces-app/202.js.map b/hw3/settings/grafana/plugins/grafana-exploretraces-app/202.js.map
new file mode 100644
index 00000000..512ab17c
--- /dev/null
+++ b/hw3/settings/grafana/plugins/grafana-exploretraces-app/202.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"202.js?_cache=71520d621a6b7c0f04bb","mappings":"86CAyBA,MAsIMA,EAAaC,IAA0B,CAC3CC,UAAWC,EAAAA,GAAG;aACHF,EAAMG,OAAOC,KAAKC;IAE7BC,UAAWJ,EAAAA,GAAG;kBACEF,EAAMO,QAAQ;IAE9BC,YAAaN,EAAAA,GAAG;kBACAF,EAAMO,QAAQ;MAI1BE,EAAwB,CAAOC,EAAkBC,IAAAA,EAAAA,YACrD,UACQC,EAAaF,EAAUC,GAI7BE,EAAAA,gBAAgBC,QAClB,CAAE,MAAOC,GACPC,QAAQC,MAAM,kCAAmCF,EACnD,CACF,EAVuDJ,GAYjDO,EAAU,CACdC,UAAW,CACTC,UAAW,2BACXC,OAAQ,yBACRC,OAAQ,yBACRC,OAAQ,+BAICX,EAAe,CAAOF,EAAkBC,IAAAA,EAAAA,YACnD,MAAMa,GAAWC,EAAAA,EAAAA,iBAAgBC,MAAM,CACrCC,IAAK,gBAAgBjB,aACrBkB,OAAQ,OACRjB,SAKF,aAF2BkB,EAAAA,EAAAA,eAAcL,IAErBb,IACtB,EAVqDA,GAYrD,EAnLkB,EAAGmB,aACnB,MAAMC,GAAIC,EAAAA,EAAAA,YAAWjC,IACf,QAAEkC,EAAO,OAAEC,EAAM,SAAEC,GAAaL,EAAOM,MACtCC,EAAOC,IAAYC,EAAAA,EAAAA,UAAgB,CACxCjB,QAAQa,aAAAA,EAAAA,EAAUb,SAAU,GAC5BD,OAAQ,GACRmB,YAAaC,QAAQN,aAAAA,EAAAA,EAAUK,eAwBjC,OACE,kBAACE,MAAAA,CAAIC,cAAazB,EAAQC,UAAUC,WAElC,kBAACwB,EAAAA,SAAQA,CAACC,MAAM,qBACZZ,GACA,oCACE,kBAACS,MAAAA,CAAII,UAAWf,EAAE9B,WAAW,wCAC7B,kBAAC8C,EAAAA,OAAMA,CACLD,UAAWf,EAAEzB,UACb0C,QAAQ,UACRC,QAAS,IACPxC,EAAsBqB,EAAOM,KAAKc,GAAI,CACpCjB,SAAS,EACTC,QAAQ,EACRC,cAGL,kBAOJF,GACC,oCACE,kBAACS,MAAAA,CAAII,UAAWf,EAAE9B,WAAW,oCAC7B,kBAAC8C,EAAAA,OAAMA,CACLD,UAAWf,EAAEzB,UACb0C,QAAQ,cACRC,QAAS,IACPxC,EAAsBqB,EAAOM,KAAKc,GAAI,CACpCjB,SAAS,EACTC,QAAQ,EACRC,cAGL,oBAQP,kBAACS,EAAAA,SAAQA,CAACC,MAAM,eAAeC,UAAWf,EAAEvB,aAE1C,kBAAC2C,EAAAA,MAAKA,CAACN,MAAM,UAAUO,YAAY,qDACjC,kBAACC,EAAAA,YAAWA,CACVC,MAAO,GACPX,cAAazB,EAAQC,UAAUE,OAC/B6B,GAAG,UACHK,MAAOlB,aAAAA,EAAAA,EAAOhB,OACdmC,aAAcnB,EAAMG,YACpBiB,YAAa,sBACbC,SArEcC,IACtBrB,EAAS,OACJD,GAAAA,CACHhB,OAAQsC,EAAMC,OAAOL,MAAMM,WAmErBC,QA7EY,IACpBxB,EAAS,OACJD,GAAAA,CACHhB,OAAQ,GACRmB,aAAa,QA8EX,kBAACW,EAAAA,MAAKA,CAACN,MAAM,UAAUO,YAAY,GAAGN,UAAWf,EAAEzB,WACjD,kBAACyD,EAAAA,MAAKA,CACJT,MAAO,GACPJ,GAAG,UACHP,cAAazB,EAAQC,UAAUG,OAC/BuB,MAAO,UACPU,MAAOlB,aAAAA,EAAAA,EAAOf,OACdmC,YAAa,oCACbC,SA5EcC,IACtBrB,EAAS,OACJD,GAAAA,CACHf,OAAQqC,EAAMC,OAAOL,MAAMM,cA6EzB,kBAACnB,MAAAA,CAAII,UAAWf,EAAEzB,WAChB,kBAACyC,EAAAA,OAAMA,CACLiB,KAAK,SACLrB,cAAazB,EAAQC,UAAUI,OAC/B0B,QAAS,IACPxC,EAAsBqB,EAAOM,KAAKc,GAAI,CACpCjB,UACAC,SACAC,SAAU,CACRb,OAAQe,EAAMf,OACdkB,aAAa,GAIfyB,eAAgB5B,EAAMG,iBAClB0B,EACA,CACE7C,OAAQgB,EAAMhB,UAIxB8C,SAAU1B,SAASJ,EAAMf,SAAYe,EAAMG,cAAgBH,EAAMhB,SAClE,wB","sources":["webpack://grafana-exploretraces-app/./components/AppConfig/AppConfig.tsx"],"sourcesContent":["import React, { useState, ChangeEvent } from 'react';\nimport { Button, Field, Input, useStyles2, FieldSet, SecretInput } from '@grafana/ui';\nimport { PluginConfigPageProps, AppPluginMeta, PluginMeta, GrafanaTheme2 } from '@grafana/data';\nimport { FetchResponse, getBackendSrv, locationService } from '@grafana/runtime';\nimport { css } from '@emotion/css';\nimport { lastValueFrom, Observable } from 'rxjs';\n\nexport type JsonData = {\n apiUrl?: string;\n isApiKeySet?: boolean;\n};\n\ntype State = {\n // The URL to reach our custom API.\n apiUrl: string;\n // Tells us if the API key secret is set.\n // Set to `true` ONLY if it has already been set and haven't been changed.\n // (We unfortunately need an auxiliary variable for this, as `secureJsonData` is never exposed to the browser after it is set)\n isApiKeySet: boolean;\n // An secret key for our custom API.\n apiKey: string;\n};\n\ninterface Props extends PluginConfigPageProps> {}\n\nconst AppConfig = ({ plugin }: Props) => {\n const s = useStyles2(getStyles);\n const { enabled, pinned, jsonData } = plugin.meta;\n const [state, setState] = useState({\n apiUrl: jsonData?.apiUrl || '',\n apiKey: '',\n isApiKeySet: Boolean(jsonData?.isApiKeySet),\n });\n\n const onResetApiKey = () =>\n setState({\n ...state,\n apiKey: '',\n isApiKeySet: false,\n });\n\n const onChangeApiKey = (event: ChangeEvent) => {\n setState({\n ...state,\n apiKey: event.target.value.trim(),\n });\n };\n\n const onChangeApiUrl = (event: ChangeEvent) => {\n setState({\n ...state,\n apiUrl: event.target.value.trim(),\n });\n };\n\n return (\n