Update tech_docs/Jinja2.md
This commit is contained in:
@@ -772,3 +772,473 @@ You're right to ask—while Jinja2 is powerful, it's not the only tool in the to
|
|||||||
- **Hybrid approaches**: Use Jinja2 for device configs + Terraform for cloud.
|
- **Hybrid approaches**: Use Jinja2 for device configs + Terraform for cloud.
|
||||||
|
|
||||||
**Your edge?** You’re not just a network engineer—you’re the **automation guru** who knows when to use which tool.
|
**Your edge?** You’re not just a network engineer—you’re the **automation guru** who knows when to use which tool.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
You're absolutely right to call this out—**Jinja2 + LaTeX is a powerhouse combo** for **precision document generation**, especially for RFPs, network design docs, or compliance reports where pixel-perfect formatting matters. Let me correct that oversight with tactical details:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **Why Jinja2 + LaTeX Beats Jinja2 + Pandoc for Critical Documents**
|
||||||
|
#### **1. Use LaTeX When You Need:**
|
||||||
|
- **Math-heavy content** (e.g., network performance formulas, QoS calculations).
|
||||||
|
- **Professional typography** (automatic hyphenation, kerning, TOC/Index generation).
|
||||||
|
- **Strict compliance templates** (e.g., government RFP responses with mandated formats).
|
||||||
|
|
||||||
|
#### **2. Jinja2 + LaTeX Workflow**
|
||||||
|
**Step 1:** Create a LaTeX template with Jinja2 placeholders (`template.tex.j2`):
|
||||||
|
```latex
|
||||||
|
\documentclass{report}
|
||||||
|
\title{ {{- project_name | latex_escape -}} }
|
||||||
|
\begin{document}
|
||||||
|
\section{Network Design}
|
||||||
|
\begin{itemize}
|
||||||
|
{% for device in devices %}
|
||||||
|
\item {{ device.name | latex_escape }} ({{ device.ip }}) \\
|
||||||
|
VLANs: {{ device.vlans | join(', ') }}
|
||||||
|
{% endfor %}
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\section{Bandwidth Calculation}
|
||||||
|
The required bandwidth is:
|
||||||
|
\[
|
||||||
|
C = \frac{{ {{ traffic_volume }} }}{{ {{ time_window }} }}
|
||||||
|
\]
|
||||||
|
\end{document}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2:** Render with Jinja2 and compile to PDF:
|
||||||
|
```bash
|
||||||
|
# Render Jinja2 -> LaTeX
|
||||||
|
jinja2 template.tex.j2 data.yml > output.tex
|
||||||
|
|
||||||
|
# Compile LaTeX (install pdflatex first)
|
||||||
|
pdflatex output.tex
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **3. Key Advantages Over Pandoc**
|
||||||
|
| Feature | LaTeX + Jinja2 | Pandoc + Jinja2 |
|
||||||
|
|------------------------|------------------------------------|-----------------------------------|
|
||||||
|
| **Math Formulas** | Native LaTeX equations (e.g., `\[ x^2 \]`) | Requires MathJax/PDF workarounds |
|
||||||
|
| **Table Formatting** | Precise column alignment (e.g., `tabularx`) | Limited Markdown table support |
|
||||||
|
| **Automated Numbering**| Auto-numbered sections/figures | Manual numbering in Markdown |
|
||||||
|
| **Bibliography** | BibTeX integration | Requires Pandoc filters |
|
||||||
|
|
||||||
|
#### **4. Real-World Use Cases**
|
||||||
|
- **RFP Responses**: Auto-generate compliant proposals with perfect margins.
|
||||||
|
- **Network Design Docs**: Embed topology diagrams (via `tikz`) with dynamic labels.
|
||||||
|
- **Academic Reports**: Generate research papers with citations from YAML data.
|
||||||
|
|
||||||
|
#### **5. Pro Tips**
|
||||||
|
- **Escape LaTeX Specials**: Use a custom filter to escape `%`, `$`, etc.:
|
||||||
|
```python
|
||||||
|
def latex_escape(s):
|
||||||
|
return s.replace('$', r'\$').replace('%', r'\%')
|
||||||
|
```
|
||||||
|
- **Precompile Headers**: Store boilerplate LaTeX in snippets:
|
||||||
|
```jinja2
|
||||||
|
{% include 'preamble.tex.j2' %} # Your firm's standard header
|
||||||
|
```
|
||||||
|
- **Version Control**: Track both `.tex.j2` templates and rendered `.tex` files.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **When to Still Use Pandoc**
|
||||||
|
- **Speed**: For quick Markdown → Word/PDF conversions.
|
||||||
|
- **Simplicity**: When LaTeX is overkill (internal docs, draft reports).
|
||||||
|
- **Collaboration**: Non-technical teams can edit Markdown source.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **Updated Verdict**
|
||||||
|
- **For internal docs/agility**: **Jinja2 + Pandoc** (faster iteration).
|
||||||
|
- **For client-facing/precision docs**: **Jinja2 + LaTeX** (unmatched polish).
|
||||||
|
|
||||||
|
**Example Repo**: [jinja2-latex-network-docs](https://github.com/example/jinja2-latex)
|
||||||
|
|
||||||
|
Want to see a live example? I can generate a sample network design PDF from your YAML data.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **Jinja2 as the Universal Templating Glue**
|
||||||
|
Yes! **Jinja2 can template *anything*—HCL (Terraform), Pulumi, CDK, HTML, SQL, even COBOL if you’re desperate**. Here’s how to wield it across your stack, with brutal pragmatism:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **1. Templating HCL (Terraform)**
|
||||||
|
**Problem**: Terraform’s `count` and `dynamic` blocks are clunky for complex logic.
|
||||||
|
**Solution**: Pre-generate Terraform files with Jinja2.
|
||||||
|
|
||||||
|
**`terraform/main.tf.j2`**
|
||||||
|
```jinja2
|
||||||
|
{% for subnet in subnets %}
|
||||||
|
resource "aws_subnet" "{{ subnet.name }}" {
|
||||||
|
vpc_id = aws_vpc.main.id
|
||||||
|
cidr_block = "{{ subnet.cidr }}"
|
||||||
|
tags = {
|
||||||
|
Name = "{{ subnet.name }}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{% endfor %}
|
||||||
|
```
|
||||||
|
**Render it**:
|
||||||
|
```bash
|
||||||
|
jinja2 terraform/main.tf.j2 vars/network.yml > terraform/main.tf
|
||||||
|
```
|
||||||
|
**When to Do This**:
|
||||||
|
- Need loops/conditionals Terraform can’t handle natively (e.g., multi-cloud variations).
|
||||||
|
- **Warning**: Loses Terraform’s state management. Use sparingly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **2. Generating Pulumi Code**
|
||||||
|
**Problem**: Pulumi (Python/TS) already has logic—why Jinja2?
|
||||||
|
**Solution**: Template *scaffolding* for repetitive stacks.
|
||||||
|
|
||||||
|
**`pulumi/__main__.py.j2`**
|
||||||
|
```jinja2
|
||||||
|
from pulumi import Output
|
||||||
|
from pulumi_aws import ec2
|
||||||
|
|
||||||
|
{% for subnet in subnets %}
|
||||||
|
subnet_{{ loop.index }} = ec2.Subnet(
|
||||||
|
"{{ subnet.name }}",
|
||||||
|
cidr_block="{{ subnet.cidr }}",
|
||||||
|
vpc_id=vpc.id
|
||||||
|
)
|
||||||
|
{% endfor %}
|
||||||
|
```
|
||||||
|
**Use Case**:
|
||||||
|
- Bootstrapping new projects with standard patterns (e.g., every VPC needs 3 subnets).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **3. CDK (AWS Cloud Development Kit)**
|
||||||
|
**Problem**: CDK’s constructs are verbose for boilerplate.
|
||||||
|
**Solution**: Jinja2 to generate repetitive CDK code.
|
||||||
|
|
||||||
|
**`cdk_stack.py.j2`**
|
||||||
|
```jinja2
|
||||||
|
from aws_cdk import Stack
|
||||||
|
from constructs import Construct
|
||||||
|
|
||||||
|
class {{ stack_name }}Stack(Stack):
|
||||||
|
def __init__(self, scope: Construct, id: str, **kwargs):
|
||||||
|
super().__init__(scope, id, **kwargs)
|
||||||
|
|
||||||
|
{% for bucket in s3_buckets %}
|
||||||
|
s3.Bucket(self, "{{ bucket.name }}", versioned=True)
|
||||||
|
{% endfor %}
|
||||||
|
```
|
||||||
|
**Render it**:
|
||||||
|
```bash
|
||||||
|
jinja2 cdk_stack.py.j2 vars/project.yml > cdk_app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **4. HTML/Web Dev**
|
||||||
|
**Problem**: Static HTML sucks for dynamic docs.
|
||||||
|
**Solution**: Jinja2 as a poor-man’s React.
|
||||||
|
|
||||||
|
**`templates/dashboard.html.j2`**
|
||||||
|
```jinja2
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<table>
|
||||||
|
{% for device in devices %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ device.name }}</td>
|
||||||
|
<td class="{% if device.status == 'up' %}green{% else %}red{% endif %}">
|
||||||
|
{{ device.status }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
**Use Case**:
|
||||||
|
- Auto-generate network dashboards from NetBox API data.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **5. SQL & Database Scripts**
|
||||||
|
**Problem**: Schema changes across environments (dev/stage/prod).
|
||||||
|
**Solution**: Jinja2-templated DDLs.
|
||||||
|
|
||||||
|
**`sql/schema.sql.j2`**
|
||||||
|
```jinja2
|
||||||
|
CREATE TABLE {{ table_name }} (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
{% for column in columns %}
|
||||||
|
{{ column.name }} {{ column.type }}{% if not loop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
);
|
||||||
|
```
|
||||||
|
**Render it**:
|
||||||
|
```bash
|
||||||
|
jinja2 sql/schema.sql.j2 vars/prod_db.yml > sql/prod_init.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **6. Even COBOL (Don’t Ask Why)**
|
||||||
|
**`cobol/program.cbl.j2`**
|
||||||
|
```jinja2
|
||||||
|
IDENTIFICATION DIVISION.
|
||||||
|
PROGRAM-ID. {{ program_name }}.
|
||||||
|
|
||||||
|
DATA DIVISION.
|
||||||
|
WORKING-STORAGE SECTION.
|
||||||
|
{% for var in variables %}
|
||||||
|
01 {{ var.name }} PIC {{ var.type }}.
|
||||||
|
{% endfor %}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **When *Not* to Use Jinja2**
|
||||||
|
1. **Terraform/Pulumi/CDK Already Have Logic**: Use native loops (`for_each`, `dynamic`) first.
|
||||||
|
2. **High-Performance Needs**: Use Go’s `text/template` or Rust’s `askama`.
|
||||||
|
3. **Frontend Apps**: React/Vue are better for DOM reactivity.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **The Jinja2 Playbook**
|
||||||
|
1. **Start Simple**:
|
||||||
|
```bash
|
||||||
|
# Render a Terraform file
|
||||||
|
jinja2 tf_template.j2 vars.yml > main.tf
|
||||||
|
```
|
||||||
|
2. **Scale with Automation**:
|
||||||
|
```bash
|
||||||
|
# Add to CI/CD (GitHub Actions)
|
||||||
|
- name: Generate configs
|
||||||
|
run: |
|
||||||
|
jinja2 templates/network.j2 vars/$ENV.yml > output.conf
|
||||||
|
```
|
||||||
|
3. **Nuclear Option**:
|
||||||
|
```python
|
||||||
|
# Programmatic rendering (Python)
|
||||||
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
env = Environment(loader=FileSystemLoader("templates"))
|
||||||
|
template = env.get_template("cisco.j2")
|
||||||
|
print(template.render(interfaces=[...]))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **Final Answer**
|
||||||
|
**Jinja2 is the duct tape of infrastructure**—use it to glue together:
|
||||||
|
- **Terraform/Pulumi/CDK** (when native logic falls short).
|
||||||
|
- **HTML/SQL/LaTeX** (for dynamic doc generation).
|
||||||
|
- **Legacy Systems** (COBOL, mainframe configs).
|
||||||
|
|
||||||
|
**Your Move**:
|
||||||
|
```bash
|
||||||
|
# Try templating something absurd today
|
||||||
|
jinja2 /dev/null <(echo '{{ "Hello, " + name }}') <<<'{"name": "Terminal"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **The Ultimate Network Visualization Stack: Self-Documenting, Auto-Generated, and Jinja2-Powered**
|
||||||
|
|
||||||
|
For network engineers and architects, **visualization isn’t just about diagrams—it’s about creating living documentation that updates with your configs**. Here’s how to combine the best tools for **self-documenting network visuals** with Jinja2 at the core.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## **1. The Best-of-All-Worlds Approach**
|
||||||
|
### **Step 1: Define Your Network in Structured Data (YAML/JSON)**
|
||||||
|
```yaml
|
||||||
|
# network.yml
|
||||||
|
network:
|
||||||
|
name: "Core Data Center"
|
||||||
|
devices:
|
||||||
|
- name: "Core-SW1"
|
||||||
|
type: "Cisco Nexus"
|
||||||
|
mgmt_ip: "10.0.0.1"
|
||||||
|
interfaces:
|
||||||
|
- name: "Eth1/1"
|
||||||
|
connected_to: "Firewall-Main"
|
||||||
|
bandwidth: "10G"
|
||||||
|
- name: "Firewall-Main"
|
||||||
|
type: "Palo Alto PA-3400"
|
||||||
|
mgmt_ip: "10.0.0.2"
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Step 2: Use Jinja2 to Generate Multiple Formats**
|
||||||
|
#### **A. Mermaid Diagrams (GitHub/Markdown)**
|
||||||
|
**`templates/topology.mmd.j2`**
|
||||||
|
```jinja2
|
||||||
|
%%{init: {'theme': 'dark'}}%%
|
||||||
|
graph TD
|
||||||
|
{% for device in network.devices %}
|
||||||
|
{{ device.name }}["{{ device.type }}\n{{ device.mgmt_ip }}"]
|
||||||
|
{% endfor %}
|
||||||
|
{% for device in network.devices %}
|
||||||
|
{% for intf in device.interfaces %}
|
||||||
|
{{ device.name }} -- {{ intf.bandwidth }} --> {{ intf.connected_to }}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
```
|
||||||
|
**Output**:
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
Core-SW1["Cisco Nexus\n10.0.0.1"]
|
||||||
|
Firewall-Main["Palo Alto PA-3400\n10.0.0.2"]
|
||||||
|
Core-SW1 -- 10G --> Firewall-Main
|
||||||
|
```
|
||||||
|
**Use Case**: Embed in Git READMEs or MkDocs.
|
||||||
|
|
||||||
|
#### **B. Graphviz (PDF/PNG, Precision Layouts)**
|
||||||
|
**`templates/topology.dot.j2`**
|
||||||
|
```jinja2
|
||||||
|
digraph Network {
|
||||||
|
label="{{ network.name }}"
|
||||||
|
{% for device in network.devices %}
|
||||||
|
"{{ device.name }}" [shape=box, label="{{ device.type }}\n{{ device.mgmt_ip }}"]
|
||||||
|
{% endfor %}
|
||||||
|
{% for device in network.devices %}
|
||||||
|
{% for intf in device.interfaces %}
|
||||||
|
"{{ device.name }}" -> "{{ intf.connected_to }}" [label="{{ intf.bandwidth }}"]
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
**Render it**:
|
||||||
|
```bash
|
||||||
|
jinja2 templates/topology.dot.j2 network.yml | dot -Tpng > topology.png
|
||||||
|
```
|
||||||
|
**Use Case**: High-quality architecture diagrams for audits.
|
||||||
|
|
||||||
|
#### **C. Draw.io (Interactive Editing)**
|
||||||
|
**`templates/drawio.xml.j2`**
|
||||||
|
```jinja2
|
||||||
|
<mxfile>
|
||||||
|
<diagram name="Page-1">
|
||||||
|
{% for device in network.devices %}
|
||||||
|
<mxCell id="{{ device.name }}" value="{{ device.type }}" style="shape=image;image=/icons/{{ device.type }}.png"/>
|
||||||
|
{% endfor %}
|
||||||
|
{% for device in network.devices %}
|
||||||
|
{% for intf in device.interfaces %}
|
||||||
|
<mxCell source="{{ device.name }}" target="{{ intf.connected_to }}" label="{{ intf.bandwidth }}"/>
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
||||||
|
```
|
||||||
|
**Use Case**: Share editable diagrams with non-technical teams.
|
||||||
|
|
||||||
|
#### **D. NetBox Integration (Source of Truth)**
|
||||||
|
**`templates/netbox_import.json.j2`**
|
||||||
|
```jinja2
|
||||||
|
[
|
||||||
|
{% for device in network.devices %}
|
||||||
|
{
|
||||||
|
"name": "{{ device.name }}",
|
||||||
|
"device_type": "{{ device.type }}",
|
||||||
|
"custom_fields": { "mgmt_ip": "{{ device.mgmt_ip }}" }
|
||||||
|
}{% if not loop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
**Use Case**: Auto-populate NetBox via API.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## **2. Self-Documenting Magic**
|
||||||
|
### **A. Auto-Generated Network Docs (MkDocs + Jinja2)**
|
||||||
|
**`docs/network.md.j2`**
|
||||||
|
```jinja2
|
||||||
|
# {{ network.name }}
|
||||||
|
|
||||||
|
## Devices
|
||||||
|
| Name | Type | IP |
|
||||||
|
|------|------|----|
|
||||||
|
{% for device in network.devices %}
|
||||||
|
| {{ device.name }} | {{ device.type }} | `{{ device.mgmt_ip }}` |
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
## Diagram
|
||||||
|
```mermaid
|
||||||
|
{% include 'topology.mmd.j2' %}
|
||||||
|
```
|
||||||
|
```
|
||||||
|
**Result**:
|
||||||
|
- Every Git commit updates docs **and** diagrams.
|
||||||
|
- Run `mkdocs serve` to see live changes.
|
||||||
|
|
||||||
|
### **B. Visio-Like Automation (Python + Jinja2)**
|
||||||
|
```python
|
||||||
|
from jinja2 import Template
|
||||||
|
import yaml, subprocess
|
||||||
|
|
||||||
|
data = yaml.safe_load(open("network.yml"))
|
||||||
|
template = Template(open("templates/topology.dot.j2").read())
|
||||||
|
with open("topology.dot", "w") as f:
|
||||||
|
f.write(template.render(**data))
|
||||||
|
subprocess.run(["dot", "-Tsvg", "topology.dot", "-o", "docs/img/topology.svg"])
|
||||||
|
```
|
||||||
|
**Output**:
|
||||||
|
- SVG diagrams in your docs, auto-updated on changes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## **3. Pro Tips for Zero-Touch Documentation**
|
||||||
|
1. **Git Hooks**: Auto-render diagrams on commit.
|
||||||
|
```bash
|
||||||
|
# .git/hooks/pre-commit
|
||||||
|
jinja2 templates/topology.mmd.j2 network.yml > docs/topology.mmd
|
||||||
|
git add docs/topology.mmd
|
||||||
|
```
|
||||||
|
2. **CI/CD Integration**:
|
||||||
|
```yaml
|
||||||
|
# GitHub Actions
|
||||||
|
- name: Generate diagrams
|
||||||
|
run: |
|
||||||
|
jinja2 templates/topology.dot.j2 network.yml | dot -Tpng > topology.png
|
||||||
|
```
|
||||||
|
3. **Dynamic Labels**:
|
||||||
|
```jinja2
|
||||||
|
{{ device.name }}["{{ device.type }}\n{% if device.vrf %}VRF: {{ device.vrf }}{% endif %}"]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## **4. When to Use Which Tool**
|
||||||
|
| **Tool** | **Best For** | **Weakness** |
|
||||||
|
|----------------|----------------------------------|-----------------------------|
|
||||||
|
| **Mermaid** | Git docs, quick Markdown | Limited layout control |
|
||||||
|
| **Graphviz** | Publication-quality diagrams | Manual tweaking needed |
|
||||||
|
| **Draw.io** | Collaborative editing | Not code-driven |
|
||||||
|
| **NetBox** | Source-of-truth visualization | Requires NetBox setup |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## **5. The Ultimate Workflow**
|
||||||
|
1. **Define** network in YAML.
|
||||||
|
2. **Generate**:
|
||||||
|
- Diagrams (Mermaid/Graphviz)
|
||||||
|
- Docs (MkDocs)
|
||||||
|
- Configs (Jinja2 → Cisco/JSON)
|
||||||
|
3. **Sync**: Git hooks or CI/CD auto-update everything.
|
||||||
|
|
||||||
|
**Result**: Your network **documents itself**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **Final Answer**
|
||||||
|
For **self-documenting network visuals**:
|
||||||
|
1. **Store configs in YAML** (single source of truth).
|
||||||
|
2. **Jinja2 → Mermaid/Graphviz** (auto-generate diagrams).
|
||||||
|
3. **Embed in MkDocs** (always-updated documentation).
|
||||||
|
|
||||||
|
**Example Repo**:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/network-automation/jinja2-mermaid-networks
|
||||||
|
```
|
||||||
|
**Your move**: Replace one manual diagram today with this pipeline.
|
||||||
Reference in New Issue
Block a user