mirror of
https://github.com/pacnpal/simpleguardhome.git
synced 2025-12-20 04:21:13 -05:00
fix(ui): update domain check and unblock functionality with improved error handling and API endpoint adjustments
This commit is contained in:
@@ -22,7 +22,7 @@ from pydantic import BaseModel
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Initialize rate limiter
|
||||
# Initialize FastAPI app
|
||||
app = FastAPI(
|
||||
title="SimpleGuardHome",
|
||||
description="AdGuard Home REST API interface",
|
||||
@@ -46,29 +46,11 @@ app.add_middleware(
|
||||
templates_path = Path(__file__).parent / "templates"
|
||||
templates = Jinja2Templates(directory=str(templates_path))
|
||||
|
||||
# Response models matching AdGuard spec
|
||||
class HealthResponse(BaseModel):
|
||||
"""Health check response model."""
|
||||
status: str
|
||||
adguard_connection: str
|
||||
filtering_enabled: bool = False
|
||||
error: str = None
|
||||
|
||||
class DomainResponse(BaseModel):
|
||||
"""Domain check response model matching AdGuard spec."""
|
||||
success: bool
|
||||
domain: str
|
||||
filtered: bool = False
|
||||
reason: str = None
|
||||
rule: str = None
|
||||
filter_list_id: int = None
|
||||
service_name: str = None
|
||||
cname: str = None
|
||||
ip_addrs: list[str] = None
|
||||
message: str = None
|
||||
# Request/Response Models
|
||||
class DomainRequest(BaseModel):
|
||||
name: str
|
||||
|
||||
class ErrorResponse(BaseModel):
|
||||
"""Error response model matching AdGuard spec."""
|
||||
message: str
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
@@ -80,43 +62,83 @@ async def home(request: Request):
|
||||
)
|
||||
|
||||
@app.get(
|
||||
"/control/status",
|
||||
response_model=HealthResponse,
|
||||
"/control/filtering/check_host",
|
||||
response_model=DomainCheckResult,
|
||||
responses={
|
||||
200: {"description": "OK"},
|
||||
503: {"description": "AdGuard Home service unavailable", "model": ErrorResponse},
|
||||
500: {"description": "Internal server error", "model": ErrorResponse}
|
||||
400: {"description": "Bad Request", "model": ErrorResponse},
|
||||
503: {"description": "AdGuard Home service unavailable", "model": ErrorResponse}
|
||||
},
|
||||
tags=["health"]
|
||||
tags=["filtering"]
|
||||
)
|
||||
async def health_check() -> Dict:
|
||||
"""Check the health of the application and AdGuard Home connection."""
|
||||
async def check_domain(name: str) -> Dict:
|
||||
"""Check if a domain is blocked by AdGuard Home using AdGuard spec."""
|
||||
if not name:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Domain name is required"
|
||||
)
|
||||
|
||||
logger.info(f"Checking domain: {name}")
|
||||
try:
|
||||
async with adguard.AdGuardClient() as client:
|
||||
status = await client.get_filter_status()
|
||||
return {
|
||||
"status": "healthy",
|
||||
"adguard_connection": "connected",
|
||||
"filtering_enabled": status.enabled if status else False
|
||||
}
|
||||
except AdGuardConnectionError:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
content={
|
||||
"status": "degraded",
|
||||
"adguard_connection": "failed",
|
||||
"error": "Could not connect to AdGuard Home"
|
||||
}
|
||||
result = await client.check_domain(name)
|
||||
logger.info(f"Domain check result: {result}")
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking domain {name}: {str(e)}")
|
||||
raise
|
||||
|
||||
@app.post(
|
||||
"/control/filtering/whitelist/add",
|
||||
response_model=Dict,
|
||||
responses={
|
||||
200: {"description": "OK"},
|
||||
400: {"description": "Bad Request", "model": ErrorResponse},
|
||||
503: {"description": "AdGuard Home service unavailable", "model": ErrorResponse}
|
||||
},
|
||||
tags=["filtering"]
|
||||
)
|
||||
async def add_to_whitelist(request: DomainRequest) -> Dict:
|
||||
"""Add a domain to the allowed list using AdGuard spec."""
|
||||
if not request.name:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Domain name is required"
|
||||
)
|
||||
|
||||
logger.info(f"Adding domain to whitelist: {request.name}")
|
||||
try:
|
||||
async with adguard.AdGuardClient() as client:
|
||||
success = await client.add_allowed_domain(request.name)
|
||||
if success:
|
||||
return {"message": f"Successfully whitelisted {request.name}"}
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to whitelist domain"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Health check failed: {str(e)}")
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
content={
|
||||
"status": "error",
|
||||
"error": "An internal error has occurred. Please try again later."
|
||||
}
|
||||
)
|
||||
logger.error(f"Error whitelisting domain {request.name}: {str(e)}")
|
||||
raise
|
||||
|
||||
@app.get(
|
||||
"/control/filtering/status",
|
||||
response_model=FilterStatus,
|
||||
responses={
|
||||
200: {"description": "OK"},
|
||||
503: {"description": "AdGuard Home service unavailable", "model": ErrorResponse}
|
||||
},
|
||||
tags=["filtering"]
|
||||
)
|
||||
async def get_filtering_status() -> FilterStatus:
|
||||
"""Get the current filtering status using AdGuard spec."""
|
||||
try:
|
||||
async with adguard.AdGuardClient() as client:
|
||||
return await client.get_filter_status()
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting filter status: {str(e)}")
|
||||
raise
|
||||
|
||||
@app.exception_handler(AdGuardError)
|
||||
async def adguard_exception_handler(request: Request, exc: AdGuardError) -> JSONResponse:
|
||||
@@ -133,96 +155,6 @@ async def adguard_exception_handler(request: Request, exc: AdGuardError) -> JSON
|
||||
content={"message": str(exc)}
|
||||
)
|
||||
|
||||
@app.post(
|
||||
"/control/filtering/check_host",
|
||||
response_model=DomainResponse,
|
||||
responses={
|
||||
200: {"description": "OK"},
|
||||
400: {"description": "Bad Request", "model": ErrorResponse},
|
||||
503: {"description": "AdGuard Home service unavailable", "model": ErrorResponse}
|
||||
},
|
||||
tags=["filtering"]
|
||||
)
|
||||
async def check_domain(domain: str = Form(...)) -> Dict:
|
||||
"""Check if a domain is blocked by AdGuard Home."""
|
||||
if not domain:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Domain is required"
|
||||
)
|
||||
|
||||
logger.info(f"Checking domain: {domain}")
|
||||
try:
|
||||
async with adguard.AdGuardClient() as client:
|
||||
result = await client.check_domain(domain)
|
||||
response = {
|
||||
"success": True,
|
||||
"domain": domain,
|
||||
"filtered": result.reason.startswith("Filtered"),
|
||||
"reason": result.reason,
|
||||
"rule": result.rule,
|
||||
"filter_list_id": result.filter_id,
|
||||
"service_name": result.service_name,
|
||||
"cname": result.cname,
|
||||
"ip_addrs": result.ip_addrs
|
||||
}
|
||||
logger.info(f"Domain check result: {response}")
|
||||
return response
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking domain {domain}: {str(e)}")
|
||||
raise
|
||||
|
||||
@app.post(
|
||||
"/control/filtering/whitelist/add",
|
||||
response_model=DomainResponse,
|
||||
responses={
|
||||
200: {"description": "OK"},
|
||||
400: {"description": "Bad Request", "model": ErrorResponse},
|
||||
503: {"description": "AdGuard Home service unavailable", "model": ErrorResponse}
|
||||
},
|
||||
tags=["filtering"]
|
||||
)
|
||||
async def unblock_domain(domain: str = Form(...)) -> Dict:
|
||||
"""Add a domain to the allowed list."""
|
||||
if not domain:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Domain is required"
|
||||
)
|
||||
|
||||
logger.info(f"Unblocking domain: {domain}")
|
||||
try:
|
||||
async with adguard.AdGuardClient() as client:
|
||||
await client.add_allowed_domain(domain)
|
||||
response = {
|
||||
"success": True,
|
||||
"domain": domain,
|
||||
"message": f"Successfully unblocked {domain}"
|
||||
}
|
||||
logger.info(f"Domain unblock result: {response}")
|
||||
return response
|
||||
except Exception as e:
|
||||
logger.error(f"Error unblocking domain {domain}: {str(e)}")
|
||||
raise
|
||||
|
||||
@app.get(
|
||||
"/control/filtering/status",
|
||||
response_model=FilterStatus,
|
||||
responses={
|
||||
200: {"description": "OK"},
|
||||
503: {"description": "AdGuard Home service unavailable", "model": ErrorResponse}
|
||||
},
|
||||
tags=["filtering"]
|
||||
)
|
||||
async def get_filtering_status() -> FilterStatus:
|
||||
"""Get the current filtering status."""
|
||||
try:
|
||||
async with adguard.AdGuardClient() as client:
|
||||
return await client.get_filter_status()
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting filter status: {str(e)}")
|
||||
raise
|
||||
|
||||
def start():
|
||||
"""Start the application using uvicorn."""
|
||||
import uvicorn
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
async function checkDomain(event) {
|
||||
event.preventDefault();
|
||||
event.preventDefault(); // Important: prevent default form submission
|
||||
const domain = document.getElementById('domain').value;
|
||||
const resultDiv = document.getElementById('result');
|
||||
const unblockDiv = document.getElementById('unblock-action');
|
||||
@@ -18,23 +18,24 @@
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.innerHTML = '<span class="inline-flex items-center">Checking... <svg class="animate-spin ml-2 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg></span>';
|
||||
|
||||
const response = await fetch('/check-domain', {
|
||||
method: 'POST',
|
||||
const response = await fetch(`/control/filtering/check_host?name=${encodeURIComponent(domain)}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: `domain=${encodeURIComponent(domain)}`
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
if (data.blocked) {
|
||||
if (response.ok) {
|
||||
if (data.reason.startsWith('Filtered')) {
|
||||
resultDiv.innerHTML = `
|
||||
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-4">
|
||||
<p class="font-bold">Domain is blocked</p>
|
||||
<p class="text-sm"><strong>${domain}</strong> is blocked by rule:</p>
|
||||
<p class="text-sm font-mono bg-red-50 p-2 mt-1 rounded">${data.rule || 'Unknown rule'}</p>
|
||||
${data.filter_list ? `<p class="text-sm mt-2">Filter List: ${data.filter_list}</p>` : ''}
|
||||
<p class="text-sm font-mono bg-red-50 p-2 mt-1 rounded">${data.rule || 'No rule specified'}</p>
|
||||
${data.filter_id ? `<p class="text-sm mt-2">Filter ID: ${data.filter_id}</p>` : ''}
|
||||
${data.service_name ? `<p class="text-sm mt-2">Service: ${data.service_name}</p>` : ''}
|
||||
</div>`;
|
||||
unblockDiv.innerHTML = `
|
||||
<button onclick="unblockDomain('${domain}')"
|
||||
@@ -46,6 +47,7 @@
|
||||
<div class="bg-green-100 border-l-4 border-green-500 text-green-700 p-4">
|
||||
<p class="font-bold">Domain is not blocked</p>
|
||||
<p class="text-sm"><strong>${domain}</strong> is allowed</p>
|
||||
<p class="text-xs mt-2">Status: ${data.reason}</p>
|
||||
</div>`;
|
||||
unblockDiv.innerHTML = '';
|
||||
}
|
||||
@@ -53,8 +55,8 @@
|
||||
// Show error message
|
||||
resultDiv.innerHTML = `
|
||||
<div class="bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4">
|
||||
<p class="font-bold">${data.error}</p>
|
||||
<p class="text-sm">${data.detail}</p>
|
||||
<p class="font-bold">Error checking domain</p>
|
||||
<p class="text-sm">${data.message || 'Unknown error occurred'}</p>
|
||||
</div>`;
|
||||
unblockDiv.innerHTML = '';
|
||||
}
|
||||
@@ -82,27 +84,28 @@
|
||||
unblockBtn.disabled = true;
|
||||
unblockBtn.innerHTML = '<span class="inline-flex items-center">Unblocking... <svg class="animate-spin ml-2 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg></span>';
|
||||
|
||||
const response = await fetch('/unblock-domain', {
|
||||
const response = await fetch('/control/filtering/whitelist/add', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: `domain=${encodeURIComponent(domain)}`
|
||||
body: JSON.stringify({ name: domain })
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
if (response.ok) {
|
||||
resultDiv.innerHTML = `
|
||||
<div class="bg-green-100 border-l-4 border-green-500 text-green-700 p-4">
|
||||
<p class="font-bold">Success!</p>
|
||||
<p class="text-sm">${data.message}</p>
|
||||
<p class="text-sm">Domain ${domain} has been added to the whitelist</p>
|
||||
</div>`;
|
||||
unblockDiv.innerHTML = '';
|
||||
} else {
|
||||
const data = await response.json();
|
||||
resultDiv.innerHTML = `
|
||||
<div class="bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4">
|
||||
<p class="font-bold">${data.error}</p>
|
||||
<p class="text-sm">${data.detail}</p>
|
||||
<p class="font-bold">Error unblocking domain</p>
|
||||
<p class="text-sm">${data.message || 'Unknown error occurred'}</p>
|
||||
</div>`;
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -120,6 +123,7 @@
|
||||
<h1 class="text-3xl font-bold text-center mb-8 text-gray-800">SimpleGuardHome</h1>
|
||||
|
||||
<div class="bg-white rounded-lg shadow-md p-6">
|
||||
<!-- Remove form action to prevent default submission -->
|
||||
<form onsubmit="checkDomain(event)" class="mb-6">
|
||||
<div class="mb-4">
|
||||
<label for="domain" class="block text-gray-700 text-sm font-bold mb-2">
|
||||
|
||||
Reference in New Issue
Block a user