diff --git a/app/config/Module.scala b/app/config/Module.scala index ce88c807..32cdbe4d 100644 --- a/app/config/Module.scala +++ b/app/config/Module.scala @@ -42,6 +42,7 @@ class Module extends play.api.inject.Module { bindz[AddAnApiCheckContextActionProvider].to(classOf[AddAnApiCheckContextActionProviderImpl]).eagerly(), bindz(classOf[IdentifierAction]).to(classOf[AuthenticatedIdentifierAction]).eagerly(), bindz(classOf[OptionalIdentifierAction]).to(classOf[OptionallyAuthenticatedIdentifierAction]), + bindz[StatusActionProvider].to(classOf[StatusActionProviderImpl]).eagerly(), bindz(classOf[Clock]).toInstance(Clock.systemDefaultZone.withZone(ZoneOffset.UTC)), bindz[Encrypter & Decrypter].toProvider[CryptoProvider], bindz[Domains].to(classOf[DomainsImpl]).eagerly(), diff --git a/app/connectors/ApplicationsConnector.scala b/app/connectors/ApplicationsConnector.scala index 73849012..41c3fa42 100644 --- a/app/connectors/ApplicationsConnector.scala +++ b/app/connectors/ApplicationsConnector.scala @@ -27,6 +27,7 @@ import models.deployment.* import models.exception.{ApplicationCredentialLimitException, ApplicationsException, TeamNameNotUniqueException} import models.requests.{AddApiRequest, ChangeTeamNameRequest, TeamMemberRequest} import models.stats.ApisInProductionStatistic +import models.status.ServiceStatuses import models.team.{NewTeam, Team} import models.user.UserContactDetails import play.api.Logging @@ -598,4 +599,11 @@ class ApplicationsConnector @Inject()( .setHeader(AUTHORIZATION -> clientAuthToken) .execute[Seq[EgressGateway]] } + + def status()(implicit hc: HeaderCarrier): Future[ServiceStatuses] = { + httpClient.get(url"$applicationsBaseUrl/api-hub-applications/status") + .setHeader(ACCEPT -> JSON) + .setHeader(AUTHORIZATION -> clientAuthToken) + .execute[ServiceStatuses] + } } diff --git a/app/controllers/IndexController.scala b/app/controllers/IndexController.scala index 36138a42..e2d57cde 100644 --- a/app/controllers/IndexController.scala +++ b/app/controllers/IndexController.scala @@ -16,10 +16,10 @@ package controllers -import controllers.actions.IdentifierAction +import controllers.actions.{IdentifierAction, StatusActionProvider} import play.api.Logging import play.api.i18n.I18nSupport -import play.api.mvc._ +import play.api.mvc.* import services.ApiHubService import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendBaseController import views.html.IndexView @@ -30,11 +30,12 @@ import scala.concurrent.ExecutionContext class IndexController @Inject()( val controllerComponents: MessagesControllerComponents, identify: IdentifierAction, + status: StatusActionProvider, view: IndexView, apiHubService: ApiHubService )(implicit ec: ExecutionContext) extends FrontendBaseController with I18nSupport with Logging { - def onPageLoad: Action[AnyContent] = identify.async { implicit request => + def onPageLoad: Action[AnyContent] = (identify andThen status()).async { implicit request => val maxApplicationsToShow = 5 val maxTeamsToShow = 5 @@ -46,7 +47,8 @@ class IndexController @Inject()( userApps.size, userTeams.sortBy(_.created).reverse.take(maxTeamsToShow), userTeams.size, - Some(request.user) + Some(request.user), + request.serviceStatuses )) } diff --git a/app/controllers/actions/StatusAction.scala b/app/controllers/actions/StatusAction.scala new file mode 100644 index 00000000..87b716a2 --- /dev/null +++ b/app/controllers/actions/StatusAction.scala @@ -0,0 +1,53 @@ +/* + * Copyright 2023 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package controllers.actions + +import com.google.inject.Inject +import connectors.ApplicationsConnector +import handlers.ErrorHandler +import models.requests.{ApiRequest, IdentifierRequest, StatusRequest} +import models.user.{LdapUser, StrideUser, UserModel, UserType} +import play.api.Logging +import play.api.mvc.Results.* +import play.api.mvc.* +import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendHeaderCarrierProvider + +import scala.concurrent.{ExecutionContext, Future} + +trait StatusAction extends ActionRefiner[IdentifierRequest, StatusRequest] + +trait StatusActionProvider { + def apply()(implicit ec: ExecutionContext): StatusAction +} + +class StatusActionProviderImpl @Inject()( + applicationsConnector: ApplicationsConnector, + errorHandler: ErrorHandler +)(implicit val executionContext: ExecutionContext) extends StatusActionProvider { + + def apply()(implicit ec: ExecutionContext): StatusAction = + new StatusAction with FrontendHeaderCarrierProvider { + override protected def refine[A](identifierRequest: IdentifierRequest[A]): Future[Either[Result, StatusRequest[A]]] = + implicit val request: Request[?] = identifierRequest + applicationsConnector.status().map(statuses => + Right(StatusRequest(identifierRequest, identifierRequest.user, statuses)) + ) + + override protected def executionContext: ExecutionContext = ec + } + +} diff --git a/app/models/requests/StatusRequest.scala b/app/models/requests/StatusRequest.scala new file mode 100644 index 00000000..e0a3d081 --- /dev/null +++ b/app/models/requests/StatusRequest.scala @@ -0,0 +1,23 @@ +/* + * Copyright 2023 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package models.requests + +import models.status.ServiceStatuses +import models.user.UserModel +import play.api.mvc.WrappedRequest + +case class StatusRequest[A](identifierRequest: IdentifierRequest[A], user: UserModel, serviceStatuses: ServiceStatuses) extends WrappedRequest[A](identifierRequest) \ No newline at end of file diff --git a/app/models/status/ServiceStatus.scala b/app/models/status/ServiceStatus.scala new file mode 100644 index 00000000..9421cb8d --- /dev/null +++ b/app/models/status/ServiceStatus.scala @@ -0,0 +1,31 @@ +/* + * Copyright 2024 HM Revenue & Customs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package models.status + +import models.application.EnvironmentName +import play.api.libs.json.{Format, Json} + +case class ServiceStatus(isDown: Boolean, service: String, environmentName: EnvironmentName) +object ServiceStatus: + given statusFormat: Format[ServiceStatus] = Json.format[ServiceStatus] + +case class ServiceStatuses(statuses: Seq[ServiceStatus]) { + lazy val hasServiceDown: Boolean = statuses.exists(_.isDown) +} +object ServiceStatuses: + given statusesFormat: Format[ServiceStatuses] = Json.format[ServiceStatuses] + diff --git a/app/views/IndexView.scala.html b/app/views/IndexView.scala.html index 44a5da4f..dbe903d3 100644 --- a/app/views/IndexView.scala.html +++ b/app/views/IndexView.scala.html @@ -15,6 +15,7 @@ *@ @import models.application.Application +@import models.status.ServiceStatuses @import models.user.UserModel @import views.ViewUtils @import models.team.Team @@ -26,14 +27,15 @@ govukTable: GovukTable ) -@(applications: Seq[Application], totalApplicationCount: Int, teams: Seq[Team], totalTeamCount: Int, user: Option[UserModel])(implicit request: Request[?], messages: Messages) +@(applications: Seq[Application], totalApplicationCount: Int, teams: Seq[Team], totalTeamCount: Int, user: Option[UserModel], statuses: ServiceStatuses)(implicit request: Request[?], messages: Messages) @layout( pageTitle = titleNoForm(messages("dashboard.title")), showBackLink = true, fullWidth = true, user = user, - activeLink = Some("dashboard") + activeLink = Some("dashboard"), + statuses = Some(statuses) ) {