~jan0sch/smederee
Showing details for patch ead7169dbb3b92689d41191bca1d632ba89b9054.
diff -rN -u old-smederee/modules/hub/src/main/resources/messages.properties new-smederee/modules/hub/src/main/resources/messages.properties --- old-smederee/modules/hub/src/main/resources/messages.properties 2025-06-20 19:29:45.911645517 +0000 +++ new-smederee/modules/hub/src/main/resources/messages.properties 2025-06-20 19:29:45.911645517 +0000 @@ -18,6 +18,9 @@ errors.internal-server-error.message=An error occured while processing the request. errors.repository.not-found.title=Not found errors.repository.not-found.message=No repository named "{0}" found for user or organisation "{1}"! +errors.repository.health.error.output=Command output +errors.repository.health.error=The repository health check returned a non-zero exit code which indicates an error! +errors.repository.health.success=No errors were found. errors.user-or-organisation.not-found.title=Not found errors.user-or-organisation.not-found.message=No user or organisation named "{0}" could be found! @@ -261,6 +264,7 @@ repository.menu.delete=Delete repository.menu.edit=Edit repository.menu.files=Files +repository.menu.health=Health repository.menu.labels=Labels repository.menu.milestones=Milestones repository.menu.overview=Overview diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala 2025-06-20 19:29:45.911645517 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepositoryRoutes.scala 2025-06-20 19:29:45.911645517 +0000 @@ -8,6 +8,9 @@ import java.io.IOException import java.nio.file.* +import java.time.OffsetDateTime +import java.time.ZoneId +import java.time.ZoneOffset import cats.* import cats.data.* @@ -661,6 +664,101 @@ } } yield resp + /** Logic for performing a repository health check and show the results. + * + * TODO: Move the actual check into a cronjob and just display the results here. + * + * @param csrf + * An optional CSRF-Token that shall be used. + * @param user + * An optional user account for whom the list of repositories shall be rendered. + * @param repositoryOwnerName + * The name of the user who owns the repository. + * @param repositoryName + * The actual name of the repository. + * @return + * An HTTP response containing the rendered HTML. + */ + def doShowRepositoryHealth( + csrf: Option[CsrfToken] + )(user: Option[Account])(repositoryOwnerName: Username, repositoryName: VcsRepositoryName): F[Response[F]] = + for { + language <- Sync[F].delay(user.flatMap(_.language).getOrElse(LanguageCode("en"))) + repoAndId <- loadRepo(user)(repositoryOwnerName, repositoryName) + repo = repoAndId.map(_._1) + directory <- Sync[F].delay( + os.Path( + Paths.get( + darcsConfig.repositoriesDirectory.toPath.toString, + repositoryOwnerName.toString + ) + ) + ) + branches <- repoAndId.map(_._2) match { + case Some(repoId) => vcsMetadataRepo.findVcsRepositoryBranches(repoId).compile.toList + case _ => Sync[F].delay(List.empty) + } + check <- darcs.repair(directory.toNIO)(repositoryName.toString)(Chain("--dry-run")) + health = VcsRepositoryHealth( + command = "darcs repair --dry-run", + exitCode = check.exitValue, + stderr = check.stderr, + stdout = check.stdout, + createdAt = OffsetDateTime.now(ZoneId.of(ZoneOffset.UTC.getId)) + ) + actionBaseUri <- Sync[F].delay( + linkConfig.createFullUri( + Uri(path = + Uri.Path( + Vector( + Uri.Path.Segment(s"~$repositoryOwnerName"), + Uri.Path.Segment(repositoryName.toString) + ) + ) + ) + ) + ) + goBackUri <- Sync[F].delay( + linkConfig.createFullUri( + Uri(path = + Uri.Path( + Vector( + Uri.Path.Segment(s"~$repositoryOwnerName"), + Uri.Path.Segment(repositoryName.toString) + ) + ) + ) + ) + ) + repositoryBaseUri <- Sync[F].delay( + linkConfig.createFullUri( + Uri(path = + Uri.Path( + Vector( + Uri.Path.Segment(s"~$repositoryOwnerName"), + Uri.Path.Segment(repositoryName.toString) + ) + ) + ) + ) + ) + pageTitle = genPageTitleBase(repositoryOwnerName)(repositoryName.some) |+| ": Health" + resp <- repo match { + case None => NotFound() + case Some(repo) => + Ok( + views.html.showRepositoryHealth(baseUri, lang = language)( + actionBaseUri, + csrf, + goBackUri.some, + linkToTicketService, + pageTitle.some, + user + )(health, repositoryBaseUri, repo, branches) + ) + } + } yield resp + /** Logic for showing the history (changes) for the requested repository. * * @param csrf @@ -1825,6 +1923,29 @@ } yield resp } + private val showRepositoryHealth: AuthedRoutes[Account, F] = AuthedRoutes.of { + case ar @ GET -> Root / UsernamePathParameter(repositoryOwnerName) / VcsRepositoryNamePathParameter( + repositoryName + ) / "health" as user => + for { + csrf <- Sync[F].delay(ar.req.getCsrfToken) + language <- Sync[F].delay(user.language.getOrElse(LanguageCode("en"))) + repoAndId <- loadRepo(user.some)(repositoryOwnerName, repositoryName) + resp <- repoAndId.fold( + NotFound( + views.html.errors.repositoryNotFound(lang = language)( + csrf = csrf, + title = None, + user.some + )( + repositoryOwnerName, + repositoryName + ) + ) + )(_ => doShowRepositoryHealth(csrf)(user.some)(repositoryOwnerName, repositoryName)) + } yield resp + } + private val showRepositoryHistory: AuthedRoutes[Account, F] = AuthedRoutes.of { case ar @ GET -> Root / UsernamePathParameter(repositoryOwnerName) / VcsRepositoryNamePathParameter( repositoryName @@ -1983,7 +2104,8 @@ createRepository <+> deleteRepository <+> editRepository <+> showCreateRepositoryForm <+> showDeleteRepositoryForm <+> showEditRepositoryForm <+> showRepositoryOverview <+> showRepositoryBranches <+> showRepositoryHistory <+> - showRepositoryStatistics <+> showRepositoryPatchDetails <+> showRepositoryFiles + showRepositoryHealth <+> showRepositoryStatistics <+> showRepositoryPatchDetails <+> + showRepositoryFiles val routes = cloneRepository <+> downloadDistributionForGuests <+> showAllRepositoriesForGuests <+> diff -rN -u old-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepository.scala new-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepository.scala --- old-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepository.scala 2025-06-20 19:29:45.911645517 +0000 +++ new-smederee/modules/hub/src/main/scala/de/smederee/hub/VcsRepository.scala 2025-06-20 19:29:45.911645517 +0000 @@ -540,6 +540,27 @@ } } +/** Container for repository health check run output. + * + * @param command + * The command that was run on the repository. + * @param exitCode + * The exit code of the command, usually 0 implies no errors. + * @param stderr + * Saved output of the standard error channel of the command. + * @param stdout + * Saved output of the standard output channel of the command. + * @param createdAt + * The timestamp of when this entry was created which should be the last time the command was run. + */ +final case class VcsRepositoryHealth( + command: String, + exitCode: Int, + stderr: Chain[String], + stdout: Chain[String], + createdAt: OffsetDateTime +) + /** Data about a VCS respository. * * @param name diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryHealth.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryHealth.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryHealth.scala.html 1970-01-01 00:00:00.000000000 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryHealth.scala.html 2025-06-20 19:29:45.911645517 +0000 @@ -0,0 +1,86 @@ +@import de.smederee.hub.* + +@( + baseUri: Uri, + lang: LanguageCode = LanguageCode("en") +)( + actionBaseUri: Uri, + csrf: Option[CsrfToken] = None, + goBackUri: Option[Uri] = None, + linkToTicketService: Option[Uri] = None, + title: Option[String] = None, + user: Option[Account] +)( + health: VcsRepositoryHealth, + repositoryBaseUri: Uri, + vcsRepository: VcsRepository, + vcsRepositoryBranches: List[(Username, VcsRepositoryName)], +) +@main(baseUri, lang)()(csrf, title, user) { +@defining(lang.toLocale) { implicit locale => + <div class="content"> + <div class="pure-g"> + <div class="pure-u-1-1 pure-u-md-1-1"> + <div class="l-box-left-right"> + <h2><a href="@{baseUri.addSegment(s"~${vcsRepository.owner.name}")}">~@vcsRepository.owner.name</a>/@vcsRepository.name</h2> + @showRepositoryMenu(baseUri, linkToTicketService)(repositoryBaseUri.addSegment("health").some, vcsRepositoryBranches.size, repositoryBaseUri, user, vcsRepository) + <div class="repo-summary-description"> + </div> + </div> + </div> + </div> + <div class="content"> + <div class="pure-g"> + <div class="pure-u-1-1 pure-u-md-1-1"> + <div class="l-box"> + @if(health.exitCode =!= 0) { + <p class="alert alert-error"> + <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> + <span class="sr-only">@Messages("global.error"):</span> + @Messages("errors.repository.health.error") + </p> + } else { + <p class="alert alert-success"> + @Messages("errors.repository.health.success") + </p> + } + </div> + </div> + </div> + <div class="pure-g"> + <div class="pure-u-1-1 pure-u-md-1-1"> + <div class="l-box"> + <h4>@Messages("errors.repository.health.error.output")</h4> + <table class="pure-table"> + <thead> + </thead> + <tbody class="repository-file-content"> + @for(tuple <- health.stdout.iterator.zipWithIndex) { + @defining(tuple._2) { lineNumber => + @defining(tuple._1) { line => + <tr class="code-line"> + <td class="code-line-number" id="L@lineNumber"><a href="#L@lineNumber">@lineNumber</a></td> + <td class="code-line"><code class="code-line">@line</code></td> + </tr> + } + } + } + </tbody> + </table> + </div> + </div> + </div> + <div class="pure-g"> + <div class="pure-u-1-1 pure-u-md-1-1"> + <div class="l-box"> + <pre> + @for(line <- health.stderr.iterator) { + @line + } + </pre> + </div> + </div> + </div> + </div> +} +} diff -rN -u old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryMenu.scala.html new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryMenu.scala.html --- old-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryMenu.scala.html 2025-06-20 19:29:45.911645517 +0000 +++ new-smederee/modules/hub/src/main/twirl/de/smederee/hub/views/showRepositoryMenu.scala.html 2025-06-20 19:29:45.911645517 +0000 @@ -36,8 +36,11 @@ <li class="pure-menu-item@if(activeUri.exists(_ === uri)){ pure-menu-active}else{}"><a class="pure-menu-link" href="@uri">@icon(baseUri)("git-branch") @Messages("repository.menu.branches", branches)</a></li> } } else {} - @if(activeUri.exists(uri => uri === repositoryBaseUri || uri === repositoryBaseUri.addSegment("edit") || uri === repositoryBaseUri.addSegment("delete"))) { + @if(activeUri.exists(uri => uri === repositoryBaseUri || uri === repositoryBaseUri.addSegment("edit") || uri === repositoryBaseUri.addSegment("delete") || uri === repositoryBaseUri.addSegment("health"))) { @if(user.exists(_.uid === vcsRepository.owner.uid)) { + @defining(repositoryBaseUri.addSegment("health")) { uri => + <li class="pure-menu-item@if(activeUri.exists(_ === uri)){ pure-menu-active}else{}"><a class="pure-menu-link" href="@uri">@icon(baseUri)("activity") @Messages("repository.menu.health")</a></li> + } @defining(repositoryBaseUri.addSegment("edit")) { uri => <li class="pure-menu-item@if(activeUri.exists(_ === uri)){ pure-menu-active}else{}"><a class="pure-menu-link" href="@uri">@icon(baseUri)("edit-2") @Messages("repository.menu.edit")</a></li> }