Spaces:
Sleeping
Sleeping
Upload 9 files
Browse files- .dockerignore +41 -0
- Dockerfile +26 -0
- blog-viewer.html +884 -0
- blogs/azure-app-service.md +85 -0
- blogs/azure-vm-setup.md +264 -0
- blogs/itimesheet.md +68 -0
- index.html +598 -0
- requirements.txt +2 -0
- server.py +282 -0
.dockerignore
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Git files
|
| 2 |
+
.git
|
| 3 |
+
.gitignore
|
| 4 |
+
.gitattributes
|
| 5 |
+
|
| 6 |
+
# Python cache
|
| 7 |
+
__pycache__/
|
| 8 |
+
*.pyc
|
| 9 |
+
*.pyo
|
| 10 |
+
*.pyd
|
| 11 |
+
.Python
|
| 12 |
+
*.so
|
| 13 |
+
|
| 14 |
+
# Virtual environments
|
| 15 |
+
.venv/
|
| 16 |
+
venv/
|
| 17 |
+
ENV/
|
| 18 |
+
env/
|
| 19 |
+
|
| 20 |
+
# IDE files
|
| 21 |
+
.vscode/
|
| 22 |
+
.idea/
|
| 23 |
+
*.swp
|
| 24 |
+
*.swo
|
| 25 |
+
|
| 26 |
+
# OS generated files
|
| 27 |
+
.DS_Store
|
| 28 |
+
.DS_Store?
|
| 29 |
+
._*
|
| 30 |
+
.Spotlight-V100
|
| 31 |
+
.Trashes
|
| 32 |
+
ehthumbs.db
|
| 33 |
+
Thumbs.db
|
| 34 |
+
|
| 35 |
+
# Documentation
|
| 36 |
+
README.md
|
| 37 |
+
*.md
|
| 38 |
+
!blogs/*.md
|
| 39 |
+
|
| 40 |
+
# Other
|
| 41 |
+
.dockerignore
|
Dockerfile
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use Python 3.11 slim image for smaller size
|
| 2 |
+
FROM python:3.11-slim
|
| 3 |
+
|
| 4 |
+
# Set working directory
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
# Set environment variables
|
| 8 |
+
ENV PYTHONUNBUFFERED=1
|
| 9 |
+
ENV PYTHONDONTWRITEBYTECODE=1
|
| 10 |
+
|
| 11 |
+
# Install system dependencies if needed
|
| 12 |
+
RUN apt-get update && apt-get install -y \
|
| 13 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 14 |
+
|
| 15 |
+
# Copy all application files
|
| 16 |
+
COPY . .
|
| 17 |
+
|
| 18 |
+
# Create a non-root user for security
|
| 19 |
+
RUN useradd -m -u 1000 user && chown -R user:user /app
|
| 20 |
+
USER user
|
| 21 |
+
|
| 22 |
+
# Expose the port that Hugging Face Spaces expects
|
| 23 |
+
EXPOSE 7860
|
| 24 |
+
|
| 25 |
+
# Command to run the application on port 7860 (Hugging Face default)
|
| 26 |
+
CMD ["python", "server.py", "--port", "7860"]
|
blog-viewer.html
ADDED
|
@@ -0,0 +1,884 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>Documentation Viewer</title>
|
| 8 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 9 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Lexend:[email protected]&display=swap" rel="stylesheet">
|
| 11 |
+
<style>
|
| 12 |
+
* {
|
| 13 |
+
margin: 0;
|
| 14 |
+
padding: 0;
|
| 15 |
+
box-sizing: border-box;
|
| 16 |
+
font-family: "Lexend", sans-serif;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
body {
|
| 20 |
+
/* font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', Roboto, sans-serif; */
|
| 21 |
+
line-height: 1.5;
|
| 22 |
+
color: #1d1d1f;
|
| 23 |
+
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
| 24 |
+
/* background: black; */
|
| 25 |
+
min-height: 100vh;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
.header {
|
| 29 |
+
background: rgba(255, 255, 255, 0.6);
|
| 30 |
+
backdrop-filter: blur(1px);
|
| 31 |
+
-webkit-backdrop-filter: blur(1px);
|
| 32 |
+
color: #1d1d1f;
|
| 33 |
+
padding: 16px;
|
| 34 |
+
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
| 35 |
+
box-shadow: 0 0px 10px rgba(0, 0, 0, 0.1);
|
| 36 |
+
position: fixed;
|
| 37 |
+
top: 0;
|
| 38 |
+
margin: 10px;
|
| 39 |
+
border-radius: 20px;
|
| 40 |
+
z-index: 100;
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
.header-content {
|
| 44 |
+
/* max-width: 1200px; */
|
| 45 |
+
margin: 0 auto;
|
| 46 |
+
display: flex;
|
| 47 |
+
justify-content: space-between;
|
| 48 |
+
align-items: center;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
.header h1 {
|
| 52 |
+
font-size: 1.3rem;
|
| 53 |
+
font-weight: 600;
|
| 54 |
+
display: flex;
|
| 55 |
+
align-items: center;
|
| 56 |
+
gap: 8px;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
#documentTitle {
|
| 60 |
+
background: rgba(255, 255, 255);
|
| 61 |
+
border: 1px solid rgba(0, 0, 0, 0.2);
|
| 62 |
+
border-radius: 15px;
|
| 63 |
+
padding: 10px 15px;
|
| 64 |
+
margin-right: 10px;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
.back-btn {
|
| 68 |
+
background: rgba(0, 122, 255, 0.3);
|
| 69 |
+
backdrop-filter: blur(200px);
|
| 70 |
+
-webkit-backdrop-filter: blur(200px);
|
| 71 |
+
/* background: rgba(255, 255, 255); */
|
| 72 |
+
color: #007aff;
|
| 73 |
+
border: none;
|
| 74 |
+
padding: 8px 16px;
|
| 75 |
+
border-radius: 20px;
|
| 76 |
+
cursor: pointer;
|
| 77 |
+
text-decoration: none;
|
| 78 |
+
display: flex;
|
| 79 |
+
align-items: center;
|
| 80 |
+
gap: 6px;
|
| 81 |
+
font-size: 0.9rem;
|
| 82 |
+
font-weight: 500;
|
| 83 |
+
transition: all 0.3s ease;
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
.back-btn:hover {
|
| 87 |
+
background: rgba(0, 122, 255, 0.2);
|
| 88 |
+
transform: translateY(-1px);
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
.container {
|
| 92 |
+
max-width: 1200px;
|
| 93 |
+
margin: 0 auto;
|
| 94 |
+
padding: 24px 20px;
|
| 95 |
+
margin-top: 90px;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
.document-meta {
|
| 99 |
+
background: rgba(255, 255, 255, 0.8);
|
| 100 |
+
backdrop-filter: blur(20px);
|
| 101 |
+
-webkit-backdrop-filter: blur(20px);
|
| 102 |
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
| 103 |
+
border-radius: 12px;
|
| 104 |
+
padding: 16px;
|
| 105 |
+
margin-bottom: 20px;
|
| 106 |
+
border-left: 3px solid #007aff;
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
.meta-row {
|
| 110 |
+
display: flex;
|
| 111 |
+
justify-content: space-between;
|
| 112 |
+
align-items: center;
|
| 113 |
+
margin-bottom: 8px;
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
.meta-row:last-child {
|
| 117 |
+
margin-bottom: 0;
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
.meta-label {
|
| 121 |
+
font-weight: 600;
|
| 122 |
+
color: #007aff;
|
| 123 |
+
font-size: 0.9rem;
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
.meta-value {
|
| 127 |
+
color: #86868b;
|
| 128 |
+
font-size: 0.9rem;
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
.content {
|
| 132 |
+
background: rgba(255, 255, 255, 0.8);
|
| 133 |
+
backdrop-filter: blur(20px);
|
| 134 |
+
-webkit-backdrop-filter: blur(20px);
|
| 135 |
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
| 136 |
+
border-radius: 12px;
|
| 137 |
+
padding: 32px;
|
| 138 |
+
min-height: 400px;
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
.loading {
|
| 142 |
+
text-align: center;
|
| 143 |
+
padding: 60px;
|
| 144 |
+
color: #007aff;
|
| 145 |
+
font-size: 1.1rem;
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
.error {
|
| 149 |
+
background: rgba(255, 59, 48, 0.1);
|
| 150 |
+
border: 1px solid rgba(255, 59, 48, 0.3);
|
| 151 |
+
color: #d70015;
|
| 152 |
+
padding: 20px;
|
| 153 |
+
border-radius: 12px;
|
| 154 |
+
text-align: center;
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
/* Markdown styling */
|
| 158 |
+
.markdown-content h1,
|
| 159 |
+
.markdown-content h2,
|
| 160 |
+
.markdown-content h3,
|
| 161 |
+
.markdown-content h4,
|
| 162 |
+
.markdown-content h5,
|
| 163 |
+
.markdown-content h6 {
|
| 164 |
+
margin-top: 2rem;
|
| 165 |
+
margin-bottom: 1rem;
|
| 166 |
+
font-weight: 600;
|
| 167 |
+
line-height: 1.25;
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
.markdown-content h1 {
|
| 171 |
+
font-size: 2rem;
|
| 172 |
+
border-bottom: 2px solid #eee;
|
| 173 |
+
padding-bottom: 0.5rem;
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
.markdown-content h2 {
|
| 177 |
+
font-size: 1.5rem;
|
| 178 |
+
border-bottom: 1px solid #eee;
|
| 179 |
+
padding-bottom: 0.3rem;
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
.markdown-content h3 {
|
| 183 |
+
font-size: 1.25rem;
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
.markdown-content p {
|
| 187 |
+
margin-bottom: 1rem;
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
.markdown-content ul,
|
| 191 |
+
.markdown-content ol {
|
| 192 |
+
margin-bottom: 1rem;
|
| 193 |
+
padding-left: 2rem;
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
.markdown-content li {
|
| 197 |
+
margin-bottom: 0.25rem;
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
.markdown-content blockquote {
|
| 201 |
+
border-left: 3px solid #007aff;
|
| 202 |
+
background: rgba(0, 122, 255, 0.05);
|
| 203 |
+
padding: 1rem;
|
| 204 |
+
margin: 1rem 0;
|
| 205 |
+
border-radius: 6px;
|
| 206 |
+
font-style: italic;
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
.markdown-content code {
|
| 210 |
+
background: rgba(0, 0, 0, 0.05);
|
| 211 |
+
padding: 0.2rem 0.4rem;
|
| 212 |
+
border-radius: 4px;
|
| 213 |
+
font-family: 'SF Mono', 'Monaco', 'Cascadia Code', 'Roboto Mono', monospace;
|
| 214 |
+
font-size: 0.9rem;
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
.markdown-content pre {
|
| 218 |
+
background: rgba(0, 0, 0, 0.05);
|
| 219 |
+
border: 1px solid rgba(0, 0, 0, 0.1);
|
| 220 |
+
border-radius: 8px;
|
| 221 |
+
padding: 1rem;
|
| 222 |
+
overflow-x: auto;
|
| 223 |
+
margin: 1rem 0;
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
.markdown-content pre code {
|
| 227 |
+
background: none;
|
| 228 |
+
padding: 0;
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
.markdown-content table {
|
| 232 |
+
border-collapse: collapse;
|
| 233 |
+
width: 100%;
|
| 234 |
+
margin: 1rem 0;
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
.markdown-content th,
|
| 238 |
+
.markdown-content td {
|
| 239 |
+
border: 1px solid #ddd;
|
| 240 |
+
padding: 0.75rem;
|
| 241 |
+
text-align: left;
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
.markdown-content th {
|
| 245 |
+
background: #f8f9fa;
|
| 246 |
+
font-weight: 600;
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
.markdown-content a {
|
| 250 |
+
color: #007aff;
|
| 251 |
+
text-decoration: none;
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
.markdown-content a:hover {
|
| 255 |
+
text-decoration: underline;
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
.markdown-content img {
|
| 259 |
+
max-width: 100%;
|
| 260 |
+
height: auto;
|
| 261 |
+
border-radius: 8px;
|
| 262 |
+
margin: 1rem 0;
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
/* Custom scrollbar */
|
| 266 |
+
::-webkit-scrollbar {
|
| 267 |
+
width: 8px;
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
::-webkit-scrollbar-track {
|
| 271 |
+
background: rgba(255, 255, 255, 0.1);
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
::-webkit-scrollbar-thumb {
|
| 275 |
+
background: rgba(0, 0, 0, 0.2);
|
| 276 |
+
border-radius: 4px;
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
::-webkit-scrollbar-thumb:hover {
|
| 280 |
+
background: rgba(0, 0, 0, 0.3);
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
@media (max-width: 768px) {
|
| 284 |
+
.header-content {
|
| 285 |
+
flex-direction: column;
|
| 286 |
+
gap: 15px;
|
| 287 |
+
text-align: center;
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
.container {
|
| 291 |
+
padding: 20px 15px;
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
.content {
|
| 295 |
+
padding: 20px;
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
.meta-row {
|
| 299 |
+
flex-direction: column;
|
| 300 |
+
align-items: flex-start;
|
| 301 |
+
gap: 5px;
|
| 302 |
+
}
|
| 303 |
+
}
|
| 304 |
+
</style>
|
| 305 |
+
<!-- Include marked.js for markdown parsing -->
|
| 306 |
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
| 307 |
+
</head>
|
| 308 |
+
|
| 309 |
+
<body>
|
| 310 |
+
<div class="header">
|
| 311 |
+
<div class="header-content">
|
| 312 |
+
<h1>
|
| 313 |
+
<!-- <span>π</span> -->
|
| 314 |
+
<span id="documentTitle">Loading Document...</span>
|
| 315 |
+
</h1>
|
| 316 |
+
<a href="javascript:void(0)" class="back-btn" onclick="window.close()">
|
| 317 |
+
<!-- <span>β</span> -->
|
| 318 |
+
<span>Close</span>
|
| 319 |
+
</a>
|
| 320 |
+
</div>
|
| 321 |
+
</div>
|
| 322 |
+
|
| 323 |
+
<div class="container">
|
| 324 |
+
<div class="document-meta" id="documentMeta" style="display: none;">
|
| 325 |
+
<div class="meta-row">
|
| 326 |
+
<span class="meta-label">π File:</span>
|
| 327 |
+
<span class="meta-value" id="fileName"></span>
|
| 328 |
+
</div>
|
| 329 |
+
<div class="meta-row">
|
| 330 |
+
<span class="meta-label">π
Last Modified:</span>
|
| 331 |
+
<span class="meta-value" id="lastModified"></span>
|
| 332 |
+
</div>
|
| 333 |
+
<div class="meta-row">
|
| 334 |
+
<span class="meta-label">π Size:</span>
|
| 335 |
+
<span class="meta-value" id="fileSize"></span>
|
| 336 |
+
</div>
|
| 337 |
+
</div>
|
| 338 |
+
|
| 339 |
+
<div class="content">
|
| 340 |
+
<div class="loading" id="loadingState">
|
| 341 |
+
π Loading document content...
|
| 342 |
+
</div>
|
| 343 |
+
<div id="markdownContent" class="markdown-content" style="display: none;"></div>
|
| 344 |
+
</div>
|
| 345 |
+
</div>
|
| 346 |
+
|
| 347 |
+
<script>
|
| 348 |
+
class BlogViewer {
|
| 349 |
+
constructor() {
|
| 350 |
+
this.filename = this.getQueryParameter('file');
|
| 351 |
+
this.documentTitle = document.getElementById('documentTitle');
|
| 352 |
+
this.documentMeta = document.getElementById('documentMeta');
|
| 353 |
+
this.fileName = document.getElementById('fileName');
|
| 354 |
+
this.lastModified = document.getElementById('lastModified');
|
| 355 |
+
this.fileSize = document.getElementById('fileSize');
|
| 356 |
+
this.loadingState = document.getElementById('loadingState');
|
| 357 |
+
this.markdownContent = document.getElementById('markdownContent');
|
| 358 |
+
|
| 359 |
+
this.init();
|
| 360 |
+
}
|
| 361 |
+
|
| 362 |
+
getQueryParameter(name) {
|
| 363 |
+
const urlParams = new URLSearchParams(window.location.search);
|
| 364 |
+
return urlParams.get(name);
|
| 365 |
+
}
|
| 366 |
+
|
| 367 |
+
async init() {
|
| 368 |
+
if (!this.filename) {
|
| 369 |
+
this.showError('No file specified');
|
| 370 |
+
return;
|
| 371 |
+
}
|
| 372 |
+
|
| 373 |
+
try {
|
| 374 |
+
await this.loadMarkdownFile();
|
| 375 |
+
} catch (error) {
|
| 376 |
+
this.showError('Failed to load the document');
|
| 377 |
+
}
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
async loadMarkdownFile() {
|
| 381 |
+
try {
|
| 382 |
+
// First, try to get file metadata from the API
|
| 383 |
+
let fileMetadata = null;
|
| 384 |
+
try {
|
| 385 |
+
const metaResponse = await fetch('./api/blogs');
|
| 386 |
+
if (metaResponse.ok) {
|
| 387 |
+
const blogs = await metaResponse.json();
|
| 388 |
+
fileMetadata = blogs.find(blog => blog.filename === this.filename);
|
| 389 |
+
}
|
| 390 |
+
} catch (error) {
|
| 391 |
+
console.log('Could not fetch metadata from API:', error);
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
// Try to fetch the actual markdown file
|
| 395 |
+
const response = await fetch(`./blogs/${this.filename}`);
|
| 396 |
+
|
| 397 |
+
if (!response.ok) {
|
| 398 |
+
// If file doesn't exist, show sample content
|
| 399 |
+
this.showSampleContent();
|
| 400 |
+
return;
|
| 401 |
+
}
|
| 402 |
+
|
| 403 |
+
const markdownText = await response.text();
|
| 404 |
+
this.renderMarkdown(markdownText);
|
| 405 |
+
this.updateMetadata(false, fileMetadata);
|
| 406 |
+
|
| 407 |
+
} catch (error) {
|
| 408 |
+
// Fallback to sample content
|
| 409 |
+
this.showSampleContent();
|
| 410 |
+
}
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
showSampleContent() {
|
| 414 |
+
const sampleContent = this.getSampleContent();
|
| 415 |
+
this.renderMarkdown(sampleContent);
|
| 416 |
+
this.updateMetadata(true, null);
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
getSampleContent() {
|
| 420 |
+
const samples = {
|
| 421 |
+
'getting-started.md': `# Getting Started Guide
|
| 422 |
+
|
| 423 |
+
Welcome to our platform! This guide will help you get up and running quickly.
|
| 424 |
+
|
| 425 |
+
## Prerequisites
|
| 426 |
+
|
| 427 |
+
Before you begin, make sure you have:
|
| 428 |
+
|
| 429 |
+
- A modern web browser
|
| 430 |
+
- Basic understanding of web technologies
|
| 431 |
+
- Access to our platform
|
| 432 |
+
|
| 433 |
+
## Quick Start
|
| 434 |
+
|
| 435 |
+
1. **Sign up for an account**
|
| 436 |
+
- Visit our registration page
|
| 437 |
+
- Fill in your details
|
| 438 |
+
- Verify your email address
|
| 439 |
+
|
| 440 |
+
2. **Set up your profile**
|
| 441 |
+
- Add your personal information
|
| 442 |
+
- Configure your preferences
|
| 443 |
+
- Upload a profile picture
|
| 444 |
+
|
| 445 |
+
3. **Create your first project**
|
| 446 |
+
- Click on "New Project"
|
| 447 |
+
- Choose a template or start from scratch
|
| 448 |
+
- Give your project a name
|
| 449 |
+
|
| 450 |
+
## Next Steps
|
| 451 |
+
|
| 452 |
+
Once you've completed the initial setup:
|
| 453 |
+
|
| 454 |
+
- Explore our [API Documentation](./api-documentation.md)
|
| 455 |
+
- Check out the [Troubleshooting Guide](./troubleshooting.md)
|
| 456 |
+
- Join our community forums
|
| 457 |
+
|
| 458 |
+
> **Tip:** Don't forget to bookmark this documentation for easy access!
|
| 459 |
+
|
| 460 |
+
## Need Help?
|
| 461 |
+
|
| 462 |
+
If you run into any issues, please:
|
| 463 |
+
|
| 464 |
+
1. Check our FAQ section
|
| 465 |
+
2. Search our knowledge base
|
| 466 |
+
3. Contact our support team
|
| 467 |
+
|
| 468 |
+
---
|
| 469 |
+
|
| 470 |
+
*Last updated: June 28, 2025*`,
|
| 471 |
+
|
| 472 |
+
'api-documentation.md': `# API Documentation
|
| 473 |
+
|
| 474 |
+
Complete reference for all API endpoints, authentication, and usage examples.
|
| 475 |
+
|
| 476 |
+
## Authentication
|
| 477 |
+
|
| 478 |
+
All API requests require authentication using API keys.
|
| 479 |
+
|
| 480 |
+
### Getting Your API Key
|
| 481 |
+
|
| 482 |
+
1. Log into your dashboard
|
| 483 |
+
2. Navigate to Settings > API Keys
|
| 484 |
+
3. Click "Generate New Key"
|
| 485 |
+
4. Store it securely - it won't be shown again
|
| 486 |
+
|
| 487 |
+
### Using Your API Key
|
| 488 |
+
|
| 489 |
+
Include your API key in the header of all requests:
|
| 490 |
+
|
| 491 |
+
\`\`\`
|
| 492 |
+
Authorization: Bearer YOUR_API_KEY
|
| 493 |
+
Content-Type: application/json
|
| 494 |
+
\`\`\`
|
| 495 |
+
|
| 496 |
+
## Base URL
|
| 497 |
+
|
| 498 |
+
All API endpoints are relative to:
|
| 499 |
+
\`\`\`
|
| 500 |
+
https://api.yourcompany.com/v1/
|
| 501 |
+
\`\`\`
|
| 502 |
+
|
| 503 |
+
## Endpoints
|
| 504 |
+
|
| 505 |
+
### Users
|
| 506 |
+
|
| 507 |
+
#### Get User Profile
|
| 508 |
+
\`\`\`
|
| 509 |
+
GET /users/me
|
| 510 |
+
\`\`\`
|
| 511 |
+
|
| 512 |
+
**Response:**
|
| 513 |
+
\`\`\`json
|
| 514 |
+
{
|
| 515 |
+
"id": "12345",
|
| 516 |
+
"name": "John Doe",
|
| 517 |
+
"email": "[email protected]",
|
| 518 |
+
"created_at": "2025-01-01T00:00:00Z"
|
| 519 |
+
}
|
| 520 |
+
\`\`\`
|
| 521 |
+
|
| 522 |
+
#### Update User Profile
|
| 523 |
+
\`\`\`
|
| 524 |
+
PUT /users/me
|
| 525 |
+
\`\`\`
|
| 526 |
+
|
| 527 |
+
**Request Body:**
|
| 528 |
+
\`\`\`json
|
| 529 |
+
{
|
| 530 |
+
"name": "John Smith",
|
| 531 |
+
"email": "[email protected]"
|
| 532 |
+
}
|
| 533 |
+
\`\`\`
|
| 534 |
+
|
| 535 |
+
### Projects
|
| 536 |
+
|
| 537 |
+
#### List Projects
|
| 538 |
+
\`\`\`
|
| 539 |
+
GET /projects
|
| 540 |
+
\`\`\`
|
| 541 |
+
|
| 542 |
+
**Query Parameters:**
|
| 543 |
+
- \`limit\` (optional): Number of results (default: 10)
|
| 544 |
+
- \`offset\` (optional): Pagination offset (default: 0)
|
| 545 |
+
|
| 546 |
+
#### Create Project
|
| 547 |
+
\`\`\`
|
| 548 |
+
POST /projects
|
| 549 |
+
\`\`\`
|
| 550 |
+
|
| 551 |
+
**Request Body:**
|
| 552 |
+
\`\`\`json
|
| 553 |
+
{
|
| 554 |
+
"name": "My Project",
|
| 555 |
+
"description": "Project description",
|
| 556 |
+
"settings": {
|
| 557 |
+
"public": false
|
| 558 |
+
}
|
| 559 |
+
}
|
| 560 |
+
\`\`\`
|
| 561 |
+
|
| 562 |
+
## Error Handling
|
| 563 |
+
|
| 564 |
+
All errors follow this format:
|
| 565 |
+
|
| 566 |
+
\`\`\`json
|
| 567 |
+
{
|
| 568 |
+
"error": {
|
| 569 |
+
"code": "INVALID_REQUEST",
|
| 570 |
+
"message": "The request is invalid",
|
| 571 |
+
"details": "Additional error information"
|
| 572 |
+
}
|
| 573 |
+
}
|
| 574 |
+
\`\`\`
|
| 575 |
+
|
| 576 |
+
### Common Error Codes
|
| 577 |
+
|
| 578 |
+
| Code | Description |
|
| 579 |
+
|------|-------------|
|
| 580 |
+
| \`UNAUTHORIZED\` | Invalid or missing API key |
|
| 581 |
+
| \`FORBIDDEN\` | Insufficient permissions |
|
| 582 |
+
| \`NOT_FOUND\` | Resource not found |
|
| 583 |
+
| \`RATE_LIMITED\` | Too many requests |
|
| 584 |
+
|
| 585 |
+
## Rate Limits
|
| 586 |
+
|
| 587 |
+
- **Free Plan:** 100 requests per hour
|
| 588 |
+
- **Pro Plan:** 1,000 requests per hour
|
| 589 |
+
- **Enterprise:** Custom limits
|
| 590 |
+
|
| 591 |
+
## SDKs and Libraries
|
| 592 |
+
|
| 593 |
+
We provide official SDKs for:
|
| 594 |
+
|
| 595 |
+
- **JavaScript/Node.js:** \`npm install @yourcompany/api-client\`
|
| 596 |
+
- **Python:** \`pip install yourcompany-api\`
|
| 597 |
+
- **PHP:** Available via Composer
|
| 598 |
+
|
| 599 |
+
---
|
| 600 |
+
|
| 601 |
+
*For more examples and advanced usage, visit our [GitHub repository](https://github.com/yourcompany/api-examples).*`,
|
| 602 |
+
|
| 603 |
+
'troubleshooting.md': `# Troubleshooting Guide
|
| 604 |
+
|
| 605 |
+
Common issues and their solutions to help you resolve problems quickly.
|
| 606 |
+
|
| 607 |
+
## Authentication Issues
|
| 608 |
+
|
| 609 |
+
### Problem: "Invalid API Key" Error
|
| 610 |
+
|
| 611 |
+
**Symptoms:**
|
| 612 |
+
- Receiving 401 Unauthorized responses
|
| 613 |
+
- Error message mentions invalid API key
|
| 614 |
+
|
| 615 |
+
**Solutions:**
|
| 616 |
+
1. **Check your API key format**
|
| 617 |
+
- Ensure it starts with the correct prefix
|
| 618 |
+
- Verify there are no extra spaces or characters
|
| 619 |
+
|
| 620 |
+
2. **Verify key permissions**
|
| 621 |
+
- Log into your dashboard
|
| 622 |
+
- Check that the key has the required permissions
|
| 623 |
+
- Regenerate the key if necessary
|
| 624 |
+
|
| 625 |
+
3. **Check request headers**
|
| 626 |
+
\`\`\`
|
| 627 |
+
Authorization: Bearer YOUR_API_KEY
|
| 628 |
+
Content-Type: application/json
|
| 629 |
+
\`\`\`
|
| 630 |
+
|
| 631 |
+
### Problem: Rate Limiting
|
| 632 |
+
|
| 633 |
+
**Symptoms:**
|
| 634 |
+
- Receiving 429 Too Many Requests
|
| 635 |
+
- Requests being rejected
|
| 636 |
+
|
| 637 |
+
**Solutions:**
|
| 638 |
+
1. **Implement exponential backoff**
|
| 639 |
+
2. **Upgrade your plan** for higher limits
|
| 640 |
+
3. **Cache responses** to reduce API calls
|
| 641 |
+
|
| 642 |
+
## Connection Issues
|
| 643 |
+
|
| 644 |
+
### Problem: Timeout Errors
|
| 645 |
+
|
| 646 |
+
**Possible Causes:**
|
| 647 |
+
- Network connectivity issues
|
| 648 |
+
- Server overload
|
| 649 |
+
- Large request payloads
|
| 650 |
+
|
| 651 |
+
**Solutions:**
|
| 652 |
+
1. **Increase timeout values**
|
| 653 |
+
\`\`\`javascript
|
| 654 |
+
// Example in JavaScript
|
| 655 |
+
fetch(url, {
|
| 656 |
+
timeout: 30000 // 30 seconds
|
| 657 |
+
})
|
| 658 |
+
\`\`\`
|
| 659 |
+
|
| 660 |
+
2. **Implement retry logic**
|
| 661 |
+
\`\`\`python
|
| 662 |
+
# Example in Python
|
| 663 |
+
import time
|
| 664 |
+
import requests
|
| 665 |
+
|
| 666 |
+
def retry_request(url, max_retries=3):
|
| 667 |
+
for attempt in range(max_retries):
|
| 668 |
+
try:
|
| 669 |
+
response = requests.get(url, timeout=30)
|
| 670 |
+
return response
|
| 671 |
+
except requests.Timeout:
|
| 672 |
+
if attempt < max_retries - 1:
|
| 673 |
+
time.sleep(2 ** attempt) # Exponential backoff
|
| 674 |
+
else:
|
| 675 |
+
raise
|
| 676 |
+
\`\`\`
|
| 677 |
+
|
| 678 |
+
3. **Break large requests into smaller chunks**
|
| 679 |
+
|
| 680 |
+
## Data Issues
|
| 681 |
+
|
| 682 |
+
### Problem: Unexpected Response Format
|
| 683 |
+
|
| 684 |
+
**Symptoms:**
|
| 685 |
+
- Missing expected fields
|
| 686 |
+
- Different data types than documented
|
| 687 |
+
|
| 688 |
+
**Solutions:**
|
| 689 |
+
1. **Check API version**
|
| 690 |
+
- Ensure you're using the correct API version
|
| 691 |
+
- Update your client library if needed
|
| 692 |
+
|
| 693 |
+
2. **Validate request parameters**
|
| 694 |
+
- Check parameter names and types
|
| 695 |
+
- Review the documentation for changes
|
| 696 |
+
|
| 697 |
+
3. **Handle optional fields gracefully**
|
| 698 |
+
\`\`\`javascript
|
| 699 |
+
// Safe property access
|
| 700 |
+
const name = user.name || 'Unknown User';
|
| 701 |
+
const email = user.email || '';
|
| 702 |
+
\`\`\`
|
| 703 |
+
|
| 704 |
+
## Performance Issues
|
| 705 |
+
|
| 706 |
+
### Problem: Slow Response Times
|
| 707 |
+
|
| 708 |
+
**Common Causes:**
|
| 709 |
+
- Inefficient queries
|
| 710 |
+
- Large datasets
|
| 711 |
+
- Network latency
|
| 712 |
+
|
| 713 |
+
**Optimization Tips:**
|
| 714 |
+
1. **Use pagination**
|
| 715 |
+
\`\`\`
|
| 716 |
+
GET /api/items?limit=10&offset=0
|
| 717 |
+
\`\`\`
|
| 718 |
+
|
| 719 |
+
2. **Request only needed fields**
|
| 720 |
+
\`\`\`
|
| 721 |
+
GET /api/users?fields=id,name,email
|
| 722 |
+
\`\`\`
|
| 723 |
+
|
| 724 |
+
3. **Implement caching**
|
| 725 |
+
- Use HTTP caching headers
|
| 726 |
+
- Implement client-side caching
|
| 727 |
+
- Consider CDN for static resources
|
| 728 |
+
|
| 729 |
+
## Integration Issues
|
| 730 |
+
|
| 731 |
+
### Problem: Webhook Delivery Failures
|
| 732 |
+
|
| 733 |
+
**Symptoms:**
|
| 734 |
+
- Webhooks not being received
|
| 735 |
+
- Delayed or duplicate deliveries
|
| 736 |
+
|
| 737 |
+
**Solutions:**
|
| 738 |
+
1. **Verify endpoint availability**
|
| 739 |
+
- Ensure your endpoint is publicly accessible
|
| 740 |
+
- Check firewall and security settings
|
| 741 |
+
|
| 742 |
+
2. **Return proper HTTP status codes**
|
| 743 |
+
- Return 200 for successful processing
|
| 744 |
+
- Return 4xx for client errors
|
| 745 |
+
- Return 5xx for server errors
|
| 746 |
+
|
| 747 |
+
3. **Implement idempotency**
|
| 748 |
+
\`\`\`javascript
|
| 749 |
+
// Track processed webhook IDs
|
| 750 |
+
const processedWebhooks = new Set();
|
| 751 |
+
|
| 752 |
+
function handleWebhook(webhookId, data) {
|
| 753 |
+
if (processedWebhooks.has(webhookId)) {
|
| 754 |
+
return; // Already processed
|
| 755 |
+
}
|
| 756 |
+
|
| 757 |
+
// Process webhook
|
| 758 |
+
processWebhookData(data);
|
| 759 |
+
processedWebhooks.add(webhookId);
|
| 760 |
+
}
|
| 761 |
+
\`\`\`
|
| 762 |
+
|
| 763 |
+
## Getting Additional Help
|
| 764 |
+
|
| 765 |
+
If you're still experiencing issues:
|
| 766 |
+
|
| 767 |
+
1. **Check our status page** - [status.yourcompany.com](https://status.yourcompany.com)
|
| 768 |
+
2. **Search our community forums** - Common issues are often discussed
|
| 769 |
+
3. **Contact support** - Include:
|
| 770 |
+
- Error messages
|
| 771 |
+
- Request/response examples
|
| 772 |
+
- Timestamps
|
| 773 |
+
- Your account ID
|
| 774 |
+
|
| 775 |
+
### Support Channels
|
| 776 |
+
|
| 777 |
+
- **Email:** [email protected]
|
| 778 |
+
- **Chat:** Available in your dashboard
|
| 779 |
+
- **Phone:** +1-555-0123 (Business hours)
|
| 780 |
+
|
| 781 |
+
---
|
| 782 |
+
|
| 783 |
+
*This guide is updated regularly. Last revision: June 28, 2025*`
|
| 784 |
+
};
|
| 785 |
+
|
| 786 |
+
return samples[this.filename] || `# ${this.filename}
|
| 787 |
+
|
| 788 |
+
This is a sample document. To see actual content, add your markdown files to the \`blogs\` folder.
|
| 789 |
+
|
| 790 |
+
## Getting Started
|
| 791 |
+
|
| 792 |
+
1. Create your markdown files in the \`blogs\` directory
|
| 793 |
+
2. Add your content using standard markdown syntax
|
| 794 |
+
3. The documentation hub will automatically list and display them
|
| 795 |
+
|
| 796 |
+
## Markdown Features Supported
|
| 797 |
+
|
| 798 |
+
- **Headers** (H1-H6)
|
| 799 |
+
- **Lists** (ordered and unordered)
|
| 800 |
+
- **Links** and images
|
| 801 |
+
- **Code blocks** and inline code
|
| 802 |
+
- **Tables**
|
| 803 |
+
- **Blockquotes**
|
| 804 |
+
- And much more!
|
| 805 |
+
|
| 806 |
+
---
|
| 807 |
+
|
| 808 |
+
*Add your own content to replace this sample.*`;
|
| 809 |
+
}
|
| 810 |
+
|
| 811 |
+
renderMarkdown(markdownText) {
|
| 812 |
+
try {
|
| 813 |
+
// Configure marked options
|
| 814 |
+
marked.setOptions({
|
| 815 |
+
breaks: true,
|
| 816 |
+
gfm: true,
|
| 817 |
+
highlight: function (code, lang) {
|
| 818 |
+
// Simple syntax highlighting placeholder
|
| 819 |
+
return code;
|
| 820 |
+
}
|
| 821 |
+
});
|
| 822 |
+
|
| 823 |
+
const htmlContent = marked.parse(markdownText);
|
| 824 |
+
|
| 825 |
+
this.loadingState.style.display = 'none';
|
| 826 |
+
this.markdownContent.innerHTML = htmlContent;
|
| 827 |
+
this.markdownContent.style.display = 'block';
|
| 828 |
+
|
| 829 |
+
// Update page title
|
| 830 |
+
const firstHeading = this.markdownContent.querySelector('h1');
|
| 831 |
+
if (firstHeading) {
|
| 832 |
+
this.documentTitle.textContent = firstHeading.textContent;
|
| 833 |
+
document.title = firstHeading.textContent;
|
| 834 |
+
} else {
|
| 835 |
+
this.documentTitle.textContent = this.filename;
|
| 836 |
+
document.title = this.filename;
|
| 837 |
+
}
|
| 838 |
+
} catch (error) {
|
| 839 |
+
this.showError('Failed to render the document');
|
| 840 |
+
}
|
| 841 |
+
}
|
| 842 |
+
|
| 843 |
+
updateMetadata(isSample = false, fileMetadata = null) {
|
| 844 |
+
this.fileName.textContent = this.filename;
|
| 845 |
+
|
| 846 |
+
if (fileMetadata) {
|
| 847 |
+
// Use actual file metadata from server
|
| 848 |
+
this.lastModified.textContent = fileMetadata.lastModified;
|
| 849 |
+
this.fileSize.textContent = fileMetadata.size;
|
| 850 |
+
} else if (isSample) {
|
| 851 |
+
// Sample content
|
| 852 |
+
this.lastModified.textContent = new Date().toLocaleDateString();
|
| 853 |
+
this.fileSize.textContent = 'Sample Content';
|
| 854 |
+
} else {
|
| 855 |
+
// Fallback when no metadata available
|
| 856 |
+
this.lastModified.textContent = new Date().toLocaleDateString();
|
| 857 |
+
this.fileSize.textContent = 'File Size Unavailable';
|
| 858 |
+
}
|
| 859 |
+
|
| 860 |
+
this.documentMeta.style.display = 'block';
|
| 861 |
+
}
|
| 862 |
+
|
| 863 |
+
showError(message) {
|
| 864 |
+
this.loadingState.style.display = 'none';
|
| 865 |
+
this.markdownContent.innerHTML = `
|
| 866 |
+
<div class="error">
|
| 867 |
+
<h3>β οΈ Error</h3>
|
| 868 |
+
<p>${message}</p>
|
| 869 |
+
<p>Please check if the file exists in the blogs directory.</p>
|
| 870 |
+
</div>
|
| 871 |
+
`;
|
| 872 |
+
this.markdownContent.style.display = 'block';
|
| 873 |
+
this.documentTitle.textContent = 'Error Loading Document';
|
| 874 |
+
}
|
| 875 |
+
}
|
| 876 |
+
|
| 877 |
+
// Initialize the blog viewer
|
| 878 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 879 |
+
new BlogViewer();
|
| 880 |
+
});
|
| 881 |
+
</script>
|
| 882 |
+
</body>
|
| 883 |
+
|
| 884 |
+
</html>
|
blogs/azure-app-service.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Azure App Service Hosting Guide
|
| 2 |
+
|
| 3 |
+
In this guide, we will walk through the process of setting up Azure App Service hosting for your application. This includes creating a new App Service plan, configuring the App Service, and setting up networking and private endpoints. Let's dive right in!
|
| 4 |
+
|
| 5 |
+
## Step 1: Create a New App Service Plan
|
| 6 |
+
|
| 7 |
+
### 1.1 Navigate to the Azure Portal
|
| 8 |
+
|
| 9 |
+
* Go to your **Azure Portal**.
|
| 10 |
+
* Select **App Service Plans** under the **App Services** section.
|
| 11 |
+
|
| 12 |
+
### 1.2 Create the Plan
|
| 13 |
+
|
| 14 |
+
* Click on **+ Add** to create a new App Service Plan.
|
| 15 |
+
* Under **Resource Group**, select the existing group `DBS_HDU_MAIN`.
|
| 16 |
+
* Enter a unique **name** for your plan.
|
| 17 |
+
* Choose the **Operating System** (Windows or Linux).
|
| 18 |
+
* Set the **Region** to **Central India**.
|
| 19 |
+
* Select a pricing plan that fits your requirements. Ensure the plan supports up to 16 GB of RAM (no GUP).
|
| 20 |
+
|
| 21 |
+
### 1.3 Finalize and Create
|
| 22 |
+
|
| 23 |
+
* Once all fields are filled, click on **Create** to provision the plan.
|
| 24 |
+
|
| 25 |
+
## Step 2: Configure the App Service
|
| 26 |
+
|
| 27 |
+
### 2.1 Create the App Service
|
| 28 |
+
|
| 29 |
+
* Navigate to the **App Services** section in the Azure portal.
|
| 30 |
+
* Under **Resource Group**, select the same group `DBS_HDU_MAIN`.
|
| 31 |
+
* Enter an **App Name** for your application.
|
| 32 |
+
* Select **Code** as the hosting option.
|
| 33 |
+
* Choose the **Runtime Stack** (such as Node.js, Python, etc.).
|
| 34 |
+
* Select **Linux** for the operating system.
|
| 35 |
+
* In the **Pricing Plan** section, select the plan created in Step 1.
|
| 36 |
+
|
| 37 |
+
### 2.2 Network Configuration
|
| 38 |
+
|
| 39 |
+
* **Enable Public Access** should be turned **off**.
|
| 40 |
+
* Turn on **Virtual Network Integration** and select **Default**.
|
| 41 |
+
* Under **Inbound Access**, turn on the switch.
|
| 42 |
+
* Enter a **Private Endpoint Name** under Inbound Access.
|
| 43 |
+
* Select an **Inbound Subnet** from the available options.
|
| 44 |
+
* Set **DNS** to **Manual**.
|
| 45 |
+
* Enable **Outbound Access** and select the available option.
|
| 46 |
+
|
| 47 |
+
### 2.3 Review and Create
|
| 48 |
+
|
| 49 |
+
* Once you have reviewed the settings, click on **Review + Create**.
|
| 50 |
+
* If there are no issues, click on **Create** to deploy the app.
|
| 51 |
+
|
| 52 |
+
> **Note:** The process might fail at first, but don't worryβyour app will still be deployed. Just proceed to the next steps.
|
| 53 |
+
|
| 54 |
+
## Step 3: Add Private Endpoint (If Required)
|
| 55 |
+
|
| 56 |
+
If the private endpoint is not showing up, you will need to manually configure it.
|
| 57 |
+
|
| 58 |
+
### 3.1 Access Networking Settings
|
| 59 |
+
|
| 60 |
+
* Go to the **App Service** settings page.
|
| 61 |
+
* Under **Networking**, check if there is a private endpoint listed.
|
| 62 |
+
|
| 63 |
+
* If it shows **0**, click on **Add New (Advanced)** to configure the endpoint.
|
| 64 |
+
|
| 65 |
+
### 3.2 Configure the Private Endpoint
|
| 66 |
+
|
| 67 |
+
* Under **Resource Group**, select `DBS_HDU_MAIN`.
|
| 68 |
+
* Fill in the necessary **Instance Details**.
|
| 69 |
+
* In the **Virtual Network** tab, select the appropriate subnet with an available IP.
|
| 70 |
+
* Go to **DNS**, and select **GTM-LZ-Central India** for the subscription.
|
| 71 |
+
* The **Resource Group** will be auto-selected.
|
| 72 |
+
|
| 73 |
+
### 3.3 Review and Create
|
| 74 |
+
|
| 75 |
+
* After reviewing the settings, click **Create**.
|
| 76 |
+
|
| 77 |
+
## Step 4: Clean Up Unused Private Endpoints
|
| 78 |
+
|
| 79 |
+
To ensure proper networking setup, make sure you have only one active private endpoint.
|
| 80 |
+
|
| 81 |
+
* If there are any unused private endpoints, remove them from the configuration.
|
| 82 |
+
|
| 83 |
+
## Final Thoughts
|
| 84 |
+
|
| 85 |
+
After completing these steps, your app should be successfully hosted on Azure with private network access configured. Keep an eye on the settings, especially the private endpoints, to ensure smooth performance and secure networking for your application.
|
blogs/azure-vm-setup.md
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Azure Virtual Machine Setup and Troubleshooting Guide
|
| 2 |
+
|
| 3 |
+
## Point of Contact
|
| 4 |
+
|
| 5 |
+
For any issues related to Azure instances or troubleshooting errors that persist even after referring to the Standard Operating Procedure (SOP) document, please reach out to the following contacts:
|
| 6 |
+
|
| 7 |
+
* **Git-Content Team**: [[email protected]](mailto:[email protected])
|
| 8 |
+
* **GIT Cloud-LZSupport-Azure**: [[email protected]](mailto:[email protected])
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
## Introduction
|
| 13 |
+
|
| 14 |
+
Setting up and creating an Azure Virtual Machine (AVM) requires a systematic approach. In this guide, we will cover the key steps involved in the process and provide troubleshooting advice for common issues that may arise.
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
## Steps for Creating and Configuring AVM
|
| 19 |
+
|
| 20 |
+
### 1. Configure VM
|
| 21 |
+
|
| 22 |
+
* **URL**: [Azure Virtual Desktop](https://client.wvd.microsoft.com/arm/webclient/index.html)
|
| 23 |
+
|
| 24 |
+
#### Process:
|
| 25 |
+
|
| 26 |
+
1. Get added to the **Desktop (DBS-HDU\_POC)** group. Once access is granted (usually within 24 hours), you will be able to log in to Azure Virtual Desktop.
|
| 27 |
+
|
| 28 |
+
2. Double-click on the Desktop shortcut to access the Virtual Machine.
|
| 29 |
+
|
| 30 |
+
3. **Common Issue**: If the connection fails on the first attempt, minimize the window and enter your HCL credentials when prompted. If the login window does not appear automatically, it will show once you minimize the screen.
|
| 31 |
+
|
| 32 |
+
4. Once connected, visit the Azure portal by navigating to [Azure Portal](https://portal.azure.com/) and enter your credentials to access your environment.
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
### 2. Switch to HCL Directory
|
| 37 |
+
|
| 38 |
+
If you are logged into a client directory, follow these steps to switch to the HCL directory:
|
| 39 |
+
|
| 40 |
+
1. Click on your profile picture at the top right.
|
| 41 |
+
2. Select **Switch Directory** and ensure the **HCL TECH** directory is active.
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
### 3. Create and Use AVM
|
| 46 |
+
|
| 47 |
+
To create the AVM using an existing resource group (e.g., `RG-Prabhat`), follow these steps:
|
| 48 |
+
|
| 49 |
+
1. In the Azure Portal, click **Virtual Machine** from the homepage.
|
| 50 |
+
2. Select **Create** > **Azure Virtual Machine**.
|
| 51 |
+
3. Choose the **Existing Resource Group** (e.g., `RG-Prabhat`).
|
| 52 |
+
4. Under **Image**, select **Shared Images** and pick the appropriate image.
|
| 53 |
+
5. **Networking**:
|
| 54 |
+
|
| 55 |
+
* Select the Virtual Network: `DBS-HDU-spoke-vnet`
|
| 56 |
+
* Subnet: `DBS-HDU-pep-subnet (10.131.108.48/28)`
|
| 57 |
+
* Public IP: **None**
|
| 58 |
+
6. Click **Create** to finalize the VM creation.
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
### 4. Configuring AVM
|
| 63 |
+
|
| 64 |
+
#### Windows Proxy Settings
|
| 65 |
+
|
| 66 |
+
To ensure smooth connectivity, you need to set up proxy configurations and environment variables.
|
| 67 |
+
|
| 68 |
+
1. **Configure Proxy Settings**:
|
| 69 |
+
|
| 70 |
+
* Contact **[[email protected]](mailto:[email protected])** for assistance in enabling URL access or solving connectivity issues.
|
| 71 |
+
|
| 72 |
+
2. **System Variables**:
|
| 73 |
+
|
| 74 |
+
* Edit the system variables to include `http_proxy` and `https_proxy` with the value: `http://199.19.250.205:80`.
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
#### PIP.ini File for Trusted Hosts
|
| 79 |
+
|
| 80 |
+
1. Navigate to the **AVM** and open **This PC** > **Disk C** > **Temp**.
|
| 81 |
+
|
| 82 |
+
2. Create a new text file, add the following content, and rename it to **pip.ini**:
|
| 83 |
+
|
| 84 |
+
```ini
|
| 85 |
+
[global]
|
| 86 |
+
trusted-host = pypi.python.org
|
| 87 |
+
pypi.org
|
| 88 |
+
files.pythonhosted.org
|
| 89 |
+
huggingface.co
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
3. Save the file.
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
#### SSL Certificate Installation
|
| 97 |
+
|
| 98 |
+
1. Install the SSL certificates by following the **Certificate Installation SOP** document.
|
| 99 |
+
2. **ActiveX Control**: Ensure ActiveX control is enabled on the machine.
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
#### Proxy Configuration in VS Code
|
| 104 |
+
|
| 105 |
+
1. Open **VS Code** and navigate to **Settings**.
|
| 106 |
+
2. Search for **Proxy** and edit the proxy settings as instructed in the SOP.
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
### 5. Common Errors and Solutions
|
| 111 |
+
|
| 112 |
+
#### Error: **Pip Installation SSL Certificate Issues**
|
| 113 |
+
|
| 114 |
+
If you encounter SSL certificate errors during `pip install`:
|
| 115 |
+
|
| 116 |
+
1. Open the certificate file provided in the SOP as **Notepad\_1**.
|
| 117 |
+
|
| 118 |
+
2. In **VS Code Terminal**, run the following command:
|
| 119 |
+
|
| 120 |
+
```bash
|
| 121 |
+
pip install --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org certify
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
3. Run the following Python code:
|
| 125 |
+
|
| 126 |
+
```python
|
| 127 |
+
import certify
|
| 128 |
+
print(certify.where())
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
4. Copy the file path output and open the corresponding file in Notepad (**Notepad\_2**).
|
| 132 |
+
|
| 133 |
+
5. Append the contents from **Notepad\_1** to the end of **Notepad\_2** and save the file.
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
#### Error: **Private Endpoint Error**
|
| 138 |
+
|
| 139 |
+
If you receive an error such as:
|
| 140 |
+
|
| 141 |
+
```
|
| 142 |
+
openai.PermissionDeniedError: Error code: 403 - {'error': {'code': '403', 'message': 'Public access is disabled. Please configure private endpoint.'}}
|
| 143 |
+
```
|
| 144 |
+
|
| 145 |
+
Solution: Use the following one-liner code to bypass this error:
|
| 146 |
+
|
| 147 |
+
```python
|
| 148 |
+
os.environ["NO_PROXY"] = "https://prabhatopenaiservice.openai.azure.com"
|
| 149 |
+
```
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
### 6. Existing AVM from Resource Group
|
| 154 |
+
|
| 155 |
+
To access an existing AVM from a resource group:
|
| 156 |
+
|
| 157 |
+
1. In the **Azure Portal**, click on the **Resource Group**.
|
| 158 |
+
2. Select **Virtual Machine** and click **Connect**.
|
| 159 |
+
3. Download the **RDP** file and open it.
|
| 160 |
+
4. Enter your credentials to connect to the VM.
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
### 7. Test Functionality
|
| 165 |
+
|
| 166 |
+
Once your AVM is configured, run the following tests to ensure everything is set up correctly:
|
| 167 |
+
|
| 168 |
+
#### Test 1: Pip Installation and API Access
|
| 169 |
+
|
| 170 |
+
```python
|
| 171 |
+
import os
|
| 172 |
+
from dotenv import load_dotenv
|
| 173 |
+
from langchain_openai import AzureChatOpenAI
|
| 174 |
+
from langchain_core.messages import SystemMessage, HumanMessage
|
| 175 |
+
|
| 176 |
+
# Load environment variables
|
| 177 |
+
load_dotenv()
|
| 178 |
+
os.environ["NO_PROXY"] = "https://prabhatopenaiservice.openai.azure.com"
|
| 179 |
+
|
| 180 |
+
# Initialize OpenAI API client
|
| 181 |
+
AZURE_OPENAI_API_KEY = os.getenv("AZURE_OPENAI_API_KEY")
|
| 182 |
+
AZURE_OPENAI_ENDPOINT = os.getenv("AZURE_OPENAI_ENDPOINT")
|
| 183 |
+
AZURE_OPENAI_DEPLOYMENT_NAME = os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME")
|
| 184 |
+
AZURE_OPENAI_API_VERSION = os.getenv("AZURE_OPENAI_API_VERSION")
|
| 185 |
+
AZURE_OPENAI_TEMPERATURE = float(os.getenv("AZURE_OPENAI_TEMPERATURE"))
|
| 186 |
+
|
| 187 |
+
llm = AzureChatOpenAI(
|
| 188 |
+
azure_endpoint=AZURE_OPENAI_ENDPOINT,
|
| 189 |
+
azure_deployment=AZURE_OPENAI_DEPLOYMENT_NAME,
|
| 190 |
+
api_key=AZURE_OPENAI_API_KEY,
|
| 191 |
+
api_version=AZURE_OPENAI_API_VERSION,
|
| 192 |
+
temperature=AZURE_OPENAI_TEMPERATURE,
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
# Test querying a document
|
| 196 |
+
with open("demo.txt", "r", encoding="utf-8") as f:
|
| 197 |
+
document_content = f.read()
|
| 198 |
+
|
| 199 |
+
system_prompt = "You are a helpful assistant that answers questions based on the given document."
|
| 200 |
+
user_query = "Who is the highest run scorer in BGT history?"
|
| 201 |
+
|
| 202 |
+
messages = [
|
| 203 |
+
SystemMessage(content=system_prompt),
|
| 204 |
+
HumanMessage(content=f"Document:\n{document_content}\n\nQuestion: {user_query}")
|
| 205 |
+
]
|
| 206 |
+
|
| 207 |
+
response = llm.invoke(messages)
|
| 208 |
+
print(response.content)
|
| 209 |
+
```
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
#### Test 2: Hugging Face Model and Embeddings
|
| 214 |
+
|
| 215 |
+
```python
|
| 216 |
+
import transformers
|
| 217 |
+
|
| 218 |
+
pipeline = transformers.pipeline(
|
| 219 |
+
"text-generation",
|
| 220 |
+
model="microsoft/phi-4",
|
| 221 |
+
model_kwargs={"torch_dtype": "auto"},
|
| 222 |
+
device_map="auto",
|
| 223 |
+
)
|
| 224 |
+
|
| 225 |
+
messages = [
|
| 226 |
+
{"role": "system", "content": "You are a medieval knight and must provide explanations to modern people."},
|
| 227 |
+
{"role": "user", "content": "How should I explain the Internet?"},
|
| 228 |
+
]
|
| 229 |
+
|
| 230 |
+
outputs = pipeline(messages, max_new_tokens=128)
|
| 231 |
+
print(outputs[0]["generated_text"][-1])
|
| 232 |
+
```
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
#### Test 3: FAISS Indexing and Search
|
| 237 |
+
|
| 238 |
+
```python
|
| 239 |
+
import time
|
| 240 |
+
import faiss
|
| 241 |
+
import numpy as np
|
| 242 |
+
|
| 243 |
+
# Start test
|
| 244 |
+
start_time = time.time()
|
| 245 |
+
|
| 246 |
+
# Create dummy vectors
|
| 247 |
+
embedding_dim = 384
|
| 248 |
+
num_vectors = 100
|
| 249 |
+
db_vectors = np.random.random((num_vectors, embedding_dim)).astype('float32')
|
| 250 |
+
query_vector = np.random.random((1, embedding_dim)).astype('float32')
|
| 251 |
+
|
| 252 |
+
# Build FAISS index
|
| 253 |
+
index = faiss.IndexFlatL2(embedding_dim)
|
| 254 |
+
index.add(db_vectors)
|
| 255 |
+
|
| 256 |
+
# Perform search
|
| 257 |
+
k = 5
|
| 258 |
+
distances, indices = index.search(query_vector, k)
|
| 259 |
+
print(f"Search results: {distances}, {indices}")
|
| 260 |
+
|
| 261 |
+
# End test
|
| 262 |
+
end_time = time.time()
|
| 263 |
+
print(f"Test 3 Finished in {end_time - start_time:.2f} seconds")
|
| 264 |
+
```
|
blogs/itimesheet.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# How to Fill Timesheets
|
| 2 |
+
|
| 3 |
+
Filling out your timesheet in the **MyHCL** portal can be straightforward if you follow the right steps. Whether you're new to the portal or just need a refresher, hereβs a quick guide to help you log your hours seamlessly.
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
## π Step 1: Log in to MyHCL
|
| 8 |
+
|
| 9 |
+
Start by visiting [**myhcl.com**](https://www.myhcl.com) and log in using your HCL credentials.
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
## π Step 2: Search for iTimesheet
|
| 14 |
+
|
| 15 |
+
Once logged in:
|
| 16 |
+
|
| 17 |
+
* Locate the **search bar** on the homepage.
|
| 18 |
+
* Type **iTimesheet** into the search field.
|
| 19 |
+
* Click on the **iTimesheet** link from the results.
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
## β Step 3: Add/Edit Project, Phase & Activity
|
| 24 |
+
|
| 25 |
+
After opening the iTimesheet portal:
|
| 26 |
+
|
| 27 |
+
1. Click on **Add/Edit Project/Phase/Activity**.
|
| 28 |
+
|
| 29 |
+
2. A modal window will appear. In this window:
|
| 30 |
+
|
| 31 |
+
* Select **Project**: Choose `hclt`
|
| 32 |
+
* Select your assigned project: `DBS GENAI`
|
| 33 |
+
* Choose the appropriate **Phase**
|
| 34 |
+
* Choose the relevant `Activity`
|
| 35 |
+
|
| 36 |
+
3. Click **Save and Proceed** to continue.
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
## π Step 4: Fill Your Timesheet
|
| 41 |
+
|
| 42 |
+
Now that your project details are set up:
|
| 43 |
+
|
| 44 |
+
* Navigate to the respective date slots.
|
| 45 |
+
* Enter your working hours or activities as required.
|
| 46 |
+
* Double-check for accuracy.
|
| 47 |
+
|
| 48 |
+
> β οΈ **Important Note:**
|
| 49 |
+
> Make sure to fill your timesheet **only for the current week**.
|
| 50 |
+
> Filling out past or future weeks may result in being **marked absent**.
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
## β
Step 5: Submit for Approval
|
| 55 |
+
|
| 56 |
+
Once you have filled in all your time entries:
|
| 57 |
+
|
| 58 |
+
* Click on **Submit for Approval**.
|
| 59 |
+
|
| 60 |
+
Your timesheet will now be forwarded to your reporting manager or supervisor for approval.
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
## π Thatβs It!
|
| 65 |
+
|
| 66 |
+
By following these steps, you'll ensure your time is accurately logged and submitted on time. Make it a weekly habit to avoid last-minute rushes or missed deadlines.
|
| 67 |
+
|
| 68 |
+
Happy working! πΌ
|
index.html
ADDED
|
@@ -0,0 +1,598 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>AI HDU - Self Help Desk</title>
|
| 8 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 9 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Lexend:[email protected]&display=swap" rel="stylesheet">
|
| 11 |
+
<style>
|
| 12 |
+
* {
|
| 13 |
+
margin: 0;
|
| 14 |
+
padding: 0;
|
| 15 |
+
box-sizing: border-box;
|
| 16 |
+
font-family: "Lexend", sans-serif;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
body {
|
| 20 |
+
/* font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', Roboto, sans-serif; */
|
| 21 |
+
line-height: 1.5;
|
| 22 |
+
color: #1d1d1f;
|
| 23 |
+
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
| 24 |
+
min-height: 100vh;
|
| 25 |
+
overflow-x: hidden;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
/* Top Navigation Bar */
|
| 29 |
+
.top-nav {
|
| 30 |
+
position: fixed;
|
| 31 |
+
top: 0;
|
| 32 |
+
left: 0;
|
| 33 |
+
right: 0;
|
| 34 |
+
height: 60px;
|
| 35 |
+
background: rgba(255, 255, 255, 0.6);
|
| 36 |
+
backdrop-filter: blur(10px);
|
| 37 |
+
-webkit-backdrop-filter: blur(10px);
|
| 38 |
+
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
| 39 |
+
z-index: 1000;
|
| 40 |
+
display: flex;
|
| 41 |
+
align-items: center;
|
| 42 |
+
justify-content: center;
|
| 43 |
+
margin: 10px;
|
| 44 |
+
border-radius: 20px;
|
| 45 |
+
box-shadow: 0 0px 10px rgba(0, 0, 0, 0.1);
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
.nav-content {
|
| 49 |
+
/* max-width: 1200px; */
|
| 50 |
+
width: 100%;
|
| 51 |
+
padding: 0 24px;
|
| 52 |
+
display: flex;
|
| 53 |
+
align-items: center;
|
| 54 |
+
justify-content: space-between;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
.nav-title {
|
| 58 |
+
font-size: 1.1rem;
|
| 59 |
+
font-weight: 600;
|
| 60 |
+
color: #1d1d1f;
|
| 61 |
+
display: flex;
|
| 62 |
+
align-items: center;
|
| 63 |
+
gap: 8px;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
.nav-stats {
|
| 67 |
+
font-size: 0.85rem;
|
| 68 |
+
color: #86868b;
|
| 69 |
+
display: flex;
|
| 70 |
+
align-items: center;
|
| 71 |
+
gap: 16px;
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
/* Main Container */
|
| 75 |
+
.container {
|
| 76 |
+
max-width: 1400px;
|
| 77 |
+
margin: 0 auto;
|
| 78 |
+
padding: 80px 20px 120px;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
/* Content Grid */
|
| 82 |
+
.blog-grid {
|
| 83 |
+
display: grid;
|
| 84 |
+
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
| 85 |
+
gap: 16px;
|
| 86 |
+
margin-top: 20px;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
.blog-card {
|
| 90 |
+
background: rgba(255, 255, 255, 0.8);
|
| 91 |
+
backdrop-filter: blur(20px);
|
| 92 |
+
-webkit-backdrop-filter: blur(20px);
|
| 93 |
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
| 94 |
+
border-radius: 12px;
|
| 95 |
+
padding: 16px;
|
| 96 |
+
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
| 97 |
+
cursor: pointer;
|
| 98 |
+
position: relative;
|
| 99 |
+
overflow: hidden;
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
.blog-card:hover {
|
| 103 |
+
transform: translateY(-2px);
|
| 104 |
+
background: rgba(255, 255, 255, 0.9);
|
| 105 |
+
border-color: rgba(0, 122, 255, 0.3);
|
| 106 |
+
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
.blog-title {
|
| 110 |
+
font-size: 1.1rem;
|
| 111 |
+
font-weight: 600;
|
| 112 |
+
color: #1d1d1f;
|
| 113 |
+
margin-bottom: 6px;
|
| 114 |
+
line-height: 1.3;
|
| 115 |
+
display: -webkit-box;
|
| 116 |
+
-webkit-line-clamp: 2;
|
| 117 |
+
line-clamp: 2;
|
| 118 |
+
-webkit-box-orient: vertical;
|
| 119 |
+
overflow: hidden;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
.blog-excerpt {
|
| 123 |
+
color: #86868b;
|
| 124 |
+
margin-bottom: 12px;
|
| 125 |
+
line-height: 1.4;
|
| 126 |
+
font-size: 0.9rem;
|
| 127 |
+
display: -webkit-box;
|
| 128 |
+
-webkit-line-clamp: 2;
|
| 129 |
+
line-clamp: 2;
|
| 130 |
+
-webkit-box-orient: vertical;
|
| 131 |
+
overflow: hidden;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
.blog-meta {
|
| 135 |
+
display: flex;
|
| 136 |
+
justify-content: space-between;
|
| 137 |
+
align-items: center;
|
| 138 |
+
font-size: 0.8rem;
|
| 139 |
+
color: #86868b;
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
.blog-date {
|
| 143 |
+
color: #007aff;
|
| 144 |
+
font-weight: 500;
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
.blog-size {
|
| 148 |
+
background: rgba(0, 122, 255, 0.1);
|
| 149 |
+
color: #007aff;
|
| 150 |
+
padding: 2px 6px;
|
| 151 |
+
border-radius: 6px;
|
| 152 |
+
font-weight: 500;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
.match-indicators {
|
| 156 |
+
font-size: 0.75rem;
|
| 157 |
+
color: #007aff;
|
| 158 |
+
margin-bottom: 6px;
|
| 159 |
+
font-weight: 500;
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
/* Floating Search Bar */
|
| 163 |
+
.search-container {
|
| 164 |
+
position: fixed;
|
| 165 |
+
bottom: 24px;
|
| 166 |
+
left: 50%;
|
| 167 |
+
transform: translateX(-50%);
|
| 168 |
+
background: rgba(255, 255, 255, 0.1);
|
| 169 |
+
backdrop-filter: blur(1px);
|
| 170 |
+
-webkit-backdrop-filter: blur(1px);
|
| 171 |
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
| 172 |
+
border-radius: 20px;
|
| 173 |
+
padding: 20px;
|
| 174 |
+
max-width: 500px;
|
| 175 |
+
width: calc(100% - 48px);
|
| 176 |
+
box-shadow: 0 0px 10px rgba(0, 0, 0, 0.1);
|
| 177 |
+
z-index: 999;
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
.search-box {
|
| 181 |
+
position: relative;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
.search-input {
|
| 185 |
+
width: 100%;
|
| 186 |
+
padding: 10px 15px;
|
| 187 |
+
font-size: 1rem;
|
| 188 |
+
border: none;
|
| 189 |
+
background: rgba(255, 255, 255, 0.9);
|
| 190 |
+
border-radius: 10px;
|
| 191 |
+
outline: none;
|
| 192 |
+
transition: all 0.3s ease;
|
| 193 |
+
color: #1d1d1f;
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
.search-input::placeholder {
|
| 197 |
+
color: #86868b;
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
.search-input:focus {
|
| 201 |
+
background: rgba(255, 255, 255, 0.9);
|
| 202 |
+
box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.3);
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
.search-icon {
|
| 206 |
+
position: absolute;
|
| 207 |
+
right: 12px;
|
| 208 |
+
top: 50%;
|
| 209 |
+
transform: translateY(-50%);
|
| 210 |
+
color: #86868b;
|
| 211 |
+
font-size: 1rem;
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
.search-highlight {
|
| 215 |
+
background: rgba(255, 214, 10, 0.7);
|
| 216 |
+
color: #1d1d1f;
|
| 217 |
+
padding: 1px 2px;
|
| 218 |
+
border-radius: 3px;
|
| 219 |
+
font-weight: 600;
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
.no-results {
|
| 223 |
+
text-align: center;
|
| 224 |
+
padding: 60px 20px;
|
| 225 |
+
color: #86868b;
|
| 226 |
+
background: rgba(255, 255, 255, 0.6);
|
| 227 |
+
backdrop-filter: blur(20px);
|
| 228 |
+
-webkit-backdrop-filter: blur(20px);
|
| 229 |
+
border-radius: 16px;
|
| 230 |
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
.no-results h3 {
|
| 234 |
+
font-size: 1.3rem;
|
| 235 |
+
margin-bottom: 8px;
|
| 236 |
+
color: #1d1d1f;
|
| 237 |
+
font-weight: 600;
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
.loading {
|
| 241 |
+
text-align: center;
|
| 242 |
+
padding: 40px;
|
| 243 |
+
color: #007aff;
|
| 244 |
+
font-size: 1rem;
|
| 245 |
+
background: rgba(255, 255, 255, 0.6);
|
| 246 |
+
backdrop-filter: blur(20px);
|
| 247 |
+
-webkit-backdrop-filter: blur(20px);
|
| 248 |
+
border-radius: 16px;
|
| 249 |
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
.error {
|
| 253 |
+
background: rgba(255, 59, 48, 0.1);
|
| 254 |
+
border: 1px solid rgba(255, 59, 48, 0.3);
|
| 255 |
+
color: #d70015;
|
| 256 |
+
padding: 16px;
|
| 257 |
+
border-radius: 12px;
|
| 258 |
+
margin: 20px 0;
|
| 259 |
+
backdrop-filter: blur(20px);
|
| 260 |
+
-webkit-backdrop-filter: blur(20px);
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
/* Smooth scrolling */
|
| 264 |
+
html {
|
| 265 |
+
scroll-behavior: smooth;
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
/* Custom scrollbar */
|
| 269 |
+
::-webkit-scrollbar {
|
| 270 |
+
width: 8px;
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
::-webkit-scrollbar-track {
|
| 274 |
+
background: rgba(255, 255, 255, 0.1);
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
::-webkit-scrollbar-thumb {
|
| 278 |
+
background: rgba(0, 0, 0, 0.2);
|
| 279 |
+
border-radius: 4px;
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
::-webkit-scrollbar-thumb:hover {
|
| 283 |
+
background: rgba(0, 0, 0, 0.3);
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
@media (max-width: 768px) {
|
| 287 |
+
.container {
|
| 288 |
+
padding: 80px 16px 120px;
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
.blog-grid {
|
| 292 |
+
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
| 293 |
+
gap: 12px;
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
.nav-content {
|
| 297 |
+
padding: 0 16px;
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
.nav-stats {
|
| 301 |
+
flex-direction: column;
|
| 302 |
+
gap: 4px;
|
| 303 |
+
align-items: flex-end;
|
| 304 |
+
font-size: 0.8rem;
|
| 305 |
+
}
|
| 306 |
+
|
| 307 |
+
.search-container {
|
| 308 |
+
bottom: 16px;
|
| 309 |
+
width: calc(100% - 32px);
|
| 310 |
+
}
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
@media (max-width: 480px) {
|
| 314 |
+
.blog-grid {
|
| 315 |
+
grid-template-columns: 1fr;
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
.nav-stats {
|
| 319 |
+
display: none;
|
| 320 |
+
}
|
| 321 |
+
}
|
| 322 |
+
</style>
|
| 323 |
+
</head>
|
| 324 |
+
|
| 325 |
+
<body>
|
| 326 |
+
<!-- Top Navigation -->
|
| 327 |
+
<div class="top-nav">
|
| 328 |
+
<div class="nav-content">
|
| 329 |
+
<div class="nav-title">
|
| 330 |
+
<span>π€</span>
|
| 331 |
+
<span>Artificial Intelligence Horizontal Division Unit - Self Help Desk</span>
|
| 332 |
+
</div>
|
| 333 |
+
<div class="nav-stats">
|
| 334 |
+
<span id="resultsCount">Loading...</span>
|
| 335 |
+
<span id="lastUpdated"></span>
|
| 336 |
+
</div>
|
| 337 |
+
</div>
|
| 338 |
+
</div>
|
| 339 |
+
|
| 340 |
+
<!-- Main Container -->
|
| 341 |
+
<div class="container">
|
| 342 |
+
<div id="blogContainer">
|
| 343 |
+
<div class="loading">
|
| 344 |
+
π Loading documentation files...
|
| 345 |
+
</div>
|
| 346 |
+
</div>
|
| 347 |
+
</div>
|
| 348 |
+
|
| 349 |
+
<!-- Floating Search Bar -->
|
| 350 |
+
<div class="search-container">
|
| 351 |
+
<div class="search-box">
|
| 352 |
+
<input type="text" class="search-input" placeholder="Search documentation..." id="searchInput">
|
| 353 |
+
<span class="search-icon">π</span>
|
| 354 |
+
</div>
|
| 355 |
+
</div>
|
| 356 |
+
|
| 357 |
+
<script>
|
| 358 |
+
class DocumentationHub {
|
| 359 |
+
constructor() {
|
| 360 |
+
this.blogs = [];
|
| 361 |
+
this.filteredBlogs = [];
|
| 362 |
+
this.allBlogsCache = []; // Cache for offline search
|
| 363 |
+
this.searchInput = document.getElementById('searchInput');
|
| 364 |
+
this.blogContainer = document.getElementById('blogContainer');
|
| 365 |
+
this.resultsCount = document.getElementById('resultsCount');
|
| 366 |
+
this.lastUpdated = document.getElementById('lastUpdated');
|
| 367 |
+
this.searchTimeout = null; // For debouncing
|
| 368 |
+
|
| 369 |
+
this.init();
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
async init() {
|
| 373 |
+
await this.loadBlogs();
|
| 374 |
+
this.setupEventListeners();
|
| 375 |
+
this.renderBlogs();
|
| 376 |
+
this.updateStats();
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
async loadBlogs() {
|
| 380 |
+
try {
|
| 381 |
+
// Try to fetch from the local API first
|
| 382 |
+
try {
|
| 383 |
+
const response = await fetch('/api/blogs');
|
| 384 |
+
if (response.ok) {
|
| 385 |
+
this.blogs = await response.json();
|
| 386 |
+
this.allBlogsCache = [...this.blogs]; // Cache for offline search
|
| 387 |
+
this.filteredBlogs = [...this.blogs];
|
| 388 |
+
return;
|
| 389 |
+
}
|
| 390 |
+
} catch (error) {
|
| 391 |
+
console.log('Local API not available, using sample data');
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
// Fallback to sample data
|
| 395 |
+
const sampleBlogs = [
|
| 396 |
+
{
|
| 397 |
+
filename: 'getting-started.md',
|
| 398 |
+
title: 'Getting Started Guide',
|
| 399 |
+
excerpt: 'Learn how to get started with our platform and set up your first project.',
|
| 400 |
+
size: '2.5 KB',
|
| 401 |
+
lastModified: '2025-06-20'
|
| 402 |
+
},
|
| 403 |
+
{
|
| 404 |
+
filename: 'api-documentation.md',
|
| 405 |
+
title: 'API Documentation',
|
| 406 |
+
excerpt: 'Complete reference for all API endpoints, authentication, and examples.',
|
| 407 |
+
size: '15.2 KB',
|
| 408 |
+
lastModified: '2025-06-25'
|
| 409 |
+
},
|
| 410 |
+
{
|
| 411 |
+
filename: 'troubleshooting.md',
|
| 412 |
+
title: 'Troubleshooting Guide',
|
| 413 |
+
excerpt: 'Common issues and their solutions to help you resolve problems quickly.',
|
| 414 |
+
size: '8.7 KB',
|
| 415 |
+
lastModified: '2025-06-22'
|
| 416 |
+
}
|
| 417 |
+
];
|
| 418 |
+
|
| 419 |
+
this.blogs = sampleBlogs;
|
| 420 |
+
this.allBlogsCache = [...sampleBlogs];
|
| 421 |
+
this.filteredBlogs = [...this.blogs];
|
| 422 |
+
} catch (error) {
|
| 423 |
+
this.showError('Failed to load documentation files');
|
| 424 |
+
}
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
setupEventListeners() {
|
| 428 |
+
this.searchInput.addEventListener('input', (e) => {
|
| 429 |
+
// Debounce search for better performance
|
| 430 |
+
if (this.searchTimeout) {
|
| 431 |
+
clearTimeout(this.searchTimeout);
|
| 432 |
+
}
|
| 433 |
+
this.searchTimeout = setTimeout(() => {
|
| 434 |
+
this.searchBlogs(e.target.value);
|
| 435 |
+
}, 300);
|
| 436 |
+
});
|
| 437 |
+
|
| 438 |
+
this.searchInput.addEventListener('keypress', (e) => {
|
| 439 |
+
if (e.key === 'Enter') {
|
| 440 |
+
if (this.searchTimeout) {
|
| 441 |
+
clearTimeout(this.searchTimeout);
|
| 442 |
+
}
|
| 443 |
+
this.searchBlogs(e.target.value);
|
| 444 |
+
}
|
| 445 |
+
});
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
async searchBlogs(query) {
|
| 449 |
+
const searchTerm = query.toLowerCase().trim();
|
| 450 |
+
|
| 451 |
+
if (!searchTerm) {
|
| 452 |
+
this.filteredBlogs = [...this.allBlogsCache];
|
| 453 |
+
this.renderBlogs();
|
| 454 |
+
this.updateStats();
|
| 455 |
+
return;
|
| 456 |
+
}
|
| 457 |
+
|
| 458 |
+
// Show loading state for search
|
| 459 |
+
this.showSearchLoading();
|
| 460 |
+
|
| 461 |
+
try {
|
| 462 |
+
// Try to use server-side search API for full-text search
|
| 463 |
+
const response = await fetch(`/api/search?q=${encodeURIComponent(searchTerm)}`);
|
| 464 |
+
if (response.ok) {
|
| 465 |
+
const searchData = await response.json();
|
| 466 |
+
this.filteredBlogs = searchData.results;
|
| 467 |
+
this.renderBlogs(searchTerm);
|
| 468 |
+
this.updateStats(searchData.query);
|
| 469 |
+
return;
|
| 470 |
+
}
|
| 471 |
+
} catch (error) {
|
| 472 |
+
console.log('Server search not available, using client-side search');
|
| 473 |
+
}
|
| 474 |
+
|
| 475 |
+
// Fallback to client-side search (title, excerpt, filename only)
|
| 476 |
+
this.filteredBlogs = this.allBlogsCache.filter(blog =>
|
| 477 |
+
blog.title.toLowerCase().includes(searchTerm) ||
|
| 478 |
+
blog.excerpt.toLowerCase().includes(searchTerm) ||
|
| 479 |
+
blog.filename.toLowerCase().includes(searchTerm)
|
| 480 |
+
);
|
| 481 |
+
|
| 482 |
+
this.renderBlogs(searchTerm);
|
| 483 |
+
this.updateStats(searchTerm);
|
| 484 |
+
}
|
| 485 |
+
|
| 486 |
+
showSearchLoading() {
|
| 487 |
+
this.blogContainer.innerHTML = `
|
| 488 |
+
<div class="loading">
|
| 489 |
+
π Searching through documentation...
|
| 490 |
+
</div>
|
| 491 |
+
`;
|
| 492 |
+
}
|
| 493 |
+
|
| 494 |
+
renderBlogs(searchTerm = '') {
|
| 495 |
+
if (this.filteredBlogs.length === 0) {
|
| 496 |
+
const noResultsHTML = searchTerm
|
| 497 |
+
? `<div class="no-results">
|
| 498 |
+
<h3>π No documents found for "${searchTerm}"</h3>
|
| 499 |
+
<p>Try different keywords or browse all available documentation.</p>
|
| 500 |
+
</div>`
|
| 501 |
+
: `<div class="no-results">
|
| 502 |
+
<h3>π No documents available</h3>
|
| 503 |
+
<p>Add markdown files to the blogs directory to get started.</p>
|
| 504 |
+
</div>`;
|
| 505 |
+
|
| 506 |
+
this.blogContainer.innerHTML = noResultsHTML;
|
| 507 |
+
return;
|
| 508 |
+
}
|
| 509 |
+
|
| 510 |
+
const blogHTML = this.filteredBlogs.map(blog => {
|
| 511 |
+
// Highlight search terms in title and excerpt
|
| 512 |
+
let highlightedTitle = blog.title;
|
| 513 |
+
let highlightedExcerpt = blog.excerpt;
|
| 514 |
+
|
| 515 |
+
if (searchTerm) {
|
| 516 |
+
highlightedTitle = this.highlightText(blog.title, searchTerm);
|
| 517 |
+
highlightedExcerpt = this.highlightText(blog.excerpt, searchTerm);
|
| 518 |
+
}
|
| 519 |
+
|
| 520 |
+
// Show match indicators if available
|
| 521 |
+
let matchIndicators = '';
|
| 522 |
+
if (blog.matches) {
|
| 523 |
+
const indicators = [];
|
| 524 |
+
if (blog.matches.filename) indicators.push('π filename');
|
| 525 |
+
if (blog.matches.title) indicators.push('π title');
|
| 526 |
+
if (blog.matches.content) indicators.push('π content');
|
| 527 |
+
|
| 528 |
+
if (indicators.length > 0) {
|
| 529 |
+
matchIndicators = `<div class="match-indicators">Found in: ${indicators.join(', ')}</div>`;
|
| 530 |
+
}
|
| 531 |
+
}
|
| 532 |
+
|
| 533 |
+
return `
|
| 534 |
+
<div class="blog-card" onclick="window.openBlog('${blog.filename}')">
|
| 535 |
+
<div class="blog-title">${highlightedTitle}</div>
|
| 536 |
+
<div class="blog-excerpt">${highlightedExcerpt}</div>
|
| 537 |
+
${matchIndicators}
|
| 538 |
+
<div class="blog-meta">
|
| 539 |
+
<span class="blog-date">π
${blog.lastModified}</span>
|
| 540 |
+
<span class="blog-size">π ${blog.size}</span>
|
| 541 |
+
</div>
|
| 542 |
+
</div>
|
| 543 |
+
`;
|
| 544 |
+
}).join('');
|
| 545 |
+
|
| 546 |
+
this.blogContainer.innerHTML = `<div class="blog-grid">${blogHTML}</div>`;
|
| 547 |
+
}
|
| 548 |
+
|
| 549 |
+
highlightText(text, searchTerm) {
|
| 550 |
+
if (!searchTerm) return text;
|
| 551 |
+
|
| 552 |
+
const regex = new RegExp(`(${this.escapeRegExp(searchTerm)})`, 'gi');
|
| 553 |
+
return text.replace(regex, '<mark class="search-highlight">$1</mark>');
|
| 554 |
+
}
|
| 555 |
+
|
| 556 |
+
escapeRegExp(string) {
|
| 557 |
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
| 558 |
+
}
|
| 559 |
+
|
| 560 |
+
updateStats(searchQuery = '') {
|
| 561 |
+
const total = this.allBlogsCache.length;
|
| 562 |
+
const shown = this.filteredBlogs.length;
|
| 563 |
+
|
| 564 |
+
if (searchQuery) {
|
| 565 |
+
this.resultsCount.textContent = `${shown} of ${total} documents found`;
|
| 566 |
+
} else if (this.searchInput.value.trim()) {
|
| 567 |
+
this.resultsCount.textContent = `${shown} of ${total} documents`;
|
| 568 |
+
} else {
|
| 569 |
+
this.resultsCount.textContent = `${total} documents`;
|
| 570 |
+
}
|
| 571 |
+
|
| 572 |
+
this.lastUpdated.textContent = `Updated ${new Date().toLocaleDateString()}`;
|
| 573 |
+
}
|
| 574 |
+
|
| 575 |
+
showError(message) {
|
| 576 |
+
this.blogContainer.innerHTML = `
|
| 577 |
+
<div class="error">
|
| 578 |
+
<strong>Error:</strong> ${message}
|
| 579 |
+
</div>
|
| 580 |
+
`;
|
| 581 |
+
}
|
| 582 |
+
}
|
| 583 |
+
|
| 584 |
+
// Global function to open blog files
|
| 585 |
+
window.openBlog = function (filename) {
|
| 586 |
+
// Open the blog viewer in a new window
|
| 587 |
+
const blogUrl = `./blog-viewer.html?file=${encodeURIComponent(filename)}`;
|
| 588 |
+
window.open(blogUrl, '_blank', 'width=1000,height=700,scrollbars=yes,resizable=yes');
|
| 589 |
+
};
|
| 590 |
+
|
| 591 |
+
// Initialize the documentation hub
|
| 592 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 593 |
+
new DocumentationHub();
|
| 594 |
+
});
|
| 595 |
+
</script>
|
| 596 |
+
</body>
|
| 597 |
+
|
| 598 |
+
</html>
|
requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# No external dependencies required - using only Python standard library
|
| 2 |
+
# The server uses only built-in modules: http.server, socketserver, json, os, pathlib, etc.
|
server.py
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Simple HTTP server for serving the documentation hub locally.
|
| 4 |
+
This allows proper CORS handling and file serving for the markdown files.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import http.server
|
| 8 |
+
import socketserver
|
| 9 |
+
import json
|
| 10 |
+
import os
|
| 11 |
+
import mimetypes
|
| 12 |
+
from urllib.parse import urlparse, parse_qs
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
|
| 15 |
+
class DocumentationHandler(http.server.SimpleHTTPRequestHandler):
|
| 16 |
+
def __init__(self, *args, **kwargs):
|
| 17 |
+
super().__init__(*args, directory=str(Path(__file__).parent), **kwargs)
|
| 18 |
+
|
| 19 |
+
def end_headers(self):
|
| 20 |
+
# Add CORS headers
|
| 21 |
+
self.send_header('Access-Control-Allow-Origin', '*')
|
| 22 |
+
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
|
| 23 |
+
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
|
| 24 |
+
super().end_headers()
|
| 25 |
+
|
| 26 |
+
def do_GET(self):
|
| 27 |
+
# Handle the blogs directory listing API
|
| 28 |
+
if self.path.startswith('/api/blogs'):
|
| 29 |
+
self.handle_blogs_api()
|
| 30 |
+
elif self.path.startswith('/api/search'):
|
| 31 |
+
self.handle_search_api()
|
| 32 |
+
else:
|
| 33 |
+
# Serve static files normally
|
| 34 |
+
super().do_GET()
|
| 35 |
+
|
| 36 |
+
def handle_blogs_api(self):
|
| 37 |
+
"""API endpoint to list blog files and their metadata"""
|
| 38 |
+
try:
|
| 39 |
+
blogs_dir = Path(__file__).parent / 'blogs'
|
| 40 |
+
if not blogs_dir.exists():
|
| 41 |
+
self.send_error(404, "Blogs directory not found")
|
| 42 |
+
return
|
| 43 |
+
|
| 44 |
+
blogs = []
|
| 45 |
+
for md_file in blogs_dir.glob('*.md'):
|
| 46 |
+
# Get file stats
|
| 47 |
+
stat = md_file.stat()
|
| 48 |
+
size_kb = round(stat.st_size / 1024, 1)
|
| 49 |
+
|
| 50 |
+
# Read first few lines to extract title and excerpt
|
| 51 |
+
try:
|
| 52 |
+
with open(md_file, 'r', encoding='utf-8') as f:
|
| 53 |
+
content = f.read()
|
| 54 |
+
lines = content.split('\n')
|
| 55 |
+
|
| 56 |
+
# Extract title (first # header)
|
| 57 |
+
title = md_file.stem.replace('-', ' ').title()
|
| 58 |
+
for line in lines:
|
| 59 |
+
if line.startswith('# '):
|
| 60 |
+
title = line[2:].strip()
|
| 61 |
+
break
|
| 62 |
+
|
| 63 |
+
# Extract excerpt (first paragraph after title)
|
| 64 |
+
excerpt = "No description available."
|
| 65 |
+
for i, line in enumerate(lines):
|
| 66 |
+
if line.startswith('# '):
|
| 67 |
+
# Look for first non-empty paragraph after title
|
| 68 |
+
for j in range(i + 1, min(i + 10, len(lines))):
|
| 69 |
+
if lines[j].strip() and not lines[j].startswith('#'):
|
| 70 |
+
excerpt = lines[j].strip()[:150]
|
| 71 |
+
if len(lines[j].strip()) > 150:
|
| 72 |
+
excerpt += "..."
|
| 73 |
+
break
|
| 74 |
+
break
|
| 75 |
+
|
| 76 |
+
blogs.append({
|
| 77 |
+
'filename': md_file.name,
|
| 78 |
+
'title': title,
|
| 79 |
+
'excerpt': excerpt,
|
| 80 |
+
'size': f"{size_kb} KB",
|
| 81 |
+
'lastModified': stat.st_mtime
|
| 82 |
+
})
|
| 83 |
+
except Exception as e:
|
| 84 |
+
print(f"Error reading {md_file}: {e}")
|
| 85 |
+
continue
|
| 86 |
+
|
| 87 |
+
# Sort by last modified (newest first)
|
| 88 |
+
blogs.sort(key=lambda x: x['lastModified'], reverse=True)
|
| 89 |
+
|
| 90 |
+
# Format dates
|
| 91 |
+
import datetime
|
| 92 |
+
for blog in blogs:
|
| 93 |
+
blog['lastModified'] = datetime.datetime.fromtimestamp(
|
| 94 |
+
blog['lastModified']
|
| 95 |
+
).strftime('%Y-%m-%d')
|
| 96 |
+
|
| 97 |
+
# Send JSON response
|
| 98 |
+
response = json.dumps(blogs, indent=2)
|
| 99 |
+
|
| 100 |
+
self.send_response(200)
|
| 101 |
+
self.send_header('Content-Type', 'application/json')
|
| 102 |
+
self.end_headers()
|
| 103 |
+
self.wfile.write(response.encode('utf-8'))
|
| 104 |
+
|
| 105 |
+
except Exception as e:
|
| 106 |
+
print(f"Error in blogs API: {e}")
|
| 107 |
+
self.send_error(500, f"Internal server error: {e}")
|
| 108 |
+
|
| 109 |
+
def handle_search_api(self):
|
| 110 |
+
"""API endpoint to search within blog content"""
|
| 111 |
+
try:
|
| 112 |
+
# Parse query parameters
|
| 113 |
+
parsed_url = urlparse(self.path)
|
| 114 |
+
query_params = parse_qs(parsed_url.query)
|
| 115 |
+
search_query = query_params.get('q', [''])[0].lower().strip()
|
| 116 |
+
|
| 117 |
+
if not search_query:
|
| 118 |
+
self.send_error(400, "Missing search query parameter 'q'")
|
| 119 |
+
return
|
| 120 |
+
|
| 121 |
+
blogs_dir = Path(__file__).parent / 'blogs'
|
| 122 |
+
if not blogs_dir.exists():
|
| 123 |
+
self.send_error(404, "Blogs directory not found")
|
| 124 |
+
return
|
| 125 |
+
|
| 126 |
+
search_results = []
|
| 127 |
+
for md_file in blogs_dir.glob('*.md'):
|
| 128 |
+
try:
|
| 129 |
+
with open(md_file, 'r', encoding='utf-8') as f:
|
| 130 |
+
content = f.read()
|
| 131 |
+
lines = content.split('\n')
|
| 132 |
+
|
| 133 |
+
# Extract title
|
| 134 |
+
title = md_file.stem.replace('-', ' ').title()
|
| 135 |
+
for line in lines:
|
| 136 |
+
if line.startswith('# '):
|
| 137 |
+
title = line[2:].strip()
|
| 138 |
+
break
|
| 139 |
+
|
| 140 |
+
# Check if search term is in title, filename, or content
|
| 141 |
+
filename_match = search_query in md_file.name.lower()
|
| 142 |
+
title_match = search_query in title.lower()
|
| 143 |
+
content_match = search_query in content.lower()
|
| 144 |
+
|
| 145 |
+
if filename_match or title_match or content_match:
|
| 146 |
+
# Get file stats
|
| 147 |
+
stat = md_file.stat()
|
| 148 |
+
size_kb = round(stat.st_size / 1024, 1)
|
| 149 |
+
|
| 150 |
+
# Extract excerpt (preferably containing search term)
|
| 151 |
+
excerpt = self.extract_search_excerpt(content, search_query)
|
| 152 |
+
|
| 153 |
+
# Calculate relevance score
|
| 154 |
+
relevance = 0
|
| 155 |
+
if filename_match:
|
| 156 |
+
relevance += 3
|
| 157 |
+
if title_match:
|
| 158 |
+
relevance += 2
|
| 159 |
+
if content_match:
|
| 160 |
+
relevance += 1
|
| 161 |
+
|
| 162 |
+
search_results.append({
|
| 163 |
+
'filename': md_file.name,
|
| 164 |
+
'title': title,
|
| 165 |
+
'excerpt': excerpt,
|
| 166 |
+
'size': f"{size_kb} KB",
|
| 167 |
+
'lastModified': stat.st_mtime,
|
| 168 |
+
'relevance': relevance,
|
| 169 |
+
'matches': {
|
| 170 |
+
'filename': filename_match,
|
| 171 |
+
'title': title_match,
|
| 172 |
+
'content': content_match
|
| 173 |
+
}
|
| 174 |
+
})
|
| 175 |
+
except Exception as e:
|
| 176 |
+
print(f"Error searching {md_file}: {e}")
|
| 177 |
+
continue
|
| 178 |
+
|
| 179 |
+
# Sort by relevance, then by last modified
|
| 180 |
+
search_results.sort(key=lambda x: (x['relevance'], x['lastModified']), reverse=True)
|
| 181 |
+
|
| 182 |
+
# Format dates
|
| 183 |
+
import datetime
|
| 184 |
+
for result in search_results:
|
| 185 |
+
result['lastModified'] = datetime.datetime.fromtimestamp(
|
| 186 |
+
result['lastModified']
|
| 187 |
+
).strftime('%Y-%m-%d')
|
| 188 |
+
# Remove relevance from final response
|
| 189 |
+
del result['relevance']
|
| 190 |
+
|
| 191 |
+
# Send JSON response
|
| 192 |
+
response = json.dumps({
|
| 193 |
+
'query': search_query,
|
| 194 |
+
'results': search_results,
|
| 195 |
+
'total': len(search_results)
|
| 196 |
+
}, indent=2)
|
| 197 |
+
|
| 198 |
+
self.send_response(200)
|
| 199 |
+
self.send_header('Content-Type', 'application/json')
|
| 200 |
+
self.end_headers()
|
| 201 |
+
self.wfile.write(response.encode('utf-8'))
|
| 202 |
+
|
| 203 |
+
except Exception as e:
|
| 204 |
+
print(f"Error in search API: {e}")
|
| 205 |
+
self.send_error(500, f"Internal server error: {e}")
|
| 206 |
+
|
| 207 |
+
def extract_search_excerpt(self, content, search_term, context_chars=150):
|
| 208 |
+
"""Extract excerpt around search term or fallback to beginning"""
|
| 209 |
+
content_lower = content.lower()
|
| 210 |
+
search_pos = content_lower.find(search_term)
|
| 211 |
+
|
| 212 |
+
if search_pos == -1:
|
| 213 |
+
# If search term not found, return beginning of content
|
| 214 |
+
lines = content.split('\n')
|
| 215 |
+
for line in lines:
|
| 216 |
+
if line.strip() and not line.startswith('#'):
|
| 217 |
+
excerpt = line.strip()[:context_chars]
|
| 218 |
+
if len(line.strip()) > context_chars:
|
| 219 |
+
excerpt += "..."
|
| 220 |
+
return excerpt
|
| 221 |
+
return "No description available."
|
| 222 |
+
|
| 223 |
+
# Extract context around the search term
|
| 224 |
+
start = max(0, search_pos - context_chars // 2)
|
| 225 |
+
end = min(len(content), search_pos + len(search_term) + context_chars // 2)
|
| 226 |
+
|
| 227 |
+
excerpt = content[start:end].strip()
|
| 228 |
+
|
| 229 |
+
# Clean up excerpt (remove incomplete words at edges)
|
| 230 |
+
if start > 0:
|
| 231 |
+
space_pos = excerpt.find(' ')
|
| 232 |
+
if space_pos > 0:
|
| 233 |
+
excerpt = excerpt[space_pos:].strip()
|
| 234 |
+
excerpt = "..." + excerpt
|
| 235 |
+
|
| 236 |
+
if end < len(content):
|
| 237 |
+
last_space = excerpt.rfind(' ')
|
| 238 |
+
if last_space > 0:
|
| 239 |
+
excerpt = excerpt[:last_space].strip()
|
| 240 |
+
excerpt += "..."
|
| 241 |
+
|
| 242 |
+
return excerpt
|
| 243 |
+
|
| 244 |
+
def run_server(port=8000):
|
| 245 |
+
"""Start the documentation server"""
|
| 246 |
+
try:
|
| 247 |
+
with socketserver.TCPServer(("", port), DocumentationHandler) as httpd:
|
| 248 |
+
print(f"π Server running at: http://localhost:{port}")
|
| 249 |
+
print(f"π Serving from: {Path(__file__).parent}")
|
| 250 |
+
print(f"π Open http://localhost:{port} in your browser")
|
| 251 |
+
print()
|
| 252 |
+
httpd.serve_forever()
|
| 253 |
+
except KeyboardInterrupt:
|
| 254 |
+
print("\nπ Server stopped by user")
|
| 255 |
+
except OSError as e:
|
| 256 |
+
if e.errno == 10048: # Windows: Address already in use
|
| 257 |
+
print(f"β Port {port} is already in use. Try a different port:")
|
| 258 |
+
print(f" python server.py --port 8001")
|
| 259 |
+
else:
|
| 260 |
+
print(f"β Error starting server: {e}")
|
| 261 |
+
|
| 262 |
+
if __name__ == "__main__":
|
| 263 |
+
import sys
|
| 264 |
+
port = 8000
|
| 265 |
+
|
| 266 |
+
# Simple command line argument parsing
|
| 267 |
+
if len(sys.argv) > 1:
|
| 268 |
+
for i, arg in enumerate(sys.argv):
|
| 269 |
+
if arg in ['--port', '-p'] and i + 1 < len(sys.argv):
|
| 270 |
+
try:
|
| 271 |
+
port = int(sys.argv[i + 1])
|
| 272 |
+
except ValueError:
|
| 273 |
+
print("β Invalid port number")
|
| 274 |
+
sys.exit(1)
|
| 275 |
+
elif arg in ['--help', '-h']:
|
| 276 |
+
print("Usage: python server.py [--port PORT]")
|
| 277 |
+
print("Options:")
|
| 278 |
+
print(" --port, -p PORT Port to run server on (default: 8000)")
|
| 279 |
+
print(" --help, -h Show this help message")
|
| 280 |
+
sys.exit(0)
|
| 281 |
+
|
| 282 |
+
run_server(port)
|