/ cgi-bin / patch
patch
  1  #!/usr/bin/python3
  2  
  3  import cgi
  4  import cgitb
  5  import html
  6  from urllib.parse import quote_plus
  7  import time
  8  import json
  9  from util import *
 10  from config import *
 11  
 12  cgitb.enable()
 13  
 14  HTML_HEAD='''
 15  <!doctype html>
 16  <html lang="en">
 17    <head>
 18      <meta charset="utf-8">
 19      <meta name="viewport" content="width=device-width, initial-scale=1">
 20      <title>Cradicle Explorer</title>
 21      <link href="/css/bootstrap/bootstrap.min.css" rel="stylesheet">
 22      <style>
 23        .form-control-dark::placeholder {
 24            color: #aaa;
 25            opacity: 1;
 26        }
 27        .diff-add { color: #22c55e; }
 28        .diff-del { color: #ef4444; }
 29        .diff-hunk { color: #6366f1; }
 30        .diff-file { color: #facc15; font-weight: bold; }
 31      </style>
 32      <link rel="stylesheet" href="/assets/fontawesome/css/all.min.css">
 33      <link rel="icon" type="image/png" href="/favicon.png">
 34  '''
 35  
 36  print(HTML_HEAD)
 37  
 38  form = cgi.FieldStorage()
 39  rid = form.getvalue('rid')
 40  patch_id = form.getvalue('id')
 41  
 42  repo_name = ''
 43  if rid and len(rid):
 44      ret,out,err = exec_command(crad_bin,['inspect','-R',rid,'--identity'])
 45      if not ret:
 46          doc = json.loads(out)
 47          payload_val = doc['payload']['xyz.radicle.project']
 48          repo_name = payload_val['name']
 49  
 50  patch_doc = None
 51  patch_err = None
 52  patch_title = ''
 53  if patch_id and len(patch_id):
 54      ret,out,err = exec_command(crad_bin,['patch','show',patch_id,'-R',rid,'--json'])
 55      if ret:
 56          patch_err = err
 57      elif len(out):
 58          patch_doc = json.loads(out)
 59          patch_title = patch_doc['title']
 60  
 61  diff_output = ''
 62  if patch_doc and patch_id:
 63      ret,out,err = exec_command(crad_bin,['patch','diff',patch_id,'-R',rid])
 64      if not ret and out:
 65          diff_output = out
 66  
 67  HTML_DASHBOARD_START=f'''
 68                  <link href="/css/dashboard.css" rel="stylesheet">
 69                  </head>
 70                  <body>
 71                  <header class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
 72                    <a class="navbar-brand col-md-3 col-lg-2 me-0 px-3 fs-6" href="/">Cradicle Explorer</a>
 73                    <button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
 74                      <span class="navbar-toggler-icon"></span>
 75                    </button>
 76                    <form method="get" action="/cgi-bin/main" style="width:100%;"><input class="form-control form-control-dark w-100 rounded-0 border-0" type="text" name="q" placeholder="Search repos" aria-label="Search"></form>
 77                    <div class="navbar-nav flex-row">
 78                      <div class="nav-item text-nowrap">
 79                        <a class="nav-link px-3 active" href="/cgi-bin/repo?id={quote_plus(rid)}">{html.escape(repo_name)}</a>
 80                      </div>
 81                      <div class="nav-item text-nowrap">
 82                        <a class="nav-link px-3 active" href="/cgi-bin/patch?id={quote_plus(patch_id)}&rid={quote_plus(rid)}">{html.escape(patch_title[:16])}</a>
 83                      </div>
 84                    </div>
 85                  </header>
 86                  <div class="container-fluid">
 87                    <div class="row">
 88                      <nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-dark sidebar collapse">
 89                        <div class="position-sticky pt-3 sidebar-sticky">
 90                          <ul class="nav flex-column">
 91                          </ul>
 92                        </div>
 93                      </nav>
 94                  <main class="col-md-9 ms-sm-auto col-lg-10">
 95                    <div class="container px-1 py-3">
 96          '''
 97  HTML_DASHBOARD_END = '''
 98  </div>
 99  </main>
100  </div>
101  </div>
102  '''
103  
104  def format_diff(diff_text):
105      result = ''
106      for line in diff_text.splitlines():
107          escaped = html.escape(line)
108          if line.startswith('+++') or line.startswith('---'):
109              result += f'<span class="diff-file">{escaped}</span>\n'
110          elif line.startswith('@@'):
111              result += f'<span class="diff-hunk">{escaped}</span>\n'
112          elif line.startswith('+'):
113              result += f'<span class="diff-add">{escaped}</span>\n'
114          elif line.startswith('-'):
115              result += f'<span class="diff-del">{escaped}</span>\n'
116          elif line.startswith('diff '):
117              result += f'<span class="diff-file">{escaped}</span>\n'
118          else:
119              result += escaped + '\n'
120      return result
121  
122  dashboard_content = ''
123  
124  if patch_doc:
125      status_html = html.escape(patch_doc['status'])
126      labels_html = ''
127      if 'labels' in patch_doc:
128          labels_html = f'''<div><span class="text-secondary">Labels</span> <span>{html.escape(','.join(patch_doc['labels']))}</span></div>'''
129      assigned_html = ''
130      if 'assignees' in patch_doc:
131          assigned_html = f'''<div><span class="text-secondary">Assigned to</span> <span>{html.escape(','.join(patch_doc['assignees']))}</span></div>'''
132      desc_html = ''
133      if patch_doc['description'] and len(patch_doc['description']):
134          desc_html = f'''<div><span class="text-secondary">Description</span> <span>{html.escape(patch_doc['description'])}</span></div>'''
135  
136      dashboard_content += f'''<div class="list-group">
137      <div class="list-group-item">
138      <div><span class="text-secondary">Title</span> <span>{html.escape(patch_doc['title'])}</span></div>
139      <div><span class="text-secondary">Patch ID</span> <span>{html.escape(patch_doc['patch'])}</span></div>
140      <div><span class="text-secondary">Author</span> <span class="repo-item">{html.escape(patch_doc['alias'])}</span>-{html.escape(patch_doc['author'][8:])}</div>
141      <div><span class="text-secondary">Opened on</span> <span>{html.escape(time.ctime(patch_doc['timestamp']))}</span></div>
142      <div><span class="text-secondary">Status</span> <span class="repo-item">{status_html}</span></div>
143      <div><span class="text-secondary">Head</span> <span>{html.escape(patch_doc['head'])}</span></div>
144      <div><span class="text-secondary">Base</span> <span>{html.escape(patch_doc['base'])}</span></div>
145      {labels_html}
146      {assigned_html}
147      {desc_html}
148      </div>
149      </div>'''
150  
151      if diff_output:
152          dashboard_content += f'''
153          <div class="list-group mt-3">
154          <div class="list-group-item">
155          <div class="mb-2" style="font-weight:bold;">Diff</div>
156          <pre style="margin:0; font-size:0.85rem; overflow-x:auto;">{format_diff(diff_output)}</pre>
157          </div>
158          </div>'''
159  
160  elif patch_err:
161      dashboard_content += f'<p class="error-message">{html.escape(patch_err)}</p>'
162  
163  print(HTML_DASHBOARD_START)
164  print(dashboard_content)
165  print(HTML_DASHBOARD_END)
166  
167  HTML_FOOTER='''
168  </body>
169  </html>
170  '''
171  print(HTML_FOOTER)