A fork of mtelver's day10 project
at main 309 lines 11 kB view raw
1(** Package list and detail pages *) 2 3let list_page ~html_dir = 4 let packages = Day10_web_data.Package_data.list_packages ~html_dir in 5 let rows = packages |> List.map (fun (name, version) -> 6 Printf.sprintf {| 7 <tr> 8 <td><a href="/packages/%s/%s">%s</a></td> 9 <td>%s</td> 10 <td>%s</td> 11 <td><a href="/docs/p/%s/%s/doc/index.html">View Docs</a></td> 12 </tr> 13 |} name version name version (Layout.badge `Success) name version 14 ) |> String.concat "\n" in 15 16 let content = Printf.sprintf {| 17 <h1>Packages</h1> 18 <div class="card"> 19 <input type="search" id="pkg-search" placeholder="Search packages..." onkeyup="filterTable()"> 20 <table id="pkg-table"> 21 <thead> 22 <tr> 23 <th>Package</th> 24 <th>Version</th> 25 <th>Docs Status</th> 26 <th>Links</th> 27 </tr> 28 </thead> 29 <tbody> 30 %s 31 </tbody> 32 </table> 33 </div> 34 <script> 35 function filterTable() { 36 const filter = document.getElementById('pkg-search').value.toLowerCase(); 37 const rows = document.querySelectorAll('#pkg-table tbody tr'); 38 rows.forEach(row => { 39 const text = row.textContent.toLowerCase(); 40 row.style.display = text.includes(filter) ? '' : 'none'; 41 }); 42 } 43 </script> 44 |} rows 45 in 46 Layout.page ~title:"Packages" ~content 47 48let detail_page ~html_dir ~cache_dir ~platform ~log_dir ~name ~version = 49 let package = name ^ "." ^ version in 50 if not (Day10_web_data.Package_data.package_has_docs ~html_dir ~name ~version) then 51 Layout.page ~title:"Package Not Found" ~content:(Printf.sprintf {| 52 <h1>Package Not Found</h1> 53 <p class="card">No documentation found for %s</p> 54 <p><a href="/packages">← Back to packages</a></p> 55 |} package) 56 else 57 let all_versions = Day10_web_data.Package_data.list_package_versions ~html_dir ~name in 58 let versions_list = all_versions |> List.map (fun v -> 59 if v = version then 60 Printf.sprintf "<li><strong>%s</strong> (current)</li>" v 61 else 62 Printf.sprintf {|<li><a href="/packages/%s/%s">%s</a></li>|} name v v 63 ) |> String.concat "\n" in 64 65 (* Get layer info for dependencies and build timestamp *) 66 let layer_info = Day10_web_data.Layer_data.get_package_layer 67 ~cache_dir ~platform ~package in 68 69 (* Get latest run ID for log links *) 70 let latest_run = Day10_web_data.Run_data.get_latest_run_id ~log_dir in 71 72 (* Determine build status from multiple sources *) 73 let build_status, build_time = match layer_info with 74 | Some info -> 75 let timestamp = Unix.gmtime info.created in 76 let time_str = Printf.sprintf "%04d-%02d-%02d %02d:%02d:%02d UTC" 77 (timestamp.Unix.tm_year + 1900) (timestamp.Unix.tm_mon + 1) 78 timestamp.Unix.tm_mday timestamp.Unix.tm_hour 79 timestamp.Unix.tm_min timestamp.Unix.tm_sec in 80 let status = if info.exit_status = 0 then `Success else `Failed in 81 (status, Some time_str) 82 | None -> 83 (* No layer info - check logs and summary *) 84 match latest_run with 85 | Some run_id -> 86 (* Check if run is still in progress *) 87 if Day10_web_data.Run_data.is_run_in_progress ~log_dir ~run_id then 88 (* Check if we have logs for this package *) 89 if Day10_web_data.Run_data.has_build_log ~log_dir ~run_id ~package then 90 (`In_progress, None) 91 else 92 (`Pending, None) 93 else 94 (* Run finished - check summary for status *) 95 begin match Day10_web_data.Run_data.get_package_status_from_summary 96 ~log_dir ~run_id ~package with 97 | Some `Success -> (`Success, None) 98 | Some (`Failed _) -> (`Failed, None) 99 | Some `Not_in_run -> (`Unknown, None) 100 | None -> (`Unknown, None) 101 end 102 | None -> (`Unknown, None) 103 in 104 105 (* Build info section *) 106 let status_badge = match build_status with 107 | `Success -> Layout.badge `Success 108 | `Failed -> Layout.badge `Failed 109 | `In_progress -> 110 {|<span class="badge badge-warning" style="animation: pulse-glow 1s ease-in-out infinite;">building</span>|} 111 | `Pending -> 112 {|<span class="badge badge-warning">pending</span>|} 113 | `Unknown -> Layout.badge `Skipped 114 in 115 116 let build_status_content = 117 let time_line = match build_time with 118 | Some t -> Printf.sprintf {|<p><strong>Built:</strong> %s</p>|} t 119 | None -> "" 120 in 121 Printf.sprintf {| 122 <p><strong>Status:</strong> %s</p> 123 %s 124 |} status_badge time_line 125 in 126 127 (* Log links - show if logs exist *) 128 let log_links = match latest_run with 129 | Some run_id -> 130 let has_build = Day10_web_data.Run_data.has_build_log ~log_dir ~run_id ~package in 131 let has_docs = Day10_web_data.Run_data.has_doc_log ~log_dir ~run_id ~package in 132 if has_build || has_docs then 133 let build_link = if has_build then 134 Printf.sprintf {|<a href="/runs/%s/build/%s">Build Log →</a>|} run_id package 135 else 136 {|<span style="color: var(--text-dim);">No build log</span>|} 137 in 138 let doc_link = if has_docs then 139 Printf.sprintf {|<a href="/runs/%s/docs/%s">Doc Log →</a>|} run_id package 140 else 141 {|<span style="color: var(--text-dim);">No doc log</span>|} 142 in 143 Printf.sprintf {| 144 <p style="margin-top: 1rem;"> 145 %s 146 <span style="margin: 0 0.5rem; color: var(--text-dim);">|</span> 147 %s 148 </p> 149 |} build_link doc_link 150 else 151 {|<p style="margin-top: 1rem; color: var(--text-dim);">No logs in latest run.</p>|} 152 | None -> 153 {|<p style="margin-top: 1rem; color: var(--text-dim);">No runs recorded yet.</p>|} 154 in 155 156 let build_info = Printf.sprintf {| 157 <div class="card"> 158 <h2>Build &amp; Logs</h2> 159 %s 160 %s 161 </div> 162 |} build_status_content log_links 163 in 164 165 (* Parse "name.version" format - version starts at first .digit or .v followed by digit *) 166 let parse_package_str s = 167 let len = String.length s in 168 let rec find_version_start i = 169 if i >= len - 1 then None 170 else if s.[i] = '.' then 171 let next = s.[i + 1] in 172 if next >= '0' && next <= '9' then Some i 173 else if next = 'v' && i + 2 < len && s.[i + 2] >= '0' && s.[i + 2] <= '9' then Some i 174 else find_version_start (i + 1) 175 else find_version_start (i + 1) 176 in 177 match find_version_start 0 with 178 | Some i -> Some (String.sub s 0 i, String.sub s (i + 1) (len - i - 1)) 179 | None -> None 180 in 181 182 (* Dependencies section - always show *) 183 let deps_section = 184 let deps_content = match layer_info with 185 | Some info when info.deps <> [] -> 186 let deps_list = info.deps 187 |> List.map (fun dep -> 188 match parse_package_str dep with 189 | Some (dep_name, dep_version) -> 190 Printf.sprintf {|<li><a href="/packages/%s/%s">%s</a></li>|} dep_name dep_version dep 191 | None -> 192 Printf.sprintf "<li>%s</li>" dep) 193 |> String.concat "\n" in 194 Printf.sprintf {|<ul>%s</ul>|} deps_list 195 | Some _ -> 196 {|<p style="color: var(--text-dim);">No dependencies.</p>|} 197 | None -> 198 {|<p style="color: var(--text-dim);">Dependency information not available.</p>|} 199 in 200 let deps_count = match layer_info with 201 | Some info -> List.length info.deps 202 | None -> 0 203 in 204 Printf.sprintf {| 205 <div class="card"> 206 <h2>Dependencies (%d)</h2> 207 %s 208 </div> 209 |} deps_count deps_content 210 in 211 212 (* Reverse dependencies section - always show *) 213 let reverse_deps = Day10_web_data.Layer_data.get_reverse_deps 214 ~cache_dir ~platform ~package in 215 let rev_deps_section = 216 let rev_deps_content = if reverse_deps <> [] then 217 let rev_deps_list = reverse_deps 218 |> List.map (fun dep -> 219 match parse_package_str dep with 220 | Some (dep_name, dep_version) -> 221 Printf.sprintf {|<li><a href="/packages/%s/%s">%s</a></li>|} dep_name dep_version dep 222 | None -> 223 Printf.sprintf "<li>%s</li>" dep) 224 |> String.concat "\n" in 225 Printf.sprintf {|<ul>%s</ul>|} rev_deps_list 226 else 227 {|<p style="color: var(--text-dim);">No packages depend on this one.</p>|} 228 in 229 Printf.sprintf {| 230 <div class="card"> 231 <h2>Reverse Dependencies (%d)</h2> 232 %s 233 </div> 234 |} (List.length reverse_deps) rev_deps_content 235 in 236 237 let content = Printf.sprintf {| 238 <h1>%s</h1> 239 <p><a href="/packages">← Back to packages</a></p> 240 241 <div class="card"> 242 <h2>Documentation</h2> 243 <p>%s</p> 244 <p><a href="/docs/p/%s/%s/doc/index.html">View Documentation →</a></p> 245 </div> 246 247 %s 248 %s 249 %s 250 251 <div class="card"> 252 <h2>Other Versions</h2> 253 <ul>%s</ul> 254 </div> 255 |} package (Layout.badge `Success) name version build_info deps_section rev_deps_section versions_list 256 in 257 Layout.page ~title:package ~content 258 259(** Combined build and doc logs page for a package *) 260let logs_page ~log_dir ~name ~version = 261 let package = name ^ "." ^ version in 262 let latest_run = Day10_web_data.Run_data.get_latest_run_id ~log_dir in 263 match latest_run with 264 | None -> 265 Layout.page ~title:(package ^ " Logs") ~content:(Printf.sprintf {| 266 <h1>%s Logs</h1> 267 <p><a href="/packages/%s/%s">← Back to package</a></p> 268 <div class="card"> 269 <p>No run data available.</p> 270 </div> 271 |} package name version) 272 | Some run_id -> 273 let build_log = Day10_web_data.Run_data.read_build_log ~log_dir ~run_id ~package in 274 let doc_log = Day10_web_data.Run_data.read_doc_log ~log_dir ~run_id ~package in 275 276 let build_section = match build_log with 277 | Some log -> 278 Printf.sprintf {| 279 <div class="card"> 280 <h2>Build Log</h2> 281 <p><em>From run %s</em></p> 282 <pre>%s</pre> 283 </div> 284 |} run_id log 285 | None -> 286 {|<div class="card"><h2>Build Log</h2><p>No build log available for this package.</p></div>|} 287 in 288 289 let doc_section = match doc_log with 290 | Some log -> 291 Printf.sprintf {| 292 <div class="card"> 293 <h2>Documentation Log</h2> 294 <p><em>From run %s</em></p> 295 <pre>%s</pre> 296 </div> 297 |} run_id log 298 | None -> 299 {|<div class="card"><h2>Documentation Log</h2><p>No doc log available for this package.</p></div>|} 300 in 301 302 let content = Printf.sprintf {| 303 <h1>%s Logs</h1> 304 <p><a href="/packages/%s/%s">← Back to package</a></p> 305 %s 306 %s 307 |} package name version build_section doc_section 308 in 309 Layout.page ~title:(package ^ " Logs") ~content