(** Package list and detail pages *)
let list_page ~html_dir =
let packages = Day10_web_data.Package_data.list_packages ~html_dir in
let rows = packages |> List.map (fun (name, version) ->
Printf.sprintf {|
| %s |
%s |
%s |
View Docs |
|} name version name version (Layout.badge `Success) name version
) |> String.concat "\n" in
let content = Printf.sprintf {|
Packages
|} rows
in
Layout.page ~title:"Packages" ~content
let detail_page ~html_dir ~cache_dir ~platform ~log_dir ~name ~version =
let package = name ^ "." ^ version in
if not (Day10_web_data.Package_data.package_has_docs ~html_dir ~name ~version) then
Layout.page ~title:"Package Not Found" ~content:(Printf.sprintf {|
Package Not Found
No documentation found for %s
← Back to packages
|} package)
else
let all_versions = Day10_web_data.Package_data.list_package_versions ~html_dir ~name in
let versions_list = all_versions |> List.map (fun v ->
if v = version then
Printf.sprintf "%s (current)" v
else
Printf.sprintf {|%s|} name v v
) |> String.concat "\n" in
(* Get layer info for dependencies and build timestamp *)
let layer_info = Day10_web_data.Layer_data.get_package_layer
~cache_dir ~platform ~package in
(* Get latest run ID for log links *)
let latest_run = Day10_web_data.Run_data.get_latest_run_id ~log_dir in
(* Determine build status from multiple sources *)
let build_status, build_time = match layer_info with
| Some info ->
let timestamp = Unix.gmtime info.created in
let time_str = Printf.sprintf "%04d-%02d-%02d %02d:%02d:%02d UTC"
(timestamp.Unix.tm_year + 1900) (timestamp.Unix.tm_mon + 1)
timestamp.Unix.tm_mday timestamp.Unix.tm_hour
timestamp.Unix.tm_min timestamp.Unix.tm_sec in
let status = if info.exit_status = 0 then `Success else `Failed in
(status, Some time_str)
| None ->
(* No layer info - check logs and summary *)
match latest_run with
| Some run_id ->
(* Check if run is still in progress *)
if Day10_web_data.Run_data.is_run_in_progress ~log_dir ~run_id then
(* Check if we have logs for this package *)
if Day10_web_data.Run_data.has_build_log ~log_dir ~run_id ~package then
(`In_progress, None)
else
(`Pending, None)
else
(* Run finished - check summary for status *)
begin match Day10_web_data.Run_data.get_package_status_from_summary
~log_dir ~run_id ~package with
| Some `Success -> (`Success, None)
| Some (`Failed _) -> (`Failed, None)
| Some `Not_in_run -> (`Unknown, None)
| None -> (`Unknown, None)
end
| None -> (`Unknown, None)
in
(* Build info section *)
let status_badge = match build_status with
| `Success -> Layout.badge `Success
| `Failed -> Layout.badge `Failed
| `In_progress ->
{|building|}
| `Pending ->
{|pending|}
| `Unknown -> Layout.badge `Skipped
in
let build_status_content =
let time_line = match build_time with
| Some t -> Printf.sprintf {|Built: %s
|} t
| None -> ""
in
Printf.sprintf {|
Status: %s
%s
|} status_badge time_line
in
(* Log links - show if logs exist *)
let log_links = match latest_run with
| Some run_id ->
let has_build = Day10_web_data.Run_data.has_build_log ~log_dir ~run_id ~package in
let has_docs = Day10_web_data.Run_data.has_doc_log ~log_dir ~run_id ~package in
if has_build || has_docs then
let build_link = if has_build then
Printf.sprintf {|Build Log →|} run_id package
else
{|No build log|}
in
let doc_link = if has_docs then
Printf.sprintf {|Doc Log →|} run_id package
else
{|No doc log|}
in
Printf.sprintf {|
%s
|
%s
|} build_link doc_link
else
{|No logs in latest run.
|}
| None ->
{|No runs recorded yet.
|}
in
let build_info = Printf.sprintf {|
Build & Logs
%s
%s
|} build_status_content log_links
in
(* Parse "name.version" format - version starts at first .digit or .v followed by digit *)
let parse_package_str s =
let len = String.length s in
let rec find_version_start i =
if i >= len - 1 then None
else if s.[i] = '.' then
let next = s.[i + 1] in
if next >= '0' && next <= '9' then Some i
else if next = 'v' && i + 2 < len && s.[i + 2] >= '0' && s.[i + 2] <= '9' then Some i
else find_version_start (i + 1)
else find_version_start (i + 1)
in
match find_version_start 0 with
| Some i -> Some (String.sub s 0 i, String.sub s (i + 1) (len - i - 1))
| None -> None
in
(* Dependencies section - always show *)
let deps_section =
let deps_content = match layer_info with
| Some info when info.deps <> [] ->
let deps_list = info.deps
|> List.map (fun dep ->
match parse_package_str dep with
| Some (dep_name, dep_version) ->
Printf.sprintf {|%s|} dep_name dep_version dep
| None ->
Printf.sprintf "%s" dep)
|> String.concat "\n" in
Printf.sprintf {||} deps_list
| Some _ ->
{|No dependencies.
|}
| None ->
{|Dependency information not available.
|}
in
let deps_count = match layer_info with
| Some info -> List.length info.deps
| None -> 0
in
Printf.sprintf {|
Dependencies (%d)
%s
|} deps_count deps_content
in
(* Reverse dependencies section - always show *)
let reverse_deps = Day10_web_data.Layer_data.get_reverse_deps
~cache_dir ~platform ~package in
let rev_deps_section =
let rev_deps_content = if reverse_deps <> [] then
let rev_deps_list = reverse_deps
|> List.map (fun dep ->
match parse_package_str dep with
| Some (dep_name, dep_version) ->
Printf.sprintf {|%s|} dep_name dep_version dep
| None ->
Printf.sprintf "%s" dep)
|> String.concat "\n" in
Printf.sprintf {||} rev_deps_list
else
{|No packages depend on this one.
|}
in
Printf.sprintf {|
Reverse Dependencies (%d)
%s
|} (List.length reverse_deps) rev_deps_content
in
let content = Printf.sprintf {|
%s
← Back to packages
%s
%s
%s
|} package (Layout.badge `Success) name version build_info deps_section rev_deps_section versions_list
in
Layout.page ~title:package ~content
(** Combined build and doc logs page for a package *)
let logs_page ~log_dir ~name ~version =
let package = name ^ "." ^ version in
let latest_run = Day10_web_data.Run_data.get_latest_run_id ~log_dir in
match latest_run with
| None ->
Layout.page ~title:(package ^ " Logs") ~content:(Printf.sprintf {|
%s Logs
← Back to package
|} package name version)
| Some run_id ->
let build_log = Day10_web_data.Run_data.read_build_log ~log_dir ~run_id ~package in
let doc_log = Day10_web_data.Run_data.read_doc_log ~log_dir ~run_id ~package in
let build_section = match build_log with
| Some log ->
Printf.sprintf {|
|} run_id log
| None ->
{|Build Log
No build log available for this package.
|}
in
let doc_section = match doc_log with
| Some log ->
Printf.sprintf {|
Documentation Log
From run %s
%s
|} run_id log
| None ->
{|Documentation Log
No doc log available for this package.
|}
in
let content = Printf.sprintf {|
%s Logs
← Back to package
%s
%s
|} package name version build_section doc_section
in
Layout.page ~title:(package ^ " Logs") ~content