Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-27 07:41:45

0001 """
0002 PanDA monitoring views for swf-remote (epic-devcloud.org).
0003 
0004 Most pages proxy full rendered HTML from swf-monitor through the SSH tunnel.
0005 The hub page is rendered locally (devcloud-specific content).
0006 """
0007 
0008 from django.contrib.auth import logout as auth_logout
0009 from django.contrib.auth.decorators import login_required
0010 from django.http import HttpResponse, JsonResponse
0011 from django.shortcuts import redirect, render
0012 from django.views.decorators.csrf import csrf_exempt
0013 
0014 from . import monitor_client
0015 
0016 
0017 # ── Auth ─────────────────────────────────────────────────────────────────────
0018 
0019 @csrf_exempt
0020 def logout_view(request):
0021     """Log out and redirect to home.
0022 
0023     csrf_exempt because all pages are proxied from swf-monitor — the CSRF
0024     token in the logout form is swf-monitor's, which we can never validate.
0025     Logout is state-destroying so CSRF risk is negligible (worst case: attacker
0026     logs the user out).
0027     """
0028     auth_logout(request)
0029     return redirect('/prod/')
0030 
0031 
0032 @login_required
0033 def account(request):
0034     return render(request, 'monitor_app/account.html')
0035 
0036 
0037 def about(request):
0038     """About page — proxied from swf-monitor for content consistency."""
0039     return monitor_client.proxy(request, '/about/')
0040 
0041 
0042 # ── Home / Hub ───────────────────────────────────────────────────────────────
0043 
0044 def home(request):
0045     """Root — always production on devcloud."""
0046     from django.shortcuts import redirect
0047     from django.urls import reverse
0048     return redirect(reverse('monitor_app:prod_home'))
0049 
0050 
0051 def prod_home(request):
0052     return monitor_client.proxy(request, '/prod/')
0053 
0054 
0055 def testbed_home(request):
0056     return monitor_client.proxy(request, '/testbed/')
0057 
0058 
0059 # ── PanDA pages (proxied from swf-monitor) ──────────────────────────────────
0060 
0061 def panda_activity(request):
0062     return monitor_client.proxy(request, '/panda/activity/')
0063 
0064 
0065 def panda_jobs_list(request):
0066     return monitor_client.proxy(request, '/panda/jobs/')
0067 
0068 
0069 def panda_jobs_datatable_ajax(request):
0070     return monitor_client.proxy(request, '/panda/jobs/datatable/')
0071 
0072 
0073 def panda_jobs_filter_counts(request):
0074     return monitor_client.proxy(request, '/panda/jobs/filter-counts/')
0075 
0076 
0077 def panda_job_detail(request, pandaid):
0078     return monitor_client.proxy(request, f'/panda/jobs/{pandaid}/')
0079 
0080 
0081 def panda_tasks_list(request):
0082     return monitor_client.proxy(request, '/panda/tasks/')
0083 
0084 
0085 def panda_tasks_datatable_ajax(request):
0086     return monitor_client.proxy(request, '/panda/tasks/datatable/')
0087 
0088 
0089 def panda_tasks_filter_counts(request):
0090     return monitor_client.proxy(request, '/panda/tasks/filter-counts/')
0091 
0092 
0093 def panda_task_detail(request, jeditaskid):
0094     return monitor_client.proxy(request, f'/panda/tasks/{jeditaskid}/')
0095 
0096 
0097 def panda_errors_list(request):
0098     return monitor_client.proxy(request, '/panda/errors/')
0099 
0100 
0101 def panda_errors_datatable_ajax(request):
0102     return monitor_client.proxy(request, '/panda/errors/datatable/')
0103 
0104 
0105 def panda_diagnostics_list(request):
0106     return monitor_client.proxy(request, '/panda/diagnostics/')
0107 
0108 
0109 def panda_diagnostics_datatable_ajax(request):
0110     return monitor_client.proxy(request, '/panda/diagnostics/datatable/')
0111 
0112 
0113 # ── PCS (Physics Configuration System) ─────────────────────────────────────
0114 # All PCS views proxy full rendered HTML from swf-monitor. Single handler
0115 # forwards based on request path — no need for per-view functions.
0116 
0117 def pcs_proxy(request, **kwargs):
0118     """Proxy any PCS page to swf-monitor based on request path."""
0119     path = request.path_info  # e.g. /pcs/tags/p/compose/ (excludes SCRIPT_NAME)
0120     return monitor_client.proxy(request, path)
0121 
0122 
0123 @csrf_exempt
0124 def pcs_api_proxy(request, path):
0125     """Proxy PCS REST API requests.
0126 
0127     GET is public. Write methods (POST/PATCH/DELETE) require login —
0128     the user's identity is forwarded to swf-monitor via X-Remote-User.
0129     CSRF is exempted here because swf-monitor's API uses token auth,
0130     not session+CSRF.
0131     """
0132     if request.method != 'GET' and not request.user.is_authenticated:
0133         return JsonResponse({'error': 'Login required'}, status=401)
0134     return monitor_client.proxy(request, f'/pcs/api/{path}')
0135 
0136 
0137 def panda_api_proxy(request, path):
0138     """Proxy PanDA REST API requests to swf-monitor /api/panda/<path>.
0139 
0140     Read-only: GET only. Upstream requires IsAuthenticated, so we inject
0141     a service identity when no Django user is logged in — the alarm engine
0142     and other service consumers hit this anonymously from localhost and
0143     appear to upstream as 'swf-remote-proxy'.
0144     """
0145     if request.method != 'GET':
0146         return JsonResponse({'error': 'GET only'}, status=405)
0147     return monitor_client.proxy(
0148         request, f'/api/panda/{path}', service_user='swf-remote-proxy'
0149     )
0150 
0151 
0152 # ── EIC PanDA Queues ──────────────────────────────────────────────────────
0153 # Proxied from swf-monitor (server-rendered pages).
0154 
0155 def epic_queues_list(request):
0156     return monitor_client.proxy(request, '/panda/epic-queues/')
0157 
0158 
0159 def epic_queue_detail(request, queue_name):
0160     return monitor_client.proxy(request, f'/panda/epic-queues/{queue_name}/')
0161 
0162 
0163 def static_proxy(request, path):
0164     """Proxy static assets from swf-monitor — CSS, JS always in sync."""
0165     return monitor_client.proxy(request, f'/static/{path}')
0166 
0167 
0168 from .alarm_views import (  # noqa: E402
0169     alarms_dashboard,
0170     alarm_event_detail,
0171     alarm_config_edit,
0172     alarm_config_save,
0173     alarm_config_version,
0174     alarm_test,
0175     alarm_run_report,
0176     alarm_task_history,
0177     team_create,
0178     team_new,
0179     team_edit,
0180     team_save,
0181     team_version,
0182 )
0183 
0184 
0185 def panda_view_text(request):
0186     """Fetch a PanDA transformation URL — self-extracting zip with embedded scripts.
0187 
0188     Extracts the bash header and all text files from the zip, presents them
0189     as readable plain text.
0190     """
0191     import httpx
0192     import io
0193     import zipfile
0194     url = request.GET.get('url', '')
0195     if not url or not url.startswith('https://'):
0196         return HttpResponse('Missing or invalid url parameter', status=400, content_type='text/plain')
0197     try:
0198         resp = httpx.get(url, timeout=15, follow_redirects=True)
0199     except Exception as e:
0200         return HttpResponse(f'Failed to fetch: {e}', status=502, content_type='text/plain')
0201     data = resp.content
0202     parts = []
0203     # Extract the bash header (text before binary zip data)
0204     try:
0205         lines = []
0206         for line in data.split(b'\n'):
0207             try:
0208                 lines.append(line.decode('utf-8'))
0209             except UnicodeDecodeError:
0210                 break
0211         if lines:
0212             parts.append(f'=== Shell header ({len(lines)} lines) ===\n')
0213             parts.append('\n'.join(lines))
0214     except Exception:
0215         pass
0216     # Extract text files from the zip
0217     try:
0218         buf = io.BytesIO(data)
0219         with zipfile.ZipFile(buf) as zf:
0220             for name in zf.namelist():
0221                 try:
0222                     content = zf.read(name).decode('utf-8')
0223                     parts.append(f'\n\n=== {name} ===\n')
0224                     parts.append(content)
0225                 except (UnicodeDecodeError, KeyError):
0226                     parts.append(f'\n\n=== {name} (binary, skipped) ===\n')
0227     except zipfile.BadZipFile:
0228         if not parts:
0229             # Not a zip, just serve as text
0230             parts.append(data.decode('utf-8', errors='replace'))
0231     return HttpResponse(''.join(parts), content_type='text/plain; charset=utf-8')