mirror of
https://github.com/prowler-cloud/prowler.git
synced 2025-12-19 05:17:47 +00:00
Compare commits
7 Commits
0d0dabe166
...
feat/ui-gg
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a3c71c435 | ||
|
|
07a995a210 | ||
|
|
73bf93a036 | ||
|
|
d53e76e30a | ||
|
|
e2344b4fc6 | ||
|
|
eb1fc2b269 | ||
|
|
4931564648 |
20
.gga
Normal file
20
.gga
Normal file
@@ -0,0 +1,20 @@
|
||||
# Gentleman Guardian Angel (gga) Configuration
|
||||
# https://github.com/Gentleman-Programming/gentleman-guardian-angel
|
||||
|
||||
# AI Provider (required)
|
||||
# Options: claude, gemini, codex, ollama:<model>
|
||||
PROVIDER="claude"
|
||||
|
||||
# File patterns to include in review (comma-separated globs)
|
||||
# Review both TypeScript (UI) and Python (SDK, API, MCP) files
|
||||
FILE_PATTERNS="*.ts,*.tsx,*.js,*.jsx,*.py"
|
||||
|
||||
# File patterns to exclude from review (comma-separated globs)
|
||||
# Excludes: test files, type definitions, and api/ folder (no AGENTS.md yet)
|
||||
EXCLUDE_PATTERNS="*.test.ts,*.test.tsx,*.spec.ts,*.spec.tsx,*.d.ts,*_test.py,test_*.py,conftest.py,api/*"
|
||||
|
||||
# File containing your coding standards (relative to repo root)
|
||||
RULES_FILE="AGENTS-CODE-REVIEW.md"
|
||||
|
||||
# Strict mode: fail if AI response is ambiguous (recommended)
|
||||
STRICT_MODE="true"
|
||||
@@ -82,7 +82,6 @@ repos:
|
||||
args: ["--directory=./"]
|
||||
pass_filenames: false
|
||||
|
||||
|
||||
- repo: https://github.com/hadolint/hadolint
|
||||
rev: v2.13.0-beta
|
||||
hooks:
|
||||
@@ -129,9 +128,20 @@ repos:
|
||||
|
||||
- id: ui-checks
|
||||
name: UI - Husky Pre-commit
|
||||
description: "Run UI pre-commit checks (Claude Code validation + healthcheck)"
|
||||
description: "Run UI pre-commit checks (healthcheck + build)"
|
||||
entry: bash -c 'cd ui && .husky/pre-commit'
|
||||
language: system
|
||||
files: '^ui/.*\.(ts|tsx|js|jsx|json|css)$'
|
||||
pass_filenames: false
|
||||
verbose: true
|
||||
|
||||
- id: gga
|
||||
name: Gentleman Guardian Angel (AI Code Review)
|
||||
description: "AI-powered code review - runs last after all formatters/linters"
|
||||
entry: ./scripts/gga-review.sh
|
||||
language: system
|
||||
files: '\.(ts|tsx|js|jsx|py)$'
|
||||
exclude: '(\.test\.|\.spec\.|_test\.py|test_.*\.py|conftest\.py|\.d\.ts)'
|
||||
pass_filenames: false
|
||||
stages: ["pre-commit"]
|
||||
verbose: true
|
||||
|
||||
89
AGENTS-CODE-REVIEW.md
Normal file
89
AGENTS-CODE-REVIEW.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Code Review Rules
|
||||
|
||||
## References
|
||||
|
||||
- UI details: `ui/AGENTS.md`
|
||||
- SDK details: `prowler/AGENTS.md`
|
||||
- MCP details: `mcp_server/AGENTS.md`
|
||||
|
||||
---
|
||||
|
||||
## ALL FILES
|
||||
|
||||
REJECT if:
|
||||
|
||||
- Hardcoded secrets/credentials
|
||||
- `any` type (TypeScript) or missing type hints (Python)
|
||||
- Code duplication (violates DRY)
|
||||
- Silent error handling (no logging)
|
||||
|
||||
---
|
||||
|
||||
## TypeScript/React (ui/)
|
||||
|
||||
REJECT if:
|
||||
|
||||
- `import React` or `import * as React` → use `import { useState }`
|
||||
- Union types `type X = "a" | "b"` → use `const X = {...} as const`
|
||||
- `var()` or hex colors in className → use Tailwind classes
|
||||
- `useMemo` or `useCallback` without justification (React 19 Compiler)
|
||||
- `z.string().email()` → use `z.email()` (Zod v4)
|
||||
- `z.string().nonempty()` → use `z.string().min(1)` (Zod v4)
|
||||
- Missing `"use client"` in client components
|
||||
- Missing `"use server"` in server actions
|
||||
- Images without `alt` attribute
|
||||
- Interactive elements without `aria` labels
|
||||
- Non-semantic HTML when semantic exists
|
||||
|
||||
PREFER:
|
||||
|
||||
- `components/shadcn/` over custom components
|
||||
- `cn()` for conditional/merged classes
|
||||
- Local files if used 1 place, `components/shared/` if 2+
|
||||
- Responsive classes: `sm:`, `md:`, `lg:`, `xl:`
|
||||
|
||||
EXCEPTION:
|
||||
|
||||
- `var()` allowed in chart/graph component props (not className)
|
||||
|
||||
---
|
||||
|
||||
## Python (prowler/, mcp_server/)
|
||||
|
||||
REJECT if:
|
||||
|
||||
- Missing type hints on public functions
|
||||
- Missing docstrings on classes/public methods
|
||||
- Bare `except:` without specific exception
|
||||
- `print()` instead of `logger`
|
||||
|
||||
REQUIRE for SDK checks:
|
||||
|
||||
- Inherit from `Check`
|
||||
- `execute()` returns `list[CheckReport]`
|
||||
- `report.status` = `"PASS"` | `"FAIL"`
|
||||
- `.metadata.json` file exists
|
||||
|
||||
REQUIRE for MCP tools:
|
||||
|
||||
- Extend `BaseTool` (auto-registration)
|
||||
- `MinimalSerializerMixin` for responses
|
||||
- `from_api_response()` for API transforms
|
||||
|
||||
---
|
||||
|
||||
## Response Format
|
||||
|
||||
FIRST LINE must be exactly:
|
||||
|
||||
```
|
||||
STATUS: PASSED
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
STATUS: FAILED
|
||||
```
|
||||
|
||||
If FAILED, list: `file:line - rule - issue`
|
||||
49
scripts/gga-review.sh
Executable file
49
scripts/gga-review.sh
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
# Gentleman Guardian Angel (gga) - AI Code Review Hook
|
||||
# This script is called by pre-commit after all formatters/linters have run
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Check if AI code review is enabled
|
||||
if [ "${CODE_REVIEW_ENABLED:-false}" != "true" ]; then
|
||||
echo -e "${YELLOW}⏭️ AI code review disabled (CODE_REVIEW_ENABLED!=true)${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if AGENTS-CODE-REVIEW.md exists
|
||||
if [ ! -f "AGENTS-CODE-REVIEW.md" ]; then
|
||||
echo -e "${YELLOW}⏭️ AI code review skipped (AGENTS-CODE-REVIEW.md not found)${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Install gga if not present
|
||||
if ! command -v gga &> /dev/null; then
|
||||
echo -e "${BLUE}📦 Installing Gentleman Guardian Angel (gga)...${NC}"
|
||||
if command -v brew &> /dev/null; then
|
||||
brew tap gentleman-programming/tap 2>/dev/null || true
|
||||
brew install gga
|
||||
else
|
||||
# Fallback: install from source for Linux/CI environments
|
||||
GGA_TMP_DIR=$(mktemp -d)
|
||||
git clone --depth 1 https://github.com/Gentleman-Programming/gentleman-guardian-angel.git "$GGA_TMP_DIR"
|
||||
chmod +x "$GGA_TMP_DIR/install.sh"
|
||||
"$GGA_TMP_DIR/install.sh"
|
||||
rm -rf "$GGA_TMP_DIR"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Verify gga is available
|
||||
if ! command -v gga &> /dev/null; then
|
||||
echo "❌ Failed to install gga"
|
||||
echo "Please install manually: https://github.com/Gentleman-Programming/gentleman-guardian-angel"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run gga code review
|
||||
# Exclusions are configured in .gga file (EXCLUDE_PATTERNS)
|
||||
exec gga run
|
||||
@@ -1,15 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Prowler UI - Pre-Commit Hook
|
||||
# Optionally validates ONLY staged files against AGENTS.md standards using Claude Code
|
||||
# Controlled by CODE_REVIEW_ENABLED in .env
|
||||
# Runs healthcheck (typecheck + lint) and build for UI changes
|
||||
# AI code review is handled by gga in .pre-commit-config.yaml
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
@@ -19,127 +18,14 @@ echo "🚀 Prowler UI - Pre-Commit Hook"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# Load .env file (look in git root directory)
|
||||
# Get git root and navigate to ui directory
|
||||
GIT_ROOT=$(git rev-parse --show-toplevel)
|
||||
if [ -f "$GIT_ROOT/ui/.env" ]; then
|
||||
CODE_REVIEW_ENABLED=$(grep "^CODE_REVIEW_ENABLED" "$GIT_ROOT/ui/.env" | cut -d'=' -f2 | tr -d ' ')
|
||||
elif [ -f "$GIT_ROOT/.env" ]; then
|
||||
CODE_REVIEW_ENABLED=$(grep "^CODE_REVIEW_ENABLED" "$GIT_ROOT/.env" | cut -d'=' -f2 | tr -d ' ')
|
||||
elif [ -f ".env" ]; then
|
||||
CODE_REVIEW_ENABLED=$(grep "^CODE_REVIEW_ENABLED" .env | cut -d'=' -f2 | tr -d ' ')
|
||||
else
|
||||
CODE_REVIEW_ENABLED="false"
|
||||
fi
|
||||
|
||||
# Normalize the value to lowercase
|
||||
CODE_REVIEW_ENABLED=$(echo "$CODE_REVIEW_ENABLED" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
echo -e "${BLUE}ℹ️ Code Review Status: ${CODE_REVIEW_ENABLED}${NC}"
|
||||
echo ""
|
||||
|
||||
# Get staged files (what will be committed)
|
||||
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(tsx?|jsx?)$' || true)
|
||||
|
||||
if [ "$CODE_REVIEW_ENABLED" = "true" ]; then
|
||||
if [ -z "$STAGED_FILES" ]; then
|
||||
echo -e "${YELLOW}⚠️ No TypeScript/JavaScript files staged to validate${NC}"
|
||||
echo ""
|
||||
else
|
||||
echo -e "${YELLOW}🔍 Running Claude Code standards validation...${NC}"
|
||||
echo ""
|
||||
echo -e "${BLUE}📋 Files to validate:${NC}"
|
||||
echo "$STAGED_FILES" | while IFS= read -r file; do echo " - $file"; done
|
||||
echo ""
|
||||
|
||||
echo -e "${BLUE}📤 Sending to Claude Code for validation...${NC}"
|
||||
echo ""
|
||||
|
||||
# Build prompt with full file contents
|
||||
VALIDATION_PROMPT=$(
|
||||
cat <<'PROMPT_EOF'
|
||||
You are a code reviewer for the Prowler UI project. Analyze the full file contents of changed files below and validate they comply with AGENTS.md standards.
|
||||
|
||||
**RULES TO CHECK:**
|
||||
1. React Imports: NO `import * as React` or `import React, {` → Use `import { useState }`
|
||||
2. TypeScript: NO union types like `type X = "a" | "b"` → Use const-based: `const X = {...} as const`
|
||||
3. Tailwind: NO `var()` or hex colors in className → Use Tailwind utilities and semantic color classes. Exception: `var()` is allowed when passing colors to chart/graph components that require CSS color strings (not Tailwind classes) for their APIs.
|
||||
4. cn(): Use for merging multiple classes or for conditionals (handles Tailwind conflicts with twMerge) → `cn(BUTTON_STYLES.base, BUTTON_STYLES.active, isLoading && "opacity-50")`
|
||||
5. React 19: NO `useMemo`/`useCallback` without reason
|
||||
6. Zod v4: Use `.min(1)` not `.nonempty()`, `z.email()` not `z.string().email()`. All inputs must be validated with Zod.
|
||||
7. File Org: 1 feature = local, 2+ features = shared
|
||||
8. Directives: Server Actions need "use server", clients need "use client"
|
||||
9. Implement DRY, KISS principles. (example: reusable components, avoid repetition)
|
||||
10. Layout must work for all the responsive breakpoints (mobile, tablet, desktop)
|
||||
11. ANY types cannot be used - CRITICAL: Check for `: any` in all visible lines
|
||||
12. Use the components inside components/shadcn if possible
|
||||
13. Check Accessibility best practices (like alt tags in images, semantic HTML, Aria labels, etc.)
|
||||
|
||||
=== FILES TO REVIEW ===
|
||||
PROMPT_EOF
|
||||
)
|
||||
|
||||
# Add full file contents for each staged file
|
||||
for file in $STAGED_FILES; do
|
||||
VALIDATION_PROMPT="$VALIDATION_PROMPT
|
||||
|
||||
=== FILE: $file ===
|
||||
$(cat "$file" 2>/dev/null || echo "Error reading file")"
|
||||
done
|
||||
|
||||
VALIDATION_PROMPT="$VALIDATION_PROMPT
|
||||
|
||||
=== END FILES ===
|
||||
|
||||
**IMPORTANT: Your response MUST start with exactly one of these lines:**
|
||||
STATUS: PASSED
|
||||
STATUS: FAILED
|
||||
|
||||
**If FAILED:** List each violation with File, Line Number, Rule Number, and Issue.
|
||||
**If PASSED:** Confirm all files comply with AGENTS.md standards.
|
||||
|
||||
**Start your response now with STATUS:**"
|
||||
|
||||
# Send to Claude Code
|
||||
if VALIDATION_OUTPUT=$(echo "$VALIDATION_PROMPT" | claude 2>&1); then
|
||||
echo "$VALIDATION_OUTPUT"
|
||||
echo ""
|
||||
|
||||
# Check result - STRICT MODE: fail if status unclear
|
||||
if echo "$VALIDATION_OUTPUT" | grep -q "^STATUS: PASSED"; then
|
||||
echo ""
|
||||
echo -e "${GREEN}✅ VALIDATION PASSED${NC}"
|
||||
echo ""
|
||||
elif echo "$VALIDATION_OUTPUT" | grep -q "^STATUS: FAILED"; then
|
||||
echo ""
|
||||
echo -e "${RED}❌ VALIDATION FAILED${NC}"
|
||||
echo -e "${RED}Fix violations before committing${NC}"
|
||||
echo ""
|
||||
exit 1
|
||||
else
|
||||
echo ""
|
||||
echo -e "${RED}❌ VALIDATION ERROR${NC}"
|
||||
echo -e "${RED}Could not determine validation status from Claude Code response${NC}"
|
||||
echo -e "${YELLOW}Response must start with 'STATUS: PASSED' or 'STATUS: FAILED'${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}To bypass validation temporarily, set CODE_REVIEW_ENABLED=false in .env${NC}"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Claude Code not available${NC}"
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}⏭️ Code review disabled (CODE_REVIEW_ENABLED=false)${NC}"
|
||||
echo ""
|
||||
fi
|
||||
cd "$GIT_ROOT/ui" || exit 1
|
||||
|
||||
# Run healthcheck (typecheck and lint check)
|
||||
echo -e "${BLUE}🏥 Running healthcheck...${NC}"
|
||||
echo ""
|
||||
|
||||
cd ui || cd .
|
||||
if pnpm run healthcheck; then
|
||||
echo ""
|
||||
echo -e "${GREEN}✅ Healthcheck passed${NC}"
|
||||
|
||||
@@ -11,6 +11,8 @@ All notable changes to the **Prowler UI** are documented in this file.
|
||||
|
||||
### 🔄 Changed
|
||||
|
||||
- Replace custom Claude pre-commit validation with Gentleman Guardian Angel (gga) for provider-agnostic, monorepo-aware AI code reviews [(#9571)](https://github.com/prowler-cloud/prowler/pull/9571)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
---
|
||||
|
||||
86
ui/README.md
86
ui/README.md
@@ -2,10 +2,12 @@
|
||||
|
||||
This repository hosts the UI component for Prowler, providing a user-friendly web interface to interact seamlessly with Prowler's features.
|
||||
|
||||
|
||||
## 🚀 Production deployment
|
||||
|
||||
### Docker deployment
|
||||
|
||||
#### Clone the repository
|
||||
|
||||
```console
|
||||
# HTTPS
|
||||
git clone https://github.com/prowler-cloud/ui.git
|
||||
@@ -14,16 +16,21 @@ git clone https://github.com/prowler-cloud/ui.git
|
||||
git clone git@github.com:prowler-cloud/ui.git
|
||||
|
||||
```
|
||||
|
||||
#### Build the Docker image
|
||||
|
||||
```bash
|
||||
docker build -t prowler-cloud/ui . --target prod
|
||||
```
|
||||
|
||||
#### Run the Docker container
|
||||
|
||||
```bash
|
||||
docker run -p 3000:3000 prowler-cloud/ui
|
||||
```
|
||||
|
||||
### Local deployment
|
||||
|
||||
#### Clone the repository
|
||||
|
||||
```console
|
||||
@@ -48,8 +55,11 @@ pnpm start
|
||||
```
|
||||
|
||||
## 🧪 Development deployment
|
||||
|
||||
### Docker deployment
|
||||
|
||||
#### Clone the repository
|
||||
|
||||
```console
|
||||
# HTTPS
|
||||
git clone https://github.com/prowler-cloud/ui.git
|
||||
@@ -58,16 +68,21 @@ git clone https://github.com/prowler-cloud/ui.git
|
||||
git clone git@github.com:prowler-cloud/ui.git
|
||||
|
||||
```
|
||||
|
||||
#### Build the Docker image
|
||||
|
||||
```bash
|
||||
docker build -t prowler-cloud/ui . --target dev
|
||||
```
|
||||
|
||||
#### Run the Docker container
|
||||
|
||||
```bash
|
||||
docker run -p 3000:3000 prowler-cloud/ui
|
||||
```
|
||||
|
||||
### Local deployment
|
||||
|
||||
#### Clone the repository
|
||||
|
||||
```console
|
||||
@@ -109,45 +124,74 @@ pnpm run dev
|
||||
|
||||
## Git Hooks & Code Review
|
||||
|
||||
This project uses Git hooks to maintain code quality. When you commit changes to TypeScript/JavaScript files, the pre-commit hook can optionally validate them against our coding standards using Claude Code.
|
||||
This project uses Git hooks to maintain code quality:
|
||||
|
||||
### Enabling Code Review
|
||||
1. **UI Pre-commit Hook** (`ui/.husky/pre-commit`): Runs healthcheck (typecheck + lint) and build for UI changes
|
||||
2. **AI Code Review** (`.pre-commit-config.yaml`): Uses [Gentleman Guardian Angel (gga)](https://github.com/Gentleman-Programming/gentleman-guardian-angel) to validate code against `AGENTS-CODE-REVIEW.md` standards
|
||||
|
||||
To enable automatic code review on commits, add this to your `.env` file in the project root:
|
||||
### Enabling AI Code Review
|
||||
|
||||
The AI code review runs **after** all formatters and linters have processed the code. To enable it, set in your environment or `.env` file:
|
||||
|
||||
```bash
|
||||
CODE_REVIEW_ENABLED=true
|
||||
export CODE_REVIEW_ENABLED=true
|
||||
```
|
||||
|
||||
When enabled, the hook will:
|
||||
- ✅ Validate your staged changes against `AGENTS.md` standards
|
||||
- ✅ Check for common issues (any types, incorrect imports, styling violations, etc.)
|
||||
- ✅ Block commits that don't comply with the standards
|
||||
- ✅ Provide helpful feedback on how to fix issues
|
||||
When enabled:
|
||||
|
||||
### Disabling Code Review
|
||||
- ✅ Validates staged changes against `AGENTS-CODE-REVIEW.md` standards
|
||||
- ✅ Reviews both TypeScript (UI) and Python (SDK, API, MCP) files
|
||||
- ✅ Runs last, after black/isort/prettier have formatted the code
|
||||
- ✅ Smart caching: skips unchanged files for faster reviews
|
||||
- ✅ Blocks commits that don't comply with standards
|
||||
|
||||
To disable code review (faster commits, useful for quick iterations):
|
||||
### Disabling AI Code Review
|
||||
|
||||
```bash
|
||||
CODE_REVIEW_ENABLED=false
|
||||
export CODE_REVIEW_ENABLED=false
|
||||
```
|
||||
|
||||
Or remove the variable from your `.env` file.
|
||||
Or simply don't set the variable (disabled by default).
|
||||
|
||||
### Requirements
|
||||
|
||||
- [Claude Code CLI](https://github.com/anthropics/claude-code) installed and authenticated
|
||||
- `.env` file in the project root with `CODE_REVIEW_ENABLED` set
|
||||
- **AI CLI**: One of the following must be installed and authenticated:
|
||||
- [Claude Code CLI](https://claude.ai/code) (default, recommended)
|
||||
- [Gemini CLI](https://github.com/google-gemini/gemini-cli)
|
||||
- [Codex CLI](https://www.npmjs.com/package/@openai/codex)
|
||||
- [Ollama](https://ollama.ai) (local models)
|
||||
|
||||
**Note:** `gga` will be installed automatically on first commit if not present.
|
||||
|
||||
### Configuration
|
||||
|
||||
The AI code review is configured via `.gga` in the **repository root**:
|
||||
|
||||
```bash
|
||||
PROVIDER="claude" # AI provider
|
||||
FILE_PATTERNS="*.ts,*.tsx,*.js,*.jsx,*.py"
|
||||
EXCLUDE_PATTERNS="*.test.ts,*.spec.ts,*_test.py,test_*.py,conftest.py,*.d.ts"
|
||||
RULES_FILE="AGENTS-CODE-REVIEW.md" # Centralized review rules
|
||||
STRICT_MODE="true"
|
||||
```
|
||||
|
||||
Available providers: `claude`, `gemini`, `codex`, `ollama:<model>`
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
If hooks aren't running after commits:
|
||||
If gga installation fails:
|
||||
|
||||
```bash
|
||||
# Verify hooks are configured
|
||||
git config --get core.hooksPath # Should output: ui/.husky
|
||||
# Homebrew (macOS)
|
||||
brew install gentleman-programming/tap/gga
|
||||
|
||||
# Reconfigure if needed
|
||||
git config core.hooksPath "ui/.husky"
|
||||
# From source (Linux/macOS)
|
||||
git clone https://github.com/Gentleman-Programming/gentleman-guardian-angel.git /tmp/gga
|
||||
cd /tmp/gga && ./install.sh
|
||||
```
|
||||
|
||||
To clear the gga cache:
|
||||
|
||||
```bash
|
||||
gga cache clear
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user