diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
new file mode 100644
index 0000000000..b895b71bf3
--- /dev/null
+++ b/.github/workflows/CI.yml
@@ -0,0 +1,70 @@
+name: CI Pipeline
+
+on: push
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v4
+ with:
+ python-version: "3.11"
+ - name: Install Dependencies
+ working-directory: app_python
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r requirements.txt
+ - name: Lint Code
+ working-directory: app_python
+ run: flake8 .
+
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v4
+ with:
+ python-version: "3.11"
+ - name: Install Dependencies
+ working-directory: app_python
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r requirements.txt
+ - name: Run Tests
+ working-directory: app_python
+ run: pytest
+
+ snyk:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ - name: Set up Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: "3.11"
+ - name: Install Snyk CLI
+ run: |
+ npm install -g snyk
+ - name: Run Snyk Vulnerability Test
+ env:
+ SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
+ run: snyk test --all-projects
+
+ docker:
+ needs: [lint, test, snyk]
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Log in to Docker Hub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+ - name: Build and Push Docker Image
+ uses: docker/build-push-action@v6
+ with:
+ context: app_python
+ push: true
+ tags: ${{ secrets.DOCKER_USERNAME }}/my-fastapi-app:latest
diff --git a/app_python/.dockerignore b/app_python/.dockerignore
new file mode 100644
index 0000000000..7a60b85e14
--- /dev/null
+++ b/app_python/.dockerignore
@@ -0,0 +1,2 @@
+__pycache__/
+*.pyc
diff --git a/app_python/.gitignore b/app_python/.gitignore
new file mode 100644
index 0000000000..c6b59254fb
--- /dev/null
+++ b/app_python/.gitignore
@@ -0,0 +1,3 @@
+*.pyc
+__pycache__/
+.pytest_cache/
\ No newline at end of file
diff --git a/app_python/CI.md b/app_python/CI.md
new file mode 100644
index 0000000000..32077de7e6
--- /dev/null
+++ b/app_python/CI.md
@@ -0,0 +1,13 @@
+# CI Pipeline Best Practices
+
+- **Job Separation**: Divided pipeline into linting, testing, security scanning, and Docker build jobs for clarity and maintainability.
+
+- **`working-directory` Usage**: Ensures tasks run in the correct directory (`app_python`).
+
+- **Efficient Dependency Management**: Installs dependencies from `requirements.txt` and upgrades `pip`.
+
+- **Parallel Job Execution**: Runs linting, testing, and security scanning in parallel to speed up the pipeline.
+
+- **Security with Snyk**: Uses **Snyk** for vulnerability scanning, with API token stored in GitHub Secrets.
+
+- **Docker Integration**: Secures Docker Hub login and only builds and pushes Docker image after passing jobs.
diff --git a/app_python/DOCKER.md b/app_python/DOCKER.md
new file mode 100644
index 0000000000..c4db2037ce
--- /dev/null
+++ b/app_python/DOCKER.md
@@ -0,0 +1,20 @@
+# Docker Best Practices
+
+## Security
+
+- The application **does not run as root**, ensuring better security.
+- A dedicated **non-root user** (`appuser`) is created to execute the app.
+
+## Efficiency
+
+- **Using a minimal base image (`python:3.10-slim`)** to keep the image lightweight.
+- **Avoiding unnecessary layers**: Commands are ordered logically to leverage Docker layer caching.
+- **Using `.dockerignore`** to exclude unnecessary files (cache).
+
+## Maintainability
+
+- The `CMD` statement starts the FastAPI application using **Uvicorn**, keeping it lightweight.
+- Dependencies are installed using `pip --no-cache-dir` to **avoid unnecessary bloat**.
+- The FastAPI app files are **copied with ownership (`COPY --chown`)** to prevent permission issues.
+
+---
diff --git a/app_python/Dockerfile b/app_python/Dockerfile
new file mode 100644
index 0000000000..579cc69bf5
--- /dev/null
+++ b/app_python/Dockerfile
@@ -0,0 +1,21 @@
+FROM python:3.10-slim
+
+ENV PYTHONUNBUFFERED=1 \
+ PIP_NO_CACHE_DIR=1 \
+ PIP_DISABLE_PIP_VERSION_CHECK=1
+
+RUN groupadd -r appgroup && useradd -r -g appgroup appuser
+WORKDIR /app
+COPY --chown=appuser:appgroup requirements.txt .
+RUN pip install --no-cache-dir -r requirements.txt
+
+COPY --chown=appuser:appgroup main.py .
+COPY --chown=appuser:appgroup templates/ templates/
+
+RUN mkdir -p /app/data && echo "0" > /app/data/visits.txt && chown -R appuser:appgroup /app/data
+
+USER appuser
+
+EXPOSE 8000
+
+CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
diff --git a/app_python/PYTHON.md b/app_python/PYTHON.md
new file mode 100644
index 0000000000..72a2cf9442
--- /dev/null
+++ b/app_python/PYTHON.md
@@ -0,0 +1,31 @@
+# Develop and Test Python Web Application
+
+## Choosing a Framework
+
+For this application, **FastAPI** was chosen due to its:
+
+- **High performance** (asynchronous support with `uvicorn`)
+- **Built-in support for type hints** and automatic API documentation
+- **Ease of development and maintainability**
+
+## Best Practices Applied
+
+- **Separation of Concerns:** HTML templates are separate from Python logic using Jinja2.
+
+- **Code Readability:** Good function names and code structure.
+- **Modular Design:** Using FastAPI and template rendering makes the project scalable.
+
+## Python Unit Tests
+
+### Best Practices
+
+- Naming of the tests describe the intent of the test clearly
+- Testing only one concern with each testing fucntion
+- Using the AAA pattern (Arange, Act, Assert)
+
+### Unit Test Overview
+
+The function `get_moscow_time()` is tested to verify:
+
+- Correct dictionary structure.
+- Valid time values.
diff --git a/app_python/README.md b/app_python/README.md
new file mode 100644
index 0000000000..f1c4b9c87e
--- /dev/null
+++ b/app_python/README.md
@@ -0,0 +1,135 @@
+[](https://github.com/KaramKhaddour/S25-core-course-labs/actions/workflows/CI.yml)
+
+# Moscow Time Display Application (FastAPI)
+
+## Overview
+
+This application is built using FastAPI and provides the current time in Moscow. It is lightweight and fast.
+
+## Prerequisites
+
+Ensure you have Python installed on your system. This application requires Python 3.7 or later.
+
+## Installation
+
+1. Clone the repository :
+
+ ```sh
+ git clone https://github.com/KaramKhaddour/S25-core-course-labs.git
+ cd S25-CORE-CORSE-LABS/app_python
+ ```
+
+2. Install the required dependencies:
+
+ ```sh
+ pip install -r requirements.txt
+ ```
+
+## Running the Application
+
+To start the FastAPI server, run the following command:
+
+```sh
+uvicorn main:app --reload
+```
+
+This will start the development server and enable automatic reloading for changes in the source code.
+
+## Accessing the API
+
+Once the application is running, you can access it via:
+
+- **API Endpoint:** `http://127.0.0.1:8000` (or another specified host/port)
+- **Interactive API Documentation:**
+ - Swagger UI: `http://127.0.0.1:8000/docs`
+
+## Docker Instructions
+
+This application can be built and run as a Docker container.
+
+### Build the Docker Image
+
+To build the Docker image, run:
+
+```bash
+docker build -t my-fastapi-app .
+```
+
+### Run the Docker Container
+
+To run the container:
+
+```bash
+docker run -d -p 8000:8000 my-fastapi-app
+```
+
+### Pull the Docker Image
+
+If you have pushed your Docker image to Docker Hub, pull it using:
+
+```bash
+docker pull karamkhaddourpro/my-fastapi-app
+```
+
+### Running the Pulled Image
+
+```bash
+docker run -d -p 8000:8000 karamkhaddourpro/my-fastapi-app
+```
+
+### Running tests
+
+```bash
+pytest
+```
+
+## Lab 12 Additions – Visit Counter & Persistence
+
+These additions were implemented as part of Lab 12:
+
+1. **Persistent Visit Counter**
+ - Each visit to the main page (`/`) increments a counter stored in `./data/visits.txt`.
+ - The counter persists across container restarts because of the Docker volume.
+
+2. **New Endpoint `/visits`**
+ - Returns the total number of visits as plain text.
+ - Example:
+
+ ```bash
+ curl http://127.0.0.1:8000/visits
+ # Output: 5
+ ```
+
+3. **Updated `/` Endpoint**
+ - Now increments the visit counter automatically on each page load.
+ - Optionally, the visit count can be displayed on the main page (requires updating `index.html`).
+
+4. **Docker Compose Volume**
+ - Ensures `visits.txt` persists on the host machine:
+
+ ```yaml
+ volumes:
+ - ./data:/data
+ ```
+
+
+## Docker Compose
+
+The `docker-compose.yml` provides persistent storage for the visit counter:
+
+```yaml
+services:
+ app:
+ build: .
+ ports:
+ - "8000:8000"
+ volumes:
+ - ./data:/data # Mount host data folder for persistent visit count
+```
+Run the app with:
+```
+docker compose up --build
+```
+
+This ensures that visits.txt persists across container restarts.
+
diff --git a/app_python/data/visits.txt b/app_python/data/visits.txt
new file mode 100755
index 0000000000..abc4eff6ac
--- /dev/null
+++ b/app_python/data/visits.txt
@@ -0,0 +1 @@
+46
\ No newline at end of file
diff --git a/app_python/docker-compose.yml b/app_python/docker-compose.yml
new file mode 100644
index 0000000000..1dcbf05c03
--- /dev/null
+++ b/app_python/docker-compose.yml
@@ -0,0 +1,8 @@
+version: "3.9"
+services:
+ app:
+ build: .
+ ports:
+ - "8000:8000"
+ volumes:
+ - ./data:/data
diff --git a/app_python/main.py b/app_python/main.py
new file mode 100644
index 0000000000..941a98c750
--- /dev/null
+++ b/app_python/main.py
@@ -0,0 +1,73 @@
+import datetime as dt
+import pytz
+import os
+import time
+from fastapi import FastAPI, Response
+from fastapi.responses import HTMLResponse, PlainTextResponse
+from fastapi.templating import Jinja2Templates
+from fastapi.requests import Request
+from prometheus_client import Summary, Counter, generate_latest, CONTENT_TYPE_LATEST
+
+# Initialize FastAPI app
+app = FastAPI()
+
+# Set up Jinja2 for rendering templates
+templates = Jinja2Templates(directory="templates")
+
+VISITS_DIR = os.getenv("VISITS_DIR", "./data")
+VISITS_FILE = os.path.join(VISITS_DIR, "visits.txt")
+
+os.makedirs(VISITS_DIR, exist_ok=True)
+
+if not os.path.exists(VISITS_FILE):
+ with open(VISITS_FILE, "w") as f:
+ f.write("0")
+
+def get_moscow_time():
+ """Returns the current time in Moscow as a dictionary."""
+ now = dt.datetime.now(pytz.timezone('Europe/Moscow'))
+ return {"hours": now.hour, "minutes": now.minute, "seconds": now.second}
+
+def read_visits():
+ """Read the current visit count from the file."""
+ with open(VISITS_FILE, "r") as f:
+ return int(f.read().strip())
+
+def increment_visits():
+ """Increment the visit count in the file."""
+ visits = read_visits() + 1
+ with open(VISITS_FILE, "w") as f:
+ f.write(str(visits))
+ return visits
+
+@app.get("/", response_class=HTMLResponse)
+async def show_moscow_time(request: Request):
+ """Renders the Moscow time page."""
+ time_data = get_moscow_time()
+ visits_count = increment_visits()
+ return templates.TemplateResponse("index.html",
+ {"request": request, **time_data})
+
+
+
+@app.get("/visits", response_class=PlainTextResponse)
+def visits():
+ """Return the total number of visits."""
+ return str(read_visits())
+# Prometheus metrics definitions
+REQUEST_TIME = Summary(
+ "request_processing_seconds",
+ "Time spent processing request"
+)
+
+REQUEST_COUNT = Counter(
+ "request_total",
+ "Total number of requests"
+)
+
+@app.get("/metrics")
+def metrics():
+ return Response(
+ generate_latest(),
+ media_type=CONTENT_TYPE_LATEST
+ )
\ No newline at end of file
diff --git a/app_python/requirements.txt b/app_python/requirements.txt
new file mode 100644
index 0000000000..432970ef21
--- /dev/null
+++ b/app_python/requirements.txt
@@ -0,0 +1,8 @@
+fastapi==0.111.1
+fastapi-cli==0.0.4
+pytz==2022.1
+uvicorn==0.30.3
+jinja2==3.1.4
+pytest==8.3.4
+flake8==7.1.1
+prometheus_client==0.20.0
diff --git a/app_python/templates/index.html b/app_python/templates/index.html
new file mode 100644
index 0000000000..845e210412
--- /dev/null
+++ b/app_python/templates/index.html
@@ -0,0 +1,100 @@
+
+
+
+
+
+ Moscow Time Now
+
+
+
+
+
+
+ {{ hours }}
+ :
+ {{ minutes }}
+ :
+ {{ seconds }}
+
+
+
+
+
diff --git a/app_python/tests/__init__.py b/app_python/tests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app_python/tests/test_moscow_time_have_hours.py b/app_python/tests/test_moscow_time_have_hours.py
new file mode 100644
index 0000000000..32834ca3c0
--- /dev/null
+++ b/app_python/tests/test_moscow_time_have_hours.py
@@ -0,0 +1,9 @@
+from main import get_moscow_time
+
+
+def test_moscow_time_have_hours():
+ # Arrange
+ time_data = get_moscow_time()
+
+ # Assert
+ assert "hours" in time_data
diff --git a/app_python/tests/test_moscow_time_have_minutes.py b/app_python/tests/test_moscow_time_have_minutes.py
new file mode 100644
index 0000000000..fa7dc23b9a
--- /dev/null
+++ b/app_python/tests/test_moscow_time_have_minutes.py
@@ -0,0 +1,9 @@
+from main import get_moscow_time
+
+
+def test_moscow_time_have_minutes():
+ # Arrange
+ time_data = get_moscow_time()
+
+ # Assert
+ assert "minutes" in time_data
diff --git a/app_python/tests/test_moscow_time_have_seconds.py b/app_python/tests/test_moscow_time_have_seconds.py
new file mode 100644
index 0000000000..c8cfb34378
--- /dev/null
+++ b/app_python/tests/test_moscow_time_have_seconds.py
@@ -0,0 +1,10 @@
+from main import get_moscow_time
+
+
+def test_moscow_time_have_seconds():
+
+ # Arrange
+ time_data = get_moscow_time()
+
+ # Assert
+ assert "seconds" in time_data
diff --git a/app_python/tests/test_the_hours_in_Moscow.py b/app_python/tests/test_the_hours_in_Moscow.py
new file mode 100644
index 0000000000..16832fa46e
--- /dev/null
+++ b/app_python/tests/test_the_hours_in_Moscow.py
@@ -0,0 +1,13 @@
+from main import get_moscow_time
+import time
+
+
+def test_the_hours_in_Moscow():
+ # Arrange
+ time_data = get_moscow_time()
+
+ # Act
+ currentDateAndTime = time.gmtime()
+
+ # Assert
+ assert ((currentDateAndTime.tm_hour+3) % 24) == time_data["hours"]
diff --git a/app_python/tests/test_the_hours_limits.py b/app_python/tests/test_the_hours_limits.py
new file mode 100644
index 0000000000..80648235d3
--- /dev/null
+++ b/app_python/tests/test_the_hours_limits.py
@@ -0,0 +1,10 @@
+from main import get_moscow_time
+
+
+def test_the_hours_limits():
+
+ # Arrange
+ time_data = get_moscow_time()
+
+ # Assert
+ assert 0 <= time_data["hours"] < 24
diff --git a/app_python/tests/test_the_minutes_in_Moscow.py b/app_python/tests/test_the_minutes_in_Moscow.py
new file mode 100644
index 0000000000..d197a85d72
--- /dev/null
+++ b/app_python/tests/test_the_minutes_in_Moscow.py
@@ -0,0 +1,13 @@
+from main import get_moscow_time
+import time
+
+
+def test_the_minutes_in_moscow():
+ # Arrange
+ time_data = get_moscow_time()
+
+ # Act
+ currentDateAndTime = time.gmtime()
+
+ # Assert
+ assert currentDateAndTime.tm_min == time_data["minutes"]
diff --git a/app_python/tests/test_the_minutes_limits.py b/app_python/tests/test_the_minutes_limits.py
new file mode 100644
index 0000000000..3eca04d341
--- /dev/null
+++ b/app_python/tests/test_the_minutes_limits.py
@@ -0,0 +1,9 @@
+from main import get_moscow_time
+
+
+def test_the_minutes_limits():
+ # Arrange
+ time_data = get_moscow_time()
+
+ # Assert
+ assert 0 <= time_data["minutes"] < 60
diff --git a/app_python/tests/test_the_seconds_in_Moscow.py b/app_python/tests/test_the_seconds_in_Moscow.py
new file mode 100644
index 0000000000..013a8f4976
--- /dev/null
+++ b/app_python/tests/test_the_seconds_in_Moscow.py
@@ -0,0 +1,13 @@
+from main import get_moscow_time
+import time
+
+
+def test_the_seconds_in_Moscow():
+ # Arrange
+ time_data = get_moscow_time()
+
+ # Act
+ currentDateAndTime = time.gmtime()
+
+ # Assert
+ assert currentDateAndTime.tm_sec == time_data["seconds"]
diff --git a/app_python/tests/test_the_seconds_limits.py b/app_python/tests/test_the_seconds_limits.py
new file mode 100644
index 0000000000..00d06d7cbe
--- /dev/null
+++ b/app_python/tests/test_the_seconds_limits.py
@@ -0,0 +1,9 @@
+from main import get_moscow_time
+
+
+def test_the_seconds_limits():
+ # Arrange
+ time_data = get_moscow_time()
+
+ # Assert
+ assert 0 <= time_data["seconds"] < 60
diff --git a/app_typescript/README.md b/app_typescript/README.md
new file mode 100644
index 0000000000..8f0a22ec22
--- /dev/null
+++ b/app_typescript/README.md
@@ -0,0 +1,35 @@
+# Moscow Time Display Application (TypeScript & React)
+
+## Overview
+
+This application is built using TypeScript and React to display the current time in Moscow. It dynamically updates the time every second using **Luxon** for time zone management.
+
+## Prerequisites
+
+Ensure you have the following installed on your system:
+
+- **Node.js** (v14 or later)
+- **npm** or **yarn**
+
+## Installation
+
+1. Clone the repository (if applicable):
+
+ ```sh
+ git clone https://github.com/KaramKhaddour/S25-core-course-labs.git
+ cd S25-CORE-CORSE-LABS/app_typescript/react_app
+ ```
+
+2. Install the required dependencies:
+
+ ```sh
+ npm install
+ ```
+
+## Running the Application
+
+To start the React development server, run:
+
+```sh
+npm run dev
+```
diff --git a/app_typescript/react_app/.gitignore b/app_typescript/react_app/.gitignore
new file mode 100644
index 0000000000..a547bf36d8
--- /dev/null
+++ b/app_typescript/react_app/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/app_typescript/react_app/REACT.md b/app_typescript/react_app/REACT.md
new file mode 100644
index 0000000000..8918819537
--- /dev/null
+++ b/app_typescript/react_app/REACT.md
@@ -0,0 +1,15 @@
+# Web application using React and Typescript
+
+## Why Use React?
+
+- **Component-Based Architecture**: React encourages building reusable UI components, which helps in managing complex UIs and making them maintainable over time.
+- **Performance**: React uses a virtual DOM to efficiently update only the parts of the UI that have changed, resulting in faster rendering and better performance.
+
+## Why Use TypeScript?
+
+- **Static Typing**: TypeScript's type system helps catch errors at compile time, reducing the likelihood of runtime errors and improving code quality.
+- **Improved Developer Experience**: Features like autocompletion, refactoring tools, and error checking in IDEs enhance the development workflow, making it faster and more efficient.
+
+## Good Practices
+
+- **Good Documentation** I have tried to provide good documentation using the markdown files and comments.
diff --git a/app_typescript/react_app/README.md b/app_typescript/react_app/README.md
new file mode 100644
index 0000000000..74872fd4af
--- /dev/null
+++ b/app_typescript/react_app/README.md
@@ -0,0 +1,50 @@
+# React + TypeScript + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
+
+- Configure the top-level `parserOptions` property like this:
+
+```js
+export default tseslint.config({
+ languageOptions: {
+ // other options...
+ parserOptions: {
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ },
+})
+```
+
+- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
+- Optionally add `...tseslint.configs.stylisticTypeChecked`
+- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
+
+```js
+// eslint.config.js
+import react from 'eslint-plugin-react'
+
+export default tseslint.config({
+ // Set the react version
+ settings: { react: { version: '18.3' } },
+ plugins: {
+ // Add the react plugin
+ react,
+ },
+ rules: {
+ // other rules...
+ // Enable its recommended rules
+ ...react.configs.recommended.rules,
+ ...react.configs['jsx-runtime'].rules,
+ },
+})
+```
diff --git a/app_typescript/react_app/eslint.config.js b/app_typescript/react_app/eslint.config.js
new file mode 100644
index 0000000000..092408a9f0
--- /dev/null
+++ b/app_typescript/react_app/eslint.config.js
@@ -0,0 +1,28 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import tseslint from 'typescript-eslint'
+
+export default tseslint.config(
+ { ignores: ['dist'] },
+ {
+ extends: [js.configs.recommended, ...tseslint.configs.recommended],
+ files: ['**/*.{ts,tsx}'],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ },
+ plugins: {
+ 'react-hooks': reactHooks,
+ 'react-refresh': reactRefresh,
+ },
+ rules: {
+ ...reactHooks.configs.recommended.rules,
+ 'react-refresh/only-export-components': [
+ 'warn',
+ { allowConstantExport: true },
+ ],
+ },
+ },
+)
diff --git a/app_typescript/react_app/index.html b/app_typescript/react_app/index.html
new file mode 100644
index 0000000000..e4b78eae12
--- /dev/null
+++ b/app_typescript/react_app/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Vite + React + TS
+
+
+
+
+
+
diff --git a/app_typescript/react_app/package-lock.json b/app_typescript/react_app/package-lock.json
new file mode 100644
index 0000000000..e84fd054c2
--- /dev/null
+++ b/app_typescript/react_app/package-lock.json
@@ -0,0 +1,3249 @@
+{
+ "name": "react_app",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "react_app",
+ "version": "0.0.0",
+ "dependencies": {
+ "luxon": "^3.5.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.17.0",
+ "@types/luxon": "^3.4.2",
+ "@types/react": "^18.3.18",
+ "@types/react-dom": "^18.3.5",
+ "@vitejs/plugin-react": "^4.3.4",
+ "eslint": "^9.17.0",
+ "eslint-plugin-react-hooks": "^5.0.0",
+ "eslint-plugin-react-refresh": "^0.4.16",
+ "globals": "^15.14.0",
+ "typescript": "~5.6.2",
+ "typescript-eslint": "^8.18.2",
+ "vite": "^6.0.5"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.26.2",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
+ "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.25.9",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.26.5",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.5.tgz",
+ "integrity": "sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.26.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.7.tgz",
+ "integrity": "sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.26.2",
+ "@babel/generator": "^7.26.5",
+ "@babel/helper-compilation-targets": "^7.26.5",
+ "@babel/helper-module-transforms": "^7.26.0",
+ "@babel/helpers": "^7.26.7",
+ "@babel/parser": "^7.26.7",
+ "@babel/template": "^7.25.9",
+ "@babel/traverse": "^7.26.7",
+ "@babel/types": "^7.26.7",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.26.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz",
+ "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.26.5",
+ "@babel/types": "^7.26.5",
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.26.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz",
+ "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.26.5",
+ "@babel/helper-validator-option": "^7.25.9",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz",
+ "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.25.9",
+ "@babel/types": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.26.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz",
+ "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.25.9",
+ "@babel/traverse": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.26.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz",
+ "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
+ "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
+ "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz",
+ "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.26.7",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz",
+ "integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.25.9",
+ "@babel/types": "^7.26.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.26.7",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz",
+ "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.26.7"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz",
+ "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz",
+ "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
+ "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.25.9",
+ "@babel/parser": "^7.25.9",
+ "@babel/types": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.26.7",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.7.tgz",
+ "integrity": "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.26.2",
+ "@babel/generator": "^7.26.5",
+ "@babel/parser": "^7.26.7",
+ "@babel/template": "^7.25.9",
+ "@babel/types": "^7.26.7",
+ "debug": "^4.3.1",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse/node_modules/globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.26.7",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz",
+ "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz",
+ "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz",
+ "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz",
+ "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz",
+ "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz",
+ "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz",
+ "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz",
+ "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz",
+ "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz",
+ "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz",
+ "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz",
+ "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz",
+ "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz",
+ "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz",
+ "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz",
+ "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz",
+ "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz",
+ "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz",
+ "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz",
+ "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz",
+ "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz",
+ "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz",
+ "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz",
+ "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz",
+ "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz",
+ "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
+ "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+ "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz",
+ "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.5",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz",
+ "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz",
+ "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.19.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz",
+ "integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz",
+ "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz",
+ "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.10.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.6",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
+ "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
+ "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz",
+ "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.8",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
+ "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/set-array": "^1.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.25",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.32.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.32.1.tgz",
+ "integrity": "sha512-/pqA4DmqyCm8u5YIDzIdlLcEmuvxb0v8fZdFhVMszSpDTgbQKdw3/mB3eMUHIbubtJ6F9j+LtmyCnHTEqIHyzA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.32.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.32.1.tgz",
+ "integrity": "sha512-If3PDskT77q7zgqVqYuj7WG3WC08G1kwXGVFi9Jr8nY6eHucREHkfpX79c0ACAjLj3QIWKPJR7w4i+f5EdLH5Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.32.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.32.1.tgz",
+ "integrity": "sha512-zCpKHioQ9KgZToFp5Wvz6zaWbMzYQ2LJHQ+QixDKq52KKrF65ueu6Af4hLlLWHjX1Wf/0G5kSJM9PySW9IrvHA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.32.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.32.1.tgz",
+ "integrity": "sha512-sFvF+t2+TyUo/ZQqUcifrJIgznx58oFZbdHS9TvHq3xhPVL9nOp+yZ6LKrO9GWTP+6DbFtoyLDbjTpR62Mbr3Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.32.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.32.1.tgz",
+ "integrity": "sha512-NbOa+7InvMWRcY9RG+B6kKIMD/FsnQPH0MWUvDlQB1iXnF/UcKSudCXZtv4lW+C276g3w5AxPbfry5rSYvyeYA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.32.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.32.1.tgz",
+ "integrity": "sha512-JRBRmwvHPXR881j2xjry8HZ86wIPK2CcDw0EXchE1UgU0ubWp9nvlT7cZYKc6bkypBt745b4bglf3+xJ7hXWWw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.32.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.32.1.tgz",
+ "integrity": "sha512-PKvszb+9o/vVdUzCCjL0sKHukEQV39tD3fepXxYrHE3sTKrRdCydI7uldRLbjLmDA3TFDmh418XH19NOsDRH8g==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.32.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.32.1.tgz",
+ "integrity": "sha512-9WHEMV6Y89eL606ReYowXuGF1Yb2vwfKWKdD1A5h+OYnPZSJvxbEjxTRKPgi7tkP2DSnW0YLab1ooy+i/FQp/Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.32.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.32.1.tgz",
+ "integrity": "sha512-tZWc9iEt5fGJ1CL2LRPw8OttkCBDs+D8D3oEM8mH8S1ICZCtFJhD7DZ3XMGM8kpqHvhGUTvNUYVDnmkj4BDXnw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.32.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.32.1.tgz",
+ "integrity": "sha512-FTYc2YoTWUsBz5GTTgGkRYYJ5NGJIi/rCY4oK/I8aKowx1ToXeoVVbIE4LGAjsauvlhjfl0MYacxClLld1VrOw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.32.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.32.1.tgz",
+ "integrity": "sha512-F51qLdOtpS6P1zJVRzYM0v6MrBNypyPEN1GfMiz0gPu9jN8ScGaEFIZQwteSsGKg799oR5EaP7+B2jHgL+d+Kw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.32.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.32.1.tgz",
+ "integrity": "sha512-wO0WkfSppfX4YFm5KhdCCpnpGbtgQNj/tgvYzrVYFKDpven8w2N6Gg5nB6w+wAMO3AIfSTWeTjfVe+uZ23zAlg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.32.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.32.1.tgz",
+ "integrity": "sha512-iWswS9cIXfJO1MFYtI/4jjlrGb/V58oMu4dYJIKnR5UIwbkzR0PJ09O0PDZT0oJ3LYWXBSWahNf/Mjo6i1E5/g==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.32.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.32.1.tgz",
+ "integrity": "sha512-RKt8NI9tebzmEthMnfVgG3i/XeECkMPS+ibVZjZ6mNekpbbUmkNWuIN2yHsb/mBPyZke4nlI4YqIdFPgKuoyQQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.32.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.32.1.tgz",
+ "integrity": "sha512-WQFLZ9c42ECqEjwg/GHHsouij3pzLXkFdz0UxHa/0OM12LzvX7DzedlY0SIEly2v18YZLRhCRoHZDxbBSWoGYg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.32.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.32.1.tgz",
+ "integrity": "sha512-BLoiyHDOWoS3uccNSADMza6V6vCNiphi94tQlVIL5de+r6r/CCQuNnerf+1g2mnk2b6edp5dk0nhdZ7aEjOBsA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.32.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.32.1.tgz",
+ "integrity": "sha512-w2l3UnlgYTNNU+Z6wOR8YdaioqfEnwPjIsJ66KxKAf0p+AuL2FHeTX6qvM+p/Ue3XPBVNyVSfCrfZiQh7vZHLQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.32.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.32.1.tgz",
+ "integrity": "sha512-Am9H+TGLomPGkBnaPWie4F3x+yQ2rr4Bk2jpwy+iV+Gel9jLAu/KqT8k3X4jxFPW6Zf8OMnehyutsd+eHoq1WQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.32.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.32.1.tgz",
+ "integrity": "sha512-ar80GhdZb4DgmW3myIS9nRFYcpJRSME8iqWgzH2i44u+IdrzmiXVxeFnExQ5v4JYUSpg94bWjevMG8JHf1Da5Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.6.8",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz",
+ "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.20.6",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz",
+ "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.20.7"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
+ "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/luxon": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz",
+ "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.14",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
+ "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.18",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
+ "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.5",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz",
+ "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.22.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.22.0.tgz",
+ "integrity": "sha512-4Uta6REnz/xEJMvwf72wdUnC3rr4jAQf5jnTkeRQ9b6soxLxhDEbS/pfMPoJLDfFPNVRdryqWUIV/2GZzDJFZw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "8.22.0",
+ "@typescript-eslint/type-utils": "8.22.0",
+ "@typescript-eslint/utils": "8.22.0",
+ "@typescript-eslint/visitor-keys": "8.22.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.3.1",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.8.0"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.22.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.22.0.tgz",
+ "integrity": "sha512-MqtmbdNEdoNxTPzpWiWnqNac54h8JDAmkWtJExBVVnSrSmi9z+sZUt0LfKqk9rjqmKOIeRhO4fHHJ1nQIjduIQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.22.0",
+ "@typescript-eslint/types": "8.22.0",
+ "@typescript-eslint/typescript-estree": "8.22.0",
+ "@typescript-eslint/visitor-keys": "8.22.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.8.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.22.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.22.0.tgz",
+ "integrity": "sha512-/lwVV0UYgkj7wPSw0o8URy6YI64QmcOdwHuGuxWIYznO6d45ER0wXUbksr9pYdViAofpUCNJx/tAzNukgvaaiQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.22.0",
+ "@typescript-eslint/visitor-keys": "8.22.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.22.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.22.0.tgz",
+ "integrity": "sha512-NzE3aB62fDEaGjaAYZE4LH7I1MUwHooQ98Byq0G0y3kkibPJQIXVUspzlFOmOfHhiDLwKzMlWxaNv+/qcZurJA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "8.22.0",
+ "@typescript-eslint/utils": "8.22.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^2.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.8.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.22.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.22.0.tgz",
+ "integrity": "sha512-0S4M4baNzp612zwpD4YOieP3VowOARgK2EkN/GBn95hpyF8E2fbMT55sRHWBq+Huaqk3b3XK+rxxlM8sPgGM6A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.22.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.22.0.tgz",
+ "integrity": "sha512-SJX99NAS2ugGOzpyhMza/tX+zDwjvwAtQFLsBo3GQxiGcvaKlqGBkmZ+Y1IdiSi9h4Q0Lr5ey+Cp9CGWNY/F/w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.22.0",
+ "@typescript-eslint/visitor-keys": "8.22.0",
+ "debug": "^4.3.4",
+ "fast-glob": "^3.3.2",
+ "is-glob": "^4.0.3",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "ts-api-utils": "^2.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <5.8.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+ "version": "7.7.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.0.tgz",
+ "integrity": "sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.22.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.22.0.tgz",
+ "integrity": "sha512-T8oc1MbF8L+Bk2msAvCUzjxVB2Z2f+vXYfcucE2wOmYs7ZUwco5Ep0fYZw8quNwOiw9K8GYVL+Kgc2pETNTLOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "@typescript-eslint/scope-manager": "8.22.0",
+ "@typescript-eslint/types": "8.22.0",
+ "@typescript-eslint/typescript-estree": "8.22.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.8.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.22.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.22.0.tgz",
+ "integrity": "sha512-AWpYAXnUgvLNabGTy3uBylkgZoosva/miNd1I8Bz3SjotmQPbVqhO4Cczo8AsZ44XVErEBPr/CRSgaj8sG7g0w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.22.0",
+ "eslint-visitor-keys": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz",
+ "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.26.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.25.9",
+ "@babel/plugin-transform-react-jsx-source": "^7.25.9",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.14.2"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.14.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
+ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.24.4",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
+ "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001688",
+ "electron-to-chromium": "^1.5.73",
+ "node-releases": "^2.0.19",
+ "update-browserslist-db": "^1.1.1"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001696",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001696.tgz",
+ "integrity": "sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
+ "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.90",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.90.tgz",
+ "integrity": "sha512-C3PN4aydfW91Natdyd449Kw+BzhLmof6tzy5W1pFC5SpQxVXT+oyiyOG9AgYYSN9OdA/ik3YkCrpwqI8ug5Tug==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/esbuild": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz",
+ "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.24.2",
+ "@esbuild/android-arm": "0.24.2",
+ "@esbuild/android-arm64": "0.24.2",
+ "@esbuild/android-x64": "0.24.2",
+ "@esbuild/darwin-arm64": "0.24.2",
+ "@esbuild/darwin-x64": "0.24.2",
+ "@esbuild/freebsd-arm64": "0.24.2",
+ "@esbuild/freebsd-x64": "0.24.2",
+ "@esbuild/linux-arm": "0.24.2",
+ "@esbuild/linux-arm64": "0.24.2",
+ "@esbuild/linux-ia32": "0.24.2",
+ "@esbuild/linux-loong64": "0.24.2",
+ "@esbuild/linux-mips64el": "0.24.2",
+ "@esbuild/linux-ppc64": "0.24.2",
+ "@esbuild/linux-riscv64": "0.24.2",
+ "@esbuild/linux-s390x": "0.24.2",
+ "@esbuild/linux-x64": "0.24.2",
+ "@esbuild/netbsd-arm64": "0.24.2",
+ "@esbuild/netbsd-x64": "0.24.2",
+ "@esbuild/openbsd-arm64": "0.24.2",
+ "@esbuild/openbsd-x64": "0.24.2",
+ "@esbuild/sunos-x64": "0.24.2",
+ "@esbuild/win32-arm64": "0.24.2",
+ "@esbuild/win32-ia32": "0.24.2",
+ "@esbuild/win32-x64": "0.24.2"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.19.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz",
+ "integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.19.0",
+ "@eslint/core": "^0.10.0",
+ "@eslint/eslintrc": "^3.2.0",
+ "@eslint/js": "9.19.0",
+ "@eslint/plugin-kit": "^0.2.5",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.1",
+ "@types/estree": "^1.0.6",
+ "@types/json-schema": "^7.0.15",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.2.0",
+ "eslint-visitor-keys": "^4.2.0",
+ "espree": "^10.3.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0.tgz",
+ "integrity": "sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.4.18",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.18.tgz",
+ "integrity": "sha512-IRGEoFn3OKalm3hjfolEWGqoF/jPqeEYFp+C8B0WMzwGwBMvlRDQd06kghDhF0C61uJ6WfSDhEZE/sAQjduKgw==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": ">=8.40"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
+ "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+ "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
+ "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.14.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.18.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz",
+ "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz",
+ "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "15.14.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz",
+ "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/luxon": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz",
+ "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
+ "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.19",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.1",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz",
+ "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.8",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.14.2",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
+ "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.32.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.32.1.tgz",
+ "integrity": "sha512-z+aeEsOeEa3mEbS1Tjl6sAZ8NE3+AalQz1RJGj81M+fizusbdDMoEJwdJNHfaB40Scr4qNu+welOfes7maKonA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.6"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.32.1",
+ "@rollup/rollup-android-arm64": "4.32.1",
+ "@rollup/rollup-darwin-arm64": "4.32.1",
+ "@rollup/rollup-darwin-x64": "4.32.1",
+ "@rollup/rollup-freebsd-arm64": "4.32.1",
+ "@rollup/rollup-freebsd-x64": "4.32.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.32.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.32.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.32.1",
+ "@rollup/rollup-linux-arm64-musl": "4.32.1",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.32.1",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.32.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.32.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.32.1",
+ "@rollup/rollup-linux-x64-gnu": "4.32.1",
+ "@rollup/rollup-linux-x64-musl": "4.32.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.32.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.32.1",
+ "@rollup/rollup-win32-x64-msvc": "4.32.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz",
+ "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.6.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
+ "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/typescript-eslint": {
+ "version": "8.22.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.22.0.tgz",
+ "integrity": "sha512-Y2rj210FW1Wb6TWXzQc5+P+EWI9/zdS57hLEc0gnyuvdzWo8+Y8brKlbj0muejonhMI/xAZCnZZwjbIfv1CkOw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.22.0",
+ "@typescript-eslint/parser": "8.22.0",
+ "@typescript-eslint/utils": "8.22.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <5.8.0"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz",
+ "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "6.0.11",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.11.tgz",
+ "integrity": "sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.24.2",
+ "postcss": "^8.4.49",
+ "rollup": "^4.23.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "jiti": ">=1.21.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/app_typescript/react_app/package.json b/app_typescript/react_app/package.json
new file mode 100644
index 0000000000..6f6d70ef00
--- /dev/null
+++ b/app_typescript/react_app/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "react_app",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "luxon": "^3.5.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.17.0",
+ "@types/luxon": "^3.4.2",
+ "@types/react": "^18.3.18",
+ "@types/react-dom": "^18.3.5",
+ "@vitejs/plugin-react": "^4.3.4",
+ "eslint": "^9.17.0",
+ "eslint-plugin-react-hooks": "^5.0.0",
+ "eslint-plugin-react-refresh": "^0.4.16",
+ "globals": "^15.14.0",
+ "typescript": "~5.6.2",
+ "typescript-eslint": "^8.18.2",
+ "vite": "^6.0.5"
+ }
+}
diff --git a/app_typescript/react_app/src/App.css b/app_typescript/react_app/src/App.css
new file mode 100644
index 0000000000..aad2f4fe3c
--- /dev/null
+++ b/app_typescript/react_app/src/App.css
@@ -0,0 +1,77 @@
+*{
+ margin:0;
+ padding:0;
+ font-family:'Poppins', 'sans-serif';
+ box-sizing:border-box;
+}
+.hero{
+ width:100%;
+ min-height:100vh;
+ background: linear-gradient(45deg, #08001f, #03197d);
+ color:#fff;
+ position:relative;
+}
+.container{
+ width:800px;
+ height:180px;
+ position:absolute;
+ top:50%;
+ left:50%;
+ transform:translate(-50%,-50%);
+}
+.clock{
+ width:100%;
+ height:100%;
+ background:rgba(235,0,255,0.11);
+ border-radius:10px;
+ display:flex;
+ justify-content :center;
+ align-items:center;
+ backdrop-filter:blur(40px);
+}
+.container::before{
+ content:'';
+ width:180px;
+ height:180px;
+ background:#f41b75;
+ border-radius:5px;
+ position:absolute;
+ left:-50px;
+ top:-50px;
+ z-index:-1;
+}
+.container::after{
+ content:'';
+ width:180px;
+ height:180px;
+ background:#419aff;
+ border-radius:50%;
+ position:absolute;
+ right:-50px;
+ bottom:-50px;
+ z-index:-1;
+}
+.clock span {
+ font-size:80px;
+ width:110px;
+ display:inline-block;
+ text-align:center;
+ position:relative;
+}
+.clock span::after {
+ content:'';
+ font-size:16px;
+ position:absolute;
+ bottom:-5px;
+ left:50%;
+ transform:translateX(-50%)
+}
+#hrs::after{
+ content:"Hours";
+}
+#min::after{
+ content:"Minutes";
+}
+#sec::after{
+ content:"Second"
+}
diff --git a/app_typescript/react_app/src/App.tsx b/app_typescript/react_app/src/App.tsx
new file mode 100644
index 0000000000..1320cd1bb5
--- /dev/null
+++ b/app_typescript/react_app/src/App.tsx
@@ -0,0 +1,37 @@
+import { useState, useEffect } from "react";
+import { DateTime } from "luxon";
+import "./App.css";
+
+function App() {
+ /* using state in react to make the website self render everysecond and update the time*/
+
+ const [time, setTime] = useState(getMoscowTime());
+ /*This function use the luxon library to get the time based on the time zone then I am converting the time using toFormat function*/
+ function getMoscowTime() {
+ return DateTime.now().setZone("Europe/Moscow").toFormat("HH:mm:ss");
+ }
+
+ useEffect(() => {
+ const interval = setInterval(() => {
+ setTime(getMoscowTime());
+ }, 1000);
+
+ return () => clearInterval(interval); // Cleanup interval on unmount
+ }, []);
+
+ return (
+
+
+
+ {time.split(":")[0]}
+ :
+ {time.split(":")[1]}
+ :
+ {time.split(":")[2]}
+
+
+
+ );
+}
+
+export default App;
diff --git a/app_typescript/react_app/src/main.tsx b/app_typescript/react_app/src/main.tsx
new file mode 100644
index 0000000000..4aff0256e0
--- /dev/null
+++ b/app_typescript/react_app/src/main.tsx
@@ -0,0 +1,9 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import App from './App.tsx'
+
+createRoot(document.getElementById('root')!).render(
+
+
+ ,
+)
diff --git a/app_typescript/react_app/src/vite-env.d.ts b/app_typescript/react_app/src/vite-env.d.ts
new file mode 100644
index 0000000000..11f02fe2a0
--- /dev/null
+++ b/app_typescript/react_app/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/app_typescript/react_app/tsconfig.app.json b/app_typescript/react_app/tsconfig.app.json
new file mode 100644
index 0000000000..358ca9ba93
--- /dev/null
+++ b/app_typescript/react_app/tsconfig.app.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src"]
+}
diff --git a/app_typescript/react_app/tsconfig.json b/app_typescript/react_app/tsconfig.json
new file mode 100644
index 0000000000..1ffef600d9
--- /dev/null
+++ b/app_typescript/react_app/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
diff --git a/app_typescript/react_app/tsconfig.node.json b/app_typescript/react_app/tsconfig.node.json
new file mode 100644
index 0000000000..db0becc8b0
--- /dev/null
+++ b/app_typescript/react_app/tsconfig.node.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2022",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/app_typescript/react_app/vite.config.ts b/app_typescript/react_app/vite.config.ts
new file mode 100644
index 0000000000..8b0f57b91a
--- /dev/null
+++ b/app_typescript/react_app/vite.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [react()],
+})
diff --git a/k8s/12.md b/k8s/12.md
new file mode 100644
index 0000000000..9bd8a504e6
--- /dev/null
+++ b/k8s/12.md
@@ -0,0 +1,82 @@
+# Task 2: ConfigMap Implementation in Kubernetes
+
+note the first task was done inside the app_python folder
+
+## Overview
+
+In this task, we implemented a Kubernetes ConfigMap to manage application configuration outside the container image. The key steps included:
+
+Creating a config.json file containing application-specific settings in JSON format.
+
+Adding a ConfigMap template in Helm (templates/config.yaml) to load the JSON file into Kubernetes.
+
+Updating the deployment (templates/deployment.yaml) to mount the ConfigMap as a file inside the pod.
+
+Configuring volumes and volume mounts via Helm values (values.yaml) to make the configuration accessible to the container.
+
+Verifying that the ConfigMap was successfully mounted in the running pods.
+
+### Files Added / Modied
+
+files/config.json – JSON configuration for the application:
+```
+{
+ "appName": "moscow-time-app",
+ "version": "1.0",
+ "env": "lab12"
+}
+```
+
+templates/config.yaml – Helm template for ConfigMap:
+```
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: my-app-config
+data:
+ config.json: |-
+{{ .Files.Get "files/config.json" | indent 4 }}
+```
+
+templates/deployment.yaml – Added:
+
+```
+envFrom:
+ - configMapRef:
+ name: my-app-config
+
+volumeMounts:
+ - name: config-volume
+ mountPath: /config/config.json
+ subPath: config.json
+
+volumes:
+ - name: config-volume
+ configMap:
+ name: my-app-config
+```
+
+values.yaml – Configured volumes and volumeMounts:
+```
+volumes:
+ - name: config-volume
+ configMap:
+ name: my-app-config
+
+volumeMounts:
+ - name: config-volume
+ mountPath: /config.json
+ subPath: config.json
+
+```
+
+## Deployment and Verification
+
+### Install / upgrade Helm chart `helm upgrade --install my-app ./my-app`:
+
+
+### `minikube kubectl -- get pods`
+
+
+### ` minikube kubectl -- exec my-app-595459685b-vw2pt -- cat /config.json`
+
\ No newline at end of file
diff --git a/k8s/13.md b/k8s/13.md
new file mode 100644
index 0000000000..0838a3abd6
--- /dev/null
+++ b/k8s/13.md
@@ -0,0 +1,229 @@
+# ArgoCD for GitOps Deployment
+
+## Installing ArgoCD via Helm
+
+Running the following command ` helm repo add argo https://argoproj.github.io/argo-helm`
+
+```
+(base) kokai@kokai:~/Desktop/S25-core-course-labs/k8s$ helm repo add argo https://argoproj.github.io/argo-helm
+"argo" has been added to your repositories
+```
+
+Verfing the Argo repo was added by rnuning this command `helm repo list`
+
+```
+(base) kokai@kokai:~/Desktop/S25-core-course-labs/k8s$ helm repo list
+NAME URL
+hashicorp https://helm.releases.hashicorp.com
+argo https://argoproj.github.io/argo-helm
+```
+
+Installing Argo-cd by running this command `helm install argo argo/argo-cd --namespace argocd --create-namespace`
+
+```
+(base) kokai@kokai:~/Desktop/S25-core-course-labs/k8s$ helm install argo argo/argo-cd --namespace argocd --create-namespace
+NAME: argo
+LAST DEPLOYED: Mon Feb 16 20:05:29 2026
+NAMESPACE: argocd
+STATUS: deployed
+REVISION: 1
+TEST SUITE: None
+NOTES:
+In order to access the server UI you have the following options:
+
+1. kubectl port-forward service/argo-argocd-server -n argocd 8080:443
+
+ and then open the browser on http://localhost:8080 and accept the certificate
+
+2. enable ingress in the values file `server.ingress.enabled` and either
+ - Add the annotation for ssl passthrough: https://argo-cd.readthedocs.io/en/stable/operator-manual/ingress/#option-1-ssl-passthrough
+ - Set the `configs.params."server.insecure"` in the values file and terminate SSL at your ingress: https://argo-cd.readthedocs.io/en/stable/operator-manual/ingress/#option-2-multiple-ingress-objects-and-hosts
+
+
+After reaching the UI the first time you can login with username: admin and the random password generated during the installation. You can find the password by running:
+
+kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
+
+(You should delete the initial secret afterwards as suggested by the Getting Started Guide: https://argo-cd.readthedocs.io/en/stable/getting_started/#4-login-using-the-cli)
+```
+
+Getting the pods running in the argocd nampscpace by running the following comand ` minikube kubectl -- get pods -n argocd`
+
+```
+ kokai@kokai:~/Desktop/S25-core-course-labs/k8s$ minikube kubectl -- get pods -n argocd
+NAME READY STATUS RESTARTS AGE
+argo-argocd-application-controller-0 1/1 Running 0 46s
+argo-argocd-applicationset-controller-799879ff78-qg4gd 1/1 Running 0 46s
+argo-argocd-dex-server-fcd4b66f6-nlrtz 0/1 PodInitializing 0 46s
+argo-argocd-notifications-controller-789cb777f4-txzdd 1/1 Running 0 46s
+argo-argocd-redis-74875bfd56-gdw9k 1/1 Running 0 46s
+argo-argocd-redis-secret-init-lvmfs 0/1 Completed 0 69s
+argo-argocd-repo-server-7ddcc596cd-rz6db 1/1 Running 0 46s
+argo-argocd-server-7f646659bd-jpfvm 1/1 Running 0 46s
+```
+
+Installing ArgoCD CLi using the following command
+
+```
+(base) kokai@kokai:~/Desktop/S25-core-course-labs/k8s$ curl -sSL -o argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
+chmod +x argocd
+sudo mv argocd /usr/local/bin/
+```
+
+Verifying the argocd version
+```
+ kokai@kokai:~/Desktop/S25-core-course-labs/k8s$ argocd version
+argocd: v3.3.0+fd6b7d5
+ BuildDate: 2026-02-02T07:53:15Z
+ GitCommit: fd6b7d5b3cba5e7aa7ad400b0fb905a81018a77b
+ GitTreeState: clean
+ GoVersion: go1.25.5
+ Compiler: gc
+ Platform: linux/amd64
+{"level":"fatal","msg":"Argo CD server address unspecified","time":"2026-02-16T20:09:14+03:00"}
+```
+
+Forward the ArgoCD server port:
+
+```
+(base) kokai@kokai:~/Desktop/S25-core-course-labs/k8s$ minikube kubectl -- port-forward svc/argo-argocd-server -n argocd 8081:443
+Forwarding from 127.0.0.1:8081 -> 8080
+Handling connection for 8081
+Handling connection for 8081
+Handling connection for 8081
+Handling connection for 8081
+Handling connection for 8081
+Handling connection for 8081
+Handling connection for 8081
+Handling connection for 8081
+Handling connection for 8081
+Handling connection for 8081
+```
+
+Getting the passward and accessing the ui
+
+
+
+An example for CLI login
+
+
+
+
+## Creating ArgoCD Application mainfest
+
+I created the `argocd-python-app.yaml`
+
+```
+apiVersion: argoproj.io/v1alpha1
+kind: Application
+metadata:
+ name: python-app
+ namespace: argocd
+spec:
+ project: default
+ source:
+ repoURL: https://github.com/KaramKhaddour/S25-core-course-labs.git
+ targetRevision: lab13
+ path:
+ helm:
+ valueFiles:
+ - values.yaml
+ destination:
+ server: https://kubernetes.default.svc
+ namespace: default
+ syncPolicy:
+ automated: {}
+```
+
+Running the following command `minikube kubectl -- apply -f ArgoCD/argocd-python-app.yaml`
+
+
+
+
+
+To checking if it's synced correctly I ran the following command
+`argocd app get python-app --port-forward --port-forward-namespace argocd`
+
+
+
+Verify Deployment
+
+
+
+
+### Test Sync Workflow
+I changed the replicaCount to 3 in values.yaml
+
+After I pushed to verify it was synced I ran the following command `minikube kubectl -- get pods`
+
+
+
+We can see now we have three replicas of python-app-my-app-569fdf5b4 pod
+
+note it took 2-3 minutes to sync which is expected
+
+
+
+
+## Multi-Environment Depyment & Auto-Sync
+
+I created two files `values-dev.yaml` and `values-prod.yaml`
+the differences between them are the number of `replicas` in `values-dev.yaml` is `1` and in `values-prod.yaml` is `2`. Also the `env` variable is `dev` in `values-dev.yaml` and `prod` in `values-prod.yaml`
+
+
+Creating dev and prod namespaces
+
+
+Verifying the namespaces
+
+
+
+Creating an ArgoCD application for values-dev (`argocd-python-dev.yaml`) and one for values-prod (`argocd-python-prod.yaml`)
+
+Applying the new two application
+
+
+After pushing the new values files to the remote repo we can see that the dev and prod instances are healthy and in sync
+
+
+also when I pushed the new values files I got one replica running on the dev namespace and two on the prod name space like what I specifed in the values files
+
+
+
+
+
+
+
+
+Now I changed the number of replicas in `values-prod.yaml` and we can see the auto sync is working because now we have three replicas in the prod namespace
+
+
+
+
+### self-healing testing
+
+test 1: Manual override of Replica count
+first let's check the current replica count by running this command `minikube kubectl -- get deploy python-app-prod-my-app -n prod`
+
+```
+(base) kokai@kokai:~/Desktop/S25-core-course-labs$ minikube kubectl -- get deploy python-app-prod-my-app -n prod
+NAME READY UP-TO-DATE AVAILABLE AGE
+python-app-prod-my-app 3/3 3 3 39m
+```
+
+Now I will manually override replicas by running `minikube kubectl -- patch deployment python-app-prod-my-app -n prod --patch '{"spec":{"replicas": 2}}' `
+
+and then checking again the replica count by running this command `minikube kubectl -- get deploy python-app-prod-my-app -n prod`
+
+
+
+test 2: now for test 2 we need to delete a replica
+
+
+
+
+
+
+### Explanation of how ArgoCD handles configuration drift vs. runtime events
+
+ArgoCD keeps your cluster in sync with Git by constantly checking for differences. If something in the cluster changes and doesn’t match Git (like a manual change to a deployment), ArgoCD automatically fixes it. For normal runtime events, like pods restarting or scaling, it ensures the cluster stays aligned with the Git-defined configuration.
\ No newline at end of file
diff --git a/k8s/14.md b/k8s/14.md
new file mode 100644
index 0000000000..949915ad5c
--- /dev/null
+++ b/k8s/14.md
@@ -0,0 +1,67 @@
+# Kubernetes StatefulSet
+
+- First step: rename `deplyment.yaml` and making it `statefulset.yaml` inside the Helm chart
+
+- Second step : editing the new file. I added some parts to `statefulset.yaml` like volumeClaimTemplates
+
+- Third step: testing the htlm chart
+ I ran the following command `(base) kokai@kokai:~/Desktop/S25-core-course-labs/k8s$ helm install --dry-run my-app ./my-app`
+
+ the output showed a fully rendered Helmchart
+
+- Fourth Step : after deploying the helm chart I ran all the listed commands
+
+
+
+
+
+
+
+
+- Fifth step: accessing the service
+
+
+
+- Sixth step: I opened muliple tabs and windows to access the root path of my app
+- 
+ 
+
+ the explaination : The visit count differs between pods because incoming requests are spread across multiple replicas, and each pod updates its own visits.txt file on its persistent volume. Since each pod maintains an independent counter, the total number of visits can vary from one pod to another.
+
+- Seventh step: Persistent Storage Validation
+ 
+ 
+
+- Eigth Step: Headless Service Access
+
+
+### Monitoring & Alerts
+ How probes ensure pod health.
+
+- Liveness probe periodically checks if the application is running (by sending an HTTP GET request to /). If the probe fails, Kubernetes considers the pod unhealthy and restarts the container. This ensures that crashed or deadlocked processes are automatically recovered.
+
+- Readiness probe determines if the pod is ready to accept traffic. If the probe fails, the pod is removed from the Service’s endpoints, so no requests are routed to it. This prevents users from experiencing errors while the pod is starting up or temporarily overloaded.
+
+Why they’re critical for stateful apps.
+
+- Stateful apps keep persistent data (e.g., in PVCs) and unique identities. Liveness probes restart unhealthy pods while preserving their data and identity. Readiness probes prevent traffic from reaching a pod until it’s fully ready, ensuring consistent and correct responses. This is crucial for maintaining data integrity and availability in stateful services.
+
+
+### Ordering Guarantee and Parallel Operations
+
+Explain why ordering guarantees are unnecessary for your app.
+
+By default, a StatefulSet uses the OrderedReady policy, creating and terminating pods sequentially (0, 1, 2…). This matters for apps that need a specific startup order, like databases with primary/replica roles.
+
+However, my FastAPI service stores a visit counter in a local PersistentVolume per pod. Pods are independent:
+
+No inter-pod communication or coordination
+
+No leader/follower roles
+
+Data is isolated per pod
+
+Pod creation or termination order doesn’t affect correctness.
+
+#### Implementing parallel pod management
+To instruct the StatefulSet controller to launch or terminate all pods in parallel, I set the podManagementPolicy field to Parallel.
\ No newline at end of file
diff --git a/k8s/15.md b/k8s/15.md
new file mode 100644
index 0000000000..24022b2646
--- /dev/null
+++ b/k8s/15.md
@@ -0,0 +1,86 @@
+# Kubernetes Monitoring and Init Containers
+
+## Kube Promethusd stack components explaination
+ - Prometheus: it's the main monitoring and alerting tool. it collects metrics and store them and trigger alerts if needed based on its configuration.
+
+ - Alertmanager: recive alerts from Prometheus and handle them and direct them to specfic notification routes
+
+ - Grafana: creates dashboards to visalize and interact with Prometheus.
+
+ - kube-state-metrics:create metrics about Kubernetes objects (e.g., Deployments, Pods, Nodes) by querying the Kubernetes API server.
+
+ - Node Exporter: collect metrics about system level Kubernetes node like CPU load or memory usage.
+
+ - Prometheus Operator: makes deploying and managing of Prometheus and alertmanager smipler.
+
+ - ServiceMonitor and PodMonitor (Custom Resources): ServiceMonitor and PodMonitor are custom resources that define how Prometheus discovers and scrapes metrics from specific Kubernetes services and pods, respectively.
+
+ - PrometheusRule (Custom Resource):Defines rules that Prometheus uses to create alerts and summarize metrics.
+---
+I installed Kube Promethusd stack and inslaled my app helm app chart after that.
+In addition I created a monitoring namespace to have all the Kube Prometheus stack realted pods.
+
+After running `minikube kubectl -- get pods -n monitoring -w`
+
+I got the following
+
+
+
+;
+
+Pods :
+- Alertmanager handles alert routing and notification.
+
+- Grafana provides visualization dashboards.
+
+- Prometheus Operator manages Prometheus/Alertmanager instances.
+
+- kube-state-metrics and node-exporter expose cluster/node metrics; Prometheus scrapes and stores them.
+
+StatefulSets:
+- Two StatefulSets manage Prometheus and Alertmanager, giving them stable identities and persistent storage. They ensure each instance retains data across restarts.
+
+Services:
+ - Internal ClusterIP services expose Grafana (port 80), Prometheus (9090), Alertmanager (9093), and exporters for metric scraping. Headless services (*-operated) enable peer discovery for clustered components.
+
+ConfigMaps:
+- Many ConfigMaps store pre-configured Grafana dashboards and Prometheus alerting rules. They are dynamically loaded by sidecar containers. The kube-root-ca.crt is the cluster's CA certificate.
+
+### Utilize Grafana Dashboards:
+
+I accessed Grafana after getting the password and login as an admin.
+
+- Check CPU and Memory consumption of your StatefulSet.
+ the cpu usage :
+ 
+ 
+ the memory usage:
+ 
+ 
+
+- Identify Pods with higher and lower CPU usage in the default namespace.
+- 
+ the highest cpuu usage is from valut0 pod the lowest money pods are similar like my-app0 my-app1 etc
+
+- Monitor node memory usage in percentage and megabytes.
+ 
+ 
+
+- Count the number of pods and containers managed by the Kubelet service.
+- the number of Running pods is 34 and running containers is 44
+
+
+- Evaluate network usage of Pods in the default namespace.
+ 
+
+- Determine the number of active alerts; also check the Web UI with minikube service monitoring-kube-prometheus-alertmanager.
+the number of alerts are 8 I got this number after running the following command `minikube kubectl -- port-forward -n monitoring service/monitoring-kube-prometheus-alertmanager 9093:9093`and analyzing rhe alertmanager UI I got
+
+
+
+
+### Init Containers
+ Provide proof of success, e.g., kubectl exec pod/demo-0 -- cat /test.html.
+I used the following command `minikube kubectl -- exec my-app-0 -- cat /static-directory/index.html` changed the given command a little to suit my code
+
+
\ No newline at end of file
diff --git a/k8s/ArgoCD/argocd-python-app.yaml b/k8s/ArgoCD/argocd-python-app.yaml
new file mode 100644
index 0000000000..c7709fee76
--- /dev/null
+++ b/k8s/ArgoCD/argocd-python-app.yaml
@@ -0,0 +1,21 @@
+apiVersion: argoproj.io/v1alpha1
+kind: Application
+metadata:
+ name: python-app
+ namespace: argocd
+spec:
+ project: default
+ source:
+ repoURL: https://github.com/KaramKhaddour/S25-core-course-labs.git
+ targetRevision: lab13
+ path: k8s/my-app
+ helm:
+ valueFiles:
+ - values.yaml
+ destination:
+ server: https://kubernetes.default.svc
+ namespace: default
+ syncPolicy:
+ automated:
+ prune: true
+ selfHeal: true
\ No newline at end of file
diff --git a/k8s/ArgoCD/argocd-python-dev.yaml b/k8s/ArgoCD/argocd-python-dev.yaml
new file mode 100644
index 0000000000..34c1371996
--- /dev/null
+++ b/k8s/ArgoCD/argocd-python-dev.yaml
@@ -0,0 +1,21 @@
+apiVersion: argoproj.io/v1alpha1
+kind: Application
+metadata:
+ name: python-app-dev
+ namespace: argocd
+spec:
+ project: default
+ source:
+ repoURL: https://github.com/KaramKhaddour/S25-core-course-labs.git
+ targetRevision: lab13
+ path: k8s/my-app
+ helm:
+ valueFiles:
+ - values-dev.yaml
+ destination:
+ server: https://kubernetes.default.svc
+ namespace: dev
+ syncPolicy:
+ automated:
+ prune: true
+ selfHeal: true
diff --git a/k8s/ArgoCD/argocd-python-prod.yaml b/k8s/ArgoCD/argocd-python-prod.yaml
new file mode 100644
index 0000000000..f695f2f924
--- /dev/null
+++ b/k8s/ArgoCD/argocd-python-prod.yaml
@@ -0,0 +1,21 @@
+apiVersion: argoproj.io/v1alpha1
+kind: Application
+metadata:
+ name: python-app-prod
+ namespace: argocd
+spec:
+ project: default
+ source:
+ repoURL: https://github.com/KaramKhaddour/S25-core-course-labs.git
+ targetRevision: lab13
+ path: k8s/my-app
+ helm:
+ valueFiles:
+ - values-prod.yaml
+ destination:
+ server: https://kubernetes.default.svc
+ namespace: prod
+ syncPolicy:
+ automated:
+ prune: true
+ selfHeal: true
diff --git a/k8s/HELM.md b/k8s/HELM.md
new file mode 100644
index 0000000000..2be993592a
--- /dev/null
+++ b/k8s/HELM.md
@@ -0,0 +1,325 @@
+# Helm Deployment Report
+
+## Pods
+Output of `kubectl get pods`:
+```
+ kokai@kokai:~/Desktop/S25-core-course-labs/k8s$ minikube kubectl -- get pods
+NAME READY STATUS RESTARTS AGE
+my-app-565549f8b8-9zf8g 1/1 Running 0 2m23s
+nginx-depl-569bd7dcf9-n54gw 1/1 Running 1 (14h ago) 18h
+```
+
+## Services
+
+Output of `kubectl get svc`:
+```
+ kokai@kokai:~/Desktop/S25-core-course-labs/k8s$ minikube kubectl -- get svc
+NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
+kubernetes ClusterIP 10.96.0.1 443/TCP 18h
+my-app NodePort 10.103.167.71 8000:31124/TCP 7m3s
+```
+
+Output of `minikube service my-app`
+
+
+
+Output of ` helm lint ./my-app`
+
+```
+kokai@kokai:~/Desktop/S25-core-course-labs/k8s$ helm lint ./my-app
+==> Linting ./my-app
+[INFO] Chart.yaml: icon is recommended
+
+1 chart(s) linted, 0 chart(s) failed
+```
+
+Output of `helm install --dry-run my-app ./my-app`
+
+```
+ kokai@kokai:~/Desktop/S25-core-course-labs/k8s$ helm install --dry-run my-app ./my-app
+NAME: my-app
+LAST DEPLOYED: Mon Feb 16 12:29:50 2026
+NAMESPACE: default
+STATUS: pending-install
+REVISION: 1
+HOOKS:
+---
+# Source: my-app/templates/postinstall-hook.yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: postinstall-hook
+ annotations:
+ "helm.sh/hook": post-install
+ "helm.sh/hook-delete-policy": hook-succeeded
+spec:
+ containers:
+ - name: postinstall
+ image: busybox
+ command: ["sh", "-c", "echo Post-install hook running; sleep 20"]
+ restartPolicy: Never
+---
+# Source: my-app/templates/preinstall-hook.yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: preinstall-hook
+ annotations:
+ "helm.sh/hook": pre-install
+ "helm.sh/hook-delete-policy": hook-succeeded
+spec:
+ containers:
+ - name: preinstall
+ image: busybox
+ command: ["sh", "-c", "echo Pre-install hook running; sleep 20"]
+ restartPolicy: Never
+---
+# Source: my-app/templates/tests/test-connection.yaml
+apiVersion: v1
+kind: Pod
+metadata:
+ name: "my-app-test-connection"
+ labels:
+ helm.sh/chart: my-app-0.1.0
+ app.kubernetes.io/name: my-app
+ app.kubernetes.io/instance: my-app
+ app.kubernetes.io/version: "1.16.0"
+ app.kubernetes.io/managed-by: Helm
+ annotations:
+ "helm.sh/hook": test
+spec:
+ containers:
+ - name: wget
+ image: busybox
+ command: ['wget']
+ args: ['my-app:8000']
+ restartPolicy: Never
+MANIFEST:
+---
+# Source: my-app/templates/serviceaccount.yaml
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: my-app
+ labels:
+ helm.sh/chart: my-app-0.1.0
+ app.kubernetes.io/name: my-app
+ app.kubernetes.io/instance: my-app
+ app.kubernetes.io/version: "1.16.0"
+ app.kubernetes.io/managed-by: Helm
+automountServiceAccountToken: true
+---
+# Source: my-app/templates/service.yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: my-app
+ labels:
+ helm.sh/chart: my-app-0.1.0
+ app.kubernetes.io/name: my-app
+ app.kubernetes.io/instance: my-app
+ app.kubernetes.io/version: "1.16.0"
+ app.kubernetes.io/managed-by: Helm
+spec:
+ type: NodePort
+ ports:
+ - port: 8000
+ targetPort: http
+ protocol: TCP
+ name: http
+ selector:
+ app.kubernetes.io/name: my-app
+ app.kubernetes.io/instance: my-app
+---
+# Source: my-app/templates/deployment.yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: my-app
+ labels:
+ helm.sh/chart: my-app-0.1.0
+ app.kubernetes.io/name: my-app
+ app.kubernetes.io/instance: my-app
+ app.kubernetes.io/version: "1.16.0"
+ app.kubernetes.io/managed-by: Helm
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: my-app
+ app.kubernetes.io/instance: my-app
+ template:
+ metadata:
+ labels:
+ helm.sh/chart: my-app-0.1.0
+ app.kubernetes.io/name: my-app
+ app.kubernetes.io/instance: my-app
+ app.kubernetes.io/version: "1.16.0"
+ app.kubernetes.io/managed-by: Helm
+ spec:
+ serviceAccountName: my-app
+ containers:
+ - name: my-app
+ image: "karamkhaddourpro/my-fastapi-app:latest"
+ imagePullPolicy: IfNotPresent
+ ports:
+ - name: http
+ containerPort: 8000
+ protocol: TCP
+ livenessProbe:
+ httpGet:
+ path: /
+ port: http
+ readinessProbe:
+ httpGet:
+ path: /
+ port: http
+
+NOTES:
+1. Get the application URL by running these commands:
+ export NODE_PORT=$(kubectl get --namespace default -o jsonpath="{.spec.ports[0].nodePort}" services my-app)
+ export NODE_IP=$(kubectl get nodes --namespace default -o jsonpath="{.items[0].status.addresses[0].address}")
+ echo http://$NODE_IP:$NODE_PORT
+```
+
+### Resinstall my-app usuing helm to see the preinstall and postinstall hooks
+
+
+
+Output of ` minikube kubectl -- get pods --watch`
+
+
+
+Output of ` minikube kubectl -- get po`
+
+
+
+Output of `minikube kubectl -- describe po preinstall-hook`
+
+```
+(base) kokai@kokai:~/Desktop/S25-core-course-labs/k8s$ minikube kubectl -- describe po preinstall-hook
+Name: preinstall-hook
+Namespace: default
+Priority: 0
+Service Account: default
+Node: minikube/192.168.49.2
+Start Time: Mon, 16 Feb 2026 12:51:01 +0300
+Labels:
+Annotations: helm.sh/hook: pre-install
+Status: Succeeded
+IP: 10.244.0.14
+IPs:
+ IP: 10.244.0.14
+Containers:
+ pre-install-container:
+ Container ID: docker://50f911e50b55c1413fabeca5a2105c2ee687ba523f72ea8d1e8e37a579268b60
+ Image: busybox
+ Image ID: docker-pullable://busybox@sha256:b3255e7dfbcd10cb367af0d409747d511aeb66dfac98cf30e97e87e4207dd76f
+ Port:
+ Host Port:
+ Command:
+ sh
+ -c
+ echo pre-install hook is running && sleep 20
+ State: Terminated
+ Reason: Completed
+ Exit Code: 0
+ Started: Mon, 16 Feb 2026 12:51:02 +0300
+ Finished: Mon, 16 Feb 2026 12:51:22 +0300
+ Ready: False
+ Restart Count: 0
+ Environment:
+ Mounts:
+ /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-2m2mr (ro)
+Conditions:
+ Type Status
+ PodReadyToStartContainers False
+ Initialized True
+ Ready False
+ ContainersReady False
+ PodScheduled True
+Volumes:
+ kube-api-access-2m2mr:
+ Type: Projected (a volume that contains injected data from multiple sources)
+ TokenExpirationSeconds: 3607
+ ConfigMapName: kube-root-ca.crt
+ Optional: false
+ DownwardAPI: true
+QoS Class: BestEffort
+Node-Selectors:
+Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
+ node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
+Events:
+ Type Reason Age From Message
+ ---- ------ ---- ---- -------
+ Normal Scheduled 111s default-scheduler Successfully assigned default/preinstall-hook to minikube
+ Normal Pulled 111s kubelet spec.containers{pre-install-container}: Container image "busybox" already present on machine and can be accessed by the pod
+ Normal Created 111s kubelet spec.containers{pre-install-container}: Container created
+ Normal Started 111s kubelet spec.containers{pre-install-container}: Container started
+(base) kokai@kokai:~/Desktop/S25-core-course-labs/k8s$
+```
+
+Output of `minikube kubectl -- describe po postinstall-hook`
+```
+(base) kokai@kokai:~/Desktop/S25-core-course-labs/k8s$ minikube kubectl -- describe po postinstall-hook
+Name: postinstall-hook
+Namespace: default
+Priority: 0
+Service Account: default
+Node: minikube/192.168.49.2
+Start Time: Mon, 16 Feb 2026 12:51:23 +0300
+Labels:
+Annotations: helm.sh/hook: post-install
+Status: Succeeded
+IP: 10.244.0.16
+IPs:
+ IP: 10.244.0.16
+Containers:
+ post-install-container:
+ Container ID: docker://d925fe1cea08ab85ddae8924089053345a39d05dcd6b89a496ea2f74a235947f
+ Image: busybox
+ Image ID: docker-pullable://busybox@sha256:b3255e7dfbcd10cb367af0d409747d511aeb66dfac98cf30e97e87e4207dd76f
+ Port:
+ Host Port:
+ Command:
+ sh
+ -c
+ echo post-install hook is running && sleep 20
+ State: Terminated
+ Reason: Completed
+ Exit Code: 0
+ Started: Mon, 16 Feb 2026 12:51:26 +0300
+ Finished: Mon, 16 Feb 2026 12:51:46 +0300
+ Ready: False
+ Restart Count: 0
+ Environment:
+ Mounts:
+ /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-fr9sz (ro)
+Conditions:
+ Type Status
+ PodReadyToStartContainers False
+ Initialized True
+ Ready False
+ ContainersReady False
+ PodScheduled True
+Volumes:
+ kube-api-access-fr9sz:
+ Type: Projected (a volume that contains injected data from multiple sources)
+ TokenExpirationSeconds: 3607
+ ConfigMapName: kube-root-ca.crt
+ Optional: false
+ DownwardAPI: true
+QoS Class: BestEffort
+Node-Selectors:
+Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
+ node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
+Events:
+ Type Reason Age From Message
+ ---- ------ ---- ---- -------
+ Normal Scheduled 2m20s default-scheduler Successfully assigned default/postinstall-hook to minikube
+ Normal Pulling 2m20s kubelet spec.containers{post-install-container}: Pulling image "busybox"
+ Normal Pulled 2m18s kubelet spec.containers{post-install-container}: Successfully pulled image "busybox" in 2s (2s including waiting). Image size: 4425286 bytes.
+ Normal Created 2m18s kubelet spec.containers{post-install-container}: Container created
+ Normal Started 2m18s kubelet spec.containers{post-install-container}: Container started
+
+```
\ No newline at end of file
diff --git a/k8s/README.md b/k8s/README.md
new file mode 100644
index 0000000000..85843c7c94
--- /dev/null
+++ b/k8s/README.md
@@ -0,0 +1,76 @@
+# Introduction to Kubernetes
+
+## Walk through the process
+
+### creating the cluster and verfiying that it;s running
+
+After startning the minikube cluster I verficed that the cluster is running by running the following command.
+
+```
+ kokai@kokai:~/Desktop/S25-core-course-labs$ minikube kubectl -- cluster-info
+
+Kubernetes control plane is running at https://192.168.49.2:8443
+CoreDNS is running at https://192.168.49.2:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
+
+To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
+
+```
+
+And the following command too
+
+```
+kokai@kokai:~/Desktop/S25-core-course-labs$ minikube kubectl -- get nodes
+NAME STATUS ROLES AGE VERSION
+minikube Ready control-plane 18h v1.35.0
+```
+### Creating the deployment
+
+```
+ kokai@kokai:~/Desktop/S25-core-course-labs/k8s$ minikube kubectl -- apply -f deployment.yml
+deployment.apps/web-app-deployment created
+```
+
+### Creating the service
+
+```
+ kokai@kokai:~/Desktop/S25-core-course-labs/k8s$ minikube kubectl -- apply -f service.yml
+service/web-app created
+```
+### checking the pods
+```
+kokai@kokai:~/Desktop/S25-core-course-labs/k8s$ minikube kubectl -- get pods
+NAME READY STATUS RESTARTS AGE
+nginx-depl-569bd7dcf9-n54gw 1/1 Running 1 (14h ago) 18h
+web-app-deployment-76dc6b5965-jgv47 1/1 Running 0 2m27s
+web-app-deployment-76dc6b5965-nrzm4 1/1 Running 0 2m27s
+web-app-deployment-76dc6b5965-rk62s 1/1 Running 0 2m27s
+```
+
+### Checking the service
+
+```
+kokai@kokai:~/Desktop/S25-core-course-labs/k8s$ minikube kubectl -- get svc
+NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
+kubernetes ClusterIP 10.96.0.1 443/TCP 18h
+web-app LoadBalancer 10.110.208.223 8000:32714/TCP 86s
+```
+
+### Service web-app
+by running the following command we can see the result:
+
+```
+ minikube service web-app
+```
+
+
+
+
+We can see that we have the same address in the terminal and in chrome and that the app is running correctly
+
+### Service --all
+
+
+
+
+### clean up
+
\ No newline at end of file
diff --git a/k8s/deployment.yml b/k8s/deployment.yml
new file mode 100644
index 0000000000..7bd688a943
--- /dev/null
+++ b/k8s/deployment.yml
@@ -0,0 +1,22 @@
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: web-app-deployment
+ labels:
+ app: web-app
+spec:
+ replicas: 3
+ selector:
+ matchLabels:
+ app: web-app
+ template:
+ metadata:
+ labels:
+ app: web-app
+ spec:
+ containers:
+ - name: web-app
+ image: karamkhaddourpro/my-fastapi-app
+ ports:
+ - containerPort: 8000
\ No newline at end of file
diff --git a/k8s/image.png b/k8s/image.png
new file mode 100644
index 0000000000..9bf94a0eb8
Binary files /dev/null and b/k8s/image.png differ
diff --git a/k8s/my-app/.helmignore b/k8s/my-app/.helmignore
new file mode 100644
index 0000000000..0e8a0eb36f
--- /dev/null
+++ b/k8s/my-app/.helmignore
@@ -0,0 +1,23 @@
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*.orig
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
+.vscode/
diff --git a/k8s/my-app/Chart.yaml b/k8s/my-app/Chart.yaml
new file mode 100644
index 0000000000..f2e9f20317
--- /dev/null
+++ b/k8s/my-app/Chart.yaml
@@ -0,0 +1,24 @@
+apiVersion: v2
+name: my-app
+description: A Helm chart for Kubernetes
+
+# A chart can be either an 'application' or a 'library' chart.
+#
+# Application charts are a collection of templates that can be packaged into versioned archives
+# to be deployed.
+#
+# Library charts provide useful utilities or functions for the chart developer. They're included as
+# a dependency of application charts to inject those utilities and functions into the rendering
+# pipeline. Library charts do not define any templates and therefore cannot be deployed.
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+version: 0.1.0
+
+# This is the version number of the application being deployed. This version number should be
+# incremented each time you make changes to the application. Versions are not expected to
+# follow Semantic Versioning. They should reflect the version the application is using.
+# It is recommended to use it with quotes.
+appVersion: "1.16.0"
diff --git a/k8s/my-app/files/config.json b/k8s/my-app/files/config.json
new file mode 100644
index 0000000000..d71e785aca
--- /dev/null
+++ b/k8s/my-app/files/config.json
@@ -0,0 +1,5 @@
+{
+ "appName": "moscow-time-app",
+ "version": "1.0",
+ "env": "lab12"
+}
\ No newline at end of file
diff --git a/k8s/my-app/templates/NOTES.txt b/k8s/my-app/templates/NOTES.txt
new file mode 100644
index 0000000000..20289187c0
--- /dev/null
+++ b/k8s/my-app/templates/NOTES.txt
@@ -0,0 +1,35 @@
+1. Get the application URL by running these commands:
+{{- if .Values.httpRoute.enabled }}
+{{- if .Values.httpRoute.hostnames }}
+ export APP_HOSTNAME={{ .Values.httpRoute.hostnames | first }}
+{{- else }}
+ export APP_HOSTNAME=$(kubectl get --namespace {{(first .Values.httpRoute.parentRefs).namespace | default .Release.Namespace }} gateway/{{ (first .Values.httpRoute.parentRefs).name }} -o jsonpath="{.spec.listeners[0].hostname}")
+ {{- end }}
+{{- if and .Values.httpRoute.rules (first .Values.httpRoute.rules).matches (first (first .Values.httpRoute.rules).matches).path.value }}
+ echo "Visit http://$APP_HOSTNAME{{ (first (first .Values.httpRoute.rules).matches).path.value }} to use your application"
+
+ NOTE: Your HTTPRoute depends on the listener configuration of your gateway and your HTTPRoute rules.
+ The rules can be set for path, method, header and query parameters.
+ You can check the gateway configuration with 'kubectl get --namespace {{(first .Values.httpRoute.parentRefs).namespace | default .Release.Namespace }} gateway/{{ (first .Values.httpRoute.parentRefs).name }} -o yaml'
+{{- end }}
+{{- else if .Values.ingress.enabled }}
+{{- range $host := .Values.ingress.hosts }}
+ {{- range .paths }}
+ http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
+ {{- end }}
+{{- end }}
+{{- else if contains "NodePort" .Values.service.type }}
+ export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "my-app.fullname" . }})
+ export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
+ echo http://$NODE_IP:$NODE_PORT
+{{- else if contains "LoadBalancer" .Values.service.type }}
+ NOTE: It may take a few minutes for the LoadBalancer IP to be available.
+ You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "my-app.fullname" . }}'
+ export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "my-app.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
+ echo http://$SERVICE_IP:{{ .Values.service.port }}
+{{- else if contains "ClusterIP" .Values.service.type }}
+ export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "my-app.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
+ export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
+ echo "Visit http://127.0.0.1:8080 to use your application"
+ kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
+{{- end }}
diff --git a/k8s/my-app/templates/_helpers.tpl b/k8s/my-app/templates/_helpers.tpl
new file mode 100644
index 0000000000..da4015bf33
--- /dev/null
+++ b/k8s/my-app/templates/_helpers.tpl
@@ -0,0 +1,62 @@
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "my-app.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "my-app.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "my-app.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "my-app.labels" -}}
+helm.sh/chart: {{ include "my-app.chart" . }}
+{{ include "my-app.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "my-app.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "my-app.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{/*
+Create the name of the service account to use
+*/}}
+{{- define "my-app.serviceAccountName" -}}
+{{- if .Values.serviceAccount.create }}
+{{- default (include "my-app.fullname" .) .Values.serviceAccount.name }}
+{{- else }}
+{{- default "default" .Values.serviceAccount.name }}
+{{- end }}
+{{- end }}
diff --git a/k8s/my-app/templates/config.yaml b/k8s/my-app/templates/config.yaml
new file mode 100644
index 0000000000..25fed23a34
--- /dev/null
+++ b/k8s/my-app/templates/config.yaml
@@ -0,0 +1,7 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: my-app-config
+data:
+ config.json: |-
+{{ .Files.Get "files/config.json" | indent 4 }}
diff --git a/k8s/my-app/templates/hpa.yaml b/k8s/my-app/templates/hpa.yaml
new file mode 100644
index 0000000000..b87d33975a
--- /dev/null
+++ b/k8s/my-app/templates/hpa.yaml
@@ -0,0 +1,32 @@
+{{- if .Values.autoscaling.enabled }}
+apiVersion: autoscaling/v2
+kind: HorizontalPodAutoscaler
+metadata:
+ name: {{ include "my-app.fullname" . }}
+ labels:
+ {{- include "my-app.labels" . | nindent 4 }}
+spec:
+ scaleTargetRef:
+ apiVersion: apps/v1
+ kind: Deployment
+ name: {{ include "my-app.fullname" . }}
+ minReplicas: {{ .Values.autoscaling.minReplicas }}
+ maxReplicas: {{ .Values.autoscaling.maxReplicas }}
+ metrics:
+ {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
+ - type: Resource
+ resource:
+ name: cpu
+ target:
+ type: Utilization
+ averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
+ {{- end }}
+ {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
+ - type: Resource
+ resource:
+ name: memory
+ target:
+ type: Utilization
+ averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
+ {{- end }}
+{{- end }}
diff --git a/k8s/my-app/templates/httproute.yaml b/k8s/my-app/templates/httproute.yaml
new file mode 100644
index 0000000000..f457a4bdcd
--- /dev/null
+++ b/k8s/my-app/templates/httproute.yaml
@@ -0,0 +1,38 @@
+{{- if .Values.httpRoute.enabled -}}
+{{- $fullName := include "my-app.fullname" . -}}
+{{- $svcPort := .Values.service.port -}}
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+ name: {{ $fullName }}
+ labels:
+ {{- include "my-app.labels" . | nindent 4 }}
+ {{- with .Values.httpRoute.annotations }}
+ annotations:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+spec:
+ parentRefs:
+ {{- with .Values.httpRoute.parentRefs }}
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+ {{- with .Values.httpRoute.hostnames }}
+ hostnames:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+ rules:
+ {{- range .Values.httpRoute.rules }}
+ {{- with .matches }}
+ - matches:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .filters }}
+ filters:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ backendRefs:
+ - name: {{ $fullName }}
+ port: {{ $svcPort }}
+ weight: 1
+ {{- end }}
+{{- end }}
diff --git a/k8s/my-app/templates/ingress.yaml b/k8s/my-app/templates/ingress.yaml
new file mode 100644
index 0000000000..094732d609
--- /dev/null
+++ b/k8s/my-app/templates/ingress.yaml
@@ -0,0 +1,43 @@
+{{- if .Values.ingress.enabled -}}
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: {{ include "my-app.fullname" . }}
+ labels:
+ {{- include "my-app.labels" . | nindent 4 }}
+ {{- with .Values.ingress.annotations }}
+ annotations:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+spec:
+ {{- with .Values.ingress.className }}
+ ingressClassName: {{ . }}
+ {{- end }}
+ {{- if .Values.ingress.tls }}
+ tls:
+ {{- range .Values.ingress.tls }}
+ - hosts:
+ {{- range .hosts }}
+ - {{ . | quote }}
+ {{- end }}
+ secretName: {{ .secretName }}
+ {{- end }}
+ {{- end }}
+ rules:
+ {{- range .Values.ingress.hosts }}
+ - host: {{ .host | quote }}
+ http:
+ paths:
+ {{- range .paths }}
+ - path: {{ .path }}
+ {{- with .pathType }}
+ pathType: {{ . }}
+ {{- end }}
+ backend:
+ service:
+ name: {{ include "my-app.fullname" $ }}
+ port:
+ number: {{ $.Values.service.port }}
+ {{- end }}
+ {{- end }}
+{{- end }}
diff --git a/k8s/my-app/templates/postinstall-hook.yaml b/k8s/my-app/templates/postinstall-hook.yaml
new file mode 100644
index 0000000000..02661b9ea7
--- /dev/null
+++ b/k8s/my-app/templates/postinstall-hook.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: postinstall-hook
+ annotations:
+ "helm.sh/hook": "post-install"
+ "helm.sh/hook-delete-policy": hook-succeeded
+spec:
+ containers:
+ - name: post-install-container
+ image: busybox
+ imagePullPolicy: Always
+ command: ["sh", "-c", "echo post-install hook is running && sleep 20"]
+ restartPolicy: Never
+ terminationGracePeriodSeconds: 0
\ No newline at end of file
diff --git a/k8s/my-app/templates/preinstall-hook.yaml b/k8s/my-app/templates/preinstall-hook.yaml
new file mode 100644
index 0000000000..8d4af02b7b
--- /dev/null
+++ b/k8s/my-app/templates/preinstall-hook.yaml
@@ -0,0 +1,16 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: preinstall-hook
+ annotations:
+ "helm.sh/hook": "pre-install"
+ "helm.sh/hook-delete-policy": hook-succeeded
+spec:
+ containers:
+ - name: pre-install-container
+ image: busybox
+ imagePullPolicy: IfNotPresent
+ command: ["sh", "-c", "echo pre-install hook is running && sleep 20"]
+ restartPolicy: Never
+ terminationGracePeriodSeconds: 0
+
\ No newline at end of file
diff --git a/k8s/my-app/templates/service.yaml b/k8s/my-app/templates/service.yaml
new file mode 100644
index 0000000000..2c92457760
--- /dev/null
+++ b/k8s/my-app/templates/service.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ include "my-app.fullname" . }}
+ labels:
+ {{- include "my-app.labels" . | nindent 4 }}
+spec:
+ type: {{ .Values.service.type }}
+ ports:
+ - port: {{ .Values.service.port }}
+ targetPort: http
+ protocol: TCP
+ name: http
+ selector:
+ {{- include "my-app.selectorLabels" . | nindent 4 }}
diff --git a/k8s/my-app/templates/serviceaccount.yaml b/k8s/my-app/templates/serviceaccount.yaml
new file mode 100644
index 0000000000..af75921008
--- /dev/null
+++ b/k8s/my-app/templates/serviceaccount.yaml
@@ -0,0 +1,13 @@
+{{- if .Values.serviceAccount.create -}}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: {{ include "my-app.serviceAccountName" . }}
+ labels:
+ {{- include "my-app.labels" . | nindent 4 }}
+ {{- with .Values.serviceAccount.annotations }}
+ annotations:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+automountServiceAccountToken: {{ .Values.serviceAccount.automount }}
+{{- end }}
diff --git a/k8s/my-app/templates/statefulset.yaml b/k8s/my-app/templates/statefulset.yaml
new file mode 100644
index 0000000000..96fcb11e9d
--- /dev/null
+++ b/k8s/my-app/templates/statefulset.yaml
@@ -0,0 +1,105 @@
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+ name: {{ include "my-app.fullname" . }}
+ labels:
+ {{- include "my-app.labels" . | nindent 4 }}
+spec:
+ {{- if not .Values.autoscaling.enabled }}
+ replicas: {{ .Values.replicaCount }}
+ {{- end }}
+ volumeClaimTemplates:
+ {{- toYaml .Values.volumeClaimTemplates | nindent 4 }}
+ selector:
+ matchLabels:
+ {{- include "my-app.selectorLabels" . | nindent 6 }}
+ template:
+ metadata:
+ {{- with .Values.podAnnotations }}
+ annotations:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ labels:
+ {{- include "my-app.labels" . | nindent 8 }}
+ {{- with .Values.podLabels }}
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ spec:
+ {{- with .Values.imagePullSecrets }}
+ imagePullSecrets:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ podManagementPolicy: {{ .Values.podManagementPolicy }}
+ serviceAccountName: {{ include "my-app.serviceAccountName" . }}
+ {{- with .Values.podSecurityContext }}
+ securityContext:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ initContainers:
+ - name: install
+ image: busybox:1.36.1
+ command: ["wget", "-O", "/static-directory/index.html", "http://info.cern.ch"]
+ volumeMounts:
+ - name: static-directory
+ mountPath: "/static-directory"
+ - name: ping
+ image: busybox:1.36.1
+ command: ["sh", "-c", "echo ping > /static-directory/ping-pong.txt"]
+ volumeMounts:
+ - name: static-directory
+ mountPath: "/static-directory"
+ - name: pong
+ image: busybox:1.36.1
+ command: ["sh", "-c", "echo pong >> /static-directory/ping-pong.txt"]
+ volumeMounts:
+ - name: static-directory
+ mountPath: "/static-directory"
+ containers:
+ - name: {{ .Chart.Name }}
+ {{- with .Values.securityContext }}
+ securityContext:
+ {{- toYaml . | nindent 12 }}
+ {{- end }}
+ image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+ imagePullPolicy: {{ .Values.image.pullPolicy }}
+ envFrom:
+ - configMapRef:
+ name: my-app-config
+ env:
+ {{- toYaml .Values.env | nindent 12 }}
+ ports:
+ - name: http
+ containerPort: {{ .Values.service.port }}
+ protocol: TCP
+ {{- with .Values.livenessProbe }}
+ livenessProbe:
+ {{- toYaml . | nindent 12 }}
+ {{- end }}
+ {{- with .Values.readinessProbe }}
+ readinessProbe:
+ {{- toYaml . | nindent 12 }}
+ {{- end }}
+ {{- with .Values.resources }}
+ resources:
+ {{- toYaml . | nindent 12 }}
+ {{- end }}
+ {{- with .Values.volumeMounts }}
+ volumeMounts:
+ {{- toYaml . | nindent 12 }}
+ {{- end }}
+ {{- with .Values.volumes }}
+ volumes:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.nodeSelector }}
+ nodeSelector:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.affinity }}
+ affinity:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.tolerations }}
+ tolerations:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
\ No newline at end of file
diff --git a/k8s/my-app/templates/tests/test-connection.yaml b/k8s/my-app/templates/tests/test-connection.yaml
new file mode 100644
index 0000000000..9e68b40388
--- /dev/null
+++ b/k8s/my-app/templates/tests/test-connection.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Pod
+metadata:
+ name: "{{ include "my-app.fullname" . }}-test-connection"
+ labels:
+ {{- include "my-app.labels" . | nindent 4 }}
+ annotations:
+ "helm.sh/hook": test
+spec:
+ containers:
+ - name: wget
+ image: busybox
+ command: ['wget']
+ args: ['{{ include "my-app.fullname" . }}:{{ .Values.service.port }}']
+ restartPolicy: Never
diff --git a/k8s/my-app/values-dev.yaml b/k8s/my-app/values-dev.yaml
new file mode 100644
index 0000000000..c979cf4dbd
--- /dev/null
+++ b/k8s/my-app/values-dev.yaml
@@ -0,0 +1,162 @@
+# Default values for my-app.
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+
+# This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/
+replicaCount: 1
+
+# This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/
+image:
+ repository: karamkhaddourpro/my-fastapi-app
+ # This sets the pull policy for images.
+ pullPolicy: IfNotPresent
+ # Overrides the image tag whose default is the chart appVersion.
+ tag: "latest"
+
+# This is for the secrets for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
+imagePullSecrets: []
+# This is to override the chart name.
+nameOverride: ""
+fullnameOverride: ""
+
+# This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/
+serviceAccount:
+ # Specifies whether a service account should be created
+ create: true
+ # Automatically mount a ServiceAccount's API credentials?
+ automount: true
+ # Annotations to add to the service account
+ annotations: {}
+ # The name of the service account to use.
+ # If not set and create is true, a name is generated using the fullname template
+ name: ""
+
+# This is for setting Kubernetes Annotations to a Pod.
+# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
+podAnnotations: {}
+# This is for setting Kubernetes Labels to a Pod.
+# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
+podLabels: {}
+
+podSecurityContext: {}
+ # fsGroup: 2000
+
+securityContext: {}
+ # capabilities:
+ # drop:
+ # - ALL
+ # readOnlyRootFilesystem: true
+ # runAsNonRoot: true
+ # runAsUser: 1000
+
+# This is for setting up a service more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/
+service:
+ # This sets the service type more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types
+ type: NodePort
+ # This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports
+ port: 8000
+ nodePort: 30080
+
+# This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/
+ingress:
+ enabled: false
+ className: ""
+ annotations: {}
+ # kubernetes.io/ingress.class: nginx
+ # kubernetes.io/tls-acme: "true"
+ hosts:
+ - host: chart-example.local
+ paths:
+ - path: /
+ pathType: ImplementationSpecific
+ tls: []
+ # - secretName: chart-example-tls
+ # hosts:
+ # - chart-example.local
+
+# -- Expose the service via gateway-api HTTPRoute
+# Requires Gateway API resources and suitable controller installed within the cluster
+# (see: https://gateway-api.sigs.k8s.io/guides/)
+httpRoute:
+ # HTTPRoute enabled.
+ enabled: false
+ # HTTPRoute annotations.
+ annotations: {}
+ # Which Gateways this Route is attached to.
+ parentRefs:
+ - name: gateway
+ sectionName: http
+ # namespace: default
+ # Hostnames matching HTTP header.
+ hostnames:
+ - chart-example.local
+ # List of rules and filters applied.
+ rules:
+ - matches:
+ - path:
+ type: PathPrefix
+ value: /headers
+ # filters:
+ # - type: RequestHeaderModifier
+ # requestHeaderModifier:
+ # set:
+ # - name: My-Overwrite-Header
+ # value: this-is-the-only-value
+ # remove:
+ # - User-Agent
+ # - matches:
+ # - path:
+ # type: PathPrefix
+ # value: /echo
+ # headers:
+ # - name: version
+ # value: v2
+
+resources: {}
+ # We usually recommend not to specify default resources and to leave this as a conscious
+ # choice for the user. This also increases chances charts run on environments with little
+ # resources, such as Minikube. If you do want to specify resources, uncomment the following
+ # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
+ # limits:
+ # cpu: 100m
+ # memory: 128Mi
+ # requests:
+ # cpu: 100m
+ # memory: 128Mi
+
+# This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
+livenessProbe:
+ httpGet:
+ path: /
+ port: http
+readinessProbe:
+ httpGet:
+ path: /
+ port: http
+
+# This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/
+autoscaling:
+ enabled: false
+ minReplicas: 1
+ maxReplicas: 100
+ targetCPUUtilizationPercentage: 80
+ # targetMemoryUtilizationPercentage: 80
+
+volumes:
+ - name: config-volume
+ configMap:
+ name: my-app-config
+
+volumeMounts:
+ - name: config-volume
+ mountPath: /config.json
+ subPath: config.json
+
+nodeSelector: {}
+
+tolerations: []
+
+affinity: {}
+
+env:
+ ENV: "dev"
\ No newline at end of file
diff --git a/k8s/my-app/values-prod.yaml b/k8s/my-app/values-prod.yaml
new file mode 100644
index 0000000000..87b72c4210
--- /dev/null
+++ b/k8s/my-app/values-prod.yaml
@@ -0,0 +1,162 @@
+# Default values for my-app.
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+
+# This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/
+replicaCount: 3
+
+# This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/
+image:
+ repository: karamkhaddourpro/my-fastapi-app
+ # This sets the pull policy for images.
+ pullPolicy: IfNotPresent
+ # Overrides the image tag whose default is the chart appVersion.
+ tag: "latest"
+
+# This is for the secrets for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
+imagePullSecrets: []
+# This is to override the chart name.
+nameOverride: ""
+fullnameOverride: ""
+
+# This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/
+serviceAccount:
+ # Specifies whether a service account should be created
+ create: true
+ # Automatically mount a ServiceAccount's API credentials?
+ automount: true
+ # Annotations to add to the service account
+ annotations: {}
+ # The name of the service account to use.
+ # If not set and create is true, a name is generated using the fullname template
+ name: ""
+
+# This is for setting Kubernetes Annotations to a Pod.
+# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
+podAnnotations: {}
+# This is for setting Kubernetes Labels to a Pod.
+# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
+podLabels: {}
+
+podSecurityContext: {}
+ # fsGroup: 2000
+
+securityContext: {}
+ # capabilities:
+ # drop:
+ # - ALL
+ # readOnlyRootFilesystem: true
+ # runAsNonRoot: true
+ # runAsUser: 1000
+
+# This is for setting up a service more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/
+service:
+ # This sets the service type more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types
+ type: NodePort
+ # This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports
+ port: 8000
+ nodePort: 30080
+
+# This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/
+ingress:
+ enabled: false
+ className: ""
+ annotations: {}
+ # kubernetes.io/ingress.class: nginx
+ # kubernetes.io/tls-acme: "true"
+ hosts:
+ - host: chart-example.local
+ paths:
+ - path: /
+ pathType: ImplementationSpecific
+ tls: []
+ # - secretName: chart-example-tls
+ # hosts:
+ # - chart-example.local
+
+# -- Expose the service via gateway-api HTTPRoute
+# Requires Gateway API resources and suitable controller installed within the cluster
+# (see: https://gateway-api.sigs.k8s.io/guides/)
+httpRoute:
+ # HTTPRoute enabled.
+ enabled: false
+ # HTTPRoute annotations.
+ annotations: {}
+ # Which Gateways this Route is attached to.
+ parentRefs:
+ - name: gateway
+ sectionName: http
+ # namespace: default
+ # Hostnames matching HTTP header.
+ hostnames:
+ - chart-example.local
+ # List of rules and filters applied.
+ rules:
+ - matches:
+ - path:
+ type: PathPrefix
+ value: /headers
+ # filters:
+ # - type: RequestHeaderModifier
+ # requestHeaderModifier:
+ # set:
+ # - name: My-Overwrite-Header
+ # value: this-is-the-only-value
+ # remove:
+ # - User-Agent
+ # - matches:
+ # - path:
+ # type: PathPrefix
+ # value: /echo
+ # headers:
+ # - name: version
+ # value: v2
+
+resources: {}
+ # We usually recommend not to specify default resources and to leave this as a conscious
+ # choice for the user. This also increases chances charts run on environments with little
+ # resources, such as Minikube. If you do want to specify resources, uncomment the following
+ # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
+ # limits:
+ # cpu: 100m
+ # memory: 128Mi
+ # requests:
+ # cpu: 100m
+ # memory: 128Mi
+
+# This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
+livenessProbe:
+ httpGet:
+ path: /
+ port: http
+readinessProbe:
+ httpGet:
+ path: /
+ port: http
+
+# This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/
+autoscaling:
+ enabled: false
+ minReplicas: 1
+ maxReplicas: 100
+ targetCPUUtilizationPercentage: 80
+ # targetMemoryUtilizationPercentage: 80
+
+volumes:
+ - name: config-volume
+ configMap:
+ name: my-app-config
+
+volumeMounts:
+ - name: config-volume
+ mountPath: /config.json
+ subPath: config.json
+
+nodeSelector: {}
+
+tolerations: []
+
+affinity: {}
+
+env:
+ ENV: "prod"
\ No newline at end of file
diff --git a/k8s/my-app/values.yaml b/k8s/my-app/values.yaml
new file mode 100644
index 0000000000..e2799bb0a7
--- /dev/null
+++ b/k8s/my-app/values.yaml
@@ -0,0 +1,189 @@
+# Default values for my-app.
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+
+# This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/
+replicaCount: 3
+
+# This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/
+image:
+ repository: karamkhaddourpro/my-fastapi-app
+ # This sets the pull policy for images.
+ pullPolicy: Always
+ # Overrides the image tag whose default is the chart appVersion.
+ tag: "latest"
+
+# This is for the secrets for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/
+imagePullSecrets: []
+# This is to override the chart name.
+nameOverride: ""
+fullnameOverride: ""
+
+# This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/
+serviceAccount:
+ # Specifies whether a service account should be created
+ create: true
+ # Automatically mount a ServiceAccount's API credentials?
+ automount: true
+ # Annotations to add to the service account
+ annotations: {}
+ # The name of the service account to use.
+ # If not set and create is true, a name is generated using the fullname template
+ name: ""
+
+# This is for setting Kubernetes Annotations to a Pod.
+# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
+podAnnotations: {}
+# This is for setting Kubernetes Labels to a Pod.
+# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
+podLabels: {}
+
+podSecurityContext:
+ fsGroup: 1000
+ runAsUser: 1000
+ runAsGroup: 1000
+
+securityContext: {}
+ # capabilities:
+ # drop:
+ # - ALL
+ # readOnlyRootFilesystem: true
+ # runAsNonRoot: true
+ # runAsUser: 1000
+
+# This is for setting up a service more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/
+service:
+ # This sets the service type more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types
+ type: NodePort
+ # This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports
+ port: 8000
+ nodePort: 30080
+
+# This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/
+ingress:
+ enabled: false
+ className: ""
+ annotations: {}
+ # kubernetes.io/ingress.class: nginx
+ # kubernetes.io/tls-acme: "true"
+ hosts:
+ - host: chart-example.local
+ paths:
+ - path: /
+ pathType: ImplementationSpecific
+ tls: []
+ # - secretName: chart-example-tls
+ # hosts:
+ # - chart-example.local
+
+# -- Expose the service via gateway-api HTTPRoute
+# Requires Gateway API resources and suitable controller installed within the cluster
+# (see: https://gateway-api.sigs.k8s.io/guides/)
+httpRoute:
+ # HTTPRoute enabled.
+ enabled: false
+ # HTTPRoute annotations.
+ annotations: {}
+ # Which Gateways this Route is attached to.
+ parentRefs:
+ - name: gateway
+ sectionName: http
+ # namespace: default
+ # Hostnames matching HTTP header.
+ hostnames:
+ - chart-example.local
+ # List of rules and filters applied.
+ rules:
+ - matches:
+ - path:
+ type: PathPrefix
+ value: /headers
+ # filters:
+ # - type: RequestHeaderModifier
+ # requestHeaderModifier:
+ # set:
+ # - name: My-Overwrite-Header
+ # value: this-is-the-only-value
+ # remove:
+ # - User-Agent
+ # - matches:
+ # - path:
+ # type: PathPrefix
+ # value: /echo
+ # headers:
+ # - name: version
+ # value: v2
+
+resources: {}
+ # We usually recommend not to specify default resources and to leave this as a conscious
+ # choice for the user. This also increases chances charts run on environments with little
+ # resources, such as Minikube. If you do want to specify resources, uncomment the following
+ # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
+ # limits:
+ # cpu: 100m
+ # memory: 128Mi
+ # requests:
+ # cpu: 100m
+ # memory: 128Mi
+
+# This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
+livenessProbe:
+ httpGet:
+ path: /
+ port: http
+ initialDelaySeconds: 90
+readinessProbe:
+ httpGet:
+ path: /
+ port: http
+ initialDelaySeconds: 90
+# This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/
+autoscaling:
+ enabled: false
+ minReplicas: 1
+ maxReplicas: 100
+ targetCPUUtilizationPercentage: 80
+ # targetMemoryUtilizationPercentage: 80
+
+volumeClaimTemplates:
+- metadata:
+ name: data
+ spec:
+ accessModes: [ "ReadWriteOnce" ]
+ resources:
+ requests:
+ storage: 1Gi
+- metadata:
+ name: static-directory
+ spec:
+ accessModes: ["ReadWriteOnce"]
+ resources:
+ requests:
+ storage: 1Gi
+
+volumes:
+ - name: config-volume
+ configMap:
+ name: my-app-config
+
+volumeMounts:
+ - name: config-volume
+ mountPath: /config.json
+ subPath: config.json
+ - name: data
+ mountPath: /app/data
+ - name: static-directory
+ mountPath: "/static-directory"
+nodeSelector: {}
+
+tolerations: []
+
+affinity: {}
+
+env:
+ - name: VISITS_DIR
+ value: /app/data
+
+podManagementPolicy: Parallel
+
+
diff --git a/k8s/screenshots/AccessingService14.png b/k8s/screenshots/AccessingService14.png
new file mode 100644
index 0000000000..caef4316b2
Binary files /dev/null and b/k8s/screenshots/AccessingService14.png differ
diff --git a/k8s/screenshots/AccessingService14UI.png b/k8s/screenshots/AccessingService14UI.png
new file mode 100644
index 0000000000..44a45f7459
Binary files /dev/null and b/k8s/screenshots/AccessingService14UI.png differ
diff --git a/k8s/screenshots/CLILogin.png b/k8s/screenshots/CLILogin.png
new file mode 100644
index 0000000000..76df3fdcec
Binary files /dev/null and b/k8s/screenshots/CLILogin.png differ
diff --git a/k8s/screenshots/HelmSerivceChrome.png b/k8s/screenshots/HelmSerivceChrome.png
new file mode 100644
index 0000000000..0ace380e3a
Binary files /dev/null and b/k8s/screenshots/HelmSerivceChrome.png differ
diff --git a/k8s/screenshots/HelmServiceTerminal.png b/k8s/screenshots/HelmServiceTerminal.png
new file mode 100644
index 0000000000..0f52b42a59
Binary files /dev/null and b/k8s/screenshots/HelmServiceTerminal.png differ
diff --git a/k8s/screenshots/ServiceAllChrome.png b/k8s/screenshots/ServiceAllChrome.png
new file mode 100644
index 0000000000..fdeed4bca3
Binary files /dev/null and b/k8s/screenshots/ServiceAllChrome.png differ
diff --git a/k8s/screenshots/ServiceAllTerminal.png b/k8s/screenshots/ServiceAllTerminal.png
new file mode 100644
index 0000000000..3f3e18442a
Binary files /dev/null and b/k8s/screenshots/ServiceAllTerminal.png differ
diff --git a/k8s/screenshots/ServiceWebApp.png b/k8s/screenshots/ServiceWebApp.png
new file mode 100644
index 0000000000..f6768364ce
Binary files /dev/null and b/k8s/screenshots/ServiceWebApp.png differ
diff --git a/k8s/screenshots/ServiceWebAppChrome.png b/k8s/screenshots/ServiceWebAppChrome.png
new file mode 100644
index 0000000000..b09e9b1c81
Binary files /dev/null and b/k8s/screenshots/ServiceWebAppChrome.png differ
diff --git a/k8s/screenshots/UISyncedDEVProd.png b/k8s/screenshots/UISyncedDEVProd.png
new file mode 100644
index 0000000000..f69406e519
Binary files /dev/null and b/k8s/screenshots/UISyncedDEVProd.png differ
diff --git a/k8s/screenshots/accessingGrafana15.png b/k8s/screenshots/accessingGrafana15.png
new file mode 100644
index 0000000000..bf1492e6c2
Binary files /dev/null and b/k8s/screenshots/accessingGrafana15.png differ
diff --git a/k8s/screenshots/aftersecondtest.png b/k8s/screenshots/aftersecondtest.png
new file mode 100644
index 0000000000..37998ca0f8
Binary files /dev/null and b/k8s/screenshots/aftersecondtest.png differ
diff --git a/k8s/screenshots/alertsingrafana.png b/k8s/screenshots/alertsingrafana.png
new file mode 100644
index 0000000000..d747031a16
Binary files /dev/null and b/k8s/screenshots/alertsingrafana.png differ
diff --git a/k8s/screenshots/applyTheArgoPythonFile.png b/k8s/screenshots/applyTheArgoPythonFile.png
new file mode 100644
index 0000000000..0180e11478
Binary files /dev/null and b/k8s/screenshots/applyTheArgoPythonFile.png differ
diff --git a/k8s/screenshots/applyingdevAndProd.png b/k8s/screenshots/applyingdevAndProd.png
new file mode 100644
index 0000000000..2d49103127
Binary files /dev/null and b/k8s/screenshots/applyingdevAndProd.png differ
diff --git a/k8s/screenshots/argoEmptyDashboard.png b/k8s/screenshots/argoEmptyDashboard.png
new file mode 100644
index 0000000000..8185f5f744
Binary files /dev/null and b/k8s/screenshots/argoEmptyDashboard.png differ
diff --git a/k8s/screenshots/catconfig.png b/k8s/screenshots/catconfig.png
new file mode 100644
index 0000000000..f54bcd8636
Binary files /dev/null and b/k8s/screenshots/catconfig.png differ
diff --git a/k8s/screenshots/checkingArgoCDSynced.png b/k8s/screenshots/checkingArgoCDSynced.png
new file mode 100644
index 0000000000..cf4af920e4
Binary files /dev/null and b/k8s/screenshots/checkingArgoCDSynced.png differ
diff --git a/k8s/screenshots/cleanup.png b/k8s/screenshots/cleanup.png
new file mode 100644
index 0000000000..e43f549571
Binary files /dev/null and b/k8s/screenshots/cleanup.png differ
diff --git a/k8s/screenshots/configmap15.png b/k8s/screenshots/configmap15.png
new file mode 100644
index 0000000000..22f86a4f3e
Binary files /dev/null and b/k8s/screenshots/configmap15.png differ
diff --git a/k8s/screenshots/deletePod14.png b/k8s/screenshots/deletePod14.png
new file mode 100644
index 0000000000..678011cc79
Binary files /dev/null and b/k8s/screenshots/deletePod14.png differ
diff --git a/k8s/screenshots/devNameSpaceReplicas.png b/k8s/screenshots/devNameSpaceReplicas.png
new file mode 100644
index 0000000000..4b81208c99
Binary files /dev/null and b/k8s/screenshots/devNameSpaceReplicas.png differ
diff --git a/k8s/screenshots/diffrencesinPodVisitsCounter.png b/k8s/screenshots/diffrencesinPodVisitsCounter.png
new file mode 100644
index 0000000000..9bf94a0eb8
Binary files /dev/null and b/k8s/screenshots/diffrencesinPodVisitsCounter.png differ
diff --git a/k8s/screenshots/evaluteNetwork.png b/k8s/screenshots/evaluteNetwork.png
new file mode 100644
index 0000000000..8cb11f3eb8
Binary files /dev/null and b/k8s/screenshots/evaluteNetwork.png differ
diff --git a/k8s/screenshots/getPo.png b/k8s/screenshots/getPo.png
new file mode 100644
index 0000000000..d972425142
Binary files /dev/null and b/k8s/screenshots/getPo.png differ
diff --git a/k8s/screenshots/getPo14.png b/k8s/screenshots/getPo14.png
new file mode 100644
index 0000000000..1a16bc1218
Binary files /dev/null and b/k8s/screenshots/getPo14.png differ
diff --git a/k8s/screenshots/getPods14.png b/k8s/screenshots/getPods14.png
new file mode 100644
index 0000000000..2a5c93d00d
Binary files /dev/null and b/k8s/screenshots/getPods14.png differ
diff --git a/k8s/screenshots/getPodsToSeePreANdPostHooks.png b/k8s/screenshots/getPodsToSeePreANdPostHooks.png
new file mode 100644
index 0000000000..c03e934ada
Binary files /dev/null and b/k8s/screenshots/getPodsToSeePreANdPostHooks.png differ
diff --git a/k8s/screenshots/getSts14.png b/k8s/screenshots/getSts14.png
new file mode 100644
index 0000000000..bf505eff77
Binary files /dev/null and b/k8s/screenshots/getSts14.png differ
diff --git a/k8s/screenshots/getSvc14.png b/k8s/screenshots/getSvc14.png
new file mode 100644
index 0000000000..84ee6978ca
Binary files /dev/null and b/k8s/screenshots/getSvc14.png differ
diff --git a/k8s/screenshots/getpvc14.png b/k8s/screenshots/getpvc14.png
new file mode 100644
index 0000000000..3cabc104cc
Binary files /dev/null and b/k8s/screenshots/getpvc14.png differ
diff --git a/k8s/screenshots/image.png b/k8s/screenshots/image.png
new file mode 100644
index 0000000000..08b95d2f4f
Binary files /dev/null and b/k8s/screenshots/image.png differ
diff --git a/k8s/screenshots/installApp14.png b/k8s/screenshots/installApp14.png
new file mode 100644
index 0000000000..81ac91d6d0
Binary files /dev/null and b/k8s/screenshots/installApp14.png differ
diff --git a/k8s/screenshots/memoryUsageAlertManagerStatefulset.png b/k8s/screenshots/memoryUsageAlertManagerStatefulset.png
new file mode 100644
index 0000000000..64d5ebb613
Binary files /dev/null and b/k8s/screenshots/memoryUsageAlertManagerStatefulset.png differ
diff --git a/k8s/screenshots/monitoringPods.png b/k8s/screenshots/monitoringPods.png
new file mode 100644
index 0000000000..25a1b21767
Binary files /dev/null and b/k8s/screenshots/monitoringPods.png differ
diff --git a/k8s/screenshots/namespacesCreation.png b/k8s/screenshots/namespacesCreation.png
new file mode 100644
index 0000000000..cc9791de2d
Binary files /dev/null and b/k8s/screenshots/namespacesCreation.png differ
diff --git a/k8s/screenshots/namespacesReplicas.png b/k8s/screenshots/namespacesReplicas.png
new file mode 100644
index 0000000000..e9b729e48c
Binary files /dev/null and b/k8s/screenshots/namespacesReplicas.png differ
diff --git a/k8s/screenshots/nodeMemoryUsage.png b/k8s/screenshots/nodeMemoryUsage.png
new file mode 100644
index 0000000000..3fa29ec75a
Binary files /dev/null and b/k8s/screenshots/nodeMemoryUsage.png differ
diff --git a/k8s/screenshots/numberOfRunnningPods.png b/k8s/screenshots/numberOfRunnningPods.png
new file mode 100644
index 0000000000..20c4592b3a
Binary files /dev/null and b/k8s/screenshots/numberOfRunnningPods.png differ
diff --git a/k8s/screenshots/numberofAlerts.png b/k8s/screenshots/numberofAlerts.png
new file mode 100644
index 0000000000..a39e00668d
Binary files /dev/null and b/k8s/screenshots/numberofAlerts.png differ
diff --git a/k8s/screenshots/podsCpuUsage.png b/k8s/screenshots/podsCpuUsage.png
new file mode 100644
index 0000000000..68a4649ec1
Binary files /dev/null and b/k8s/screenshots/podsCpuUsage.png differ
diff --git a/k8s/screenshots/podsLab12.png b/k8s/screenshots/podsLab12.png
new file mode 100644
index 0000000000..e63df019ec
Binary files /dev/null and b/k8s/screenshots/podsLab12.png differ
diff --git a/k8s/screenshots/prodNameSpaceReplica.png b/k8s/screenshots/prodNameSpaceReplica.png
new file mode 100644
index 0000000000..7ad912c0ac
Binary files /dev/null and b/k8s/screenshots/prodNameSpaceReplica.png differ
diff --git a/k8s/screenshots/proofInitContainers.png b/k8s/screenshots/proofInitContainers.png
new file mode 100644
index 0000000000..081633a6e6
Binary files /dev/null and b/k8s/screenshots/proofInitContainers.png differ
diff --git a/k8s/screenshots/reinstallHelmImage.png b/k8s/screenshots/reinstallHelmImage.png
new file mode 100644
index 0000000000..5cbc98d9a3
Binary files /dev/null and b/k8s/screenshots/reinstallHelmImage.png differ
diff --git a/k8s/screenshots/secondPicMemoryUSage.png b/k8s/screenshots/secondPicMemoryUSage.png
new file mode 100644
index 0000000000..db8b7b60a4
Binary files /dev/null and b/k8s/screenshots/secondPicMemoryUSage.png differ
diff --git a/k8s/screenshots/staefulAndSerivcesMonitoring15.png b/k8s/screenshots/staefulAndSerivcesMonitoring15.png
new file mode 100644
index 0000000000..0026427b47
Binary files /dev/null and b/k8s/screenshots/staefulAndSerivcesMonitoring15.png differ
diff --git a/k8s/screenshots/statefulSetCpuUsage0.png b/k8s/screenshots/statefulSetCpuUsage0.png
new file mode 100644
index 0000000000..b242a51df1
Binary files /dev/null and b/k8s/screenshots/statefulSetCpuUsage0.png differ
diff --git a/k8s/screenshots/statefulsetCpuUsageAlertmanager.png b/k8s/screenshots/statefulsetCpuUsageAlertmanager.png
new file mode 100644
index 0000000000..f9028473c1
Binary files /dev/null and b/k8s/screenshots/statefulsetCpuUsageAlertmanager.png differ
diff --git a/k8s/screenshots/statefulsetmemoryUsage0.png b/k8s/screenshots/statefulsetmemoryUsage0.png
new file mode 100644
index 0000000000..18b3f833ad
Binary files /dev/null and b/k8s/screenshots/statefulsetmemoryUsage0.png differ
diff --git a/k8s/screenshots/syncedDashboard.png b/k8s/screenshots/syncedDashboard.png
new file mode 100644
index 0000000000..8b1fd66ca2
Binary files /dev/null and b/k8s/screenshots/syncedDashboard.png differ
diff --git a/k8s/screenshots/test1.png b/k8s/screenshots/test1.png
new file mode 100644
index 0000000000..c24494b1ad
Binary files /dev/null and b/k8s/screenshots/test1.png differ
diff --git a/k8s/screenshots/test1Terminal.png b/k8s/screenshots/test1Terminal.png
new file mode 100644
index 0000000000..08b95d2f4f
Binary files /dev/null and b/k8s/screenshots/test1Terminal.png differ
diff --git a/k8s/screenshots/test1UI.png b/k8s/screenshots/test1UI.png
new file mode 100644
index 0000000000..ace0353fe2
Binary files /dev/null and b/k8s/screenshots/test1UI.png differ
diff --git a/k8s/screenshots/test2UI.png b/k8s/screenshots/test2UI.png
new file mode 100644
index 0000000000..dc5275a033
Binary files /dev/null and b/k8s/screenshots/test2UI.png differ
diff --git a/k8s/screenshots/test2terminal.png b/k8s/screenshots/test2terminal.png
new file mode 100644
index 0000000000..a9bc0bcf99
Binary files /dev/null and b/k8s/screenshots/test2terminal.png differ
diff --git a/k8s/screenshots/threeProdReplicas.png b/k8s/screenshots/threeProdReplicas.png
new file mode 100644
index 0000000000..c6e89af84c
Binary files /dev/null and b/k8s/screenshots/threeProdReplicas.png differ
diff --git a/k8s/screenshots/threeProdReplicasUI.png b/k8s/screenshots/threeProdReplicasUI.png
new file mode 100644
index 0000000000..51da1253f1
Binary files /dev/null and b/k8s/screenshots/threeProdReplicasUI.png differ
diff --git a/k8s/screenshots/threeReplicas.png b/k8s/screenshots/threeReplicas.png
new file mode 100644
index 0000000000..3d7d44ee38
Binary files /dev/null and b/k8s/screenshots/threeReplicas.png differ
diff --git a/k8s/screenshots/threeSyncedReplicas.png b/k8s/screenshots/threeSyncedReplicas.png
new file mode 100644
index 0000000000..5b201446d5
Binary files /dev/null and b/k8s/screenshots/threeSyncedReplicas.png differ
diff --git a/k8s/screenshots/upgradHelmFor12.png b/k8s/screenshots/upgradHelmFor12.png
new file mode 100644
index 0000000000..3fcc5e7a45
Binary files /dev/null and b/k8s/screenshots/upgradHelmFor12.png differ
diff --git a/k8s/screenshots/verifyArgoCDDeployment.png b/k8s/screenshots/verifyArgoCDDeployment.png
new file mode 100644
index 0000000000..298089fef4
Binary files /dev/null and b/k8s/screenshots/verifyArgoCDDeployment.png differ
diff --git a/k8s/screenshots/verifyNamespaces.png b/k8s/screenshots/verifyNamespaces.png
new file mode 100644
index 0000000000..1744812dcb
Binary files /dev/null and b/k8s/screenshots/verifyNamespaces.png differ
diff --git a/k8s/screenshots/verifyPvcAndDataPersistence.png b/k8s/screenshots/verifyPvcAndDataPersistence.png
new file mode 100644
index 0000000000..033aaac8da
Binary files /dev/null and b/k8s/screenshots/verifyPvcAndDataPersistence.png differ
diff --git a/k8s/service.yml b/k8s/service.yml
new file mode 100644
index 0000000000..e92d210d02
--- /dev/null
+++ b/k8s/service.yml
@@ -0,0 +1,12 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: web-app
+spec:
+ selector:
+ app: web-app
+ ports:
+ - protocol: 'TCP'
+ port: 8000
+ targetPort: 8000
+ type: LoadBalancer
\ No newline at end of file
diff --git a/lab1.md b/lab1.md
index 30b74c95f5..beb3fc43d1 100644
--- a/lab1.md
+++ b/lab1.md
@@ -31,7 +31,7 @@ In this lab assignment, you will develop a simple web application using Python a
3. Ensure:
- Maintain a clean `.gitignore` file.
- - Use a concise `requirements.txt` file for required dependencies.
+ - Use a con_cise `requirements.txt` file for required dependencies.
### List of Requirements
diff --git a/monitoring/LOGGING.md b/monitoring/LOGGING.md
new file mode 100644
index 0000000000..e396154324
--- /dev/null
+++ b/monitoring/LOGGING.md
@@ -0,0 +1,43 @@
+# Overview
+
+The logging stack contains Loki, Promtail, and Grafana. which enables efficient log collection and monitoring for debugging.
+
+## Components
+
+In this system, we have the following components (according to the docker-compose file):
+
+- Grafana: This provides the dashboard for the monitoring system
+
+- Loki: This is the log aggregation system that stores logs from the applications and let us query them
+
+- Promtail: This is the agent that collects logs from the applications and sends them to Loki
+
+- app_python: This is the python web app that returns the current Moscow Time
+
+## Screenshots
+
+### Running docker compose comand
+
+
+
+### Active containers
+
+
+
+
+### App Logs
+
+
+
+### Grafana logs
+
+
+
+### Loki logs
+
+
+
+### Promtail logs
+
+
+
diff --git a/monitoring/METRICS.md b/monitoring/METRICS.md
new file mode 100644
index 0000000000..c2249814ec
--- /dev/null
+++ b/monitoring/METRICS.md
@@ -0,0 +1,64 @@
+# Metrics lab
+
+## Screenshots
+
+- screenshot that confirm the successful setup
+
+
+
+- Dashboards for Loki
+
+
+
+
+
+
+
+- Dashboards for Prometheus
+
+
+
+
+
+
+
+- Metrics for python web app
+
+
+
+## Service configuration updates
+
+I added the following mechanism to docker-compose.yml
+
+```yml
+x-logging:
+ &default-logging
+ driver: "json-file"
+ options:
+ tag: "{{.ImageName}}|{{.Name}}"
+ max-size: '50m'
+ max-file: '5'
+```
+
+For memory added
+
+```yml
+x-deploy:
+ &default-deploy
+ resources:
+ limits:
+ memory: 200M
+```
+
+## Healthceck
+
+For health check I added the following script
+
+```yml
+healthcheck:
+ test: [ "CMD-SHELL", "curl --fail http://localhost:{port}/ || exit 1" ]
+ interval: 1m
+ timeout: 15s
+ retries: 3
+ start_period: 15s
+```
\ No newline at end of file
diff --git a/monitoring/docker-compose.yml b/monitoring/docker-compose.yml
new file mode 100644
index 0000000000..14ff9a6eec
--- /dev/null
+++ b/monitoring/docker-compose.yml
@@ -0,0 +1,104 @@
+version: "3.9"
+
+networks:
+ default:
+ name: monitoring
+
+x-logging:
+ &default-logging
+ driver: "json-file"
+ options:
+ tag: "{{.ImageName}}|{{.Name}}"
+ max-size: '50m'
+ max-file: '5'
+
+x-deploy:
+ &default-deploy
+ resources:
+ limits:
+ memory: 200M
+
+services:
+ loki:
+ image: grafana/loki:2.9.2
+ mem_limit: 200m
+ ports:
+ - "3100:3100"
+ command: -config.file=/etc/loki/local-config.yaml
+ logging: *default-logging
+ deploy: *default-deploy
+
+ promtail:
+ image: grafana/promtail:2.9.2
+ volumes:
+ - /var/log:/var/log
+ - ./promtail.yml:/etc/promtail/config.yml
+ - /var/lib/docker/containers:/var/lib/docker/containers:ro
+ command: -config.file=/etc/promtail/config.yml
+ logging: *default-logging
+ deploy: *default-deploy
+
+ grafana:
+ environment:
+ - GF_PATHS_PROVISIONING=/etc/grafana/provisioning
+ - GF_AUTH_ANONYMOUS_ENABLED=true
+ - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
+ entrypoint:
+ - sh
+ - -euc
+ - |
+ mkdir -p /etc/grafana/provisioning/datasources
+ cat < /etc/grafana/provisioning/datasources/ds.yaml
+ apiVersion: 1
+ datasources:
+ - name: Loki
+ type: loki
+ access: proxy
+ orgId: 1
+ url: http://loki:3100
+ basicAuth: false
+ isDefault: true
+ version: 1
+ editable: false
+ - name: Prometheus
+ type: prometheus
+ access: proxy
+ orgId: 1
+ url: http://prometheus:9090
+ basicAuth: false
+ isDefault: false
+ version: 1
+ editable: true
+ EOF
+ /run.sh
+ image: grafana/grafana:latest
+ ports:
+ - "3000:3000"
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
+ interval: 30s
+ timeout: 15s
+ retries: 3
+ logging: *default-logging
+ deploy: *default-deploy
+
+ prometheus:
+ image: prom/prometheus:v2.51.0
+ volumes:
+ - ./prometheus.yml:/etc/prometheus/prometheus.yml
+ ports:
+ - "9095:9090"
+ logging: *default-logging
+ deploy: *default-deploy
+
+ app_python:
+ image: karamkhaddourpro/my-fastapi-app:latest
+ ports:
+ - "8878:8000"
+ healthcheck:
+ test: ["CMD", "curl", "-f", "app_python:8878"]
+ interval: 30s
+ timeout: 15s
+ retries: 3
+ logging: *default-logging
+ deploy: *default-deploy
\ No newline at end of file
diff --git a/monitoring/prometheus.yml b/monitoring/prometheus.yml
new file mode 100644
index 0000000000..8da2bf0638
--- /dev/null
+++ b/monitoring/prometheus.yml
@@ -0,0 +1,24 @@
+global:
+ scrape_interval: 5s
+
+
+scrape_configs:
+- job_name: "app_python"
+ static_configs:
+ - targets: ["app_python:8000"]
+
+- job_name: "prometheus"
+ static_configs:
+ - targets: ["prometheus:9090"]
+
+- job_name: "loki"
+ static_configs:
+ - targets: ["loki:3100"]
+
+- job_name: "grafana"
+ static_configs:
+ - targets: ["grafana:3000"]
+
+- job_name: "promtail"
+ static_configs:
+ - targets: ["promtail:9080"]
\ No newline at end of file
diff --git a/monitoring/promtail.yml b/monitoring/promtail.yml
new file mode 100644
index 0000000000..c556675e56
--- /dev/null
+++ b/monitoring/promtail.yml
@@ -0,0 +1,15 @@
+server:
+ http_listen_port: 9080
+ grpc_listen_port: 0
+positions:
+ filename: /tmp/positions.yaml
+client:
+ url: http://loki:3100/api/prom/push
+scrape_configs:
+ - job_name: docker_logs
+ static_configs:
+ - targets:
+ - localhost
+ labels:
+ job: docker
+ __path__: /var/lib/docker/containers/*/*-json.log
\ No newline at end of file
diff --git a/monitoring/screenshots/activeDockerContainers.png b/monitoring/screenshots/activeDockerContainers.png
new file mode 100644
index 0000000000..7cbeb98a37
Binary files /dev/null and b/monitoring/screenshots/activeDockerContainers.png differ
diff --git a/monitoring/screenshots/applicationQuery.png b/monitoring/screenshots/applicationQuery.png
new file mode 100644
index 0000000000..e6f5e18fa1
Binary files /dev/null and b/monitoring/screenshots/applicationQuery.png differ
diff --git a/monitoring/screenshots/creatingDockerContainers.png b/monitoring/screenshots/creatingDockerContainers.png
new file mode 100644
index 0000000000..a29b7db9eb
Binary files /dev/null and b/monitoring/screenshots/creatingDockerContainers.png differ
diff --git a/monitoring/screenshots/grafanaQuery.png b/monitoring/screenshots/grafanaQuery.png
new file mode 100644
index 0000000000..5d7b2a3436
Binary files /dev/null and b/monitoring/screenshots/grafanaQuery.png differ
diff --git a/monitoring/screenshots/image.png b/monitoring/screenshots/image.png
new file mode 100644
index 0000000000..8bec867a52
Binary files /dev/null and b/monitoring/screenshots/image.png differ
diff --git a/monitoring/screenshots/lokiDashboard.png b/monitoring/screenshots/lokiDashboard.png
new file mode 100644
index 0000000000..f493fea5c5
Binary files /dev/null and b/monitoring/screenshots/lokiDashboard.png differ
diff --git a/monitoring/screenshots/lokiQuery.png b/monitoring/screenshots/lokiQuery.png
new file mode 100644
index 0000000000..fc0a2c5f53
Binary files /dev/null and b/monitoring/screenshots/lokiQuery.png differ
diff --git a/monitoring/screenshots/lokidashboard2.png b/monitoring/screenshots/lokidashboard2.png
new file mode 100644
index 0000000000..10304e9368
Binary files /dev/null and b/monitoring/screenshots/lokidashboard2.png differ
diff --git a/monitoring/screenshots/prmetheusTargets.png b/monitoring/screenshots/prmetheusTargets.png
new file mode 100644
index 0000000000..c26c03605b
Binary files /dev/null and b/monitoring/screenshots/prmetheusTargets.png differ
diff --git a/monitoring/screenshots/prometheusDashBoard2.png b/monitoring/screenshots/prometheusDashBoard2.png
new file mode 100644
index 0000000000..ecef0eb181
Binary files /dev/null and b/monitoring/screenshots/prometheusDashBoard2.png differ
diff --git a/monitoring/screenshots/prometheusDashBoard3.png b/monitoring/screenshots/prometheusDashBoard3.png
new file mode 100644
index 0000000000..3053ecb637
Binary files /dev/null and b/monitoring/screenshots/prometheusDashBoard3.png differ
diff --git a/monitoring/screenshots/prometheusDashboard.png b/monitoring/screenshots/prometheusDashboard.png
new file mode 100644
index 0000000000..df393d2c91
Binary files /dev/null and b/monitoring/screenshots/prometheusDashboard.png differ
diff --git a/monitoring/screenshots/pythonAppMetrics.png b/monitoring/screenshots/pythonAppMetrics.png
new file mode 100644
index 0000000000..7b94b63cbd
Binary files /dev/null and b/monitoring/screenshots/pythonAppMetrics.png differ
diff --git a/terraform/.gitignore b/terraform/.gitignore
new file mode 100644
index 0000000000..f0cba8faea
--- /dev/null
+++ b/terraform/.gitignore
@@ -0,0 +1,7 @@
+*.tfstate
+*.tfstate.backup
+.terraform/
+*.tfvars
+*.lock.hcl
+.env
+key.json
\ No newline at end of file
diff --git a/terraform/TF.md b/terraform/TF.md
new file mode 100644
index 0000000000..9d14434fbf
--- /dev/null
+++ b/terraform/TF.md
@@ -0,0 +1,783 @@
+# Documentation for Terraform
+
+This document contains the outputs, state information, and key configurations of the Terraform infrastructure that provisions a Docker container running a FastAPI app to display Moscow time.
+
+---
+
+## File Structure
+
+```
+
+terraform/
+├── TF.md
+├── .gitignore
+└── docker/
+ ├── main.tf
+ ├── variables.tf
+ └── outputs.tf
+└── yandex/
+ ├── main.tf
+ ├── variables.tf
+ └── outputs.tf
+└── github/
+ ├── main.tf
+ ├── variables.tf
+ ├── outputs.tf
+
+```
+
+---
+
+## Docker
+
+### Terraform Commands & Outputs
+
+#### `terraform init`
+
+
+command-output
+
+```
+kokai@kokai:~/Desktop/S25-core-course-labs/terraform/docker$ terraform init
+Initializing the backend...
+Initializing provider plugins...
+- Finding kreuzwerker/docker versions matching "~> 3.0.2"...
+- Installing kreuzwerker/docker v3.0.2...
+- Installed kreuzwerker/docker v3.0.2 (self-signed, key ID BD080C4571C6104C)
+Partner and community providers are signed by their developers.
+If you'd like to know more about provider signing, you can read about it here:
+https://www.terraform.io/docs/cli/plugins/signing.html
+Terraform has created a lock file .terraform.lock.hcl to record the provider
+selections it made above. Include this file in your version control repository
+so that Terraform can guarantee to make the same selections by default when
+you run "terraform init" in the future.
+
+Terraform has been successfully initialized!
+
+You may now begin working with Terraform. Try running "terraform plan" to see
+any changes that are required for your infrastructure. All Terraform commands
+should now work.
+
+If you ever set or change modules or backend configuration for Terraform,
+rerun this command to reinitialize your working directory. If you forget, other
+commands will detect it and remind you to do so if necessary.
+```
+
+
+---
+
+#### `terraform fmt`
+
+
+command-output
+
+```
+No output
+```
+
+
+---
+
+
+#### `terraform validate`
+
+
+
+command-output
+
+```
+kokai@kokai:~/Desktop/S25-core-course-labs/terraform/docker$ terraform validate
+Success! The configuration is valid.
+```
+
+
+
+---
+
+#### `terraform apply`
+
+
+command-output
+
+```
+kokai@kokai:~/Desktop/S25-core-course-labs/terraform/docker$ terraform apply
+docker_image.moscow_time_app: Refreshing state... [id=sha256:dba8a2f516085b7b99baeca76fb1a48ee4294900dbef82f8dba5fd7f15c9a80dkaramkhaddourpro/my-fastapi-app:latest]
+docker_container.moscow_time_app: Refreshing state... [id=ebf3a244147f1479aa19af57483ab561063ace082587852d2211eefd1b61f28d]
+
+Note: Objects have changed outside of Terraform
+
+Terraform detected the following changes made outside of Terraform since the last "terraform apply" which may have affected this plan:
+
+ # docker_container.moscow_time_app has been deleted
+ - resource "docker_container" "moscow_time_app" {
+ - id = "ebf3a244147f1479aa19af57483ab561063ace082587852d2211eefd1b61f28d" -> null
+ name = "moscow-time-app"
+ # (16 unchanged attributes hidden)
+
+ # (1 unchanged block hidden)
+ }
+
+
+Unless you have made equivalent changes to your configuration, or ignored the relevant attributes using ignore_changes, the following plan may include
+actions to undo or respond to these changes.
+
+───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
+
+Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ + create
+
+Terraform will perform the following actions:
+
+ # docker_container.moscow_time_app will be created
+ + resource "docker_container" "moscow_time_app" {
+ + attach = false
+ + bridge = (known after apply)
+ + command = (known after apply)
+ + container_logs = (known after apply)
+ + container_read_refresh_timeout_milliseconds = 15000
+ + entrypoint = (known after apply)
+ + env = (known after apply)
+ + exit_code = (known after apply)
+ + hostname = (known after apply)
+ + id = (known after apply)
+ + image = "sha256:dba8a2f516085b7b99baeca76fb1a48ee4294900dbef82f8dba5fd7f15c9a80d"
+ + init = (known after apply)
+ + ipc_mode = (known after apply)
+ + log_driver = (known after apply)
+ + logs = false
+ + must_run = true
+ + name = "moscow-time-app"
+ + network_data = (known after apply)
+ + read_only = false
+ + remove_volumes = true
+ + restart = "no"
+ + rm = false
+ + runtime = (known after apply)
+ + security_opts = (known after apply)
+ + shm_size = (known after apply)
+ + start = true
+ + stdin_open = false
+ + stop_signal = (known after apply)
+ + stop_timeout = (known after apply)
+ + tty = false
+ + wait = false
+ + wait_timeout = 60
+
+ + healthcheck (known after apply)
+
+ + labels (known after apply)
+
+ + ports {
+ + external = 8080
+ + internal = 8000
+ + ip = "0.0.0.0"
+ + protocol = "tcp"
+ }
+ }
+
+Plan: 1 to add, 0 to change, 0 to destroy.
+
+Changes to Outputs:
+ + container_id = (known after apply)
+
+Do you want to perform these actions?
+ Terraform will perform the actions described above.
+ Only 'yes' will be accepted to approve.
+
+ Enter a value: yes
+
+docker_container.moscow_time_app: Creating...
+docker_container.moscow_time_app: Creation complete after 0s [id=62a249ea6101f26fac7950c54502b8ddd52ad1cb1a31ebaadbd8bc0f87caf674]
+
+Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
+
+Outputs:
+
+container_id = "62a249ea6101f26fac7950c54502b8ddd52ad1cb1a31ebaadbd8bc0f87caf674"
+container_ports = tolist([
+ {
+ "external" = 8080
+ "internal" = 8000
+ "ip" = "0.0.0.0"
+ "protocol" = "tcp"
+ },
+])
+```
+
+
+
+
+---
+#### `terraform state list`
+
+
+command-output
+
+```
+kokai@kokai:~/Desktop/S25-core-course-labs/terraform/docker$ terraform state list
+docker_container.moscow_time_app
+docker_image.moscow_time_app
+```
+
+
+
+---
+
+#### `terraform state show docker_container.moscow_time_app`
+
+
+
+command-output
+
+```
+kokai@kokai:~/Desktop/S25-core-course-labs/terraform/docker$ terraform state show docker_container.moscow_time_app
+# docker_container.moscow_time_app:
+resource "docker_container" "moscow_time_app" {
+ attach = false
+ bridge = null
+ command = [
+ "uvicorn",
+ "main:app",
+ "--host",
+ "0.0.0.0",
+ "--port",
+ "8000",
+ ]
+ container_read_refresh_timeout_milliseconds = 15000
+ cpu_set = null
+ cpu_shares = 0
+ domainname = null
+ entrypoint = []
+ env = []
+ hostname = "62a249ea6101"
+ id = "62a249ea6101f26fac7950c54502b8ddd52ad1cb1a31ebaadbd8bc0f87caf674"
+ image = "sha256:dba8a2f516085b7b99baeca76fb1a48ee4294900dbef82f8dba5fd7f15c9a80d"
+ init = false
+ ipc_mode = "private"
+ log_driver = "json-file"
+ logs = false
+ max_retry_count = 0
+ memory = 0
+ memory_swap = 0
+ must_run = true
+ name = "moscow-time-app"
+ network_data = [
+ {
+ gateway = "172.17.0.1"
+ global_ipv6_address = null
+ global_ipv6_prefix_length = 0
+ ip_address = "172.17.0.3"
+ ip_prefix_length = 16
+ ipv6_gateway = null
+ mac_address = "62:06:d4:28:e6:13"
+ network_name = "bridge"
+ },
+ ]
+ network_mode = "bridge"
+ pid_mode = null
+ privileged = false
+ publish_all_ports = false
+ read_only = false
+ remove_volumes = true
+ restart = "no"
+ rm = false
+ runtime = "runc"
+ security_opts = []
+ shm_size = 64
+ start = true
+ stdin_open = false
+ stop_signal = null
+ stop_timeout = 0
+ tty = false
+ user = "appuser"
+ userns_mode = null
+ wait = false
+ wait_timeout = 60
+ working_dir = "/app"
+
+ ports {
+ external = 8080
+ internal = 8000
+ ip = "0.0.0.0"
+ protocol = "tcp"
+ }
+}
+```
+
+
+
+---
+
+#### `terraform output`
+
+command-output
+
+```
+kokai@kokai:~/Desktop/S25-core-course-labs/terraform/docker$ terraform output
+container_id = "62a249ea6101f26fac7950c54502b8ddd52ad1cb1a31ebaadbd8bc0f87caf674"
+container_ports = tolist([
+ {
+ "external" = 8080
+ "internal" = 8000
+ "ip" = "0.0.0.0"
+ "protocol" = "tcp"
+ },
+])
+```
+
+
+---
+
+### Input Variables
+
+| Variable Name | Description | Default Value |
+|-------------------------|-----------------------------------------|----------------------------------------------------|
+| `python_container_name` | Name of the Docker container | `moscow-time-app` |
+| `app_image_name` | Docker image name | `karamkhaddourpro/my-fastapi-app:latest` |
+| `internal_port` | Internal port exposed by the app | `8000` |
+| `external_port` | Host port mapped to the app container | `8080` |
+
+---
+
+---
+
+
+## Yandex Cloud
+I created a service account in Yandex Cloud with admin permissions.
+Generated a secure key (key.json) for authentication, avoiding hardcoding credentials.
+Used this key in Terraform and CLI to manage resources programmatically and securely. The hardest part for me was the UI—it wasn’t very intuitive, and I struggled to assign myself an admin role.
+
+
+
+#### `terraform init`
+
+
+command-output
+
+
+```
+Initializing the backend...
+Initializing provider plugins...
+- Finding yandex-cloud/yandex versions matching "~> 0.75"...
+- Installing yandex-cloud/yandex v0.142.0...
+- Installed yandex-cloud/yandex v0.142.0 (self-signed, key ID E40F590B50BB8E40)
+Partner and community providers are signed by their developers.
+If you'd like to know more about provider signing, you can read about it here:
+https://www.terraform.io/docs/cli/plugins/signing.html
+Terraform has created a lock file .terraform.lock.hcl to record the provider
+selections it made above. Include this file in your version control repository
+so that Terraform can guarantee to make the same selections by default when
+you run "terraform init" in the future.
+
+Terraform has been successfully initialized!
+
+You may now begin working with Terraform. Try running "terraform plan" to see
+any changes that are required for your infrastructure. All Terraform commands
+should now work.
+
+If you ever set or change modules or backend configuration for Terraform,
+rerun this command to reinitialize your working directory. If you forget, other
+commands will detect it and remind you to do so if necessary.
+```
+
+
+#### `terraform fmt`
+
+
+command-output
+
+```
+
+kokai@kokai:~/Desktop/S25-core-course-labs/terraform/yandex$ terraform fmt
+variables.tf
+
+```
+
+
+
+
+#### `terraform validate`
+
+
+
+command-output
+
+```
+kokai@kokai:~/Desktop/S25-core-course-labs/terraform/yandex$ terraform validate
+Success! The configuration is valid.
+```
+
+
+#### `terraform apply`
+
+
+command-output
+
+```
+kokai@kokai:~/Desktop/S25-core-course-labs/terraform/yandex$ terraform apply
+
+Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ + create
+
+Terraform will perform the following actions:
+
+ # yandex_compute_disk.boot_disk will be created
+ + resource "yandex_compute_disk" "boot_disk" {
+ + block_size = 4096
+ + created_at = (known after apply)
+ + folder_id = (known after apply)
+ + id = (known after apply)
+ + image_id = "fd865v46cboopthn7u0k"
+ + name = "terraform-boot-disk"
+ + product_ids = (known after apply)
+ + size = 20
+ + status = (known after apply)
+ + type = "network-hdd"
+ + zone = "ru-central1-a"
+
+ + disk_placement_policy (known after apply)
+
+ + hardware_generation (known after apply)
+ }
+
+ # yandex_compute_instance.vm will be created
+ + resource "yandex_compute_instance" "vm" {
+ + created_at = (known after apply)
+ + folder_id = (known after apply)
+ + fqdn = (known after apply)
+ + gpu_cluster_id = (known after apply)
+ + hardware_generation = (known after apply)
+ + hostname = (known after apply)
+ + id = (known after apply)
+ + maintenance_grace_period = (known after apply)
+ + maintenance_policy = (known after apply)
+ + metadata = {
+ + "ssh-keys" = <<-EOT
+ ubuntu:ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCkyMWPCG2OjpoLRryBZph+Ruv67V+dZsBZaZi2tt1+/yP1VVYJoZQ0z6GezI2jJuca6vqY2q9aLFzC0zyy2OjdAowX8DuB+Qdy+pdMW9Uep9r8cG9gSKQ+3yycZZney4cpH7hZPh2lr5BnFYm7QSIH0jLxDh6mSZ1rr/Qy1pEnvEPLVsK7UkV+0oIIFaogt226WkFePk0WQ/AvVTMksNbNKNEC4XAs/V2y9g9fDUYC7mo1CJHc2bSOIL/yMif2gfWYbj+3hZhVKzsqJ5lEYYd8KyUabsjClqNdcs94af0GbhaK7RCAdRkpPZD9eAYSfUgWvLyR3eFb/NbS2iFONDPP24qb0PxPdIQHUPKz8O/NhNVBDVR9Rtt4oLFTmTsN1Ttr2PNDxCPr2JWrtWI/ALo8Qw4iS+MAYQAkl2aeewCFDcW6ypkByR2A6moOvf0I7nAU5VyMcH70PlDr/KPPmQLqSs/xy/HciqkhZ1Va0e/QtbQ772l2RHHH/Yd1PJzrDnPIHsZwR7cP6k9jyIdfZCiVJqwGEifpHugSvGRCg+yOG9kpUFNaVoUcpEqRoZ17x7O8vdBPpGvbOpLo8T/nkJA3tvqP/4bnH8POWu8adlMvLDg41MFld40S0dwYUYNK48+4wIC6+oe1w475rNc69r29jjI96EnJCv+3qE0S0WzjKw== karam13549@gmail.com
+ EOT
+ }
+ + name = "terraform-vm"
+ + network_acceleration_type = "standard"
+ + platform_id = "standard-v1"
+ + service_account_id = (known after apply)
+ + status = (known after apply)
+ + zone = "ru-central1-a"
+
+ + boot_disk {
+ + auto_delete = true
+ + device_name = (known after apply)
+ + disk_id = (known after apply)
+ + mode = (known after apply)
+
+ + initialize_params (known after apply)
+ }
+
+ + metadata_options (known after apply)
+
+ + network_interface {
+ + index = (known after apply)
+ + ip_address = (known after apply)
+ + ipv4 = true
+ + ipv6 = (known after apply)
+ + ipv6_address = (known after apply)
+ + mac_address = (known after apply)
+ + nat = true
+ + nat_ip_address = (known after apply)
+ + nat_ip_version = (known after apply)
+ + security_group_ids = (known after apply)
+ + subnet_id = (known after apply)
+ }
+
+ + placement_policy (known after apply)
+
+ + resources {
+ + core_fraction = 20
+ + cores = 2
+ + memory = 2
+ }
+
+ + scheduling_policy {
+ + preemptible = true
+ }
+ }
+
+ # yandex_vpc_network.network will be created
+ + resource "yandex_vpc_network" "network" {
+ + created_at = (known after apply)
+ + default_security_group_id = (known after apply)
+ + folder_id = (known after apply)
+ + id = (known after apply)
+ + labels = (known after apply)
+ + name = "terraform-network"
+ + subnet_ids = (known after apply)
+ }
+
+ # yandex_vpc_subnet.subnet will be created
+ + resource "yandex_vpc_subnet" "subnet" {
+ + created_at = (known after apply)
+ + folder_id = (known after apply)
+ + id = (known after apply)
+ + labels = (known after apply)
+ + name = "terraform-subnet"
+ + network_id = (known after apply)
+ + v4_cidr_blocks = [
+ + "192.168.10.0/24",
+ ]
+ + v6_cidr_blocks = (known after apply)
+ + zone = "ru-central1-a"
+ }
+
+Plan: 4 to add, 0 to change, 0 to destroy.
+
+Changes to Outputs:
+ + external_ip = (known after apply)
+ + internal_ip = (known after apply)
+
+Do you want to perform these actions?
+ Terraform will perform the actions described above.
+ Only 'yes' will be accepted to approve.
+
+ Enter a value: yes
+
+yandex_vpc_network.network: Creating...
+yandex_compute_disk.boot_disk: Creating...
+yandex_vpc_network.network: Creation complete after 4s [id=enpgn1a5erb0jnb6j70i]
+yandex_vpc_subnet.subnet: Creating...
+yandex_vpc_subnet.subnet: Creation complete after 1s [id=e9bdtu2o252hoj8vrpjr]
+yandex_compute_disk.boot_disk: Still creating... [10s elapsed]
+yandex_compute_disk.boot_disk: Creation complete after 15s [id=fhmn87klqhf9ifcumb5o]
+yandex_compute_instance.vm: Creating...
+yandex_compute_instance.vm: Still creating... [10s elapsed]
+yandex_compute_instance.vm: Still creating... [20s elapsed]
+yandex_compute_instance.vm: Still creating... [30s elapsed]
+yandex_compute_instance.vm: Creation complete after 38s [id=fhmp9oduglg02moefva7]
+
+Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
+
+Outputs:
+
+external_ip = "84.201.132.123"
+internal_ip = "192.168.10.34"
+```
+
+
+
+#### `terraform output`
+
+command-output
+
+```
+kokai@kokai:~/Desktop/S25-core-course-labs/terraform/yandex$ terraform output
+external_ip = "84.201.132.123"
+internal_ip = "192.168.10.34"
+```
+
+
+
+## GitHub
+
+### Terraform Commands & Outputs
+
+#### `terraform init`
+
+
+command-output
+
+
+```
+kokai@kokai:~/Desktop/S25-core-course-labs/terraform/github$ terraform init
+Initializing the backend...
+Initializing provider plugins...
+- Reusing previous version of integrations/github from the dependency lock file
+- Using previously-installed integrations/github v4.31.0
+
+Terraform has been successfully initialized!
+
+You may now begin working with Terraform. Try running "terraform plan" to see
+any changes that are required for your infrastructure. All Terraform commands
+should now work.
+
+If you ever set or change modules or backend configuration for Terraform,
+rerun this command to reinitialize your working directory. If you forget, other
+commands will detect it and remind you to do so if necessary.
+
+```
+
+
+
+
+#### `terraform fmt`
+
+
+command-output
+
+```
+
+
+main.tf
+
+```
+
+
+
+
+
+#### `terraform validate`
+
+
+
+command-output
+
+```
+
+kokai@kokai:~/Desktop/S25-core-course-labs/terraform/github$ terraform validate
+Success! The configuration is valid.
+
+```
+
+
+
+#### `terraform import`
+
+
+
+command-output
+
+```
+kokai@kokai:~/Desktop/S25-core-course-labs/terraform/github$ terraform import github_repository.repo S25-core-course-labs
+github_repository.repo: Importing from ID "S25-core-course-labs"...
+github_repository.repo: Import prepared!
+ Prepared github_repository for import
+github_repository.repo: Refreshing state... [id=S25-core-course-labs]
+
+Import successful!
+
+The resources that were imported are shown above. These resources are now in
+your Terraform state and will henceforth be managed by Terraform.
+
+```
+
+
+
+
+
+#### `terraform apply`
+
+
+command-output
+
+```
+kokai@kokai:~/Desktop/S25-core-course-labs/terraform/github$ terraform apply
+github_repository.repo: Refreshing state... [id=S25-core-course-labs]
+
+Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ + create
+ ~ update in-place
+
+Terraform will perform the following actions:
+
+ # github_branch_default.master will be created
+ + resource "github_branch_default" "master" {
+ + branch = "master"
+ + id = (known after apply)
+ + repository = "S25-core-course-labs"
+ }
+
+ # github_branch_protection.default will be created
+ + resource "github_branch_protection" "default" {
+ + allows_deletions = false
+ + allows_force_pushes = false
+ + blocks_creations = false
+ + enforce_admins = true
+ + id = (known after apply)
+ + pattern = "master"
+ + repository_id = "S25-core-course-labs"
+ + require_conversation_resolution = true
+ + require_signed_commits = false
+ + required_linear_history = false
+
+ + required_pull_request_reviews {
+ + required_approving_review_count = 1
+ }
+ }
+
+ # github_repository.repo will be updated in-place
+ ~ resource "github_repository" "repo" {
+ ~ auto_init = false -> true
+ + description = "Devops course repo"
+ + gitignore_template = "VisualStudio"
+ - has_downloads = true -> null
+ ~ has_issues = false -> true
+ - has_projects = true -> null
+ id = "S25-core-course-labs"
+ + license_template = "mit"
+ name = "S25-core-course-labs"
+ # (28 unchanged attributes hidden)
+ }
+
+Plan: 2 to add, 1 to change, 0 to destroy.
+
+Do you want to perform these actions?
+ Terraform will perform the actions described above.
+ Only 'yes' will be accepted to approve.
+
+ Enter a value: yes
+
+github_repository.repo: Modifying... [id=S25-core-course-labs]
+github_repository.repo: Modifications complete after 2s [id=S25-core-course-labs]
+github_branch_default.master: Creating...
+github_branch_default.master: Creation complete after 1s [id=S25-core-course-labs]
+github_branch_protection.default: Creating...
+github_branch_protection.default: Creation complete after 5s [id=BPR_kwDONxiVLM4DxL_c]
+
+Apply complete! Resources: 2 added, 1 changed, 0 destroyed.
+
+```
+
+
+
+#### `terraform output`
+
+command-output
+
+```
+repo_url = "https://github.com/KaramKhaddour/S25-core-course-labs"
+```
+
+
+
+
+
+
+## Best Practices
+
+### Secrets Management
+- **Sensitive data** such as API tokens and private environment variables were **never hardcoded**.
+- Used **environment variables** and `.env` files to reference secrets.
+- These files were **excluded from version control** via a properly configured `.gitignore`.
+
+### Code Formatting and Validation
+- `terraform fmt` was consistently used to **enforce standard code formatting**.
+- `terraform validate` was run before every execution to **verify syntax** and **catch configuration errors early**.
+
+### Planning and State Management
+- `terraform plan` was used before each `terraform apply` to **preview infrastructure changes** and prevent unintended modifications.
+- `terraform state list` and `terraform state show` were used prior to destroying any resource to **inspect and confirm** state details.
+
+### Resource Importing
+- Existing infrastructure components were **imported** using `terraform import` to avoid resource recreation and to bring them under Terraform management seamlessly.
+
+### Version Locking
+- **Terraform CLI version** and **provider versions** were explicitly defined using constraints to **avoid unexpected updates or breaking changes**.
+
+### Naming and Structure
+- The configuration files followed **Terraform best practices**:
+ - `main.tf` for providers and core logic
+ - `variables.tf` for input variables
+ - `outputs.tf` for declared outputs
+
+### Version Control Hygiene
+- `.gitignore` was configured to **exclude**:
+ - `.terraform/` directories
+ - local state files (`*.tfstate`, `*.tfstate.backup`)
+ - `.env` and other system- or user-specific files
diff --git a/terraform/docker/main.tf b/terraform/docker/main.tf
new file mode 100644
index 0000000000..e1d45751b1
--- /dev/null
+++ b/terraform/docker/main.tf
@@ -0,0 +1,25 @@
+terraform {
+ required_providers {
+ docker = {
+ source = "kreuzwerker/docker"
+ version = "~> 3.0.2"
+ }
+ }
+}
+
+provider "docker" {}
+
+resource "docker_image" "moscow_time_app" {
+ name = var.app_image_name
+ keep_locally = true
+}
+
+resource "docker_container" "moscow_time_app" {
+ name = var.python_container_name
+ image = docker_image.moscow_time_app.image_id
+
+ ports {
+ internal = var.internal_port
+ external = var.external_port
+ }
+}
diff --git a/terraform/docker/outputs.tf b/terraform/docker/outputs.tf
new file mode 100644
index 0000000000..8a627c1993
--- /dev/null
+++ b/terraform/docker/outputs.tf
@@ -0,0 +1,9 @@
+output "container_id" {
+ description = "ID of the Moscow Time Docker container"
+ value = docker_container.moscow_time_app.id
+}
+
+output "container_ports" {
+ description = "Ports used by the Moscow Time app container"
+ value = docker_container.moscow_time_app.ports
+}
diff --git a/terraform/docker/variables.tf b/terraform/docker/variables.tf
new file mode 100644
index 0000000000..e571f809a8
--- /dev/null
+++ b/terraform/docker/variables.tf
@@ -0,0 +1,23 @@
+variable "python_container_name" {
+ description = "Name of the container"
+ type = string
+ default = "moscow-time-app"
+}
+
+variable "app_image_name" {
+ description = "Docker image name"
+ type = string
+ default = "karamkhaddourpro/my-fastapi-app:latest"
+}
+
+variable "internal_port" {
+ description = "Internal port used by the app"
+ type = number
+ default = 8000
+}
+
+variable "external_port" {
+ description = "External port exposed on the host"
+ type = number
+ default = 8080
+}
diff --git a/terraform/github/main.tf b/terraform/github/main.tf
new file mode 100644
index 0000000000..4dda3a2f0e
--- /dev/null
+++ b/terraform/github/main.tf
@@ -0,0 +1,39 @@
+terraform {
+ required_providers {
+ github = {
+ source = "integrations/github"
+ version = "~> 4.0"
+ }
+ }
+}
+
+provider "github" {
+ token = var.token
+}
+
+resource "github_repository" "repo" {
+ name = "S25-core-course-labs"
+ description = "Devops course repo"
+ visibility = "public"
+ has_issues = true
+ has_wiki = true
+ auto_init = true
+ license_template = "mit"
+ gitignore_template = "VisualStudio"
+}
+
+resource "github_branch_default" "master" {
+ repository = github_repository.repo.name
+ branch = "master"
+}
+
+resource "github_branch_protection" "default" {
+ repository_id = github_repository.repo.id
+ pattern = github_branch_default.master.branch
+ require_conversation_resolution = true
+ enforce_admins = true
+
+ required_pull_request_reviews {
+ required_approving_review_count = 1
+ }
+}
\ No newline at end of file
diff --git a/terraform/github/outputs.tf b/terraform/github/outputs.tf
new file mode 100644
index 0000000000..eb167b2e81
--- /dev/null
+++ b/terraform/github/outputs.tf
@@ -0,0 +1,3 @@
+output "repo_url" {
+ value = github_repository.repo.html_url
+}
diff --git a/terraform/github/variables.tf b/terraform/github/variables.tf
new file mode 100644
index 0000000000..6a0813b330
--- /dev/null
+++ b/terraform/github/variables.tf
@@ -0,0 +1,5 @@
+variable "token" {
+ type = string
+ description = "GITHUB_TOKEN"
+ sensitive = true
+}
\ No newline at end of file
diff --git a/terraform/yandex/main.tf b/terraform/yandex/main.tf
new file mode 100644
index 0000000000..35a616bb15
--- /dev/null
+++ b/terraform/yandex/main.tf
@@ -0,0 +1,63 @@
+terraform {
+ required_providers {
+ yandex = {
+ source = "yandex-cloud/yandex"
+ version = "~> 0.75"
+ }
+ }
+ required_version = ">= 0.13"
+}
+
+provider "yandex" {
+ token = var.token
+ cloud_id = var.cloud_id
+ folder_id = var.folder_id
+ zone = var.zone
+}
+
+resource "yandex_vpc_network" "network" {
+ name = var.network_name
+}
+
+resource "yandex_vpc_subnet" "subnet" {
+ name = var.subnet_name
+ zone = var.zone
+ network_id = yandex_vpc_network.network.id
+ v4_cidr_blocks = var.subnet_cidr_blocks
+}
+
+resource "yandex_compute_disk" "boot_disk" {
+ name = var.disk.name
+ type = var.disk.type
+ size = var.disk.size
+ zone = var.zone
+ image_id = var.disk.image_id
+}
+
+resource "yandex_compute_instance" "vm" {
+ name = var.vm.name
+ zone = var.zone
+
+ resources {
+ cores = var.vm.cores
+ memory = var.vm.memory
+ core_fraction = var.vm.core_fraction
+ }
+
+ boot_disk {
+ disk_id = yandex_compute_disk.boot_disk.id
+ }
+
+ network_interface {
+ subnet_id = yandex_vpc_subnet.subnet.id
+ nat = var.vm.nat
+ }
+
+ scheduling_policy {
+ preemptible = var.vm.preemptible
+ }
+
+ metadata = {
+ ssh-keys = "ubuntu:${file(var.ssh_key_path)}"
+ }
+}
diff --git a/terraform/yandex/outputs.tf b/terraform/yandex/outputs.tf
new file mode 100644
index 0000000000..0ecb315536
--- /dev/null
+++ b/terraform/yandex/outputs.tf
@@ -0,0 +1,9 @@
+output "internal_ip" {
+ description = "Internal IP address of the VM"
+ value = yandex_compute_instance.vm.network_interface[0].ip_address
+}
+
+output "external_ip" {
+ description = "External NAT IP address of the VM"
+ value = yandex_compute_instance.vm.network_interface[0].nat_ip_address
+}
diff --git a/terraform/yandex/variables.tf b/terraform/yandex/variables.tf
new file mode 100644
index 0000000000..335fca2d55
--- /dev/null
+++ b/terraform/yandex/variables.tf
@@ -0,0 +1,80 @@
+variable "zone" {
+ description = "Yandex Cloud zone"
+ type = string
+ default = "ru-central1-a"
+}
+
+variable "network_name" {
+ description = "VPC network name"
+ type = string
+ default = "terraform-network"
+}
+
+variable "subnet_name" {
+ description = "VPC subnet name"
+ type = string
+ default = "terraform-subnet"
+}
+
+variable "subnet_cidr_blocks" {
+ description = "Subnet IPv4 CIDR blocks"
+ type = list(string)
+ default = ["192.168.10.0/24"]
+}
+
+variable "disk" {
+ description = "Boot disk configuration"
+ type = object({
+ name = string
+ type = string
+ size = number
+ image_id = string
+ })
+ default = {
+ name = "terraform-boot-disk"
+ type = "network-hdd"
+ size = 20
+ image_id = "fd865v46cboopthn7u0k"
+ }
+}
+
+variable "vm" {
+ description = "VM instance configuration"
+ type = object({
+ name = string
+ cores = number
+ memory = number
+ core_fraction = number
+ nat = bool
+ preemptible = bool
+ })
+ default = {
+ name = "terraform-vm"
+ cores = 2
+ memory = 2
+ core_fraction = 20
+ nat = true
+ preemptible = true
+ }
+}
+
+variable "folder_id" {
+ type = string
+ description = "Yandex Cloud folder ID"
+}
+
+variable "cloud_id" {
+ description = "Yandex Cloud cloud ID"
+ type = string
+}
+
+variable "token" {
+ description = "Yandex Cloud OAuth token"
+ type = string
+}
+
+variable "ssh_key_path" {
+ description = "Path to SSH public key file"
+ type = string
+ default = "~/.ssh/id_rsa.pub"
+}