src/Pure/Build/build_manager.scala
changeset 80535 417fcf9f5e71
parent 80534 f5da84211ac0
child 80536 63afde05a820
equal deleted inserted replaced
80534:f5da84211ac0 80535:417fcf9f5e71
  1069 
  1069 
  1070     object Page {
  1070     object Page {
  1071       val HOME = Path.basic("home")
  1071       val HOME = Path.basic("home")
  1072       val OVERVIEW = Path.basic("overview")
  1072       val OVERVIEW = Path.basic("overview")
  1073       val BUILD = Path.basic("build")
  1073       val BUILD = Path.basic("build")
       
  1074       val DIFF = Path.basic("diff")
  1074     }
  1075     }
  1075 
  1076 
  1076     def paths(store: Store): Web_App.Paths =
  1077     def paths(store: Store): Web_App.Paths =
  1077       Web_App.Paths(store.address, Path.current, serve_frontend = true, landing = Page.HOME)
  1078       Web_App.Paths(store.address, Path.current, serve_frontend = true, landing = Page.HOME)
  1078 
  1079 
  1119       case Error extends Model
  1120       case Error extends Model
  1120       case Cancelled extends Model
  1121       case Cancelled extends Model
  1121       case Home(state: State) extends Model
  1122       case Home(state: State) extends Model
  1122       case Overview(kind: String, state: State) extends Model
  1123       case Overview(kind: String, state: State) extends Model
  1123       case Details(build: Build, state: State, public: Boolean = true) extends Model
  1124       case Details(build: Build, state: State, public: Boolean = true) extends Model
       
  1125       case Diff(build: Build, state: State) extends Model
  1124     }
  1126     }
  1125 
  1127 
  1126     object View {
  1128     object View {
  1127       import HTML.*
  1129       import HTML.*
  1128       import More_HTML.*
  1130       import More_HTML.*
  1214           def render_cancel(uuid: UUID.T): XML.Body =
  1216           def render_cancel(uuid: UUID.T): XML.Body =
  1215             render_if(!public, List(
  1217             render_if(!public, List(
  1216               submit_form("", List(hidden(ID, uuid.toString),
  1218               submit_form("", List(hidden(ID, uuid.toString),
  1217                 api_button(paths.api_route(API.BUILD_CANCEL), "cancel build")))))
  1219                 api_button(paths.api_route(API.BUILD_CANCEL), "cancel build")))))
  1218 
  1220 
  1219           def render_diff(diff: String): XML.Body = {
  1221           def non_local(components: List[Component]): List[Component] =
  1220             val Colored = "\u001b\\[([0-9;]+)m(.*)\u001b\\[0m".r
  1222             for (component <- components if !component.is_local) yield component
  1221             val colors = List("black", "red", "green", "yellow", "blue", "magenta", "cyan", "white")
  1223 
  1222 
  1224           def render_rev(components: List[Component], data: Report.Data): XML.Body = {
  1223             val lines = split_lines(diff).map {
  1225             val relevant = non_local(components)
  1224               case Colored(code, s) =>
  1226             val hg_info = data.component_logs.map(_._1) ++ data.component_diffs.map(_._1)
  1225                 val codes = space_explode(';', code.stripSuffix(";1")).map(Value.Int.parse)
  1227             val s = relevant.mkString(", ")
  1226                 val fg = codes match { case 0 :: i :: Nil => colors.unapply(i - 30) case _ => None }
  1228 
  1227 
  1229             if (!relevant.map(_.name).exists(hg_info.toSet)) text(s)
  1228                 val sp = span(if (code.endsWith(";1")) List(bold(text(s))) else text(s))
  1230             else List(page_link(Page.DIFF, s, Markup.Name(build.name)))
  1229                 List(fg.map(color => sp + ("style" -> ("color:" + color))).getOrElse(sp))
       
  1230               case line => text(Library.strip_ansi_color(line))
       
  1231             }
       
  1232 
       
  1233             List(source(Library.separate(nl, lines).flatten))
       
  1234           }
  1231           }
  1235 
       
  1236           def render_rev(components: List[Component], diffs: Map[String, String]): XML.Body =
       
  1237             for (component <- components if !component.is_local)
       
  1238             yield par(text(component.toString) ++ diffs.get(component.name).flatMap(render_diff))
       
  1239 
  1232 
  1240           build match {
  1233           build match {
  1241             case task: Task =>
  1234             case task: Task =>
  1242               par(text("Task from " + Build_Log.print_date(task.submit_date) + ". ")) ::
  1235               par(text("Task from " + Build_Log.print_date(task.submit_date) + ". ")) ::
  1243               render_rev(task.components, Map.empty) :::
  1236               par(text(non_local(task.components).mkString(", "))) ::
  1244               render_cancel(task.uuid)
  1237               render_cancel(task.uuid)
  1245 
  1238 
  1246             case job: Job =>
  1239             case job: Job =>
  1247               val report_data = cache.lookup(store.report(job.kind, job.id))
  1240               val report_data = cache.lookup(store.report(job.kind, job.id))
  1248 
  1241 
  1249               par(text("Start: " + Build_Log.print_date(job.start_date))) ::
  1242               par(text("Start: " + Build_Log.print_date(job.start_date))) ::
  1250               par(
  1243               par(
  1251                 if (job.cancelled) text("Cancelling ...")
  1244                 if (job.cancelled) text("Cancelling ...")
  1252                 else text("Running ...") ::: render_cancel(job.uuid)) ::
  1245                 else text("Running ...") ::: render_cancel(job.uuid)) ::
  1253               render_rev(job.components, report_data.component_diffs.toMap) :::
  1246               par(render_rev(job.components, report_data)) ::
  1254               par(text("Log") :+ source(report_data.build_log)) :: Nil
  1247               par(List(source(report_data.build_log))) :: Nil
  1255 
  1248 
  1256             case result: Result =>
  1249             case result: Result =>
  1257               val report_data = cache.lookup(store.report(result.kind, result.id))
  1250               val report_data = cache.lookup(store.report(result.kind, result.id))
  1258 
  1251 
  1259               par(text("Start: " + Build_Log.print_date(result.start_date) +
  1252               par(text("Start: " + Build_Log.print_date(result.start_date) +
  1260                 if_proper(result.end_date,
  1253                 if_proper(result.end_date,
  1261                   ", took " + (result.end_date.get - result.start_date).message_hms))) ::
  1254                   ", took " + (result.end_date.get - result.start_date).message_hms))) ::
  1262               par(text("Status: " + result.status)) ::
  1255               par(text("Status: " + result.status)) ::
  1263               render_rev(result.components, report_data.component_diffs.toMap) :::
  1256               par(render_rev(result.components, report_data)) ::
  1264               par(text("Log") :+ source(report_data.build_log)) :: Nil
  1257               par(List(source(report_data.build_log))) :: Nil
  1265           }
  1258           }
  1266         }
  1259         }
       
  1260 
       
  1261       def render_diff(build: Build, state: State): XML.Body = render_page("Diff: " + build.name) {
       
  1262         def colored(s: String): XML.Body = {
       
  1263           val Colored = "([^\u001b]*)\u001b\\[([0-9;]+)m(.*)\u001b\\[0m([^\u001b]*)".r
       
  1264           val colors = List("black", "red", "green", "yellow", "blue", "magenta", "cyan", "white")
       
  1265 
       
  1266           val lines = split_lines(s).map {
       
  1267             case Colored(pre, code, s, post) =>
       
  1268               val codes = space_explode(';', code.stripSuffix(";1")).map(Value.Int.parse)
       
  1269               val fg = codes match { case 0 :: i :: Nil => colors.unapply(i - 30) case _ => None }
       
  1270 
       
  1271               val sp = span(if (code.endsWith(";1")) List(bold(text(s))) else text(s))
       
  1272               val sp1 = fg.map(color => sp + ("style" -> ("color:" + color))).getOrElse(sp)
       
  1273               List(span(text(pre)), sp1, span(text(post)))
       
  1274             case line => text(Library.strip_ansi_color(line))
       
  1275           }
       
  1276 
       
  1277           List(source(Library.separate(nl, lines).flatten))
       
  1278         }
       
  1279 
       
  1280         def render_diff(data: Report.Data, components: List[Component]): XML.Body =
       
  1281           par(List(page_link(Page.BUILD, "back to build", Markup.Name(build.name)))) ::
       
  1282           (for (component <- components if !component.is_local) yield {
       
  1283             par(
       
  1284               text(component.name) :::
       
  1285               data.component_logs.toMap.get(component.name).toList.flatMap(colored) :::
       
  1286               data.component_diffs.toMap.get(component.name).toList.flatMap(colored))
       
  1287           })
       
  1288 
       
  1289         build match {
       
  1290           case job: Job =>
       
  1291             render_diff(cache.lookup(store.report(job.kind, job.id)), job.components)
       
  1292           case result: Result =>
       
  1293             render_diff(cache.lookup(store.report(result.kind, result.id)), result.components)
       
  1294           case _ => Nil
       
  1295         }
       
  1296       }
  1267 
  1297 
  1268       def render_cancelled: XML.Body =
  1298       def render_cancelled: XML.Body =
  1269         render_page("Build cancelled")(List(page_link(Page.HOME, "Home")))
  1299         render_page("Build cancelled")(List(page_link(Page.HOME, "Home")))
  1270 
  1300 
  1271       def parse_uuid(params: Params.Data): Option[UUID.T] =
  1301       def parse_uuid(params: Params.Data): Option[UUID.T] =
  1292             val state = _state
  1322             val state = _state
  1293             state.get(name).map(Model.Details(_, state))
  1323             state.get(name).map(Model.Details(_, state))
  1294           case Web_Server.Id(UUID(uuid)) =>
  1324           case Web_Server.Id(UUID(uuid)) =>
  1295             val state = _state
  1325             val state = _state
  1296             state.get(uuid).map(Model.Details(_, state, public = false))
  1326             state.get(uuid).map(Model.Details(_, state, public = false))
       
  1327           case _ => None
       
  1328         }
       
  1329 
       
  1330       def get_diff(props: Properties.T): Option[Model.Diff] =
       
  1331         props match {
       
  1332           case Markup.Name(name) =>
       
  1333             val state = _state
       
  1334             state.get(name).map(Model.Diff(_, state))
  1297           case _ => None
  1335           case _ => None
  1298         }
  1336         }
  1299 
  1337 
  1300       def cancel_build(params: Params.Data): Option[Model] =
  1338       def cancel_build(params: Params.Data): Option[Model] =
  1301         for {
  1339         for {
  1319           model match {
  1357           model match {
  1320             case Model.Error => HTML.text("invalid request")
  1358             case Model.Error => HTML.text("invalid request")
  1321             case Model.Home(state) => View.render_home(state)
  1359             case Model.Home(state) => View.render_home(state)
  1322             case Model.Overview(kind, state) => View.render_overview(kind, state)
  1360             case Model.Overview(kind, state) => View.render_overview(kind, state)
  1323             case Model.Details(build, state, public) => View.render_details(build, state, public)
  1361             case Model.Details(build, state, public) => View.render_details(build, state, public)
       
  1362             case Model.Diff(build, state) => View.render_diff(build, state)
  1324             case Model.Cancelled => View.render_cancelled
  1363             case Model.Cancelled => View.render_cancelled
  1325           })
  1364           })
  1326 
  1365 
  1327       val error_model: Model = Model.Error
  1366       val error_model: Model = Model.Error
  1328       val endpoints = List(
  1367       val endpoints = List(
  1329         Get(Page.HOME, "home", _ => overview),
  1368         Get(Page.HOME, "home", _ => overview),
  1330         Get(Page.OVERVIEW, "overview", get_overview),
  1369         Get(Page.OVERVIEW, "overview", get_overview),
  1331         Get(Page.BUILD, "build", get_build),
  1370         Get(Page.BUILD, "build", get_build),
       
  1371         Get(Page.DIFF, "diff", get_diff),
  1332         Post(API.BUILD_CANCEL, "cancel build", cancel_build))
  1372         Post(API.BUILD_CANCEL, "cancel build", cancel_build))
  1333       val logo = Bytes.read(Path.explode("$ISABELLE_HOME/lib/logo/isabelle_transparent-48.gif"))
  1373       val logo = Bytes.read(Path.explode("$ISABELLE_HOME/lib/logo/isabelle_transparent-48.gif"))
  1334       val head =
  1374       val head =
  1335         List(
  1375         List(
  1336           HTML.title("Isabelle Build Manager"),
  1376           HTML.title("Isabelle Build Manager"),