mirror of
https://github.com/Sea-Haven-Industries/procurement-ingest.git
synced 2026-10-05 17:31:58 +00:00
Harden WO web UI and fix JS-context XSS in both dashboards
- Use json.dumps for onclick URLs to prevent JS string breakout - Add .lower() to WO render_badge color lookup matching PO pattern - Add pagination to get_comments query - Cap get_work_orders to 500 results matching PO pattern
This commit is contained in:
parent
4ba5c37f47
commit
57bcec5a2e
2 changed files with 17 additions and 10 deletions
|
|
@ -5,6 +5,7 @@ Serves a simple HTML dashboard for viewing purchase orders.
|
||||||
Accessed via Lambda Function URL.
|
Accessed via Lambda Function URL.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from html import escape as esc
|
from html import escape as esc
|
||||||
|
|
@ -195,7 +196,7 @@ def render_po_list(purchase_orders):
|
||||||
processed = esc((po.get("processed_at") or "")[:16])
|
processed = esc((po.get("processed_at") or "")[:16])
|
||||||
|
|
||||||
rows += f"""
|
rows += f"""
|
||||||
<tr style="border-bottom:1px solid #f1f5f9;cursor:pointer;" onclick="window.location='/po?id={po_number}'">
|
<tr style="border-bottom:1px solid #f1f5f9;cursor:pointer;" onclick="window.location={esc(json.dumps(f'/po?id={po.get("po_number", "")}'), quote=True)}">
|
||||||
<td style="padding:12px;font-weight:500;color:#3b82f6;">{po_number}</td>
|
<td style="padding:12px;font-weight:500;color:#3b82f6;">{po_number}</td>
|
||||||
<td style="padding:12px;">{supplier}</td>
|
<td style="padding:12px;">{supplier}</td>
|
||||||
<td style="padding:12px;font-weight:500;">{site_code}</td>
|
<td style="padding:12px;font-weight:500;">{site_code}</td>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ Serves a simple HTML dashboard for viewing work orders and comments.
|
||||||
Accessed via Lambda Function URL.
|
Accessed via Lambda Function URL.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
from html import escape as esc
|
from html import escape as esc
|
||||||
|
|
||||||
|
|
@ -16,7 +17,7 @@ WORK_ORDERS_TABLE = os.environ.get("WORK_ORDERS_TABLE", "WorkOrders")
|
||||||
COMMENTS_TABLE = os.environ.get("COMMENTS_TABLE", "WorkOrderComments")
|
COMMENTS_TABLE = os.environ.get("COMMENTS_TABLE", "WorkOrderComments")
|
||||||
|
|
||||||
|
|
||||||
def get_work_orders():
|
def get_work_orders(limit=500):
|
||||||
table = dynamodb.Table(WORK_ORDERS_TABLE)
|
table = dynamodb.Table(WORK_ORDERS_TABLE)
|
||||||
items = []
|
items = []
|
||||||
response = table.scan()
|
response = table.scan()
|
||||||
|
|
@ -25,16 +26,21 @@ def get_work_orders():
|
||||||
response = table.scan(ExclusiveStartKey=response["LastEvaluatedKey"])
|
response = table.scan(ExclusiveStartKey=response["LastEvaluatedKey"])
|
||||||
items.extend(response.get("Items", []))
|
items.extend(response.get("Items", []))
|
||||||
items.sort(key=lambda x: x.get("updated_at", ""), reverse=True)
|
items.sort(key=lambda x: x.get("updated_at", ""), reverse=True)
|
||||||
return items
|
return items[:limit]
|
||||||
|
|
||||||
|
|
||||||
def get_comments(work_order_id):
|
def get_comments(work_order_id):
|
||||||
table = dynamodb.Table(COMMENTS_TABLE)
|
table = dynamodb.Table(COMMENTS_TABLE)
|
||||||
response = table.query(
|
items = []
|
||||||
KeyConditionExpression="work_order_id = :woid",
|
kwargs = {
|
||||||
ExpressionAttributeValues={":woid": work_order_id},
|
"KeyConditionExpression": "work_order_id = :woid",
|
||||||
)
|
"ExpressionAttributeValues": {":woid": work_order_id},
|
||||||
items = response.get("Items", [])
|
}
|
||||||
|
response = table.query(**kwargs)
|
||||||
|
items.extend(response.get("Items", []))
|
||||||
|
while "LastEvaluatedKey" in response:
|
||||||
|
response = table.query(**kwargs, ExclusiveStartKey=response["LastEvaluatedKey"])
|
||||||
|
items.extend(response.get("Items", []))
|
||||||
items.sort(key=lambda x: x.get("created_at", ""), reverse=True)
|
items.sort(key=lambda x: x.get("created_at", ""), reverse=True)
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
|
@ -42,7 +48,7 @@ def get_comments(work_order_id):
|
||||||
def render_badge(value, color_map):
|
def render_badge(value, color_map):
|
||||||
if not value:
|
if not value:
|
||||||
value = "unknown"
|
value = "unknown"
|
||||||
color = color_map.get(value, "#9ca3af")
|
color = color_map.get(value.lower(), "#9ca3af")
|
||||||
label = esc(value.replace("_", " ").title())
|
label = esc(value.replace("_", " ").title())
|
||||||
return f'<span style="background:{color};color:#fff;padding:2px 10px;border-radius:12px;font-size:12px;font-weight:500;">{label}</span>'
|
return f'<span style="background:{color};color:#fff;padding:2px 10px;border-radius:12px;font-size:12px;font-weight:500;">{label}</span>'
|
||||||
|
|
||||||
|
|
@ -168,7 +174,7 @@ def render_work_orders_list(work_orders):
|
||||||
updated = esc((wo.get("updated_at") or "")[:16])
|
updated = esc((wo.get("updated_at") or "")[:16])
|
||||||
|
|
||||||
rows += f"""
|
rows += f"""
|
||||||
<tr style="border-bottom:1px solid #f1f5f9;cursor:pointer;" onclick="window.location='/wo?id={wo_id}'">
|
<tr style="border-bottom:1px solid #f1f5f9;cursor:pointer;" onclick="window.location={esc(json.dumps(f'/wo?id={wo.get("work_order_id", "")}'), quote=True)}">
|
||||||
<td style="padding:12px;font-weight:500;color:#3b82f6;">{wo_id}</td>
|
<td style="padding:12px;font-weight:500;color:#3b82f6;">{wo_id}</td>
|
||||||
<td style="padding:12px;max-width:250px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">{desc}</td>
|
<td style="padding:12px;max-width:250px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">{desc}</td>
|
||||||
<td style="padding:12px;">{site}</td>
|
<td style="padding:12px;">{site}</td>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue