diff --git a/app/assets/css/main.css b/app/assets/css/main.css index 621764d..34eaebb 100644 --- a/app/assets/css/main.css +++ b/app/assets/css/main.css @@ -115,6 +115,35 @@ h3.library-identification{ padding-left: 10px; } +.projects-watching li.with-buttons{ + list-style-type: none; +} .projects-watching .watched{ font-weight: bold; -} \ No newline at end of file +} + +.projects-watching .collapsed ul{ + display: none; +} +.projects-watching .watching-btn-expand { + display: none; +} +.projects-watching .collapsed .watching-btn-expand{ + display: inline; +} +.projects-watching .collapsed .watching-btn-collapse{ + display: none; +} +.projects-watching li{ + position: relative; +} + +.projects-watching .toggle-buttons button{ + margin: 0; + padding: 0; +} +.projects-watching .toggle-buttons{ + position: absolute; + top: 2px; + left: -19px; +} diff --git a/app/com/ysoft/concurrent/FutureLock.scala b/app/com/ysoft/concurrent/FutureLock.scala new file mode 100644 index 0000000..7faaec0 --- /dev/null +++ b/app/com/ysoft/concurrent/FutureLock.scala @@ -0,0 +1,40 @@ +package com.ysoft.concurrent + +import java.util.concurrent.atomic.AtomicBoolean + +import scala.concurrent.{ExecutionContext, Future} + +trait FutureLock[T] { + def whenLocked(cannotLock: => Future[T])(implicit executionContext: ExecutionContext): Future[T] +} + +object FutureLock { + + def futureLock[T](lock: AtomicBoolean)(f: => Future[T]): FutureLock[T] = new FutureLock[T]() { + override def whenLocked(cannotLock: => Future[T])(implicit executionContext: ExecutionContext): Future[T] = { + if (lock.compareAndSet(/*expect = */ false, /*update = */ true)) { + try { + f.andThen { case _ => + val wasLocked = lock.getAndSet(false) + if (!wasLocked) { + throw new RuntimeException("The lock was not being held when trying to unlock") + } + } + } catch { + case e: Throwable => + // So, the Exception was raised before creation of the Future. As a result, the Future will not relase the lock. + // In other words, its our responsibility to release the lock: + val wasLocked = lock.getAndSet(false) + if (!wasLocked) { + throw new RuntimeException("The lock was not being held when throwing the following exception", e) + } + throw e + } + } else { + cannotLock + } + } + } + + +} diff --git a/app/controllers/DependencyCheckReportsParser.scala b/app/controllers/DependencyCheckReportsParser.scala index 9e150d9..76b24f1 100644 --- a/app/controllers/DependencyCheckReportsParser.scala +++ b/app/controllers/DependencyCheckReportsParser.scala @@ -22,7 +22,7 @@ sealed trait Filter{ private final case class ProjectFilter(project: ReportInfo) extends Filter{ override def filters: Boolean = true override def descriptionHtml: Html = views.html.filters.project(project) - override def descriptionText: String = s"project ${friendlyProjectName(project)}" + override def descriptionText: String = s"project ${friendlyProjectNameString(project)}" override def subReports(r: Result): Option[Result] = { @inline def reportInfo = project def f[T](m: Map[ReportInfo, T]): Map[String, T] = ( @@ -39,7 +39,7 @@ private final case class TeamFilter(team: Team) extends Filter{ override def filters: Boolean = true override def subReports(r: Result): Option[Result] = { val Wildcard = """^(.*): \*$""".r - val reportInfoByFriendlyProjectNameMap = r.projectsReportInfo.ungroupedReportsInfo.map(ri => friendlyProjectName(ri) -> ri).toSeq.groupBy(_._1).mapValues{ + val reportInfoByFriendlyProjectNameMap = r.projectsReportInfo.ungroupedReportsInfo.map(ri => friendlyProjectNameString(ri) -> ri).toSeq.groupBy(_._1).mapValues{ case Seq((_, ri)) => ri case other => sys.error("some duplicate value: "+other) }.map(identity) diff --git a/app/controllers/DependencyCheckReportsProcessor.scala b/app/controllers/DependencyCheckReportsProcessor.scala index 2024bb8..7669bf9 100644 --- a/app/controllers/DependencyCheckReportsProcessor.scala +++ b/app/controllers/DependencyCheckReportsProcessor.scala @@ -36,7 +36,7 @@ final class DependencyCheckReportsProcessor @Inject() ( @deprecated("use HTML output instead", "SNAPSHOT") private val showDependencies: (Seq[GroupedDependency]) => Seq[String] = { _.map { s => - s.dependencies.map { case (dep, projects) => s"${dep.fileName} @ ${projects.toSeq.sorted.map(friendlyProjectName).mkString(", ")}" }.mkString(", ") + " " + s.hashes + s.dependencies.map { case (dep, projects) => s"${dep.fileName} @ ${projects.toSeq.sorted.map(friendlyProjectNameString).mkString(", ")}" }.mkString(", ") + " " + s.hashes } } diff --git a/app/controllers/ProjectsWithReports.scala b/app/controllers/ProjectsWithReports.scala index 07cb6ff..0bc46c7 100644 --- a/app/controllers/ProjectsWithReports.scala +++ b/app/controllers/ProjectsWithReports.scala @@ -24,6 +24,9 @@ final case class ReportInfo( def bare = copy(subprojectNameOption = None, fullId = fullId.takeWhile(_ != '/')) + def isBare = subprojectNameOption.isEmpty + def isNotBare = !isBare + } object ProjectsWithReports{ diff --git a/app/controllers/package.scala b/app/controllers/package.scala index 0b76cae..9b0a9b4 100644 --- a/app/controllers/package.scala +++ b/app/controllers/package.scala @@ -32,6 +32,6 @@ package object controllers { val subProjectOption = Some(removeMess(theRest)).filter(_ != "") subProjectOption.fold(baseName)(baseName+"/"+_) }*/ - def friendlyProjectName(reportInfo: ReportInfo) = reportInfo.subprojectNameOption.fold(reportInfo.projectName)(reportInfo.projectName+": "+_) + def friendlyProjectNameString(reportInfo: ReportInfo) = reportInfo.subprojectNameOption.fold(reportInfo.projectName)(reportInfo.projectName+": "+_) } diff --git a/app/services/EmailExportService.scala b/app/services/EmailExportService.scala index 322be40..57e44fe 100644 --- a/app/services/EmailExportService.scala +++ b/app/services/EmailExportService.scala @@ -24,7 +24,7 @@ class EmailExportService(from: String, nobodyInterestedContact: String, mailerCl def mailForVulnerabilityProjectsChange(vuln: Vulnerability, emailMessageId: EmailMessageId, diff: SetDiff[String], projects: ProjectsWithReports) = { def showProjects(s: Set[String]) = s.map(p => "* " + (try{ - friendlyProjectName(projects.parseUnfriendlyName(p)) + friendlyProjectNameString(projects.parseUnfriendlyName(p)) }catch{ // It might fail on project that has been removed case e: NoSuchElementException => s"unknown project $p" }) diff --git a/app/views/friendlyProjectName.scala.html b/app/views/friendlyProjectName.scala.html new file mode 100644 index 0000000..84cbc59 --- /dev/null +++ b/app/views/friendlyProjectName.scala.html @@ -0,0 +1,5 @@ +@(reportInfo: ReportInfo) +@reportInfo.projectName@reportInfo.subprojectNameOption match { + case None => { (with all subprojects)} + case Some(subproject) => {: @subproject} +} \ No newline at end of file diff --git a/app/views/notifications/index.scala.html b/app/views/notifications/index.scala.html index c58ba89..80d222e 100644 --- a/app/views/notifications/index.scala.html +++ b/app/views/notifications/index.scala.html @@ -3,30 +3,79 @@ @button(action: Call)(label: String) = { @form(action, 'style -> "display: inline-block"){ @CSRF.formField - + } } -@main("Watch projects"){ -